StatusServiceTest.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace OCA\UserStatus\Tests\Service;
  8. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  9. use OC\DB\Exceptions\DbalException;
  10. use OCA\UserStatus\Db\UserStatus;
  11. use OCA\UserStatus\Db\UserStatusMapper;
  12. use OCA\UserStatus\Exception\InvalidClearAtException;
  13. use OCA\UserStatus\Exception\InvalidMessageIdException;
  14. use OCA\UserStatus\Exception\InvalidStatusIconException;
  15. use OCA\UserStatus\Exception\InvalidStatusTypeException;
  16. use OCA\UserStatus\Exception\StatusMessageTooLongException;
  17. use OCA\UserStatus\Service\PredefinedStatusService;
  18. use OCA\UserStatus\Service\StatusService;
  19. use OCP\AppFramework\Db\DoesNotExistException;
  20. use OCP\AppFramework\Utility\ITimeFactory;
  21. use OCP\DB\Exception;
  22. use OCP\IConfig;
  23. use OCP\IEmojiHelper;
  24. use OCP\IUserManager;
  25. use OCP\UserStatus\IUserStatus;
  26. use PHPUnit\Framework\MockObject\MockObject;
  27. use Psr\Log\LoggerInterface;
  28. use Test\TestCase;
  29. class StatusServiceTest extends TestCase {
  30. /** @var UserStatusMapper|MockObject */
  31. private $mapper;
  32. /** @var ITimeFactory|MockObject */
  33. private $timeFactory;
  34. /** @var PredefinedStatusService|MockObject */
  35. private $predefinedStatusService;
  36. /** @var IEmojiHelper|MockObject */
  37. private $emojiHelper;
  38. /** @var IConfig|MockObject */
  39. private $config;
  40. /** @var IUserManager|MockObject */
  41. private $userManager;
  42. /** @var LoggerInterface|MockObject */
  43. private $logger;
  44. private StatusService $service;
  45. protected function setUp(): void {
  46. parent::setUp();
  47. $this->mapper = $this->createMock(UserStatusMapper::class);
  48. $this->timeFactory = $this->createMock(ITimeFactory::class);
  49. $this->predefinedStatusService = $this->createMock(PredefinedStatusService::class);
  50. $this->emojiHelper = $this->createMock(IEmojiHelper::class);
  51. $this->userManager = $this->createMock(IUserManager::class);
  52. $this->config = $this->createMock(IConfig::class);
  53. $this->logger = $this->createMock(LoggerInterface::class);
  54. $this->config->method('getAppValue')
  55. ->willReturnMap([
  56. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  57. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no']
  58. ]);
  59. $this->service = new StatusService($this->mapper,
  60. $this->timeFactory,
  61. $this->predefinedStatusService,
  62. $this->emojiHelper,
  63. $this->config,
  64. $this->userManager,
  65. $this->logger,
  66. );
  67. }
  68. public function testFindAll(): void {
  69. $status1 = $this->createMock(UserStatus::class);
  70. $status2 = $this->createMock(UserStatus::class);
  71. $this->mapper->expects($this->once())
  72. ->method('findAll')
  73. ->with(20, 50)
  74. ->willReturn([$status1, $status2]);
  75. $this->assertEquals([
  76. $status1,
  77. $status2,
  78. ], $this->service->findAll(20, 50));
  79. }
  80. public function testFindAllRecentStatusChanges(): void {
  81. $status1 = $this->createMock(UserStatus::class);
  82. $status2 = $this->createMock(UserStatus::class);
  83. $this->mapper->expects($this->once())
  84. ->method('findAllRecent')
  85. ->with(20, 50)
  86. ->willReturn([$status1, $status2]);
  87. $this->assertEquals([
  88. $status1,
  89. $status2,
  90. ], $this->service->findAllRecentStatusChanges(20, 50));
  91. }
  92. public function testFindAllRecentStatusChangesNoEnumeration(): void {
  93. $status1 = $this->createMock(UserStatus::class);
  94. $status2 = $this->createMock(UserStatus::class);
  95. $this->mapper->method('findAllRecent')
  96. ->with(20, 50)
  97. ->willReturn([$status1, $status2]);
  98. // Rebuild $this->service with user enumeration turned off
  99. $this->config = $this->createMock(IConfig::class);
  100. $this->config->method('getAppValue')
  101. ->willReturnMap([
  102. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
  103. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no']
  104. ]);
  105. $this->service = new StatusService($this->mapper,
  106. $this->timeFactory,
  107. $this->predefinedStatusService,
  108. $this->emojiHelper,
  109. $this->config,
  110. $this->userManager,
  111. $this->logger,
  112. );
  113. $this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
  114. // Rebuild $this->service with user enumeration limited to common groups
  115. $this->config = $this->createMock(IConfig::class);
  116. $this->config->method('getAppValue')
  117. ->willReturnMap([
  118. ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
  119. ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes']
  120. ]);
  121. $this->service = new StatusService($this->mapper,
  122. $this->timeFactory,
  123. $this->predefinedStatusService,
  124. $this->emojiHelper,
  125. $this->config,
  126. $this->userManager,
  127. $this->logger,
  128. );
  129. $this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
  130. }
  131. public function testFindByUserIdDoesNotExist(): void {
  132. $this->mapper->expects($this->once())
  133. ->method('findByUserId')
  134. ->with('john.doe')
  135. ->willThrowException(new DoesNotExistException(''));
  136. $this->expectException(DoesNotExistException::class);
  137. $this->service->findByUserId('john.doe');
  138. }
  139. public function testFindAllAddDefaultMessage(): void {
  140. $status = new UserStatus();
  141. $status->setMessageId('commuting');
  142. $this->predefinedStatusService->expects($this->once())
  143. ->method('getDefaultStatusById')
  144. ->with('commuting')
  145. ->willReturn([
  146. 'id' => 'commuting',
  147. 'icon' => '🚌',
  148. 'message' => 'Commuting',
  149. 'clearAt' => [
  150. 'type' => 'period',
  151. 'time' => 1800,
  152. ],
  153. ]);
  154. $this->mapper->expects($this->once())
  155. ->method('findByUserId')
  156. ->with('john.doe')
  157. ->willReturn($status);
  158. $this->assertEquals($status, $this->service->findByUserId('john.doe'));
  159. $this->assertEquals('🚌', $status->getCustomIcon());
  160. $this->assertEquals('Commuting', $status->getCustomMessage());
  161. }
  162. public function testFindAllClearStatus(): void {
  163. $status = new UserStatus();
  164. $status->setStatus('online');
  165. $status->setStatusTimestamp(1000);
  166. $status->setIsUserDefined(true);
  167. $this->timeFactory->method('getTime')
  168. ->willReturn(2600);
  169. $this->mapper->expects($this->once())
  170. ->method('findByUserId')
  171. ->with('john.doe')
  172. ->willReturn($status);
  173. $this->assertEquals($status, $this->service->findByUserId('john.doe'));
  174. $this->assertEquals('offline', $status->getStatus());
  175. $this->assertEquals(2600, $status->getStatusTimestamp());
  176. $this->assertFalse($status->getIsUserDefined());
  177. }
  178. public function testFindAllClearMessage(): void {
  179. $status = new UserStatus();
  180. $status->setClearAt(50);
  181. $status->setMessageId('commuting');
  182. $status->setStatusTimestamp(60);
  183. $this->timeFactory->method('getTime')
  184. ->willReturn(60);
  185. $this->predefinedStatusService->expects($this->never())
  186. ->method('getDefaultStatusById');
  187. $this->mapper->expects($this->once())
  188. ->method('findByUserId')
  189. ->with('john.doe')
  190. ->willReturn($status);
  191. $this->assertEquals($status, $this->service->findByUserId('john.doe'));
  192. $this->assertNull($status->getClearAt());
  193. $this->assertNull($status->getMessageId());
  194. }
  195. /**
  196. * @param string $userId
  197. * @param string $status
  198. * @param int|null $statusTimestamp
  199. * @param bool $isUserDefined
  200. * @param bool $expectExisting
  201. * @param bool $expectSuccess
  202. * @param bool $expectTimeFactory
  203. * @param bool $expectException
  204. * @param string|null $expectedExceptionClass
  205. * @param string|null $expectedExceptionMessage
  206. *
  207. * @dataProvider setStatusDataProvider
  208. */
  209. public function testSetStatus(string $userId,
  210. string $status,
  211. ?int $statusTimestamp,
  212. bool $isUserDefined,
  213. bool $expectExisting,
  214. bool $expectSuccess,
  215. bool $expectTimeFactory,
  216. bool $expectException,
  217. ?string $expectedExceptionClass,
  218. ?string $expectedExceptionMessage): void {
  219. $userStatus = new UserStatus();
  220. if ($expectExisting) {
  221. $userStatus->setId(42);
  222. $userStatus->setUserId($userId);
  223. $this->mapper->expects($this->once())
  224. ->method('findByUserId')
  225. ->with($userId)
  226. ->willReturn($userStatus);
  227. } else {
  228. $this->mapper->expects($this->once())
  229. ->method('findByUserId')
  230. ->with($userId)
  231. ->willThrowException(new DoesNotExistException(''));
  232. }
  233. if ($expectTimeFactory) {
  234. $this->timeFactory
  235. ->method('getTime')
  236. ->willReturn(40);
  237. }
  238. if ($expectException) {
  239. $this->expectException($expectedExceptionClass);
  240. $this->expectExceptionMessage($expectedExceptionMessage);
  241. $this->service->setStatus($userId, $status, $statusTimestamp, $isUserDefined);
  242. }
  243. if ($expectSuccess) {
  244. if ($expectExisting) {
  245. $this->mapper->expects($this->once())
  246. ->method('update')
  247. ->willReturnArgument(0);
  248. } else {
  249. $this->mapper->expects($this->once())
  250. ->method('insert')
  251. ->willReturnArgument(0);
  252. }
  253. $actual = $this->service->setStatus($userId, $status, $statusTimestamp, $isUserDefined);
  254. $this->assertEquals('john.doe', $actual->getUserId());
  255. $this->assertEquals($status, $actual->getStatus());
  256. $this->assertEquals($statusTimestamp ?? 40, $actual->getStatusTimestamp());
  257. $this->assertEquals($isUserDefined, $actual->getIsUserDefined());
  258. }
  259. }
  260. public function setStatusDataProvider(): array {
  261. return [
  262. ['john.doe', 'online', 50, true, true, true, false, false, null, null],
  263. ['john.doe', 'online', 50, true, false, true, false, false, null, null],
  264. ['john.doe', 'online', 50, false, true, true, false, false, null, null],
  265. ['john.doe', 'online', 50, false, false, true, false, false, null, null],
  266. ['john.doe', 'online', null, true, true, true, true, false, null, null],
  267. ['john.doe', 'online', null, true, false, true, true, false, null, null],
  268. ['john.doe', 'online', null, false, true, true, true, false, null, null],
  269. ['john.doe', 'online', null, false, false, true, true, false, null, null],
  270. ['john.doe', 'away', 50, true, true, true, false, false, null, null],
  271. ['john.doe', 'away', 50, true, false, true, false, false, null, null],
  272. ['john.doe', 'away', 50, false, true, true, false, false, null, null],
  273. ['john.doe', 'away', 50, false, false, true, false, false, null, null],
  274. ['john.doe', 'away', null, true, true, true, true, false, null, null],
  275. ['john.doe', 'away', null, true, false, true, true, false, null, null],
  276. ['john.doe', 'away', null, false, true, true, true, false, null, null],
  277. ['john.doe', 'away', null, false, false, true, true, false, null, null],
  278. ['john.doe', 'dnd', 50, true, true, true, false, false, null, null],
  279. ['john.doe', 'dnd', 50, true, false, true, false, false, null, null],
  280. ['john.doe', 'dnd', 50, false, true, true, false, false, null, null],
  281. ['john.doe', 'dnd', 50, false, false, true, false, false, null, null],
  282. ['john.doe', 'dnd', null, true, true, true, true, false, null, null],
  283. ['john.doe', 'dnd', null, true, false, true, true, false, null, null],
  284. ['john.doe', 'dnd', null, false, true, true, true, false, null, null],
  285. ['john.doe', 'dnd', null, false, false, true, true, false, null, null],
  286. ['john.doe', 'invisible', 50, true, true, true, false, false, null, null],
  287. ['john.doe', 'invisible', 50, true, false, true, false, false, null, null],
  288. ['john.doe', 'invisible', 50, false, true, true, false, false, null, null],
  289. ['john.doe', 'invisible', 50, false, false, true, false, false, null, null],
  290. ['john.doe', 'invisible', null, true, true, true, true, false, null, null],
  291. ['john.doe', 'invisible', null, true, false, true, true, false, null, null],
  292. ['john.doe', 'invisible', null, false, true, true, true, false, null, null],
  293. ['john.doe', 'invisible', null, false, false, true, true, false, null, null],
  294. ['john.doe', 'offline', 50, true, true, true, false, false, null, null],
  295. ['john.doe', 'offline', 50, true, false, true, false, false, null, null],
  296. ['john.doe', 'offline', 50, false, true, true, false, false, null, null],
  297. ['john.doe', 'offline', 50, false, false, true, false, false, null, null],
  298. ['john.doe', 'offline', null, true, true, true, true, false, null, null],
  299. ['john.doe', 'offline', null, true, false, true, true, false, null, null],
  300. ['john.doe', 'offline', null, false, true, true, true, false, null, null],
  301. ['john.doe', 'offline', null, false, false, true, true, false, null, null],
  302. ['john.doe', 'illegal-status', 50, true, true, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  303. ['john.doe', 'illegal-status', 50, true, false, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  304. ['john.doe', 'illegal-status', 50, false, true, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  305. ['john.doe', 'illegal-status', 50, false, false, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  306. ['john.doe', 'illegal-status', null, true, true, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  307. ['john.doe', 'illegal-status', null, true, false, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  308. ['john.doe', 'illegal-status', null, false, true, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  309. ['john.doe', 'illegal-status', null, false, false, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
  310. ];
  311. }
  312. /**
  313. * @param string $userId
  314. * @param string $messageId
  315. * @param bool $isValidMessageId
  316. * @param int|null $clearAt
  317. * @param bool $expectExisting
  318. * @param bool $expectSuccess
  319. * @param bool $expectException
  320. * @param string|null $expectedExceptionClass
  321. * @param string|null $expectedExceptionMessage
  322. *
  323. * @dataProvider setPredefinedMessageDataProvider
  324. */
  325. public function testSetPredefinedMessage(string $userId,
  326. string $messageId,
  327. bool $isValidMessageId,
  328. ?int $clearAt,
  329. bool $expectExisting,
  330. bool $expectSuccess,
  331. bool $expectException,
  332. ?string $expectedExceptionClass,
  333. ?string $expectedExceptionMessage): void {
  334. $userStatus = new UserStatus();
  335. if ($expectExisting) {
  336. $userStatus->setId(42);
  337. $userStatus->setUserId($userId);
  338. $userStatus->setStatus('offline');
  339. $userStatus->setStatusTimestamp(0);
  340. $userStatus->setIsUserDefined(false);
  341. $userStatus->setCustomIcon('😀');
  342. $userStatus->setCustomMessage('Foo');
  343. $this->mapper->expects($this->once())
  344. ->method('findByUserId')
  345. ->with($userId)
  346. ->willReturn($userStatus);
  347. } else {
  348. $this->mapper->expects($this->once())
  349. ->method('findByUserId')
  350. ->with($userId)
  351. ->willThrowException(new DoesNotExistException(''));
  352. }
  353. $this->predefinedStatusService->expects($this->once())
  354. ->method('isValidId')
  355. ->with($messageId)
  356. ->willReturn($isValidMessageId);
  357. $this->timeFactory
  358. ->method('getTime')
  359. ->willReturn(40);
  360. if ($expectException) {
  361. $this->expectException($expectedExceptionClass);
  362. $this->expectExceptionMessage($expectedExceptionMessage);
  363. $this->service->setPredefinedMessage($userId, $messageId, $clearAt);
  364. }
  365. if ($expectSuccess) {
  366. if ($expectExisting) {
  367. $this->mapper->expects($this->once())
  368. ->method('update')
  369. ->willReturnArgument(0);
  370. } else {
  371. $this->mapper->expects($this->once())
  372. ->method('insert')
  373. ->willReturnArgument(0);
  374. }
  375. $actual = $this->service->setPredefinedMessage($userId, $messageId, $clearAt);
  376. $this->assertEquals('john.doe', $actual->getUserId());
  377. $this->assertEquals('offline', $actual->getStatus());
  378. $this->assertEquals(0, $actual->getStatusTimestamp());
  379. $this->assertEquals(false, $actual->getIsUserDefined());
  380. $this->assertEquals($messageId, $actual->getMessageId());
  381. $this->assertNull($actual->getCustomIcon());
  382. $this->assertNull($actual->getCustomMessage());
  383. $this->assertEquals($clearAt, $actual->getClearAt());
  384. }
  385. }
  386. public function setPredefinedMessageDataProvider(): array {
  387. return [
  388. ['john.doe', 'sick-leave', true, null, true, true, false, null, null],
  389. ['john.doe', 'sick-leave', true, null, false, true, false, null, null],
  390. ['john.doe', 'sick-leave', true, 20, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
  391. ['john.doe', 'sick-leave', true, 20, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
  392. ['john.doe', 'sick-leave', true, 60, true, true, false, null, null],
  393. ['john.doe', 'sick-leave', true, 60, false, true, false, null, null],
  394. ['john.doe', 'illegal-message-id', false, null, true, false, true, InvalidMessageIdException::class, 'Message-Id "illegal-message-id" is not supported'],
  395. ['john.doe', 'illegal-message-id', false, null, false, false, true, InvalidMessageIdException::class, 'Message-Id "illegal-message-id" is not supported'],
  396. ];
  397. }
  398. /**
  399. * @param string $userId
  400. * @param string|null $statusIcon
  401. * @param bool $supportsEmoji
  402. * @param string $message
  403. * @param int|null $clearAt
  404. * @param bool $expectExisting
  405. * @param bool $expectSuccess
  406. * @param bool $expectException
  407. * @param string|null $expectedExceptionClass
  408. * @param string|null $expectedExceptionMessage
  409. *
  410. * @dataProvider setCustomMessageDataProvider
  411. */
  412. public function testSetCustomMessage(string $userId,
  413. ?string $statusIcon,
  414. bool $supportsEmoji,
  415. string $message,
  416. ?int $clearAt,
  417. bool $expectExisting,
  418. bool $expectSuccess,
  419. bool $expectException,
  420. ?string $expectedExceptionClass,
  421. ?string $expectedExceptionMessage): void {
  422. $userStatus = new UserStatus();
  423. if ($expectExisting) {
  424. $userStatus->setId(42);
  425. $userStatus->setUserId($userId);
  426. $userStatus->setStatus('offline');
  427. $userStatus->setStatusTimestamp(0);
  428. $userStatus->setIsUserDefined(false);
  429. $userStatus->setMessageId('messageId-42');
  430. $this->mapper->expects($this->once())
  431. ->method('findByUserId')
  432. ->with($userId)
  433. ->willReturn($userStatus);
  434. } else {
  435. $this->mapper->expects($this->once())
  436. ->method('findByUserId')
  437. ->with($userId)
  438. ->willThrowException(new DoesNotExistException(''));
  439. }
  440. $this->emojiHelper->method('isValidSingleEmoji')
  441. ->with($statusIcon)
  442. ->willReturn($supportsEmoji);
  443. $this->timeFactory
  444. ->method('getTime')
  445. ->willReturn(40);
  446. if ($expectException) {
  447. $this->expectException($expectedExceptionClass);
  448. $this->expectExceptionMessage($expectedExceptionMessage);
  449. $this->service->setCustomMessage($userId, $statusIcon, $message, $clearAt);
  450. }
  451. if ($expectSuccess) {
  452. if ($expectExisting) {
  453. $this->mapper->expects($this->once())
  454. ->method('update')
  455. ->willReturnArgument(0);
  456. } else {
  457. $this->mapper->expects($this->once())
  458. ->method('insert')
  459. ->willReturnArgument(0);
  460. }
  461. $actual = $this->service->setCustomMessage($userId, $statusIcon, $message, $clearAt);
  462. $this->assertEquals('john.doe', $actual->getUserId());
  463. $this->assertEquals('offline', $actual->getStatus());
  464. $this->assertEquals(0, $actual->getStatusTimestamp());
  465. $this->assertEquals(false, $actual->getIsUserDefined());
  466. $this->assertNull($actual->getMessageId());
  467. $this->assertEquals($statusIcon, $actual->getCustomIcon());
  468. $this->assertEquals($message, $actual->getCustomMessage());
  469. $this->assertEquals($clearAt, $actual->getClearAt());
  470. }
  471. }
  472. public function setCustomMessageDataProvider(): array {
  473. return [
  474. ['john.doe', '😁', true, 'Custom message', null, true, true, false, null, null],
  475. ['john.doe', '😁', true, 'Custom message', null, false, true, false, null, null],
  476. ['john.doe', null, false, 'Custom message', null, true, true, false, null, null],
  477. ['john.doe', null, false, 'Custom message', null, false, true, false, null, null],
  478. ['john.doe', '😁', false, 'Custom message', null, true, false, true, InvalidStatusIconException::class, 'Status-Icon is longer than one character'],
  479. ['john.doe', '😁', false, 'Custom message', null, false, false, true, InvalidStatusIconException::class, 'Status-Icon is longer than one character'],
  480. ['john.doe', null, false, 'Custom message that is way too long and violates the maximum length and hence should be rejected', null, true, false, true, StatusMessageTooLongException::class, 'Message is longer than supported length of 80 characters'],
  481. ['john.doe', null, false, 'Custom message that is way too long and violates the maximum length and hence should be rejected', null, false, false, true, StatusMessageTooLongException::class, 'Message is longer than supported length of 80 characters'],
  482. ['john.doe', '😁', true, 'Custom message', 80, true, true, false, null, null],
  483. ['john.doe', '😁', true, 'Custom message', 80, false, true, false, null, null],
  484. ['john.doe', '😁', true, 'Custom message', 20, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
  485. ['john.doe', '😁', true, 'Custom message', 20, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
  486. ];
  487. }
  488. public function testClearStatus(): void {
  489. $status = new UserStatus();
  490. $status->setId(1);
  491. $status->setUserId('john.doe');
  492. $status->setStatus('dnd');
  493. $status->setStatusTimestamp(1337);
  494. $status->setIsUserDefined(true);
  495. $status->setMessageId('messageId-42');
  496. $status->setCustomIcon('🙊');
  497. $status->setCustomMessage('My custom status message');
  498. $status->setClearAt(42);
  499. $this->mapper->expects($this->once())
  500. ->method('findByUserId')
  501. ->with('john.doe')
  502. ->willReturn($status);
  503. $this->mapper->expects($this->once())
  504. ->method('update')
  505. ->with($status);
  506. $actual = $this->service->clearStatus('john.doe');
  507. $this->assertTrue($actual);
  508. $this->assertEquals('offline', $status->getStatus());
  509. $this->assertEquals(0, $status->getStatusTimestamp());
  510. $this->assertFalse($status->getIsUserDefined());
  511. }
  512. public function testClearStatusDoesNotExist(): void {
  513. $this->mapper->expects($this->once())
  514. ->method('findByUserId')
  515. ->with('john.doe')
  516. ->willThrowException(new DoesNotExistException(''));
  517. $this->mapper->expects($this->never())
  518. ->method('update');
  519. $actual = $this->service->clearStatus('john.doe');
  520. $this->assertFalse($actual);
  521. }
  522. public function testClearMessage(): void {
  523. $status = new UserStatus();
  524. $status->setId(1);
  525. $status->setUserId('john.doe');
  526. $status->setStatus('dnd');
  527. $status->setStatusTimestamp(1337);
  528. $status->setIsUserDefined(true);
  529. $status->setMessageId('messageId-42');
  530. $status->setCustomIcon('🙊');
  531. $status->setCustomMessage('My custom status message');
  532. $status->setClearAt(42);
  533. $this->mapper->expects($this->once())
  534. ->method('findByUserId')
  535. ->with('john.doe')
  536. ->willReturn($status);
  537. $this->mapper->expects($this->once())
  538. ->method('update')
  539. ->with($status);
  540. $actual = $this->service->clearMessage('john.doe');
  541. $this->assertTrue($actual);
  542. $this->assertNull($status->getMessageId());
  543. $this->assertNull($status->getCustomMessage());
  544. $this->assertNull($status->getCustomIcon());
  545. $this->assertNull($status->getClearAt());
  546. }
  547. public function testClearMessageDoesNotExist(): void {
  548. $this->mapper->expects($this->once())
  549. ->method('findByUserId')
  550. ->with('john.doe')
  551. ->willThrowException(new DoesNotExistException(''));
  552. $this->mapper->expects($this->never())
  553. ->method('update');
  554. $actual = $this->service->clearMessage('john.doe');
  555. $this->assertFalse($actual);
  556. }
  557. public function testRemoveUserStatus(): void {
  558. $status = $this->createMock(UserStatus::class);
  559. $this->mapper->expects($this->once())
  560. ->method('findByUserId')
  561. ->with('john.doe')
  562. ->willReturn($status);
  563. $this->mapper->expects($this->once())
  564. ->method('delete')
  565. ->with($status);
  566. $actual = $this->service->removeUserStatus('john.doe');
  567. $this->assertTrue($actual);
  568. }
  569. public function testRemoveUserStatusDoesNotExist(): void {
  570. $this->mapper->expects($this->once())
  571. ->method('findByUserId')
  572. ->with('john.doe')
  573. ->willThrowException(new DoesNotExistException(''));
  574. $this->mapper->expects($this->never())
  575. ->method('delete');
  576. $actual = $this->service->removeUserStatus('john.doe');
  577. $this->assertFalse($actual);
  578. }
  579. public function testCleanStatusAutomaticOnline(): void {
  580. $status = new UserStatus();
  581. $status->setStatus(IUserStatus::ONLINE);
  582. $status->setStatusTimestamp(1337);
  583. $status->setIsUserDefined(false);
  584. $this->mapper->expects(self::once())
  585. ->method('update')
  586. ->with($status);
  587. parent::invokePrivate($this->service, 'cleanStatus', [$status]);
  588. }
  589. public function testCleanStatusCustomOffline(): void {
  590. $status = new UserStatus();
  591. $status->setStatus(IUserStatus::OFFLINE);
  592. $status->setStatusTimestamp(1337);
  593. $status->setIsUserDefined(true);
  594. $this->mapper->expects(self::once())
  595. ->method('update')
  596. ->with($status);
  597. parent::invokePrivate($this->service, 'cleanStatus', [$status]);
  598. }
  599. public function testCleanStatusCleanedAlready(): void {
  600. $status = new UserStatus();
  601. $status->setStatus(IUserStatus::OFFLINE);
  602. $status->setStatusTimestamp(1337);
  603. $status->setIsUserDefined(false);
  604. // Don't update the status again and again when no value changed
  605. $this->mapper->expects(self::never())
  606. ->method('update')
  607. ->with($status);
  608. parent::invokePrivate($this->service, 'cleanStatus', [$status]);
  609. }
  610. public function testBackupWorkingHasBackupAlready(): void {
  611. $p = $this->createMock(UniqueConstraintViolationException::class);
  612. $e = DbalException::wrap($p);
  613. $this->mapper->expects($this->once())
  614. ->method('createBackupStatus')
  615. ->with('john')
  616. ->willThrowException($e);
  617. $this->assertFalse($this->service->backupCurrentStatus('john'));
  618. }
  619. public function testBackupThrowsOther(): void {
  620. $e = new Exception('', Exception::REASON_CONNECTION_LOST);
  621. $this->mapper->expects($this->once())
  622. ->method('createBackupStatus')
  623. ->with('john')
  624. ->willThrowException($e);
  625. $this->expectException(Exception::class);
  626. $this->service->backupCurrentStatus('john');
  627. }
  628. public function testBackup(): void {
  629. $this->mapper->expects($this->once())
  630. ->method('createBackupStatus')
  631. ->with('john')
  632. ->willReturn(true);
  633. $this->assertTrue($this->service->backupCurrentStatus('john'));
  634. }
  635. public function testRevertMultipleUserStatus(): void {
  636. $john = new UserStatus();
  637. $john->setId(1);
  638. $john->setStatus(IUserStatus::AWAY);
  639. $john->setStatusTimestamp(1337);
  640. $john->setIsUserDefined(false);
  641. $john->setMessageId('call');
  642. $john->setUserId('john');
  643. $john->setIsBackup(false);
  644. $johnBackup = new UserStatus();
  645. $johnBackup->setId(2);
  646. $johnBackup->setStatus(IUserStatus::ONLINE);
  647. $johnBackup->setStatusTimestamp(1337);
  648. $johnBackup->setIsUserDefined(true);
  649. $johnBackup->setMessageId('hello');
  650. $johnBackup->setUserId('_john');
  651. $johnBackup->setIsBackup(true);
  652. $noBackup = new UserStatus();
  653. $noBackup->setId(3);
  654. $noBackup->setStatus(IUserStatus::AWAY);
  655. $noBackup->setStatusTimestamp(1337);
  656. $noBackup->setIsUserDefined(false);
  657. $noBackup->setMessageId('call');
  658. $noBackup->setUserId('nobackup');
  659. $noBackup->setIsBackup(false);
  660. $backupOnly = new UserStatus();
  661. $backupOnly->setId(4);
  662. $backupOnly->setStatus(IUserStatus::ONLINE);
  663. $backupOnly->setStatusTimestamp(1337);
  664. $backupOnly->setIsUserDefined(true);
  665. $backupOnly->setMessageId('hello');
  666. $backupOnly->setUserId('_backuponly');
  667. $backupOnly->setIsBackup(true);
  668. $noBackupDND = new UserStatus();
  669. $noBackupDND->setId(5);
  670. $noBackupDND->setStatus(IUserStatus::DND);
  671. $noBackupDND->setStatusTimestamp(1337);
  672. $noBackupDND->setIsUserDefined(false);
  673. $noBackupDND->setMessageId('call');
  674. $noBackupDND->setUserId('nobackupanddnd');
  675. $noBackupDND->setIsBackup(false);
  676. $this->mapper->expects($this->once())
  677. ->method('findByUserIds')
  678. ->with(['john', 'nobackup', 'backuponly', 'nobackupanddnd', '_john', '_nobackup', '_backuponly', '_nobackupanddnd'])
  679. ->willReturn([
  680. $john,
  681. $johnBackup,
  682. $noBackup,
  683. $backupOnly,
  684. $noBackupDND,
  685. ]);
  686. $this->mapper->expects($this->once())
  687. ->method('deleteByIds')
  688. ->with([1, 3, 5]);
  689. $this->mapper->expects($this->once())
  690. ->method('restoreBackupStatuses')
  691. ->with([2]);
  692. $this->service->revertMultipleUserStatus(['john', 'nobackup', 'backuponly', 'nobackupanddnd'], 'call');
  693. }
  694. public function dataSetUserStatus(): array {
  695. return [
  696. [IUserStatus::MESSAGE_CALENDAR_BUSY, '', false],
  697. // Call > Meeting
  698. [IUserStatus::MESSAGE_CALENDAR_BUSY, IUserStatus::MESSAGE_CALL, false],
  699. [IUserStatus::MESSAGE_CALL, IUserStatus::MESSAGE_CALENDAR_BUSY, true],
  700. // Availability > Call&Meeting
  701. [IUserStatus::MESSAGE_CALENDAR_BUSY, IUserStatus::MESSAGE_AVAILABILITY, false],
  702. [IUserStatus::MESSAGE_CALL, IUserStatus::MESSAGE_AVAILABILITY, false],
  703. [IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::MESSAGE_CALENDAR_BUSY, true],
  704. [IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::MESSAGE_CALL, true],
  705. // Out-of-office > Availability&Call&Meeting
  706. [IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::MESSAGE_OUT_OF_OFFICE, false],
  707. [IUserStatus::MESSAGE_CALENDAR_BUSY, IUserStatus::MESSAGE_OUT_OF_OFFICE, false],
  708. [IUserStatus::MESSAGE_CALL, IUserStatus::MESSAGE_OUT_OF_OFFICE, false],
  709. [IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::MESSAGE_AVAILABILITY, true],
  710. [IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::MESSAGE_CALENDAR_BUSY, true],
  711. [IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::MESSAGE_CALL, true],
  712. ];
  713. }
  714. /**
  715. * @dataProvider dataSetUserStatus
  716. */
  717. public function testSetUserStatus(string $messageId, string $oldMessageId, bool $expectedUpdateShortcut): void {
  718. $previous = new UserStatus();
  719. $previous->setId(1);
  720. $previous->setStatus(IUserStatus::AWAY);
  721. $previous->setStatusTimestamp(1337);
  722. $previous->setIsUserDefined(false);
  723. $previous->setMessageId($oldMessageId);
  724. $previous->setUserId('john');
  725. $previous->setIsBackup(false);
  726. $this->mapper->expects($this->once())
  727. ->method('findByUserId')
  728. ->with('john')
  729. ->willReturn($previous);
  730. $e = DbalException::wrap($this->createMock(UniqueConstraintViolationException::class));
  731. $this->mapper->expects($expectedUpdateShortcut ? $this->never() : $this->once())
  732. ->method('createBackupStatus')
  733. ->willThrowException($e);
  734. $this->mapper->expects($this->any())
  735. ->method('update')
  736. ->willReturnArgument(0);
  737. $this->predefinedStatusService->expects($this->once())
  738. ->method('isValidId')
  739. ->with($messageId)
  740. ->willReturn(true);
  741. $this->service->setUserStatus('john', IUserStatus::DND, $messageId, true);
  742. }
  743. }