UpdateAvailableNotificationsTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\UpdateNotification\Tests\BackgroundJob;
  8. use OC\Installer;
  9. use OC\Updater\VersionCheck;
  10. use OCA\UpdateNotification\BackgroundJob\UpdateAvailableNotifications;
  11. use OCP\App\IAppManager;
  12. use OCP\AppFramework\Services\IAppConfig;
  13. use OCP\AppFramework\Utility\ITimeFactory;
  14. use OCP\IConfig;
  15. use OCP\IGroup;
  16. use OCP\IGroupManager;
  17. use OCP\IUser;
  18. use OCP\Notification\IManager;
  19. use OCP\Notification\INotification;
  20. use PHPUnit\Framework\MockObject\MockObject;
  21. use Test\TestCase;
  22. class UpdateAvailableNotificationsTest extends TestCase {
  23. private IConfig|MockObject $config;
  24. private IManager|MockObject $notificationManager;
  25. private IGroupManager|MockObject $groupManager;
  26. private IAppManager|MockObject $appManager;
  27. private IAppConfig|MockObject $appConfig;
  28. private ITimeFactory|MockObject $timeFactory;
  29. private Installer|MockObject $installer;
  30. private VersionCheck|MockObject $versionCheck;
  31. protected function setUp(): void {
  32. parent::setUp();
  33. $this->config = $this->createMock(IConfig::class);
  34. $this->appConfig = $this->createMock(IAppConfig::class);
  35. $this->notificationManager = $this->createMock(IManager::class);
  36. $this->groupManager = $this->createMock(IGroupManager::class);
  37. $this->appManager = $this->createMock(IAppManager::class);
  38. $this->timeFactory = $this->createMock(ITimeFactory::class);
  39. $this->installer = $this->createMock(Installer::class);
  40. $this->versionCheck = $this->createMock(VersionCheck::class);
  41. }
  42. /**
  43. * @param array $methods
  44. * @return UpdateAvailableNotifications|MockObject
  45. */
  46. protected function getJob(array $methods = []) {
  47. if (empty($methods)) {
  48. return new UpdateAvailableNotifications(
  49. $this->timeFactory,
  50. $this->config,
  51. $this->appConfig,
  52. $this->notificationManager,
  53. $this->groupManager,
  54. $this->appManager,
  55. $this->installer,
  56. $this->versionCheck,
  57. );
  58. }
  59. {
  60. return $this->getMockBuilder(UpdateAvailableNotifications::class)
  61. ->setConstructorArgs([
  62. $this->timeFactory,
  63. $this->config,
  64. $this->appConfig,
  65. $this->notificationManager,
  66. $this->groupManager,
  67. $this->appManager,
  68. $this->installer,
  69. $this->versionCheck,
  70. ])
  71. ->onlyMethods($methods)
  72. ->getMock();
  73. }
  74. }
  75. public function testRun() {
  76. $job = $this->getJob([
  77. 'checkCoreUpdate',
  78. 'checkAppUpdates',
  79. ]);
  80. $job->expects($this->once())
  81. ->method('checkCoreUpdate');
  82. $job->expects($this->once())
  83. ->method('checkAppUpdates');
  84. $this->config->expects($this->exactly(2))
  85. ->method('getSystemValueBool')
  86. ->withConsecutive(
  87. ['has_internet_connection', true],
  88. ['debug', false],
  89. )
  90. ->willReturnOnConsecutiveCalls(
  91. true,
  92. true,
  93. );
  94. self::invokePrivate($job, 'run', [null]);
  95. }
  96. public function testRunNoInternet() {
  97. $job = $this->getJob([
  98. 'checkCoreUpdate',
  99. 'checkAppUpdates',
  100. ]);
  101. $job->expects($this->never())
  102. ->method('checkCoreUpdate');
  103. $job->expects($this->never())
  104. ->method('checkAppUpdates');
  105. $this->config->method('getSystemValueBool')
  106. ->with('has_internet_connection', true)
  107. ->willReturn(false);
  108. self::invokePrivate($job, 'run', [null]);
  109. }
  110. public function dataCheckCoreUpdate(): array {
  111. return [
  112. ['daily', null, null, null, null],
  113. ['git', null, null, null, null],
  114. ['beta', [], null, null, null],
  115. ['beta', false, false, null, null],
  116. ['beta', false, false, null, 13],
  117. ['beta', [
  118. 'version' => '9.2.0',
  119. 'versionstring' => 'Nextcloud 11.0.0',
  120. ], '9.2.0', 'Nextcloud 11.0.0', null],
  121. ['stable', [], null, null, null],
  122. ['stable', false, false, null, null],
  123. ['stable', false, false, null, 6],
  124. ['stable', [
  125. 'version' => '9.2.0',
  126. 'versionstring' => 'Nextcloud 11.0.0',
  127. ], '9.2.0', 'Nextcloud 11.0.0', null],
  128. ['production', [], null, null, null],
  129. ['production', false, false, null, null],
  130. ['production', false, false, null, 2],
  131. ['production', [
  132. 'version' => '9.2.0',
  133. 'versionstring' => 'Nextcloud 11.0.0',
  134. ], '9.2.0', 'Nextcloud 11.0.0', null],
  135. ];
  136. }
  137. /**
  138. * @dataProvider dataCheckCoreUpdate
  139. *
  140. * @param string $channel
  141. * @param mixed $versionCheck
  142. * @param null|string $version
  143. * @param null|string $readableVersion
  144. * @param null|int $errorDays
  145. */
  146. public function testCheckCoreUpdate(string $channel, $versionCheck, $version, $readableVersion, $errorDays) {
  147. $job = $this->getJob([
  148. 'getChannel',
  149. 'createNotifications',
  150. 'clearErrorNotifications',
  151. 'sendErrorNotifications',
  152. ]);
  153. $job->expects($this->once())
  154. ->method('getChannel')
  155. ->willReturn($channel);
  156. if ($versionCheck === null) {
  157. $this->versionCheck->expects($this->never())
  158. ->method('check');
  159. } else {
  160. $this->versionCheck->expects($this->once())
  161. ->method('check')
  162. ->willReturn($versionCheck);
  163. }
  164. if ($version === null) {
  165. $job->expects($this->never())
  166. ->method('createNotifications');
  167. $job->expects($versionCheck === null ? $this->never() : $this->once())
  168. ->method('clearErrorNotifications');
  169. } elseif ($version === false) {
  170. $job->expects($this->never())
  171. ->method('createNotifications');
  172. $job->expects($this->never())
  173. ->method('clearErrorNotifications');
  174. $this->appConfig->expects($this->once())
  175. ->method('getAppValueInt')
  176. ->willReturn($errorDays ?? 0);
  177. $this->appConfig->expects($this->once())
  178. ->method('setAppValueInt')
  179. ->with('update_check_errors', $errorDays + 1);
  180. $job->expects($errorDays !== null ? $this->once() : $this->never())
  181. ->method('sendErrorNotifications')
  182. ->with($errorDays + 1);
  183. } else {
  184. $this->appConfig->expects($this->once())
  185. ->method('setAppValueInt')
  186. ->with('update_check_errors', 0);
  187. $job->expects($this->once())
  188. ->method('clearErrorNotifications');
  189. $job->expects($this->once())
  190. ->method('createNotifications')
  191. ->with('core', $version, $readableVersion);
  192. }
  193. self::invokePrivate($job, 'checkCoreUpdate');
  194. }
  195. public function dataCheckAppUpdates(): array {
  196. return [
  197. [
  198. ['app1', 'app2'],
  199. [
  200. ['app1', false],
  201. ['app2', '1.9.2'],
  202. ],
  203. [
  204. ['app2', '1.9.2'],
  205. ],
  206. ],
  207. ];
  208. }
  209. /**
  210. * @dataProvider dataCheckAppUpdates
  211. *
  212. * @param string[] $apps
  213. * @param array $isUpdateAvailable
  214. * @param array $notifications
  215. */
  216. public function testCheckAppUpdates(array $apps, array $isUpdateAvailable, array $notifications) {
  217. $job = $this->getJob([
  218. 'isUpdateAvailable',
  219. 'createNotifications',
  220. ]);
  221. $this->appManager->expects($this->once())
  222. ->method('getInstalledApps')
  223. ->willReturn($apps);
  224. $job->expects($this->exactly(\count($apps)))
  225. ->method('isUpdateAvailable')
  226. ->willReturnMap($isUpdateAvailable);
  227. $mockedMethod = $job->expects($this->exactly(\count($notifications)))
  228. ->method('createNotifications');
  229. \call_user_func_array([$mockedMethod, 'withConsecutive'], $notifications);
  230. self::invokePrivate($job, 'checkAppUpdates');
  231. }
  232. public function dataCreateNotifications(): array {
  233. return [
  234. ['app1', '1.0.0', '1.0.0', false, false, null, null],
  235. ['app2', '1.0.1', '1.0.0', '1.0.0', true, ['user1'], [['user1']]],
  236. ['app3', '1.0.1', false, false, true, ['user2', 'user3'], [['user2'], ['user3']]],
  237. ];
  238. }
  239. /**
  240. * @dataProvider dataCreateNotifications
  241. *
  242. * @param string $app
  243. * @param string $version
  244. * @param string|false $lastNotification
  245. * @param string|false $callDelete
  246. * @param bool $createNotification
  247. * @param string[]|null $users
  248. * @param array|null $userNotifications
  249. */
  250. public function testCreateNotifications(string $app, string $version, $lastNotification, $callDelete, $createNotification, $users, $userNotifications) {
  251. $job = $this->getJob([
  252. 'deleteOutdatedNotifications',
  253. 'getUsersToNotify',
  254. ]);
  255. $this->appConfig->expects($this->once())
  256. ->method('getAppValueString')
  257. ->with($app, '')
  258. ->willReturn($lastNotification ? $lastNotification : '');
  259. if ($lastNotification !== $version) {
  260. $this->appConfig->expects($this->once())
  261. ->method('setAppValueString')
  262. ->with($app, $version);
  263. }
  264. if ($callDelete === false) {
  265. $job->expects($this->never())
  266. ->method('deleteOutdatedNotifications');
  267. } else {
  268. $job->expects($this->once())
  269. ->method('deleteOutdatedNotifications')
  270. ->with($app, $callDelete);
  271. }
  272. if ($users === null) {
  273. $job->expects($this->never())
  274. ->method('getUsersToNotify');
  275. } else {
  276. $job->expects($this->once())
  277. ->method('getUsersToNotify')
  278. ->willReturn($users);
  279. }
  280. if ($createNotification) {
  281. $notification = $this->createMock(INotification::class);
  282. $notification->expects($this->once())
  283. ->method('setApp')
  284. ->with('updatenotification')
  285. ->willReturnSelf();
  286. $notification->expects($this->once())
  287. ->method('setDateTime')
  288. ->willReturnSelf();
  289. $notification->expects($this->once())
  290. ->method('setObject')
  291. ->with($app, $version)
  292. ->willReturnSelf();
  293. $notification->expects($this->once())
  294. ->method('setSubject')
  295. ->with('update_available')
  296. ->willReturnSelf();
  297. if ($userNotifications !== null) {
  298. $mockedMethod = $notification->expects($this->exactly(\count($userNotifications)))
  299. ->method('setUser')
  300. ->willReturnSelf();
  301. \call_user_func_array([$mockedMethod, 'withConsecutive'], $userNotifications);
  302. $this->notificationManager->expects($this->exactly(\count($userNotifications)))
  303. ->method('notify');
  304. }
  305. $this->notificationManager->expects($this->once())
  306. ->method('createNotification')
  307. ->willReturn($notification);
  308. } else {
  309. $this->notificationManager->expects($this->never())
  310. ->method('createNotification');
  311. }
  312. self::invokePrivate($job, 'createNotifications', [$app, $version]);
  313. }
  314. public function dataGetUsersToNotify(): array {
  315. return [
  316. [['g1', 'g2'], ['g1' => null, 'g2' => ['u1', 'u2']], ['u1', 'u2']],
  317. [['g3', 'g4'], ['g3' => ['u1', 'u2'], 'g4' => ['u2', 'u3']], ['u1', 'u2', 'u3']],
  318. ];
  319. }
  320. /**
  321. * @dataProvider dataGetUsersToNotify
  322. * @param string[] $groups
  323. * @param array $groupUsers
  324. * @param string[] $expected
  325. */
  326. public function testGetUsersToNotify(array $groups, array $groupUsers, array $expected) {
  327. $job = $this->getJob();
  328. $this->appConfig->expects($this->once())
  329. ->method('getAppValueArray')
  330. ->with('notify_groups', ['admin'])
  331. ->willReturn($groups);
  332. $groupMap = [];
  333. foreach ($groupUsers as $gid => $uids) {
  334. if ($uids === null) {
  335. $group = null;
  336. } else {
  337. $group = $this->getGroup($gid);
  338. $group->expects($this->any())
  339. ->method('getUsers')
  340. ->willReturn($this->getUsers($uids));
  341. }
  342. $groupMap[] = [$gid, $group];
  343. }
  344. $this->groupManager->expects($this->exactly(\count($groups)))
  345. ->method('get')
  346. ->willReturnMap($groupMap);
  347. $result = self::invokePrivate($job, 'getUsersToNotify');
  348. $this->assertEquals($expected, $result);
  349. // Test caching
  350. $result = self::invokePrivate($job, 'getUsersToNotify');
  351. $this->assertEquals($expected, $result);
  352. }
  353. public function dataDeleteOutdatedNotifications(): array {
  354. return [
  355. ['app1', '1.1.0'],
  356. ['app2', '1.2.0'],
  357. ];
  358. }
  359. /**
  360. * @dataProvider dataDeleteOutdatedNotifications
  361. * @param string $app
  362. * @param string $version
  363. */
  364. public function testDeleteOutdatedNotifications(string $app, string $version) {
  365. $notification = $this->createMock(INotification::class);
  366. $notification->expects($this->once())
  367. ->method('setApp')
  368. ->with('updatenotification')
  369. ->willReturnSelf();
  370. $notification->expects($this->once())
  371. ->method('setObject')
  372. ->with($app, $version)
  373. ->willReturnSelf();
  374. $this->notificationManager->expects($this->once())
  375. ->method('createNotification')
  376. ->willReturn($notification);
  377. $this->notificationManager->expects($this->once())
  378. ->method('markProcessed')
  379. ->with($notification);
  380. $job = $this->getJob();
  381. self::invokePrivate($job, 'deleteOutdatedNotifications', [$app, $version]);
  382. }
  383. /**
  384. * @param string[] $userIds
  385. * @return IUser[]|MockObject[]
  386. */
  387. protected function getUsers(array $userIds): array {
  388. $users = [];
  389. foreach ($userIds as $uid) {
  390. $user = $this->createMock(IUser::class);
  391. $user->expects($this->any())
  392. ->method('getUID')
  393. ->willReturn($uid);
  394. $users[] = $user;
  395. }
  396. return $users;
  397. }
  398. /**
  399. * @param string $gid
  400. * @return \OCP\IGroup|MockObject
  401. */
  402. protected function getGroup(string $gid) {
  403. $group = $this->createMock(IGroup::class);
  404. $group->expects($this->any())
  405. ->method('getGID')
  406. ->willReturn($gid);
  407. return $group;
  408. }
  409. }