AppTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace Test;
  8. use OC\App\AppManager;
  9. use OC\App\InfoParser;
  10. use OC\AppConfig;
  11. use OCP\EventDispatcher\IEventDispatcher;
  12. use OCP\IAppConfig;
  13. use OCP\IURLGenerator;
  14. use PHPUnit\Framework\MockObject\MockObject;
  15. use Psr\Log\LoggerInterface;
  16. /**
  17. * Class AppTest
  18. *
  19. * @group DB
  20. */
  21. class AppTest extends \Test\TestCase {
  22. public const TEST_USER1 = 'user1';
  23. public const TEST_USER2 = 'user2';
  24. public const TEST_USER3 = 'user3';
  25. public const TEST_GROUP1 = 'group1';
  26. public const TEST_GROUP2 = 'group2';
  27. public function appVersionsProvider() {
  28. return [
  29. // exact match
  30. [
  31. '6.0.0.0',
  32. [
  33. 'requiremin' => '6.0',
  34. 'requiremax' => '6.0',
  35. ],
  36. true
  37. ],
  38. // in-between match
  39. [
  40. '6.0.0.0',
  41. [
  42. 'requiremin' => '5.0',
  43. 'requiremax' => '7.0',
  44. ],
  45. true
  46. ],
  47. // app too old
  48. [
  49. '6.0.0.0',
  50. [
  51. 'requiremin' => '5.0',
  52. 'requiremax' => '5.0',
  53. ],
  54. false
  55. ],
  56. // app too new
  57. [
  58. '5.0.0.0',
  59. [
  60. 'requiremin' => '6.0',
  61. 'requiremax' => '6.0',
  62. ],
  63. false
  64. ],
  65. // only min specified
  66. [
  67. '6.0.0.0',
  68. [
  69. 'requiremin' => '6.0',
  70. ],
  71. true
  72. ],
  73. // only min specified fail
  74. [
  75. '5.0.0.0',
  76. [
  77. 'requiremin' => '6.0',
  78. ],
  79. false
  80. ],
  81. // only min specified legacy
  82. [
  83. '6.0.0.0',
  84. [
  85. 'require' => '6.0',
  86. ],
  87. true
  88. ],
  89. // only min specified legacy fail
  90. [
  91. '4.0.0.0',
  92. [
  93. 'require' => '6.0',
  94. ],
  95. false
  96. ],
  97. // only max specified
  98. [
  99. '5.0.0.0',
  100. [
  101. 'requiremax' => '6.0',
  102. ],
  103. true
  104. ],
  105. // only max specified fail
  106. [
  107. '7.0.0.0',
  108. [
  109. 'requiremax' => '6.0',
  110. ],
  111. false
  112. ],
  113. // variations of versions
  114. // single OC number
  115. [
  116. '4',
  117. [
  118. 'require' => '4.0',
  119. ],
  120. true
  121. ],
  122. // multiple OC number
  123. [
  124. '4.3.1',
  125. [
  126. 'require' => '4.3',
  127. ],
  128. true
  129. ],
  130. // single app number
  131. [
  132. '4',
  133. [
  134. 'require' => '4',
  135. ],
  136. true
  137. ],
  138. // single app number fail
  139. [
  140. '4.3',
  141. [
  142. 'require' => '5',
  143. ],
  144. false
  145. ],
  146. // complex
  147. [
  148. '5.0.0',
  149. [
  150. 'require' => '4.5.1',
  151. ],
  152. true
  153. ],
  154. // complex fail
  155. [
  156. '4.3.1',
  157. [
  158. 'require' => '4.3.2',
  159. ],
  160. false
  161. ],
  162. // two numbers
  163. [
  164. '4.3.1',
  165. [
  166. 'require' => '4.4',
  167. ],
  168. false
  169. ],
  170. // one number fail
  171. [
  172. '4.3.1',
  173. [
  174. 'require' => '5',
  175. ],
  176. false
  177. ],
  178. // pre-alpha app
  179. [
  180. '5.0.3',
  181. [
  182. 'require' => '4.93',
  183. ],
  184. true
  185. ],
  186. // pre-alpha OC
  187. [
  188. '6.90.0.2',
  189. [
  190. 'require' => '6.90',
  191. ],
  192. true
  193. ],
  194. // pre-alpha OC max
  195. [
  196. '6.90.0.2',
  197. [
  198. 'requiremax' => '7',
  199. ],
  200. true
  201. ],
  202. // expect same major number match
  203. [
  204. '5.0.3',
  205. [
  206. 'require' => '5',
  207. ],
  208. true
  209. ],
  210. // expect same major number match
  211. [
  212. '5.0.3',
  213. [
  214. 'requiremax' => '5',
  215. ],
  216. true
  217. ],
  218. // dependencies versions before require*
  219. [
  220. '6.0.0.0',
  221. [
  222. 'requiremin' => '5.0',
  223. 'requiremax' => '7.0',
  224. 'dependencies' => [
  225. 'owncloud' => [
  226. '@attributes' => [
  227. 'min-version' => '7.0',
  228. 'max-version' => '7.0',
  229. ],
  230. ],
  231. ],
  232. ],
  233. false
  234. ],
  235. [
  236. '6.0.0.0',
  237. [
  238. 'requiremin' => '5.0',
  239. 'requiremax' => '7.0',
  240. 'dependencies' => [
  241. 'owncloud' => [
  242. '@attributes' => [
  243. 'min-version' => '5.0',
  244. 'max-version' => '5.0',
  245. ],
  246. ],
  247. ],
  248. ],
  249. false
  250. ],
  251. [
  252. '6.0.0.0',
  253. [
  254. 'requiremin' => '5.0',
  255. 'requiremax' => '5.0',
  256. 'dependencies' => [
  257. 'owncloud' => [
  258. '@attributes' => [
  259. 'min-version' => '5.0',
  260. 'max-version' => '7.0',
  261. ],
  262. ],
  263. ],
  264. ],
  265. true
  266. ],
  267. [
  268. '9.2.0.0',
  269. [
  270. 'dependencies' => [
  271. 'owncloud' => [
  272. '@attributes' => [
  273. 'min-version' => '9.0',
  274. 'max-version' => '9.1',
  275. ],
  276. ],
  277. 'nextcloud' => [
  278. '@attributes' => [
  279. 'min-version' => '9.1',
  280. 'max-version' => '9.2',
  281. ],
  282. ],
  283. ],
  284. ],
  285. true
  286. ],
  287. [
  288. '9.2.0.0',
  289. [
  290. 'dependencies' => [
  291. 'nextcloud' => [
  292. '@attributes' => [
  293. 'min-version' => '9.1',
  294. 'max-version' => '9.2',
  295. ],
  296. ],
  297. ],
  298. ],
  299. true
  300. ],
  301. ];
  302. }
  303. /**
  304. * @dataProvider appVersionsProvider
  305. */
  306. public function testIsAppCompatible($ocVersion, $appInfo, $expectedResult): void {
  307. $this->assertEquals($expectedResult, \OC_App::isAppCompatible($ocVersion, $appInfo));
  308. }
  309. /**
  310. * Tests that the app order is correct
  311. */
  312. public function testGetEnabledAppsIsSorted(): void {
  313. $apps = \OC_App::getEnabledApps();
  314. // copy array
  315. $sortedApps = $apps;
  316. sort($sortedApps);
  317. // 'files' is always on top
  318. unset($sortedApps[array_search('files', $sortedApps)]);
  319. array_unshift($sortedApps, 'files');
  320. $this->assertEquals($sortedApps, $apps);
  321. }
  322. /**
  323. * Providers for the app config values
  324. */
  325. public function appConfigValuesProvider() {
  326. return [
  327. // logged in user1
  328. [
  329. self::TEST_USER1,
  330. [
  331. 'files',
  332. 'app1',
  333. 'app3',
  334. 'appforgroup1',
  335. 'appforgroup12',
  336. 'cloud_federation_api',
  337. 'dav',
  338. 'federatedfilesharing',
  339. 'lookup_server_connector',
  340. 'oauth2',
  341. 'profile',
  342. 'provisioning_api',
  343. 'settings',
  344. 'theming',
  345. 'twofactor_backupcodes',
  346. 'viewer',
  347. 'workflowengine',
  348. ],
  349. false
  350. ],
  351. // logged in user2
  352. [
  353. self::TEST_USER2,
  354. [
  355. 'files',
  356. 'app1',
  357. 'app3',
  358. 'appforgroup12',
  359. 'appforgroup2',
  360. 'cloud_federation_api',
  361. 'dav',
  362. 'federatedfilesharing',
  363. 'lookup_server_connector',
  364. 'oauth2',
  365. 'profile',
  366. 'provisioning_api',
  367. 'settings',
  368. 'theming',
  369. 'twofactor_backupcodes',
  370. 'viewer',
  371. 'workflowengine',
  372. ],
  373. false
  374. ],
  375. // logged in user3
  376. [
  377. self::TEST_USER3,
  378. [
  379. 'files',
  380. 'app1',
  381. 'app3',
  382. 'appforgroup1',
  383. 'appforgroup12',
  384. 'appforgroup2',
  385. 'cloud_federation_api',
  386. 'dav',
  387. 'federatedfilesharing',
  388. 'lookup_server_connector',
  389. 'oauth2',
  390. 'profile',
  391. 'provisioning_api',
  392. 'settings',
  393. 'theming',
  394. 'twofactor_backupcodes',
  395. 'viewer',
  396. 'workflowengine',
  397. ],
  398. false
  399. ],
  400. // no user, returns all apps
  401. [
  402. null,
  403. [
  404. 'files',
  405. 'app1',
  406. 'app3',
  407. 'appforgroup1',
  408. 'appforgroup12',
  409. 'appforgroup2',
  410. 'cloud_federation_api',
  411. 'dav',
  412. 'federatedfilesharing',
  413. 'lookup_server_connector',
  414. 'oauth2',
  415. 'profile',
  416. 'provisioning_api',
  417. 'settings',
  418. 'theming',
  419. 'twofactor_backupcodes',
  420. 'viewer',
  421. 'workflowengine',
  422. ],
  423. false,
  424. ],
  425. // user given, but ask for all
  426. [
  427. self::TEST_USER1,
  428. [
  429. 'files',
  430. 'app1',
  431. 'app3',
  432. 'appforgroup1',
  433. 'appforgroup12',
  434. 'appforgroup2',
  435. 'cloud_federation_api',
  436. 'dav',
  437. 'federatedfilesharing',
  438. 'lookup_server_connector',
  439. 'oauth2',
  440. 'profile',
  441. 'provisioning_api',
  442. 'settings',
  443. 'theming',
  444. 'twofactor_backupcodes',
  445. 'viewer',
  446. 'workflowengine',
  447. ],
  448. true,
  449. ],
  450. ];
  451. }
  452. /**
  453. * Test enabled apps
  454. *
  455. * @dataProvider appConfigValuesProvider
  456. */
  457. public function testEnabledApps($user, $expectedApps, $forceAll): void {
  458. $userManager = \OC::$server->getUserManager();
  459. $groupManager = \OC::$server->getGroupManager();
  460. $user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
  461. $user2 = $userManager->createUser(self::TEST_USER2, 'NotAnEasyPassword123456_');
  462. $user3 = $userManager->createUser(self::TEST_USER3, 'NotAnEasyPassword123456?');
  463. $group1 = $groupManager->createGroup(self::TEST_GROUP1);
  464. $group1->addUser($user1);
  465. $group1->addUser($user3);
  466. $group2 = $groupManager->createGroup(self::TEST_GROUP2);
  467. $group2->addUser($user2);
  468. $group2->addUser($user3);
  469. \OC_User::setUserId($user);
  470. $this->setupAppConfigMock()->expects($this->once())
  471. ->method('getValues')
  472. ->willReturn(
  473. [
  474. 'app3' => 'yes',
  475. 'app2' => 'no',
  476. 'app1' => 'yes',
  477. 'appforgroup1' => '["group1"]',
  478. 'appforgroup2' => '["group2"]',
  479. 'appforgroup12' => '["group2","group1"]',
  480. ]
  481. );
  482. $apps = \OC_App::getEnabledApps(false, $forceAll);
  483. $this->restoreAppConfig();
  484. \OC_User::setUserId(null);
  485. $user1->delete();
  486. $user2->delete();
  487. $user3->delete();
  488. $group1->delete();
  489. $group2->delete();
  490. $this->assertEquals($expectedApps, $apps);
  491. }
  492. /**
  493. * Test isEnabledApps() with cache, not re-reading the list of
  494. * enabled apps more than once when a user is set.
  495. */
  496. public function testEnabledAppsCache(): void {
  497. $userManager = \OC::$server->getUserManager();
  498. $user1 = $userManager->createUser(self::TEST_USER1, 'NotAnEasyPassword123456+');
  499. \OC_User::setUserId(self::TEST_USER1);
  500. $this->setupAppConfigMock()->expects($this->once())
  501. ->method('getValues')
  502. ->willReturn(
  503. [
  504. 'app3' => 'yes',
  505. 'app2' => 'no',
  506. ]
  507. );
  508. $apps = \OC_App::getEnabledApps();
  509. $this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
  510. // mock should not be called again here
  511. $apps = \OC_App::getEnabledApps();
  512. $this->assertEquals(['files', 'app3', 'cloud_federation_api', 'dav', 'federatedfilesharing', 'lookup_server_connector', 'oauth2', 'profile', 'provisioning_api', 'settings', 'theming', 'twofactor_backupcodes', 'viewer', 'workflowengine'], $apps);
  513. $this->restoreAppConfig();
  514. \OC_User::setUserId(null);
  515. $user1->delete();
  516. }
  517. private function setupAppConfigMock() {
  518. /** @var AppConfig|MockObject */
  519. $appConfig = $this->getMockBuilder(AppConfig::class)
  520. ->setMethods(['getValues'])
  521. ->setConstructorArgs([\OC::$server->getDatabaseConnection()])
  522. ->disableOriginalConstructor()
  523. ->getMock();
  524. $this->registerAppConfig($appConfig);
  525. return $appConfig;
  526. }
  527. /**
  528. * Register an app config mock for testing purposes.
  529. *
  530. * @param IAppConfig $appConfig app config mock
  531. */
  532. private function registerAppConfig(AppConfig $appConfig) {
  533. $this->overwriteService(AppConfig::class, $appConfig);
  534. $this->overwriteService(AppManager::class, new AppManager(
  535. \OC::$server->getUserSession(),
  536. \OC::$server->getConfig(),
  537. \OC::$server->getGroupManager(),
  538. \OC::$server->getMemCacheFactory(),
  539. \OC::$server->get(IEventDispatcher::class),
  540. \OC::$server->get(LoggerInterface::class),
  541. \OC::$server->get(IURLGenerator::class),
  542. ));
  543. }
  544. /**
  545. * Restore the original app config service.
  546. */
  547. private function restoreAppConfig() {
  548. $this->restoreService(AppConfig::class);
  549. $this->restoreService(AppManager::class);
  550. // Remove the cache of the mocked apps list with a forceRefresh
  551. \OC_App::getEnabledApps();
  552. }
  553. /**
  554. * Providers for the app data values
  555. */
  556. public function appDataProvider() {
  557. return [
  558. [
  559. ['description' => " \t This is a multiline \n test with \n \t \n \n some new lines "],
  560. ['description' => "This is a multiline \n test with \n \t \n \n some new lines"],
  561. ],
  562. [
  563. ['description' => " \t This is a multiline \n test with \n \t some new lines "],
  564. ['description' => "This is a multiline \n test with \n \t some new lines"],
  565. ],
  566. [
  567. ['description' => hex2bin('5065726d657420646520732761757468656e7469666965722064616e732070697769676f20646972656374656d656e74206176656320736573206964656e74696669616e7473206f776e636c6f75642073616e73206c65732072657461706572206574206d657420c3a0206a6f757273206365757820636920656e20636173206465206368616e67656d656e74206465206d6f742064652070617373652e0d0a0d')],
  568. ['description' => "Permet de s'authentifier dans piwigo directement avec ses identifiants owncloud sans les retaper et met à jours ceux ci en cas de changement de mot de passe."],
  569. ],
  570. [
  571. ['not-a-description' => " \t This is a multiline \n test with \n \t some new lines "],
  572. [
  573. 'not-a-description' => " \t This is a multiline \n test with \n \t some new lines ",
  574. 'description' => '',
  575. ],
  576. ],
  577. [
  578. ['description' => [100, 'bla']],
  579. ['description' => ''],
  580. ],
  581. ];
  582. }
  583. /**
  584. * Test app info parser
  585. *
  586. * @dataProvider appDataProvider
  587. * @param array $data
  588. * @param array $expected
  589. */
  590. public function testParseAppInfo(array $data, array $expected): void {
  591. $this->assertSame($expected, \OC_App::parseAppInfo($data));
  592. }
  593. public function testParseAppInfoL10N(): void {
  594. $parser = new InfoParser();
  595. $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-multi-lang.xml');
  596. $this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
  597. $this->assertEquals('German', \OC_App::parseAppInfo($data, 'de')['description']);
  598. }
  599. public function testParseAppInfoL10NSingleLanguage(): void {
  600. $parser = new InfoParser();
  601. $data = $parser->parse(\OC::$SERVERROOT . '/tests/data/app/description-single-lang.xml');
  602. $this->assertEquals('English', \OC_App::parseAppInfo($data, 'en')['description']);
  603. }
  604. }