UpdateAvailableNotificationsTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  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 OCP\ServerVersion;
  21. use PHPUnit\Framework\MockObject\MockObject;
  22. use Test\TestCase;
  23. class UpdateAvailableNotificationsTest extends TestCase {
  24. private ServerVersion $serverVersion;
  25. private IConfig|MockObject $config;
  26. private IManager|MockObject $notificationManager;
  27. private IGroupManager|MockObject $groupManager;
  28. private IAppManager|MockObject $appManager;
  29. private IAppConfig|MockObject $appConfig;
  30. private ITimeFactory|MockObject $timeFactory;
  31. private Installer|MockObject $installer;
  32. private VersionCheck|MockObject $versionCheck;
  33. protected function setUp(): void {
  34. parent::setUp();
  35. $this->serverVersion = $this->createMock(ServerVersion::class);
  36. $this->config = $this->createMock(IConfig::class);
  37. $this->appConfig = $this->createMock(IAppConfig::class);
  38. $this->notificationManager = $this->createMock(IManager::class);
  39. $this->groupManager = $this->createMock(IGroupManager::class);
  40. $this->appManager = $this->createMock(IAppManager::class);
  41. $this->timeFactory = $this->createMock(ITimeFactory::class);
  42. $this->installer = $this->createMock(Installer::class);
  43. $this->versionCheck = $this->createMock(VersionCheck::class);
  44. }
  45. /**
  46. * @param array $methods
  47. * @return UpdateAvailableNotifications|MockObject
  48. */
  49. protected function getJob(array $methods = []) {
  50. if (empty($methods)) {
  51. return new UpdateAvailableNotifications(
  52. $this->timeFactory,
  53. $this->serverVersion,
  54. $this->config,
  55. $this->appConfig,
  56. $this->notificationManager,
  57. $this->groupManager,
  58. $this->appManager,
  59. $this->installer,
  60. $this->versionCheck,
  61. );
  62. }
  63. {
  64. return $this->getMockBuilder(UpdateAvailableNotifications::class)
  65. ->setConstructorArgs([
  66. $this->timeFactory,
  67. $this->serverVersion,
  68. $this->config,
  69. $this->appConfig,
  70. $this->notificationManager,
  71. $this->groupManager,
  72. $this->appManager,
  73. $this->installer,
  74. $this->versionCheck,
  75. ])
  76. ->onlyMethods($methods)
  77. ->getMock();
  78. }
  79. }
  80. public function testRun(): void {
  81. $job = $this->getJob([
  82. 'checkCoreUpdate',
  83. 'checkAppUpdates',
  84. ]);
  85. $job->expects($this->once())
  86. ->method('checkCoreUpdate');
  87. $job->expects($this->once())
  88. ->method('checkAppUpdates');
  89. $this->config->expects($this->exactly(2))
  90. ->method('getSystemValueBool')
  91. ->willReturnMap([
  92. ['has_internet_connection', true, true],
  93. ['debug', false, true],
  94. ]);
  95. self::invokePrivate($job, 'run', [null]);
  96. }
  97. public function testRunNoInternet(): void {
  98. $job = $this->getJob([
  99. 'checkCoreUpdate',
  100. 'checkAppUpdates',
  101. ]);
  102. $job->expects($this->never())
  103. ->method('checkCoreUpdate');
  104. $job->expects($this->never())
  105. ->method('checkAppUpdates');
  106. $this->config->method('getSystemValueBool')
  107. ->with('has_internet_connection', true)
  108. ->willReturn(false);
  109. self::invokePrivate($job, 'run', [null]);
  110. }
  111. public function dataCheckCoreUpdate(): array {
  112. return [
  113. ['daily', null, null, null, null],
  114. ['git', null, null, null, null],
  115. ['beta', [], null, null, null],
  116. ['beta', false, false, null, null],
  117. ['beta', false, false, null, 13],
  118. ['beta', [
  119. 'version' => '9.2.0',
  120. 'versionstring' => 'Nextcloud 11.0.0',
  121. ], '9.2.0', 'Nextcloud 11.0.0', null],
  122. ['stable', [], null, null, null],
  123. ['stable', false, false, null, null],
  124. ['stable', false, false, null, 6],
  125. ['stable', [
  126. 'version' => '9.2.0',
  127. 'versionstring' => 'Nextcloud 11.0.0',
  128. ], '9.2.0', 'Nextcloud 11.0.0', null],
  129. ['production', [], null, null, null],
  130. ['production', false, false, null, null],
  131. ['production', false, false, null, 2],
  132. ['production', [
  133. 'version' => '9.2.0',
  134. 'versionstring' => 'Nextcloud 11.0.0',
  135. ], '9.2.0', 'Nextcloud 11.0.0', null],
  136. ];
  137. }
  138. /**
  139. * @dataProvider dataCheckCoreUpdate
  140. *
  141. * @param string $channel
  142. * @param mixed $versionCheck
  143. * @param null|string $version
  144. * @param null|string $readableVersion
  145. * @param null|int $errorDays
  146. */
  147. public function testCheckCoreUpdate(string $channel, $versionCheck, $version, $readableVersion, $errorDays): void {
  148. $job = $this->getJob([
  149. 'createNotifications',
  150. 'clearErrorNotifications',
  151. 'sendErrorNotifications',
  152. ]);
  153. $this->serverVersion->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): void {
  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. $i = 0;
  228. $job->expects($this->exactly(\count($notifications)))
  229. ->method('createNotifications')
  230. ->willReturnCallback(function () use ($notifications, &$i): void {
  231. $this->assertEquals($notifications[$i], func_get_args());
  232. $i++;
  233. });
  234. self::invokePrivate($job, 'checkAppUpdates');
  235. }
  236. public function dataCreateNotifications(): array {
  237. return [
  238. ['app1', '1.0.0', '1.0.0', false, false, null, null],
  239. ['app2', '1.0.1', '1.0.0', '1.0.0', true, ['user1'], [['user1']]],
  240. ['app3', '1.0.1', false, false, true, ['user2', 'user3'], [['user2'], ['user3']]],
  241. ];
  242. }
  243. /**
  244. * @dataProvider dataCreateNotifications
  245. *
  246. * @param string $app
  247. * @param string $version
  248. * @param string|false $lastNotification
  249. * @param string|false $callDelete
  250. * @param bool $createNotification
  251. * @param string[]|null $users
  252. * @param array|null $userNotifications
  253. */
  254. public function testCreateNotifications(string $app, string $version, $lastNotification, $callDelete, $createNotification, $users, $userNotifications): void {
  255. $job = $this->getJob([
  256. 'deleteOutdatedNotifications',
  257. 'getUsersToNotify',
  258. ]);
  259. $this->appConfig->expects($this->once())
  260. ->method('getAppValueString')
  261. ->with($app, '')
  262. ->willReturn($lastNotification ?: '');
  263. if ($lastNotification !== $version) {
  264. $this->appConfig->expects($this->once())
  265. ->method('setAppValueString')
  266. ->with($app, $version);
  267. }
  268. if ($callDelete === false) {
  269. $job->expects($this->never())
  270. ->method('deleteOutdatedNotifications');
  271. } else {
  272. $job->expects($this->once())
  273. ->method('deleteOutdatedNotifications')
  274. ->with($app, $callDelete);
  275. }
  276. if ($users === null) {
  277. $job->expects($this->never())
  278. ->method('getUsersToNotify');
  279. } else {
  280. $job->expects($this->once())
  281. ->method('getUsersToNotify')
  282. ->willReturn($users);
  283. }
  284. if ($createNotification) {
  285. $notification = $this->createMock(INotification::class);
  286. $notification->expects($this->once())
  287. ->method('setApp')
  288. ->with('updatenotification')
  289. ->willReturnSelf();
  290. $notification->expects($this->once())
  291. ->method('setDateTime')
  292. ->willReturnSelf();
  293. $notification->expects($this->once())
  294. ->method('setObject')
  295. ->with($app, $version)
  296. ->willReturnSelf();
  297. $notification->expects($this->once())
  298. ->method('setSubject')
  299. ->with('update_available')
  300. ->willReturnSelf();
  301. if ($userNotifications !== null) {
  302. $notification->expects($this->exactly(\count($userNotifications)))
  303. ->method('setUser')
  304. ->willReturnSelf();
  305. $this->notificationManager->expects($this->exactly(\count($userNotifications)))
  306. ->method('notify');
  307. }
  308. $this->notificationManager->expects($this->once())
  309. ->method('createNotification')
  310. ->willReturn($notification);
  311. } else {
  312. $this->notificationManager->expects($this->never())
  313. ->method('createNotification');
  314. }
  315. self::invokePrivate($job, 'createNotifications', [$app, $version]);
  316. }
  317. public function dataGetUsersToNotify(): array {
  318. return [
  319. [['g1', 'g2'], ['g1' => null, 'g2' => ['u1', 'u2']], ['u1', 'u2']],
  320. [['g3', 'g4'], ['g3' => ['u1', 'u2'], 'g4' => ['u2', 'u3']], ['u1', 'u2', 'u3']],
  321. ];
  322. }
  323. /**
  324. * @dataProvider dataGetUsersToNotify
  325. * @param string[] $groups
  326. * @param array $groupUsers
  327. * @param string[] $expected
  328. */
  329. public function testGetUsersToNotify(array $groups, array $groupUsers, array $expected): void {
  330. $job = $this->getJob();
  331. $this->appConfig->expects($this->once())
  332. ->method('getAppValueArray')
  333. ->with('notify_groups', ['admin'])
  334. ->willReturn($groups);
  335. $groupMap = [];
  336. foreach ($groupUsers as $gid => $uids) {
  337. if ($uids === null) {
  338. $group = null;
  339. } else {
  340. $group = $this->getGroup($gid);
  341. $group->expects($this->any())
  342. ->method('getUsers')
  343. ->willReturn($this->getUsers($uids));
  344. }
  345. $groupMap[] = [$gid, $group];
  346. }
  347. $this->groupManager->expects($this->exactly(\count($groups)))
  348. ->method('get')
  349. ->willReturnMap($groupMap);
  350. $result = self::invokePrivate($job, 'getUsersToNotify');
  351. $this->assertEquals($expected, $result);
  352. // Test caching
  353. $result = self::invokePrivate($job, 'getUsersToNotify');
  354. $this->assertEquals($expected, $result);
  355. }
  356. public function dataDeleteOutdatedNotifications(): array {
  357. return [
  358. ['app1', '1.1.0'],
  359. ['app2', '1.2.0'],
  360. ];
  361. }
  362. /**
  363. * @dataProvider dataDeleteOutdatedNotifications
  364. * @param string $app
  365. * @param string $version
  366. */
  367. public function testDeleteOutdatedNotifications(string $app, string $version): void {
  368. $notification = $this->createMock(INotification::class);
  369. $notification->expects($this->once())
  370. ->method('setApp')
  371. ->with('updatenotification')
  372. ->willReturnSelf();
  373. $notification->expects($this->once())
  374. ->method('setObject')
  375. ->with($app, $version)
  376. ->willReturnSelf();
  377. $this->notificationManager->expects($this->once())
  378. ->method('createNotification')
  379. ->willReturn($notification);
  380. $this->notificationManager->expects($this->once())
  381. ->method('markProcessed')
  382. ->with($notification);
  383. $job = $this->getJob();
  384. self::invokePrivate($job, 'deleteOutdatedNotifications', [$app, $version]);
  385. }
  386. /**
  387. * @param string[] $userIds
  388. * @return IUser[]|MockObject[]
  389. */
  390. protected function getUsers(array $userIds): array {
  391. $users = [];
  392. foreach ($userIds as $uid) {
  393. $user = $this->createMock(IUser::class);
  394. $user->expects($this->any())
  395. ->method('getUID')
  396. ->willReturn($uid);
  397. $users[] = $user;
  398. }
  399. return $users;
  400. }
  401. /**
  402. * @param string $gid
  403. * @return IGroup|MockObject
  404. */
  405. protected function getGroup(string $gid) {
  406. $group = $this->createMock(IGroup::class);
  407. $group->expects($this->any())
  408. ->method('getGID')
  409. ->willReturn($gid);
  410. return $group;
  411. }
  412. }