diff --git a/apps/updatenotification/appinfo/app.php b/apps/updatenotification/appinfo/app.php index de78da5da7..e9780757cc 100644 --- a/apps/updatenotification/appinfo/app.php +++ b/apps/updatenotification/appinfo/app.php @@ -45,7 +45,8 @@ if(\OC::$server->getConfig()->getSystemValue('updatechecker', true) === true) { $manager, \OC::$server->getL10NFactory() ); - }, function() use ($l) { + }, function() { + $l = \OC::$server->getL10N('updatenotification'); return [ 'id' => 'updatenotification', 'name' => $l->t('Update notifications'), diff --git a/apps/updatenotification/lib/Controller/AdminController.php b/apps/updatenotification/lib/Controller/AdminController.php index c622b9690a..2fc12140b2 100644 --- a/apps/updatenotification/lib/Controller/AdminController.php +++ b/apps/updatenotification/lib/Controller/AdminController.php @@ -101,7 +101,7 @@ class AdminController extends Controller { } $updateState = $this->updateChecker->getUpdateState(); - $notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]')); + $notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]'), true); $params = [ 'isNewVersionAvailable' => ($updateState === []) ? false : true, diff --git a/apps/updatenotification/lib/Notification/BackgroundJob.php b/apps/updatenotification/lib/Notification/BackgroundJob.php index f2cf7e1cd8..3a89f3813e 100644 --- a/apps/updatenotification/lib/Notification/BackgroundJob.php +++ b/apps/updatenotification/lib/Notification/BackgroundJob.php @@ -82,15 +82,12 @@ class BackgroundJob extends TimedJob { * Check for ownCloud update */ protected function checkCoreUpdate() { - if (in_array(\OC_Util::getChannel(), ['daily', 'git'])) { + if (in_array($this->getChannel(), ['daily', 'git'])) { // "These aren't the update channels you're looking for." - Ben Obi-Wan Kenobi return; } - $updater = new VersionCheck( - $this->client, - $this->config - ); + $updater = $this->createVersionCheck(); $status = $updater->check(); if (isset($status['version'])) { @@ -104,7 +101,7 @@ class BackgroundJob extends TimedJob { protected function checkAppUpdates() { $apps = $this->appManager->getInstalledApps(); foreach ($apps as $app) { - $update = Installer::isUpdateAvailable($app); + $update = $this->isUpdateAvailable($app); if ($update !== false) { $this->createNotifications($app, $update); } @@ -134,8 +131,8 @@ class BackgroundJob extends TimedJob { ->setObject($app, $version) ->setSubject('update_available'); - foreach ($this->getUsersToNotify() as $user) { - $notification->setUser($user->getUID()); + foreach ($this->getUsersToNotify() as $uid) { + $notification->setUser($uid); $this->notificationManager->notify($notification); } @@ -143,21 +140,26 @@ class BackgroundJob extends TimedJob { } /** - * @return \OCP\IUser[] + * @return string[] */ protected function getUsersToNotify() { if ($this->users !== null) { return $this->users; } - $notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]')); + $notifyGroups = json_decode($this->config->getAppValue('updatenotification', 'notify_groups', '["admin"]'), true); + $this->users = []; foreach ($notifyGroups as $group) { $groupToNotify = $this->groupManager->get($group); if ($groupToNotify instanceof IGroup) { - $this->users = array_merge($this->users, $groupToNotify->getUsers()); + foreach ($groupToNotify->getUsers() as $user) { + $this->users[$user->getUID()] = true; + } } } + $this->users = array_keys($this->users); + return $this->users; } @@ -173,4 +175,29 @@ class BackgroundJob extends TimedJob { ->setObject($app, $version); $this->notificationManager->markProcessed($notification); } + + /** + * @return VersionCheck + */ + protected function createVersionCheck() { + return new VersionCheck( + $this->client, + $this->config + ); + } + + /** + * @return string + */ + protected function getChannel() { + return \OC_Util::getChannel(); + } + + /** + * @param string $app + * @return string|false + */ + protected function isUpdateAvailable($app) { + return Installer::isUpdateAvailable($app); + } } diff --git a/apps/updatenotification/tests/Notification/BackgroundJobTest.php b/apps/updatenotification/tests/Notification/BackgroundJobTest.php new file mode 100644 index 0000000000..9b920ec4ce --- /dev/null +++ b/apps/updatenotification/tests/Notification/BackgroundJobTest.php @@ -0,0 +1,409 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\UpdateNotification\Tests\Notification; + + +use OC\Installer; +use OC\Updater\VersionCheck; +use OCA\UpdateNotification\Notification\BackgroundJob; +use OCP\App\IAppManager; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\IGroup; +use OCP\IGroupManager; +use OCP\IUser; +use OCP\Notification\IManager; +use Test\TestCase; + +class BackgroundJobTest extends TestCase { + + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + protected $config; + /** @var IManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $notificationManager; + /** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $groupManager; + /** @var IAppManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $appManager; + /** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */ + protected $client; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMock('OCP\IConfig'); + $this->notificationManager = $this->getMock('OCP\Notification\IManager'); + $this->groupManager = $this->getMock('OCP\IGroupManager'); + $this->appManager = $this->getMock('OCP\App\IAppManager'); + $this->client = $this->getMock('OCP\Http\Client\IClientService'); + } + + /** + * @param array $methods + * @return BackgroundJob|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getJob(array $methods = []) { + if (empty($methods)) { + return new BackgroundJob( + $this->config, + $this->notificationManager, + $this->groupManager, + $this->appManager, + $this->client + ); + } { + return $this->getMockBuilder('OCA\UpdateNotification\Notification\BackgroundJob') + ->setConstructorArgs([ + $this->config, + $this->notificationManager, + $this->groupManager, + $this->appManager, + $this->client, + ]) + ->setMethods($methods) + ->getMock(); + } + } + + public function testRun() { + $job = $this->getJob([ + 'checkCoreUpdate', + 'checkAppUpdates', + ]); + + $job->expects($this->once()) + ->method('checkCoreUpdate'); + $job->expects($this->once()) + ->method('checkAppUpdates'); + + $this->invokePrivate($job, 'run', [null]); + } + + public function dataCheckCoreUpdate() { + return [ + ['daily', null, null], + ['git', null, null], + ['beta', false, null], + ['beta', [ + 'version' => '9.2.0', + ], '9.2.0'], + ['stable', false, null], + ['stable', [ + 'version' => '9.2.0', + ], '9.2.0'], + ['production', false, null], + ['production', [ + 'version' => '9.2.0', + ], '9.2.0'], + ]; + } + + /** + * @dataProvider dataCheckCoreUpdate + * + * @param string $channel + * @param mixed $versionCheck + * @param null|string $notification + */ + public function testCheckCoreUpdate($channel, $versionCheck, $notification) { + $job = $this->getJob([ + 'getChannel', + 'createVersionCheck', + 'createNotifications', + ]); + + $job->expects($this->once()) + ->method('getChannel') + ->willReturn($channel); + + if ($versionCheck === null) { + $job->expects($this->never()) + ->method('createVersionCheck'); + } else { + $check = $this->getMockBuilder('OC\Updater\VersionCheck') + ->disableOriginalConstructor() + ->getMock(); + $check->expects($this->once()) + ->method('check') + ->willReturn($versionCheck); + + $job->expects($this->once()) + ->method('createVersionCheck') + ->willReturn($check); + } + + if ($notification === null) { + $job->expects($this->never()) + ->method('createNotifications'); + } else { + $job->expects($this->once()) + ->method('createNotifications') + ->willReturn('core', $notification); + } + + $this->invokePrivate($job, 'checkCoreUpdate'); + } + + public function dataCheckAppUpdates() { + return [ + [ + ['app1', 'app2'], + [ + ['app1', false], + ['app2', '1.9.2'], + ], + [ + ['app2', '1.9.2'], + ], + ], + ]; + } + + /** + * @dataProvider dataCheckAppUpdates + * + * @param string[] $apps + * @param array $isUpdateAvailable + * @param array $notifications + */ + public function testCheckAppUpdates(array $apps, array $isUpdateAvailable, array $notifications) { + $job = $this->getJob([ + 'isUpdateAvailable', + 'createNotifications', + ]); + + $this->appManager->expects($this->once()) + ->method('getInstalledApps') + ->willReturn($apps); + + $job->expects($this->exactly(sizeof($apps))) + ->method('isUpdateAvailable') + ->willReturnMap($isUpdateAvailable); + + $mockedMethod = $job->expects($this->exactly(sizeof($notifications))) + ->method('createNotifications'); + call_user_func_array([$mockedMethod, 'withConsecutive'], $notifications); + + $this->invokePrivate($job, 'checkAppUpdates'); + } + + public function dataCreateNotifications() { + return [ + ['app1', '1.0.0', '1.0.0', false, false, null, null], + ['app2', '1.0.1', '1.0.0', '1.0.0', true, ['user1'], [['user1']]], + ['app3', '1.0.1', false, false, true, ['user2', 'user3'], [['user2'], ['user3']]], + ]; + } + + /** + * @dataProvider dataCreateNotifications + * + * @param string $app + * @param string $version + * @param string|false $lastNotification + * @param string|false $callDelete + * @param bool $createNotification + * @param string[]|null $users + * @param array|null $userNotifications + */ + public function testCreateNotifications($app, $version, $lastNotification, $callDelete, $createNotification, $users, $userNotifications) { + $job = $this->getJob([ + 'deleteOutdatedNotifications', + 'getUsersToNotify', + ]); + + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('updatenotification', $app, false) + ->willReturn($lastNotification); + + if ($lastNotification !== $version) { + $this->config->expects($this->once()) + ->method('setAppValue') + ->with('updatenotification', $app, $version); + } + + if ($callDelete === false) { + $job->expects($this->never()) + ->method('deleteOutdatedNotifications'); + } else { + $job->expects($this->once()) + ->method('deleteOutdatedNotifications') + ->with($app, $callDelete); + } + + if ($users === null) { + $job->expects($this->never()) + ->method('getUsersToNotify'); + } else { + $job->expects($this->once()) + ->method('getUsersToNotify') + ->willReturn($users); + } + + if ($createNotification) { + $notification = $this->getMock('OCP\Notification\INotification'); + $notification->expects($this->once()) + ->method('setApp') + ->with('updatenotification') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setDateTime') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setObject') + ->with($app, $version) + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setSubject') + ->with('update_available') + ->willReturnSelf(); + + if ($userNotifications !== null) { + $mockedMethod = $notification->expects($this->exactly(sizeof($userNotifications))) + ->method('setUser') + ->willReturnSelf(); + call_user_func_array([$mockedMethod, 'withConsecutive'], $userNotifications); + + $this->notificationManager->expects($this->exactly(sizeof($userNotifications))) + ->method('notify') + ->willReturn($notification); + } + + $this->notificationManager->expects($this->once()) + ->method('createNotification') + ->willReturn($notification); + } else { + $this->notificationManager->expects($this->never()) + ->method('createNotification'); + } + + $this->invokePrivate($job, 'createNotifications', [$app, $version]); + } + + public function dataGetUsersToNotify() { + return [ + [['g1', 'g2'], ['g1' => null, 'g2' => ['u1', 'u2']], ['u1', 'u2']], + [['g3', 'g4'], ['g3' => ['u1', 'u2'], 'g4' => ['u2', 'u3']], ['u1', 'u2', 'u3']], + ]; + } + + /** + * @dataProvider dataGetUsersToNotify + * @param string[] $groups + * @param array $groupUsers + * @param string[] $expected + */ + public function testGetUsersToNotify($groups, array $groupUsers, array $expected) { + $job = $this->getJob(); + + $this->config->expects($this->once()) + ->method('getAppValue') + ->with('updatenotification', 'notify_groups', '["admin"]') + ->willReturn(json_encode($groups)); + + $groupMap = []; + foreach ($groupUsers as $gid => $uids) { + if ($uids === null) { + $group = null; + } else { + $group = $this->getGroup($gid); + $group->expects($this->any()) + ->method('getUsers') + ->willReturn($this->getUsers($uids)); + } + $groupMap[] = [$gid, $group]; + } + $this->groupManager->expects($this->exactly(sizeof($groups))) + ->method('get') + ->willReturnMap($groupMap); + + $result = $this->invokePrivate($job, 'getUsersToNotify'); + $this->assertEquals($expected, $result); + + // Test caching + $result = $this->invokePrivate($job, 'getUsersToNotify'); + $this->assertEquals($expected, $result); + } + + public function dataDeleteOutdatedNotifications() { + return [ + ['app1', '1.1.0'], + ['app2', '1.2.0'], + ]; + } + + /** + * @dataProvider dataDeleteOutdatedNotifications + * @param string $app + * @param string $version + */ + public function testDeleteOutdatedNotifications($app, $version) { + $notification = $this->getMock('OCP\Notification\INotification'); + $notification->expects($this->once()) + ->method('setApp') + ->with('updatenotification') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setObject') + ->with($app, $version) + ->willReturnSelf(); + + $this->notificationManager->expects($this->once()) + ->method('createNotification') + ->willReturn($notification); + $this->notificationManager->expects($this->once()) + ->method('markProcessed') + ->with($notification); + + $job = $this->getJob(); + $this->invokePrivate($job, 'deleteOutdatedNotifications', [$app, $version]); + } + + /** + * @param string[] $userIds + * @return IUser[]|\PHPUnit_Framework_MockObject_MockObject[] + */ + protected function getUsers(array $userIds) { + $users = []; + foreach ($userIds as $uid) { + $user = $this->getMock('OCP\IUser'); + $user->expects($this->any()) + ->method('getUID') + ->willReturn($uid); + $users[] = $user; + } + return $users; + } + + /** + * @param $gid + * @return \OCP\IGroup|\PHPUnit_Framework_MockObject_MockObject + */ + protected function getGroup($gid) { + $group = $this->getMock('OCP\IGroup'); + $group->expects($this->any()) + ->method('getGID') + ->willReturn($gid); + return $group; + } +}