AppConfigControllerTest.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OCA\Provisioning_API\Tests\Controller;
  7. use OC\AppConfig;
  8. use OCA\Provisioning_API\Controller\AppConfigController;
  9. use OCP\App\IAppManager;
  10. use OCP\AppFramework\Http;
  11. use OCP\AppFramework\Http\DataResponse;
  12. use OCP\Exceptions\AppConfigUnknownKeyException;
  13. use OCP\IAppConfig;
  14. use OCP\IGroupManager;
  15. use OCP\IL10N;
  16. use OCP\IRequest;
  17. use OCP\IUser;
  18. use OCP\IUserSession;
  19. use OCP\Settings\IManager;
  20. use PHPUnit\Framework\MockObject\MockObject;
  21. use Test\TestCase;
  22. use function json_decode;
  23. use function json_encode;
  24. /**
  25. * Class AppConfigControllerTest
  26. *
  27. * @package OCA\Provisioning_API\Tests
  28. */
  29. class AppConfigControllerTest extends TestCase {
  30. private IAppConfig&MockObject $appConfig;
  31. private IUserSession&MockObject $userSession;
  32. private IL10N&MockObject $l10n;
  33. private IManager&MockObject $settingManager;
  34. private IGroupManager&MockObject $groupManager;
  35. private IAppManager $appManager;
  36. protected function setUp(): void {
  37. parent::setUp();
  38. $this->appConfig = $this->createMock(AppConfig::class);
  39. $this->userSession = $this->createMock(IUserSession::class);
  40. $this->l10n = $this->createMock(IL10N::class);
  41. $this->settingManager = $this->createMock(IManager::class);
  42. $this->groupManager = $this->createMock(IGroupManager::class);
  43. $this->appManager = \OCP\Server::get(IAppManager::class);
  44. }
  45. /**
  46. * @param string[] $methods
  47. * @return AppConfigController|\PHPUnit\Framework\MockObject\MockObject
  48. */
  49. protected function getInstance(array $methods = []) {
  50. $request = $this->createMock(IRequest::class);
  51. if (empty($methods)) {
  52. return new AppConfigController(
  53. 'provisioning_api',
  54. $request,
  55. $this->appConfig,
  56. $this->userSession,
  57. $this->l10n,
  58. $this->groupManager,
  59. $this->settingManager,
  60. $this->appManager,
  61. );
  62. } else {
  63. return $this->getMockBuilder(AppConfigController::class)
  64. ->setConstructorArgs([
  65. 'provisioning_api',
  66. $request,
  67. $this->appConfig,
  68. $this->userSession,
  69. $this->l10n,
  70. $this->groupManager,
  71. $this->settingManager,
  72. $this->appManager,
  73. ])
  74. ->setMethods($methods)
  75. ->getMock();
  76. }
  77. }
  78. public function testGetApps(): void {
  79. $this->appConfig->expects($this->once())
  80. ->method('getApps')
  81. ->willReturn(['apps']);
  82. $result = $this->getInstance()->getApps();
  83. $this->assertInstanceOf(DataResponse::class, $result);
  84. $this->assertSame(Http::STATUS_OK, $result->getStatus());
  85. $this->assertEquals(['data' => ['apps']], $result->getData());
  86. }
  87. public function dataGetKeys() {
  88. return [
  89. ['app1 ', null, new \InvalidArgumentException('error'), Http::STATUS_FORBIDDEN],
  90. ['app2', ['keys'], null, Http::STATUS_OK],
  91. ];
  92. }
  93. /**
  94. * @dataProvider dataGetKeys
  95. * @param string $app
  96. * @param array|null $keys
  97. * @param \Exception|null $throws
  98. * @param int $status
  99. */
  100. public function testGetKeys($app, $keys, $throws, $status): void {
  101. $api = $this->getInstance(['verifyAppId']);
  102. if ($throws instanceof \Exception) {
  103. $api->expects($this->once())
  104. ->method('verifyAppId')
  105. ->with($app)
  106. ->willThrowException($throws);
  107. $this->appConfig->expects($this->never())
  108. ->method('getKeys');
  109. } else {
  110. $api->expects($this->once())
  111. ->method('verifyAppId')
  112. ->with($app);
  113. $this->appConfig->expects($this->once())
  114. ->method('getKeys')
  115. ->with($app)
  116. ->willReturn($keys);
  117. }
  118. $result = $api->getKeys($app);
  119. $this->assertInstanceOf(DataResponse::class, $result);
  120. $this->assertSame($status, $result->getStatus());
  121. if ($throws instanceof \Exception) {
  122. $this->assertEquals(['data' => ['message' => $throws->getMessage()]], $result->getData());
  123. } else {
  124. $this->assertEquals(['data' => $keys], $result->getData());
  125. }
  126. }
  127. public function dataGetValue() {
  128. return [
  129. ['app1', 'key', 'default', null, new \InvalidArgumentException('error'), Http::STATUS_FORBIDDEN],
  130. ['app2', 'key', 'default', 'return', null, Http::STATUS_OK],
  131. ];
  132. }
  133. /**
  134. * @dataProvider dataGetValue
  135. * @param string $app
  136. * @param string|null $key
  137. * @param string|null $default
  138. * @param string|null $return
  139. * @param \Exception|null $throws
  140. * @param int $status
  141. */
  142. public function testGetValue($app, $key, $default, $return, $throws, $status): void {
  143. $api = $this->getInstance(['verifyAppId']);
  144. if ($throws instanceof \Exception) {
  145. $api->expects($this->once())
  146. ->method('verifyAppId')
  147. ->with($app)
  148. ->willThrowException($throws);
  149. } else {
  150. $api->expects($this->once())
  151. ->method('verifyAppId')
  152. ->with($app);
  153. $this->appConfig->expects($this->once())
  154. ->method('getValueMixed')
  155. ->with($app, $key, $default)
  156. ->willReturn($return);
  157. }
  158. $result = $api->getValue($app, $key, $default);
  159. $this->assertInstanceOf(DataResponse::class, $result);
  160. $this->assertSame($status, $result->getStatus());
  161. if ($throws instanceof \Exception) {
  162. $this->assertEquals(['data' => ['message' => $throws->getMessage()]], $result->getData());
  163. } else {
  164. $this->assertEquals(['data' => $return], $result->getData());
  165. }
  166. }
  167. public function dataSetValue() {
  168. return [
  169. ['app1', 'key', 'default', new \InvalidArgumentException('error1'), null, Http::STATUS_FORBIDDEN],
  170. ['app2', 'key', 'default', null, new \InvalidArgumentException('error2'), Http::STATUS_FORBIDDEN],
  171. ['app2', 'key', 'default', null, null, Http::STATUS_OK],
  172. ['app2', 'key', '1', null, null, Http::STATUS_OK, IAppConfig::VALUE_BOOL],
  173. ['app2', 'key', '42', null, null, Http::STATUS_OK, IAppConfig::VALUE_INT],
  174. ['app2', 'key', '4.2', null, null, Http::STATUS_OK, IAppConfig::VALUE_FLOAT],
  175. ['app2', 'key', '42', null, null, Http::STATUS_OK, IAppConfig::VALUE_STRING],
  176. ['app2', 'key', 'secret', null, null, Http::STATUS_OK, IAppConfig::VALUE_STRING | IAppConfig::VALUE_SENSITIVE],
  177. ['app2', 'key', json_encode([4, 2]), null, null, Http::STATUS_OK, IAppConfig::VALUE_ARRAY],
  178. ['app2', 'key', json_encode([4, 2]), null, null, Http::STATUS_OK, new AppConfigUnknownKeyException()],
  179. ];
  180. }
  181. /**
  182. * @dataProvider dataSetValue
  183. * @param string $app
  184. * @param string|null $key
  185. * @param string|null $value
  186. * @param \Exception|null $appThrows
  187. * @param \Exception|null $keyThrows
  188. * @param int|\Throwable $status
  189. */
  190. public function testSetValue($app, $key, $value, $appThrows, $keyThrows, $status, int|\Throwable $type = IAppConfig::VALUE_MIXED): void {
  191. $adminUser = $this->createMock(IUser::class);
  192. $adminUser->expects($this->once())
  193. ->method('getUid')
  194. ->willReturn('admin');
  195. $this->userSession->expects($this->once())
  196. ->method('getUser')
  197. ->willReturn($adminUser);
  198. $this->groupManager->expects($this->once())
  199. ->method('isAdmin')
  200. ->with('admin')
  201. ->willReturn(true);
  202. $api = $this->getInstance(['verifyAppId', 'verifyConfigKey']);
  203. if ($appThrows instanceof \Exception) {
  204. $api->expects($this->once())
  205. ->method('verifyAppId')
  206. ->with($app)
  207. ->willThrowException($appThrows);
  208. $api->expects($this->never())
  209. ->method('verifyConfigKey');
  210. $this->appConfig->expects($this->never())
  211. ->method('setValueMixed');
  212. } elseif ($keyThrows instanceof \Exception) {
  213. $api->expects($this->once())
  214. ->method('verifyAppId')
  215. ->with($app);
  216. $api->expects($this->once())
  217. ->method('verifyConfigKey')
  218. ->with($app, $key)
  219. ->willThrowException($keyThrows);
  220. $this->appConfig->expects($this->never())
  221. ->method('setValueMixed');
  222. } else {
  223. $api->expects($this->once())
  224. ->method('verifyAppId')
  225. ->with($app);
  226. $api->expects($this->once())
  227. ->method('verifyConfigKey')
  228. ->with($app, $key);
  229. if ($type instanceof \Throwable) {
  230. $this->appConfig->expects($this->once())
  231. ->method('getDetails')
  232. ->with($app, $key)
  233. ->willThrowException($type);
  234. } else {
  235. $this->appConfig->expects($this->once())
  236. ->method('getDetails')
  237. ->with($app, $key)
  238. ->willReturn([
  239. 'app' => $app,
  240. 'key' => $key,
  241. 'value' => '', // 🤷
  242. 'type' => $type,
  243. 'lazy' => false,
  244. 'typeString' => (string)$type, // this is not accurate, but acceptable
  245. 'sensitive' => ($type & IAppConfig::VALUE_SENSITIVE) !== 0,
  246. ]);
  247. }
  248. $configValueSetter = match ($type) {
  249. IAppConfig::VALUE_BOOL => 'setValueBool',
  250. IAppConfig::VALUE_FLOAT => 'setValueFloat',
  251. IAppConfig::VALUE_INT => 'setValueInt',
  252. IAppConfig::VALUE_STRING => 'setValueString',
  253. IAppConfig::VALUE_ARRAY => 'setValueArray',
  254. default => 'setValueMixed',
  255. };
  256. $this->appConfig->expects($this->once())
  257. ->method($configValueSetter)
  258. ->with($app, $key, $configValueSetter === 'setValueArray' ? json_decode($value, true) : $value);
  259. }
  260. $result = $api->setValue($app, $key, $value);
  261. $this->assertInstanceOf(DataResponse::class, $result);
  262. $this->assertSame($status, $result->getStatus());
  263. if ($appThrows instanceof \Exception) {
  264. $this->assertEquals(['data' => ['message' => $appThrows->getMessage()]], $result->getData());
  265. } elseif ($keyThrows instanceof \Exception) {
  266. $this->assertEquals(['data' => ['message' => $keyThrows->getMessage()]], $result->getData());
  267. } else {
  268. $this->assertEquals([], $result->getData());
  269. }
  270. }
  271. public function dataDeleteValue() {
  272. return [
  273. ['app1', 'key', new \InvalidArgumentException('error1'), null, Http::STATUS_FORBIDDEN],
  274. ['app2', 'key', null, new \InvalidArgumentException('error2'), Http::STATUS_FORBIDDEN],
  275. ['app2', 'key', null, null, Http::STATUS_OK],
  276. ];
  277. }
  278. /**
  279. * @dataProvider dataDeleteValue
  280. * @param string $app
  281. * @param string|null $key
  282. * @param \Exception|null $appThrows
  283. * @param \Exception|null $keyThrows
  284. * @param int $status
  285. */
  286. public function testDeleteValue($app, $key, $appThrows, $keyThrows, $status): void {
  287. $api = $this->getInstance(['verifyAppId', 'verifyConfigKey']);
  288. if ($appThrows instanceof \Exception) {
  289. $api->expects($this->once())
  290. ->method('verifyAppId')
  291. ->with($app)
  292. ->willThrowException($appThrows);
  293. $api->expects($this->never())
  294. ->method('verifyConfigKey');
  295. $this->appConfig->expects($this->never())
  296. ->method('deleteKey');
  297. } elseif ($keyThrows instanceof \Exception) {
  298. $api->expects($this->once())
  299. ->method('verifyAppId')
  300. ->with($app);
  301. $api->expects($this->once())
  302. ->method('verifyConfigKey')
  303. ->with($app, $key)
  304. ->willThrowException($keyThrows);
  305. $this->appConfig->expects($this->never())
  306. ->method('deleteKey');
  307. } else {
  308. $api->expects($this->once())
  309. ->method('verifyAppId')
  310. ->with($app);
  311. $api->expects($this->once())
  312. ->method('verifyConfigKey')
  313. ->with($app, $key);
  314. $this->appConfig->expects($this->once())
  315. ->method('deleteKey')
  316. ->with($app, $key);
  317. }
  318. $result = $api->deleteKey($app, $key);
  319. $this->assertInstanceOf(DataResponse::class, $result);
  320. $this->assertSame($status, $result->getStatus());
  321. if ($appThrows instanceof \Exception) {
  322. $this->assertEquals(['data' => ['message' => $appThrows->getMessage()]], $result->getData());
  323. } elseif ($keyThrows instanceof \Exception) {
  324. $this->assertEquals(['data' => ['message' => $keyThrows->getMessage()]], $result->getData());
  325. } else {
  326. $this->assertEquals([], $result->getData());
  327. }
  328. }
  329. public function testVerifyAppId(): void {
  330. $api = $this->getInstance();
  331. $this->invokePrivate($api, 'verifyAppId', ['activity']);
  332. $this->addToAssertionCount(1);
  333. }
  334. public function dataVerifyAppIdThrows() {
  335. return [
  336. ['activity..'],
  337. ['activity/'],
  338. ['activity\\'],
  339. ['activity\0'],
  340. ];
  341. }
  342. /**
  343. * @dataProvider dataVerifyAppIdThrows
  344. * @param string $app
  345. */
  346. public function testVerifyAppIdThrows($app): void {
  347. $this->expectException(\InvalidArgumentException::class);
  348. $api = $this->getInstance();
  349. $this->invokePrivate($api, 'verifyAppId', [$app]);
  350. }
  351. public function dataVerifyConfigKey() {
  352. return [
  353. ['activity', 'abc', ''],
  354. ['dav', 'public_route', ''],
  355. ['files', 'remote_route', ''],
  356. ['core', 'encryption_enabled', 'yes'],
  357. ];
  358. }
  359. /**
  360. * @dataProvider dataVerifyConfigKey
  361. * @param string $app
  362. * @param string $key
  363. * @param string $value
  364. */
  365. public function testVerifyConfigKey($app, $key, $value): void {
  366. $api = $this->getInstance();
  367. $this->invokePrivate($api, 'verifyConfigKey', [$app, $key, $value]);
  368. $this->addToAssertionCount(1);
  369. }
  370. public function dataVerifyConfigKeyThrows() {
  371. return [
  372. ['activity', 'installed_version', ''],
  373. ['calendar', 'enabled', ''],
  374. ['contacts', 'types', ''],
  375. ['core', 'encryption_enabled', 'no'],
  376. ['core', 'encryption_enabled', ''],
  377. ['core', 'public_files', ''],
  378. ['core', 'public_dav', ''],
  379. ['core', 'remote_files', ''],
  380. ['core', 'remote_dav', ''],
  381. ];
  382. }
  383. /**
  384. * @dataProvider dataVerifyConfigKeyThrows
  385. * @param string $app
  386. * @param string $key
  387. * @param string $value
  388. */
  389. public function testVerifyConfigKeyThrows($app, $key, $value): void {
  390. $this->expectException(\InvalidArgumentException::class);
  391. $api = $this->getInstance();
  392. $this->invokePrivate($api, 'verifyConfigKey', [$app, $key, $value]);
  393. }
  394. }