StatusServiceTest.php 28 KB

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