AppManagerTest.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  6. * SPDX-License-Identifier: AGPL-3.0-or-later
  7. */
  8. namespace Test\App;
  9. use OC\App\AppManager;
  10. use OC\AppConfig;
  11. use OCP\App\AppPathNotFoundException;
  12. use OCP\App\Events\AppDisableEvent;
  13. use OCP\App\Events\AppEnableEvent;
  14. use OCP\App\IAppManager;
  15. use OCP\EventDispatcher\IEventDispatcher;
  16. use OCP\ICache;
  17. use OCP\ICacheFactory;
  18. use OCP\IConfig;
  19. use OCP\IGroup;
  20. use OCP\IGroupManager;
  21. use OCP\IURLGenerator;
  22. use OCP\IUser;
  23. use OCP\IUserSession;
  24. use PHPUnit\Framework\MockObject\MockObject;
  25. use Psr\Log\LoggerInterface;
  26. use Test\TestCase;
  27. /**
  28. * Class AppManagerTest
  29. *
  30. * @package Test\App
  31. */
  32. class AppManagerTest extends TestCase {
  33. /**
  34. * @return AppConfig|MockObject
  35. */
  36. protected function getAppConfig() {
  37. $appConfig = [];
  38. $config = $this->createMock(AppConfig::class);
  39. $config->expects($this->any())
  40. ->method('getValue')
  41. ->willReturnCallback(function ($app, $key, $default) use (&$appConfig) {
  42. return (isset($appConfig[$app]) and isset($appConfig[$app][$key])) ? $appConfig[$app][$key] : $default;
  43. });
  44. $config->expects($this->any())
  45. ->method('setValue')
  46. ->willReturnCallback(function ($app, $key, $value) use (&$appConfig) {
  47. if (!isset($appConfig[$app])) {
  48. $appConfig[$app] = [];
  49. }
  50. $appConfig[$app][$key] = $value;
  51. });
  52. $config->expects($this->any())
  53. ->method('getValues')
  54. ->willReturnCallback(function ($app, $key) use (&$appConfig) {
  55. if ($app) {
  56. return $appConfig[$app];
  57. } else {
  58. $values = [];
  59. foreach ($appConfig as $appid => $appData) {
  60. if (isset($appData[$key])) {
  61. $values[$appid] = $appData[$key];
  62. }
  63. }
  64. return $values;
  65. }
  66. });
  67. return $config;
  68. }
  69. /** @var IUserSession|MockObject */
  70. protected $userSession;
  71. /** @var IConfig|MockObject */
  72. private $config;
  73. /** @var IGroupManager|MockObject */
  74. protected $groupManager;
  75. /** @var AppConfig|MockObject */
  76. protected $appConfig;
  77. /** @var ICache|MockObject */
  78. protected $cache;
  79. /** @var ICacheFactory|MockObject */
  80. protected $cacheFactory;
  81. /** @var IEventDispatcher|MockObject */
  82. protected $eventDispatcher;
  83. /** @var LoggerInterface|MockObject */
  84. protected $logger;
  85. protected IURLGenerator&MockObject $urlGenerator;
  86. /** @var IAppManager */
  87. protected $manager;
  88. protected function setUp(): void {
  89. parent::setUp();
  90. $this->userSession = $this->createMock(IUserSession::class);
  91. $this->groupManager = $this->createMock(IGroupManager::class);
  92. $this->config = $this->createMock(IConfig::class);
  93. $this->appConfig = $this->getAppConfig();
  94. $this->cacheFactory = $this->createMock(ICacheFactory::class);
  95. $this->cache = $this->createMock(ICache::class);
  96. $this->eventDispatcher = $this->createMock(IEventDispatcher::class);
  97. $this->logger = $this->createMock(LoggerInterface::class);
  98. $this->urlGenerator = $this->createMock(IURLGenerator::class);
  99. $this->overwriteService(AppConfig::class, $this->appConfig);
  100. $this->overwriteService(IURLGenerator::class, $this->urlGenerator);
  101. $this->cacheFactory->expects($this->any())
  102. ->method('createDistributed')
  103. ->with('settings')
  104. ->willReturn($this->cache);
  105. $this->config
  106. ->method('getSystemValueBool')
  107. ->with('installed', false)
  108. ->willReturn(true);
  109. $this->manager = new AppManager(
  110. $this->userSession,
  111. $this->config,
  112. $this->groupManager,
  113. $this->cacheFactory,
  114. $this->eventDispatcher,
  115. $this->logger,
  116. );
  117. }
  118. /**
  119. * @dataProvider dataGetAppIcon
  120. */
  121. public function testGetAppIcon($callback, ?bool $dark, string|null $expected) {
  122. $this->urlGenerator->expects($this->atLeastOnce())
  123. ->method('imagePath')
  124. ->willReturnCallback($callback);
  125. if ($dark !== null) {
  126. $this->assertEquals($expected, $this->manager->getAppIcon('test', $dark));
  127. } else {
  128. $this->assertEquals($expected, $this->manager->getAppIcon('test'));
  129. }
  130. }
  131. public function dataGetAppIcon(): array {
  132. $nothing = function ($appId) {
  133. $this->assertEquals('test', $appId);
  134. throw new \RuntimeException();
  135. };
  136. $createCallback = function ($workingIcons) {
  137. return function ($appId, $icon) use ($workingIcons) {
  138. $this->assertEquals('test', $appId);
  139. if (in_array($icon, $workingIcons)) {
  140. return '/path/' . $icon;
  141. }
  142. throw new \RuntimeException();
  143. };
  144. };
  145. return [
  146. 'does not find anything' => [
  147. $nothing,
  148. false,
  149. null,
  150. ],
  151. 'nothing if request dark but only bright available' => [
  152. $createCallback(['app.svg']),
  153. true,
  154. null,
  155. ],
  156. 'nothing if request bright but only dark available' => [
  157. $createCallback(['app-dark.svg']),
  158. false,
  159. null,
  160. ],
  161. 'bright and only app.svg' => [
  162. $createCallback(['app.svg']),
  163. false,
  164. '/path/app.svg',
  165. ],
  166. 'dark and only app-dark.svg' => [
  167. $createCallback(['app-dark.svg']),
  168. true,
  169. '/path/app-dark.svg',
  170. ],
  171. 'dark only appname -dark.svg' => [
  172. $createCallback(['test-dark.svg']),
  173. true,
  174. '/path/test-dark.svg',
  175. ],
  176. 'bright and only appname.svg' => [
  177. $createCallback(['test.svg']),
  178. false,
  179. '/path/test.svg',
  180. ],
  181. 'priotize custom over default' => [
  182. $createCallback(['app.svg', 'test.svg']),
  183. false,
  184. '/path/test.svg',
  185. ],
  186. 'defaults to bright' => [
  187. $createCallback(['test-dark.svg', 'test.svg']),
  188. null,
  189. '/path/test.svg',
  190. ],
  191. 'no dark icon on default' => [
  192. $createCallback(['test-dark.svg', 'test.svg', 'app-dark.svg', 'app.svg']),
  193. false,
  194. '/path/test.svg',
  195. ],
  196. 'no bright icon on dark' => [
  197. $createCallback(['test-dark.svg', 'test.svg', 'app-dark.svg', 'app.svg']),
  198. true,
  199. '/path/test-dark.svg',
  200. ],
  201. ];
  202. }
  203. public function testEnableApp() {
  204. // making sure "files_trashbin" is disabled
  205. if ($this->manager->isEnabledForUser('files_trashbin')) {
  206. $this->manager->disableApp('files_trashbin');
  207. }
  208. $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('files_trashbin'));
  209. $this->manager->enableApp('files_trashbin');
  210. $this->assertEquals('yes', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
  211. }
  212. public function testDisableApp() {
  213. $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppDisableEvent('files_trashbin'));
  214. $this->manager->disableApp('files_trashbin');
  215. $this->assertEquals('no', $this->appConfig->getValue('files_trashbin', 'enabled', 'no'));
  216. }
  217. public function testNotEnableIfNotInstalled() {
  218. try {
  219. $this->manager->enableApp('some_random_name_which_i_hope_is_not_an_app');
  220. $this->assertFalse(true, 'If this line is reached the expected exception is not thrown.');
  221. } catch (AppPathNotFoundException $e) {
  222. // Exception is expected
  223. $this->assertEquals('Could not find path for some_random_name_which_i_hope_is_not_an_app', $e->getMessage());
  224. }
  225. $this->assertEquals('no', $this->appConfig->getValue(
  226. 'some_random_name_which_i_hope_is_not_an_app', 'enabled', 'no'
  227. ));
  228. }
  229. public function testEnableAppForGroups() {
  230. $group1 = $this->createMock(IGroup::class);
  231. $group1->method('getGID')
  232. ->willReturn('group1');
  233. $group2 = $this->createMock(IGroup::class);
  234. $group2->method('getGID')
  235. ->willReturn('group2');
  236. $groups = [$group1, $group2];
  237. /** @var AppManager|MockObject $manager */
  238. $manager = $this->getMockBuilder(AppManager::class)
  239. ->setConstructorArgs([
  240. $this->userSession,
  241. $this->config,
  242. $this->groupManager,
  243. $this->cacheFactory,
  244. $this->eventDispatcher,
  245. $this->logger,
  246. ])
  247. ->onlyMethods([
  248. 'getAppPath',
  249. ])
  250. ->getMock();
  251. $manager->expects($this->exactly(2))
  252. ->method('getAppPath')
  253. ->with('test')
  254. ->willReturn('apps/test');
  255. $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
  256. $manager->enableAppForGroups('test', $groups);
  257. $this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
  258. }
  259. public function dataEnableAppForGroupsAllowedTypes() {
  260. return [
  261. [[]],
  262. [[
  263. 'types' => [],
  264. ]],
  265. [[
  266. 'types' => ['nickvergessen'],
  267. ]],
  268. ];
  269. }
  270. /**
  271. * @dataProvider dataEnableAppForGroupsAllowedTypes
  272. *
  273. * @param array $appInfo
  274. */
  275. public function testEnableAppForGroupsAllowedTypes(array $appInfo) {
  276. $group1 = $this->createMock(IGroup::class);
  277. $group1->method('getGID')
  278. ->willReturn('group1');
  279. $group2 = $this->createMock(IGroup::class);
  280. $group2->method('getGID')
  281. ->willReturn('group2');
  282. $groups = [$group1, $group2];
  283. /** @var AppManager|MockObject $manager */
  284. $manager = $this->getMockBuilder(AppManager::class)
  285. ->setConstructorArgs([
  286. $this->userSession,
  287. $this->config,
  288. $this->groupManager,
  289. $this->cacheFactory,
  290. $this->eventDispatcher,
  291. $this->logger,
  292. ])
  293. ->onlyMethods([
  294. 'getAppPath',
  295. 'getAppInfo',
  296. ])
  297. ->getMock();
  298. $manager->expects($this->once())
  299. ->method('getAppPath')
  300. ->with('test')
  301. ->willReturn(null);
  302. $manager->expects($this->once())
  303. ->method('getAppInfo')
  304. ->with('test')
  305. ->willReturn($appInfo);
  306. $this->eventDispatcher->expects($this->once())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
  307. $manager->enableAppForGroups('test', $groups);
  308. $this->assertEquals('["group1","group2"]', $this->appConfig->getValue('test', 'enabled', 'no'));
  309. }
  310. public function dataEnableAppForGroupsForbiddenTypes() {
  311. return [
  312. ['filesystem'],
  313. ['prelogin'],
  314. ['authentication'],
  315. ['logging'],
  316. ['prevent_group_restriction'],
  317. ];
  318. }
  319. /**
  320. * @dataProvider dataEnableAppForGroupsForbiddenTypes
  321. *
  322. * @param string $type
  323. *
  324. */
  325. public function testEnableAppForGroupsForbiddenTypes($type) {
  326. $this->expectException(\Exception::class);
  327. $this->expectExceptionMessage('test can\'t be enabled for groups.');
  328. $group1 = $this->createMock(IGroup::class);
  329. $group1->method('getGID')
  330. ->willReturn('group1');
  331. $group2 = $this->createMock(IGroup::class);
  332. $group2->method('getGID')
  333. ->willReturn('group2');
  334. $groups = [$group1, $group2];
  335. /** @var AppManager|MockObject $manager */
  336. $manager = $this->getMockBuilder(AppManager::class)
  337. ->setConstructorArgs([
  338. $this->userSession,
  339. $this->config,
  340. $this->groupManager,
  341. $this->cacheFactory,
  342. $this->eventDispatcher,
  343. $this->logger,
  344. ])
  345. ->onlyMethods([
  346. 'getAppPath',
  347. 'getAppInfo',
  348. ])
  349. ->getMock();
  350. $manager->expects($this->once())
  351. ->method('getAppPath')
  352. ->with('test')
  353. ->willReturn(null);
  354. $manager->expects($this->once())
  355. ->method('getAppInfo')
  356. ->with('test')
  357. ->willReturn([
  358. 'types' => [$type],
  359. ]);
  360. $this->eventDispatcher->expects($this->never())->method('dispatchTyped')->with(new AppEnableEvent('test', ['group1', 'group2']));
  361. $manager->enableAppForGroups('test', $groups);
  362. }
  363. public function testIsInstalledEnabled() {
  364. $this->appConfig->setValue('test', 'enabled', 'yes');
  365. $this->assertTrue($this->manager->isInstalled('test'));
  366. }
  367. public function testIsInstalledDisabled() {
  368. $this->appConfig->setValue('test', 'enabled', 'no');
  369. $this->assertFalse($this->manager->isInstalled('test'));
  370. }
  371. public function testIsInstalledEnabledForGroups() {
  372. $this->appConfig->setValue('test', 'enabled', '["foo"]');
  373. $this->assertTrue($this->manager->isInstalled('test'));
  374. }
  375. private function newUser($uid) {
  376. $user = $this->createMock(IUser::class);
  377. $user->method('getUID')
  378. ->willReturn($uid);
  379. return $user;
  380. }
  381. public function testIsEnabledForUserEnabled() {
  382. $this->appConfig->setValue('test', 'enabled', 'yes');
  383. $user = $this->newUser('user1');
  384. $this->assertTrue($this->manager->isEnabledForUser('test', $user));
  385. }
  386. public function testIsEnabledForUserDisabled() {
  387. $this->appConfig->setValue('test', 'enabled', 'no');
  388. $user = $this->newUser('user1');
  389. $this->assertFalse($this->manager->isEnabledForUser('test', $user));
  390. }
  391. public function testGetAppPath() {
  392. $this->assertEquals(\OC::$SERVERROOT . '/apps/files', $this->manager->getAppPath('files'));
  393. }
  394. public function testGetAppPathSymlink() {
  395. $fakeAppDirname = sha1(uniqid('test', true));
  396. $fakeAppPath = sys_get_temp_dir() . '/' . $fakeAppDirname;
  397. $fakeAppLink = \OC::$SERVERROOT . '/' . $fakeAppDirname;
  398. mkdir($fakeAppPath);
  399. if (symlink($fakeAppPath, $fakeAppLink) === false) {
  400. $this->markTestSkipped('Failed to create symlink');
  401. }
  402. // Use the symlink as the app path
  403. \OC::$APPSROOTS[] = [
  404. 'path' => $fakeAppLink,
  405. 'url' => \OC::$WEBROOT . '/' . $fakeAppDirname,
  406. 'writable' => false,
  407. ];
  408. $fakeTestAppPath = $fakeAppPath . '/' . 'test-test-app';
  409. mkdir($fakeTestAppPath);
  410. $generatedAppPath = $this->manager->getAppPath('test-test-app');
  411. rmdir($fakeTestAppPath);
  412. unlink($fakeAppLink);
  413. rmdir($fakeAppPath);
  414. $this->assertEquals($fakeAppLink . '/test-test-app', $generatedAppPath);
  415. }
  416. public function testGetAppPathFail() {
  417. $this->expectException(AppPathNotFoundException::class);
  418. $this->manager->getAppPath('testnotexisting');
  419. }
  420. public function testIsEnabledForUserEnabledForGroup() {
  421. $user = $this->newUser('user1');
  422. $this->groupManager->expects($this->once())
  423. ->method('getUserGroupIds')
  424. ->with($user)
  425. ->willReturn(['foo', 'bar']);
  426. $this->appConfig->setValue('test', 'enabled', '["foo"]');
  427. $this->assertTrue($this->manager->isEnabledForUser('test', $user));
  428. }
  429. public function testIsEnabledForUserDisabledForGroup() {
  430. $user = $this->newUser('user1');
  431. $this->groupManager->expects($this->once())
  432. ->method('getUserGroupIds')
  433. ->with($user)
  434. ->willReturn(['bar']);
  435. $this->appConfig->setValue('test', 'enabled', '["foo"]');
  436. $this->assertFalse($this->manager->isEnabledForUser('test', $user));
  437. }
  438. public function testIsEnabledForUserLoggedOut() {
  439. $this->appConfig->setValue('test', 'enabled', '["foo"]');
  440. $this->assertFalse($this->manager->isEnabledForUser('test'));
  441. }
  442. public function testIsEnabledForUserLoggedIn() {
  443. $user = $this->newUser('user1');
  444. $this->userSession->expects($this->once())
  445. ->method('getUser')
  446. ->willReturn($user);
  447. $this->groupManager->expects($this->once())
  448. ->method('getUserGroupIds')
  449. ->with($user)
  450. ->willReturn(['foo', 'bar']);
  451. $this->appConfig->setValue('test', 'enabled', '["foo"]');
  452. $this->assertTrue($this->manager->isEnabledForUser('test'));
  453. }
  454. public function testGetInstalledApps() {
  455. $this->appConfig->setValue('test1', 'enabled', 'yes');
  456. $this->appConfig->setValue('test2', 'enabled', 'no');
  457. $this->appConfig->setValue('test3', 'enabled', '["foo"]');
  458. $apps = [
  459. 'cloud_federation_api',
  460. 'dav',
  461. 'federatedfilesharing',
  462. 'files',
  463. 'lookup_server_connector',
  464. 'oauth2',
  465. 'provisioning_api',
  466. 'settings',
  467. 'test1',
  468. 'test3',
  469. 'theming',
  470. 'twofactor_backupcodes',
  471. 'viewer',
  472. 'workflowengine',
  473. ];
  474. $this->assertEquals($apps, $this->manager->getInstalledApps());
  475. }
  476. public function testGetAppsForUser() {
  477. $user = $this->newUser('user1');
  478. $this->groupManager->expects($this->any())
  479. ->method('getUserGroupIds')
  480. ->with($user)
  481. ->willReturn(['foo', 'bar']);
  482. $this->appConfig->setValue('test1', 'enabled', 'yes');
  483. $this->appConfig->setValue('test2', 'enabled', 'no');
  484. $this->appConfig->setValue('test3', 'enabled', '["foo"]');
  485. $this->appConfig->setValue('test4', 'enabled', '["asd"]');
  486. $enabled = [
  487. 'cloud_federation_api',
  488. 'dav',
  489. 'federatedfilesharing',
  490. 'files',
  491. 'lookup_server_connector',
  492. 'oauth2',
  493. 'provisioning_api',
  494. 'settings',
  495. 'test1',
  496. 'test3',
  497. 'theming',
  498. 'twofactor_backupcodes',
  499. 'viewer',
  500. 'workflowengine',
  501. ];
  502. $this->assertEquals($enabled, $this->manager->getEnabledAppsForUser($user));
  503. }
  504. public function testGetAppsNeedingUpgrade() {
  505. /** @var AppManager|MockObject $manager */
  506. $manager = $this->getMockBuilder(AppManager::class)
  507. ->setConstructorArgs([
  508. $this->userSession,
  509. $this->config,
  510. $this->groupManager,
  511. $this->cacheFactory,
  512. $this->eventDispatcher,
  513. $this->logger,
  514. ])
  515. ->onlyMethods(['getAppInfo'])
  516. ->getMock();
  517. $appInfos = [
  518. 'cloud_federation_api' => ['id' => 'cloud_federation_api'],
  519. 'dav' => ['id' => 'dav'],
  520. 'files' => ['id' => 'files'],
  521. 'federatedfilesharing' => ['id' => 'federatedfilesharing'],
  522. 'provisioning_api' => ['id' => 'provisioning_api'],
  523. 'lookup_server_connector' => ['id' => 'lookup_server_connector'],
  524. 'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '9.0.0'],
  525. 'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
  526. 'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
  527. 'test4' => ['id' => 'test4', 'version' => '3.0.0', 'requiremin' => '8.1.0'],
  528. 'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
  529. 'settings' => ['id' => 'settings'],
  530. 'theming' => ['id' => 'theming'],
  531. 'twofactor_backupcodes' => ['id' => 'twofactor_backupcodes'],
  532. 'viewer' => ['id' => 'viewer'],
  533. 'workflowengine' => ['id' => 'workflowengine'],
  534. 'oauth2' => ['id' => 'oauth2'],
  535. ];
  536. $manager->expects($this->any())
  537. ->method('getAppInfo')
  538. ->willReturnCallback(
  539. function ($appId) use ($appInfos) {
  540. return $appInfos[$appId];
  541. }
  542. );
  543. $this->appConfig->setValue('test1', 'enabled', 'yes');
  544. $this->appConfig->setValue('test1', 'installed_version', '1.0.0');
  545. $this->appConfig->setValue('test2', 'enabled', 'yes');
  546. $this->appConfig->setValue('test2', 'installed_version', '1.0.0');
  547. $this->appConfig->setValue('test3', 'enabled', 'yes');
  548. $this->appConfig->setValue('test3', 'installed_version', '1.0.0');
  549. $this->appConfig->setValue('test4', 'enabled', 'yes');
  550. $this->appConfig->setValue('test4', 'installed_version', '2.4.0');
  551. $apps = $manager->getAppsNeedingUpgrade('8.2.0');
  552. $this->assertCount(2, $apps);
  553. $this->assertEquals('test1', $apps[0]['id']);
  554. $this->assertEquals('test4', $apps[1]['id']);
  555. }
  556. public function testGetIncompatibleApps() {
  557. /** @var AppManager|MockObject $manager */
  558. $manager = $this->getMockBuilder(AppManager::class)
  559. ->setConstructorArgs([
  560. $this->userSession,
  561. $this->config,
  562. $this->groupManager,
  563. $this->cacheFactory,
  564. $this->eventDispatcher,
  565. $this->logger,
  566. ])
  567. ->onlyMethods(['getAppInfo'])
  568. ->getMock();
  569. $appInfos = [
  570. 'cloud_federation_api' => ['id' => 'cloud_federation_api'],
  571. 'dav' => ['id' => 'dav'],
  572. 'files' => ['id' => 'files'],
  573. 'federatedfilesharing' => ['id' => 'federatedfilesharing'],
  574. 'provisioning_api' => ['id' => 'provisioning_api'],
  575. 'lookup_server_connector' => ['id' => 'lookup_server_connector'],
  576. 'test1' => ['id' => 'test1', 'version' => '1.0.1', 'requiremax' => '8.0.0'],
  577. 'test2' => ['id' => 'test2', 'version' => '1.0.0', 'requiremin' => '8.2.0'],
  578. 'test3' => ['id' => 'test3', 'version' => '1.2.4', 'requiremin' => '9.0.0'],
  579. 'settings' => ['id' => 'settings'],
  580. 'testnoversion' => ['id' => 'testnoversion', 'requiremin' => '8.2.0'],
  581. 'theming' => ['id' => 'theming'],
  582. 'twofactor_backupcodes' => ['id' => 'twofactor_backupcodes'],
  583. 'workflowengine' => ['id' => 'workflowengine'],
  584. 'oauth2' => ['id' => 'oauth2'],
  585. 'viewer' => ['id' => 'viewer'],
  586. ];
  587. $manager->expects($this->any())
  588. ->method('getAppInfo')
  589. ->willReturnCallback(
  590. function ($appId) use ($appInfos) {
  591. return $appInfos[$appId];
  592. }
  593. );
  594. $this->appConfig->setValue('test1', 'enabled', 'yes');
  595. $this->appConfig->setValue('test2', 'enabled', 'yes');
  596. $this->appConfig->setValue('test3', 'enabled', 'yes');
  597. $apps = $manager->getIncompatibleApps('8.2.0');
  598. $this->assertCount(2, $apps);
  599. $this->assertEquals('test1', $apps[0]['id']);
  600. $this->assertEquals('test3', $apps[1]['id']);
  601. }
  602. public function testGetEnabledAppsForGroup() {
  603. $group = $this->createMock(IGroup::class);
  604. $group->expects($this->any())
  605. ->method('getGID')
  606. ->willReturn('foo');
  607. $this->appConfig->setValue('test1', 'enabled', 'yes');
  608. $this->appConfig->setValue('test2', 'enabled', 'no');
  609. $this->appConfig->setValue('test3', 'enabled', '["foo"]');
  610. $this->appConfig->setValue('test4', 'enabled', '["asd"]');
  611. $enabled = [
  612. 'cloud_federation_api',
  613. 'dav',
  614. 'federatedfilesharing',
  615. 'files',
  616. 'lookup_server_connector',
  617. 'oauth2',
  618. 'provisioning_api',
  619. 'settings',
  620. 'test1',
  621. 'test3',
  622. 'theming',
  623. 'twofactor_backupcodes',
  624. 'viewer',
  625. 'workflowengine',
  626. ];
  627. $this->assertEquals($enabled, $this->manager->getEnabledAppsForGroup($group));
  628. }
  629. public function testGetAppRestriction() {
  630. $this->appConfig->setValue('test1', 'enabled', 'yes');
  631. $this->appConfig->setValue('test2', 'enabled', 'no');
  632. $this->appConfig->setValue('test3', 'enabled', '["foo"]');
  633. $this->assertEquals([], $this->manager->getAppRestriction('test1'));
  634. $this->assertEquals([], $this->manager->getAppRestriction('test2'));
  635. $this->assertEquals(['foo'], $this->manager->getAppRestriction('test3'));
  636. }
  637. public function provideDefaultApps(): array {
  638. return [
  639. // none specified, default to files
  640. [
  641. '',
  642. '',
  643. '{}',
  644. true,
  645. 'files',
  646. ],
  647. // none specified, without fallback
  648. [
  649. '',
  650. '',
  651. '{}',
  652. false,
  653. '',
  654. ],
  655. // unexisting or inaccessible app specified, default to files
  656. [
  657. 'unexist',
  658. '',
  659. '{}',
  660. true,
  661. 'files',
  662. ],
  663. // unexisting or inaccessible app specified, without fallbacks
  664. [
  665. 'unexist',
  666. '',
  667. '{}',
  668. false,
  669. '',
  670. ],
  671. // non-standard app
  672. [
  673. 'settings',
  674. '',
  675. '{}',
  676. true,
  677. 'settings',
  678. ],
  679. // non-standard app, without fallback
  680. [
  681. 'settings',
  682. '',
  683. '{}',
  684. false,
  685. 'settings',
  686. ],
  687. // non-standard app with fallback
  688. [
  689. 'unexist,settings',
  690. '',
  691. '{}',
  692. true,
  693. 'settings',
  694. ],
  695. // system default app and user apporder
  696. [
  697. // system default is settings
  698. 'unexist,settings',
  699. '',
  700. // apporder says default app is files (order is lower)
  701. '{"files_id":{"app":"files","order":1},"settings_id":{"app":"settings","order":2}}',
  702. true,
  703. // system default should override apporder
  704. 'settings'
  705. ],
  706. // user-customized defaultapp
  707. [
  708. '',
  709. 'files',
  710. '',
  711. true,
  712. 'files',
  713. ],
  714. // user-customized defaultapp with systemwide
  715. [
  716. 'unexist,settings',
  717. 'files',
  718. '',
  719. true,
  720. 'files',
  721. ],
  722. // user-customized defaultapp with system wide and apporder
  723. [
  724. 'unexist,settings',
  725. 'files',
  726. '{"settings_id":{"app":"settings","order":1},"files_id":{"app":"files","order":2}}',
  727. true,
  728. 'files',
  729. ],
  730. // user-customized apporder fallback
  731. [
  732. '',
  733. '',
  734. '{"settings_id":{"app":"settings","order":1},"files":{"app":"files","order":2}}',
  735. true,
  736. 'settings',
  737. ],
  738. // user-customized apporder fallback with missing app key (entries added by closures does not always have an app key set (Nextcloud 27 spreed app for example))
  739. [
  740. '',
  741. '',
  742. '{"spreed":{"order":1},"files":{"app":"files","order":2}}',
  743. true,
  744. 'files',
  745. ],
  746. // user-customized apporder, but called without fallback
  747. [
  748. '',
  749. '',
  750. '{"settings":{"app":"settings","order":1},"files":{"app":"files","order":2}}',
  751. false,
  752. '',
  753. ],
  754. // user-customized apporder with an app that has multiple routes
  755. [
  756. '',
  757. '',
  758. '{"settings_id":{"app":"settings","order":1},"settings_id_2":{"app":"settings","order":3},"id_files":{"app":"files","order":2}}',
  759. true,
  760. 'settings',
  761. ],
  762. ];
  763. }
  764. /**
  765. * @dataProvider provideDefaultApps
  766. */
  767. public function testGetDefaultAppForUser($defaultApps, $userDefaultApps, $userApporder, $withFallbacks, $expectedApp) {
  768. $user = $this->newUser('user1');
  769. $this->userSession->expects($this->once())
  770. ->method('getUser')
  771. ->willReturn($user);
  772. $this->config->expects($this->once())
  773. ->method('getSystemValueString')
  774. ->with('defaultapp', $this->anything())
  775. ->willReturn($defaultApps);
  776. $this->config->expects($this->atLeastOnce())
  777. ->method('getUserValue')
  778. ->willReturnMap([
  779. ['user1', 'core', 'defaultapp', '', $userDefaultApps],
  780. ['user1', 'core', 'apporder', '[]', $userApporder],
  781. ]);
  782. $this->assertEquals($expectedApp, $this->manager->getDefaultAppForUser(null, $withFallbacks));
  783. }
  784. public static function isBackendRequiredDataProvider(): array {
  785. return [
  786. // backend available
  787. [
  788. 'caldav',
  789. ['app1' => ['caldav']],
  790. true,
  791. ],
  792. [
  793. 'caldav',
  794. ['app1' => [], 'app2' => ['foo'], 'app3' => ['caldav']],
  795. true,
  796. ],
  797. // backend not available
  798. [
  799. 'caldav',
  800. ['app3' => [], 'app1' => ['foo'], 'app2' => ['bar', 'baz']],
  801. false,
  802. ],
  803. // no app available
  804. [
  805. 'caldav',
  806. [],
  807. false,
  808. ],
  809. ];
  810. }
  811. /**
  812. * @dataProvider isBackendRequiredDataProvider
  813. */
  814. public function testIsBackendRequired(
  815. string $backend,
  816. array $appBackends,
  817. bool $expected,
  818. ): void {
  819. $appInfoData = array_map(
  820. static fn (array $backends) => ['dependencies' => ['backend' => $backends]],
  821. $appBackends,
  822. );
  823. $reflection = new \ReflectionClass($this->manager);
  824. $property = $reflection->getProperty('appInfos');
  825. $property->setValue($this->manager, $appInfoData);
  826. $this->assertEquals($expected, $this->manager->isBackendRequired($backend));
  827. }
  828. }