AppConfigControllerTest.php 13 KB

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