SyncTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. namespace OCA\User_LDAP\Tests\Jobs;
  7. use OCA\User_LDAP\Access;
  8. use OCA\User_LDAP\AccessFactory;
  9. use OCA\User_LDAP\Connection;
  10. use OCA\User_LDAP\ConnectionFactory;
  11. use OCA\User_LDAP\Helper;
  12. use OCA\User_LDAP\Jobs\Sync;
  13. use OCA\User_LDAP\LDAP;
  14. use OCA\User_LDAP\Mapping\UserMapping;
  15. use OCA\User_LDAP\User\Manager;
  16. use OCP\AppFramework\Utility\ITimeFactory;
  17. use OCP\EventDispatcher\IEventDispatcher;
  18. use OCP\IAvatarManager;
  19. use OCP\IConfig;
  20. use OCP\IDBConnection;
  21. use OCP\IUserManager;
  22. use OCP\Notification\IManager;
  23. use OCP\Server;
  24. use PHPUnit\Framework\MockObject\MockObject;
  25. use Psr\Log\LoggerInterface;
  26. use Test\TestCase;
  27. class SyncTest extends TestCase {
  28. /** @var array */
  29. protected $arguments;
  30. /** @var Helper|MockObject */
  31. protected $helper;
  32. /** @var LDAP|MockObject */
  33. protected $ldapWrapper;
  34. /** @var Manager|MockObject */
  35. protected $userManager;
  36. /** @var UserMapping|MockObject */
  37. protected $mapper;
  38. protected Sync $sync;
  39. /** @var IConfig|MockObject */
  40. protected $config;
  41. /** @var IAvatarManager|MockObject */
  42. protected $avatarManager;
  43. /** @var IDBConnection|MockObject */
  44. protected $dbc;
  45. /** @var IUserManager|MockObject */
  46. protected $ncUserManager;
  47. /** @var IManager|MockObject */
  48. protected $notificationManager;
  49. /** @var ConnectionFactory|MockObject */
  50. protected $connectionFactory;
  51. /** @var AccessFactory|MockObject */
  52. protected $accessFactory;
  53. protected function setUp(): void {
  54. parent::setUp();
  55. $this->helper = $this->createMock(Helper::class);
  56. $this->ldapWrapper = $this->createMock(LDAP::class);
  57. $this->userManager = $this->createMock(Manager::class);
  58. $this->mapper = $this->createMock(UserMapping::class);
  59. $this->config = $this->createMock(IConfig::class);
  60. $this->avatarManager = $this->createMock(IAvatarManager::class);
  61. $this->dbc = $this->createMock(IDBConnection::class);
  62. $this->ncUserManager = $this->createMock(IUserManager::class);
  63. $this->notificationManager = $this->createMock(IManager::class);
  64. $this->connectionFactory = $this->createMock(ConnectionFactory::class);
  65. $this->accessFactory = $this->createMock(AccessFactory::class);
  66. $this->sync = new Sync(
  67. Server::get(ITimeFactory::class),
  68. Server::get(IEventDispatcher::class),
  69. $this->config,
  70. $this->dbc,
  71. $this->avatarManager,
  72. $this->ncUserManager,
  73. Server::get(LoggerInterface::class),
  74. $this->notificationManager,
  75. $this->mapper,
  76. $this->helper,
  77. $this->connectionFactory,
  78. $this->accessFactory,
  79. );
  80. $this->sync->overwritePropertiesForTest($this->ldapWrapper);
  81. }
  82. public function intervalDataProvider(): array {
  83. return [
  84. [
  85. 0, 1000, 750
  86. ],
  87. [
  88. 22, 0, 50
  89. ],
  90. [
  91. 500, 500, 500
  92. ],
  93. [
  94. 1357, 0, 0
  95. ],
  96. [
  97. 421337, 2000, 3000
  98. ]
  99. ];
  100. }
  101. /**
  102. * @dataProvider intervalDataProvider
  103. */
  104. public function testUpdateInterval(int $userCount, int $pagingSize1, int $pagingSize2): void {
  105. $this->config->expects($this->once())
  106. ->method('setAppValue')
  107. ->with('user_ldap', 'background_sync_interval', $this->anything())
  108. ->willReturnCallback(function ($a, $k, $interval) {
  109. $this->assertTrue($interval >= SYNC::MIN_INTERVAL);
  110. $this->assertTrue($interval <= SYNC::MAX_INTERVAL);
  111. return true;
  112. });
  113. $this->config->expects($this->atLeastOnce())
  114. ->method('getAppKeys')
  115. ->willReturn([
  116. 'blabla',
  117. 'ldap_paging_size',
  118. 's07blabla',
  119. 'installed',
  120. 's07ldap_paging_size'
  121. ]);
  122. $this->config->expects($this->exactly(2))
  123. ->method('getAppValue')
  124. ->willReturnOnConsecutiveCalls($pagingSize1, $pagingSize2);
  125. $this->mapper->expects($this->atLeastOnce())
  126. ->method('count')
  127. ->willReturn($userCount);
  128. $this->sync->setArgument($this->arguments);
  129. $this->sync->updateInterval();
  130. }
  131. public function moreResultsProvider() {
  132. return [
  133. [ 3, 3, true ],
  134. [ 3, 5, true ],
  135. [ 3, 2, false],
  136. [ 0, 4, false],
  137. [ null, 4, false]
  138. ];
  139. }
  140. /**
  141. * @dataProvider moreResultsProvider
  142. */
  143. public function testMoreResults($pagingSize, $results, $expected): void {
  144. $connection = $this->createMock(Connection::class);
  145. $this->connectionFactory->expects($this->any())
  146. ->method('get')
  147. ->willReturn($connection);
  148. $connection->expects($this->any())
  149. ->method('__get')
  150. ->willReturnCallback(function ($key) use ($pagingSize) {
  151. if ($key === 'ldapPagingSize') {
  152. return $pagingSize;
  153. }
  154. return null;
  155. });
  156. /** @var Access|MockObject $access */
  157. $access = $this->createMock(Access::class);
  158. $this->accessFactory->expects($this->any())
  159. ->method('get')
  160. ->with($connection)
  161. ->willReturn($access);
  162. $this->userManager->expects($this->any())
  163. ->method('getAttributes')
  164. ->willReturn(['dn', 'uid', 'mail', 'displayname']);
  165. $access->expects($this->once())
  166. ->method('fetchListOfUsers')
  167. ->willReturn(array_pad([], $results, 'someUser'));
  168. $access->expects($this->any())
  169. ->method('combineFilterWithAnd')
  170. ->willReturn('pseudo=filter');
  171. $access->connection = $connection;
  172. $access->userManager = $this->userManager;
  173. $this->sync->setArgument($this->arguments);
  174. $hasMoreResults = $this->sync->runCycle(['prefix' => 's01', 'offset' => 100]);
  175. $this->assertSame($expected, $hasMoreResults);
  176. }
  177. public function cycleDataProvider() {
  178. $lastCycle = ['prefix' => 's01', 'offset' => 1000];
  179. $lastCycle2 = ['prefix' => '', 'offset' => 1000];
  180. return [
  181. [ null, ['s01'], ['prefix' => 's01', 'offset' => 0] ],
  182. [ null, [''], ['prefix' => '', 'offset' => 0] ],
  183. [ $lastCycle, ['s01', 's02'], ['prefix' => 's02', 'offset' => 0] ],
  184. [ $lastCycle, [''], ['prefix' => '', 'offset' => 0] ],
  185. [ $lastCycle2, ['', 's01'], ['prefix' => 's01', 'offset' => 0] ],
  186. [ $lastCycle, [], null ],
  187. ];
  188. }
  189. /**
  190. * @dataProvider cycleDataProvider
  191. */
  192. public function testDetermineNextCycle($cycleData, $prefixes, $expectedCycle): void {
  193. $this->helper->expects($this->any())
  194. ->method('getServerConfigurationPrefixes')
  195. ->with(true)
  196. ->willReturn($prefixes);
  197. if (is_array($expectedCycle)) {
  198. $this->config->expects($this->exactly(2))
  199. ->method('setAppValue')
  200. ->withConsecutive(
  201. ['user_ldap', 'background_sync_prefix', $expectedCycle['prefix']],
  202. ['user_ldap', 'background_sync_offset', $expectedCycle['offset']]
  203. );
  204. } else {
  205. $this->config->expects($this->never())
  206. ->method('setAppValue');
  207. }
  208. $this->sync->setArgument($this->arguments);
  209. $nextCycle = $this->sync->determineNextCycle($cycleData);
  210. if ($expectedCycle === null) {
  211. $this->assertNull($nextCycle);
  212. } else {
  213. $this->assertSame($expectedCycle['prefix'], $nextCycle['prefix']);
  214. $this->assertSame($expectedCycle['offset'], $nextCycle['offset']);
  215. }
  216. }
  217. public function testQualifiesToRun(): void {
  218. $cycleData = ['prefix' => 's01'];
  219. $this->config->expects($this->exactly(2))
  220. ->method('getAppValue')
  221. ->willReturnOnConsecutiveCalls(time() - 60 * 40, time() - 60 * 20);
  222. $this->sync->setArgument($this->arguments);
  223. $this->assertTrue($this->sync->qualifiesToRun($cycleData));
  224. $this->assertFalse($this->sync->qualifiesToRun($cycleData));
  225. }
  226. public function runDataProvider(): array {
  227. return [
  228. #0 - one LDAP server, reset
  229. [[
  230. 'prefixes' => [''],
  231. 'scheduledCycle' => ['prefix' => '', 'offset' => '4500'],
  232. 'pagingSize' => 500,
  233. 'usersThisCycle' => 0,
  234. 'expectedNextCycle' => ['prefix' => '', 'offset' => '0'],
  235. 'mappedUsers' => 123,
  236. ]],
  237. #1 - 2 LDAP servers, next prefix
  238. [[
  239. 'prefixes' => ['', 's01'],
  240. 'scheduledCycle' => ['prefix' => '', 'offset' => '4500'],
  241. 'pagingSize' => 500,
  242. 'usersThisCycle' => 0,
  243. 'expectedNextCycle' => ['prefix' => 's01', 'offset' => '0'],
  244. 'mappedUsers' => 123,
  245. ]],
  246. #2 - 2 LDAP servers, rotate prefix
  247. [[
  248. 'prefixes' => ['', 's01'],
  249. 'scheduledCycle' => ['prefix' => 's01', 'offset' => '4500'],
  250. 'pagingSize' => 500,
  251. 'usersThisCycle' => 0,
  252. 'expectedNextCycle' => ['prefix' => '', 'offset' => '0'],
  253. 'mappedUsers' => 123,
  254. ]],
  255. ];
  256. }
  257. /**
  258. * @dataProvider runDataProvider
  259. */
  260. public function testRun($runData): void {
  261. $this->config->expects($this->any())
  262. ->method('getAppValue')
  263. ->willReturnCallback(function ($app, $key, $default) use ($runData) {
  264. if ($app === 'core' && $key === 'backgroundjobs_mode') {
  265. return 'cron';
  266. }
  267. if ($app = 'user_ldap') {
  268. // for getCycle()
  269. if ($key === 'background_sync_prefix') {
  270. return $runData['scheduledCycle']['prefix'];
  271. }
  272. if ($key === 'background_sync_offset') {
  273. return $runData['scheduledCycle']['offset'];
  274. }
  275. // for qualifiesToRun()
  276. if ($key === $runData['scheduledCycle']['prefix'] . '_lastChange') {
  277. return time() - 60 * 40;
  278. }
  279. // for getMinPagingSize
  280. if ($key === $runData['scheduledCycle']['prefix'] . 'ldap_paging_size') {
  281. return $runData['pagingSize'];
  282. }
  283. }
  284. return $default;
  285. });
  286. $this->config->expects($this->exactly(3))
  287. ->method('setAppValue')
  288. ->withConsecutive(
  289. ['user_ldap', 'background_sync_prefix', $runData['expectedNextCycle']['prefix']],
  290. ['user_ldap', 'background_sync_offset', $runData['expectedNextCycle']['offset']],
  291. ['user_ldap', 'background_sync_interval', $this->anything()]
  292. );
  293. $this->config->expects($this->any())
  294. ->method('getAppKeys')
  295. ->with('user_ldap')
  296. ->willReturn([$runData['scheduledCycle']['prefix'] . 'ldap_paging_size']);
  297. $this->helper->expects($this->any())
  298. ->method('getServerConfigurationPrefixes')
  299. ->with(true)
  300. ->willReturn($runData['prefixes']);
  301. $connection = $this->createMock(Connection::class);
  302. $this->connectionFactory->expects($this->any())
  303. ->method('get')
  304. ->willReturn($connection);
  305. $connection->expects($this->any())
  306. ->method('__get')
  307. ->willReturnCallback(function ($key) use ($runData) {
  308. if ($key === 'ldapPagingSize') {
  309. return $runData['pagingSize'];
  310. }
  311. return null;
  312. });
  313. /** @var Access|MockObject $access */
  314. $access = $this->createMock(Access::class);
  315. $this->accessFactory->expects($this->any())
  316. ->method('get')
  317. ->with($connection)
  318. ->willReturn($access);
  319. $this->userManager->expects($this->any())
  320. ->method('getAttributes')
  321. ->willReturn(['dn', 'uid', 'mail', 'displayname']);
  322. $access->expects($this->once())
  323. ->method('fetchListOfUsers')
  324. ->willReturn(array_pad([], $runData['usersThisCycle'], 'someUser'));
  325. $access->expects($this->any())
  326. ->method('combineFilterWithAnd')
  327. ->willReturn('pseudo=filter');
  328. $access->connection = $connection;
  329. $access->userManager = $this->userManager;
  330. $this->mapper->expects($this->any())
  331. ->method('count')
  332. ->willReturn($runData['mappedUsers']);
  333. $this->sync->run($this->arguments);
  334. }
  335. }