aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_status/tests/Unit/Service/StatusServiceTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_status/tests/Unit/Service/StatusServiceTest.php')
-rw-r--r--apps/user_status/tests/Unit/Service/StatusServiceTest.php828
1 files changed, 828 insertions, 0 deletions
diff --git a/apps/user_status/tests/Unit/Service/StatusServiceTest.php b/apps/user_status/tests/Unit/Service/StatusServiceTest.php
new file mode 100644
index 00000000000..7dfa5b0d064
--- /dev/null
+++ b/apps/user_status/tests/Unit/Service/StatusServiceTest.php
@@ -0,0 +1,828 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+namespace OCA\UserStatus\Tests\Service;
+
+use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OC\DB\Exceptions\DbalException;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Db\UserStatusMapper;
+use OCA\UserStatus\Exception\InvalidClearAtException;
+use OCA\UserStatus\Exception\InvalidMessageIdException;
+use OCA\UserStatus\Exception\InvalidStatusIconException;
+use OCA\UserStatus\Exception\InvalidStatusTypeException;
+use OCA\UserStatus\Exception\StatusMessageTooLongException;
+use OCA\UserStatus\Service\PredefinedStatusService;
+use OCA\UserStatus\Service\StatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\DB\Exception;
+use OCP\IConfig;
+use OCP\IEmojiHelper;
+use OCP\IUserManager;
+use OCP\UserStatus\IUserStatus;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class StatusServiceTest extends TestCase {
+ private UserStatusMapper&MockObject $mapper;
+ private ITimeFactory&MockObject $timeFactory;
+ private PredefinedStatusService&MockObject $predefinedStatusService;
+ private IEmojiHelper&MockObject $emojiHelper;
+ private IConfig&MockObject $config;
+ private IUserManager&MockObject $userManager;
+ private LoggerInterface&MockObject $logger;
+
+ private StatusService $service;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mapper = $this->createMock(UserStatusMapper::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->predefinedStatusService = $this->createMock(PredefinedStatusService::class);
+ $this->emojiHelper = $this->createMock(IEmojiHelper::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->config = $this->createMock(IConfig::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->config->method('getAppValue')
+ ->willReturnMap([
+ ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
+ ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no']
+ ]);
+
+ $this->service = new StatusService($this->mapper,
+ $this->timeFactory,
+ $this->predefinedStatusService,
+ $this->emojiHelper,
+ $this->config,
+ $this->userManager,
+ $this->logger,
+ );
+ }
+
+ public function testFindAll(): void {
+ $status1 = $this->createMock(UserStatus::class);
+ $status2 = $this->createMock(UserStatus::class);
+
+ $this->mapper->expects($this->once())
+ ->method('findAll')
+ ->with(20, 50)
+ ->willReturn([$status1, $status2]);
+
+ $this->assertEquals([
+ $status1,
+ $status2,
+ ], $this->service->findAll(20, 50));
+ }
+
+ public function testFindAllRecentStatusChanges(): void {
+ $status1 = $this->createMock(UserStatus::class);
+ $status2 = $this->createMock(UserStatus::class);
+
+ $this->mapper->expects($this->once())
+ ->method('findAllRecent')
+ ->with(20, 50)
+ ->willReturn([$status1, $status2]);
+
+ $this->assertEquals([
+ $status1,
+ $status2,
+ ], $this->service->findAllRecentStatusChanges(20, 50));
+ }
+
+ public function testFindAllRecentStatusChangesNoEnumeration(): void {
+ $status1 = $this->createMock(UserStatus::class);
+ $status2 = $this->createMock(UserStatus::class);
+
+ $this->mapper->method('findAllRecent')
+ ->with(20, 50)
+ ->willReturn([$status1, $status2]);
+
+ // Rebuild $this->service with user enumeration turned off
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->config->method('getAppValue')
+ ->willReturnMap([
+ ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'no'],
+ ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'no']
+ ]);
+
+ $this->service = new StatusService($this->mapper,
+ $this->timeFactory,
+ $this->predefinedStatusService,
+ $this->emojiHelper,
+ $this->config,
+ $this->userManager,
+ $this->logger,
+ );
+
+ $this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
+
+ // Rebuild $this->service with user enumeration limited to common groups
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->config->method('getAppValue')
+ ->willReturnMap([
+ ['core', 'shareapi_allow_share_dialog_user_enumeration', 'yes', 'yes'],
+ ['core', 'shareapi_restrict_user_enumeration_to_group', 'no', 'yes']
+ ]);
+
+ $this->service = new StatusService($this->mapper,
+ $this->timeFactory,
+ $this->predefinedStatusService,
+ $this->emojiHelper,
+ $this->config,
+ $this->userManager,
+ $this->logger,
+ );
+
+ $this->assertEquals([], $this->service->findAllRecentStatusChanges(20, 50));
+ }
+
+ public function testFindByUserIdDoesNotExist(): void {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willThrowException(new DoesNotExistException(''));
+
+ $this->expectException(DoesNotExistException::class);
+ $this->service->findByUserId('john.doe');
+ }
+
+ public function testFindAllAddDefaultMessage(): void {
+ $status = new UserStatus();
+ $status->setMessageId('commuting');
+
+ $this->predefinedStatusService->expects($this->once())
+ ->method('getDefaultStatusById')
+ ->with('commuting')
+ ->willReturn([
+ 'id' => 'commuting',
+ 'icon' => '🚌',
+ 'message' => 'Commuting',
+ 'clearAt' => [
+ 'type' => 'period',
+ 'time' => 1800,
+ ],
+ ]);
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+
+ $this->assertEquals($status, $this->service->findByUserId('john.doe'));
+ $this->assertEquals('🚌', $status->getCustomIcon());
+ $this->assertEquals('Commuting', $status->getCustomMessage());
+ }
+
+ public function testFindAllClearStatus(): void {
+ $status = new UserStatus();
+ $status->setStatus('online');
+ $status->setStatusTimestamp(1000);
+ $status->setIsUserDefined(true);
+
+ $this->timeFactory->method('getTime')
+ ->willReturn(2600);
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+
+ $this->assertEquals($status, $this->service->findByUserId('john.doe'));
+ $this->assertEquals('offline', $status->getStatus());
+ $this->assertEquals(2600, $status->getStatusTimestamp());
+ $this->assertFalse($status->getIsUserDefined());
+ }
+
+ public function testFindAllClearMessage(): void {
+ $status = new UserStatus();
+ $status->setClearAt(50);
+ $status->setMessageId('commuting');
+ $status->setStatusTimestamp(60);
+
+ $this->timeFactory->method('getTime')
+ ->willReturn(60);
+ $this->predefinedStatusService->expects($this->never())
+ ->method('getDefaultStatusById');
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+ $this->assertEquals($status, $this->service->findByUserId('john.doe'));
+ $this->assertNull($status->getClearAt());
+ $this->assertNull($status->getMessageId());
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('setStatusDataProvider')]
+ public function testSetStatus(
+ string $userId,
+ string $status,
+ ?int $statusTimestamp,
+ bool $isUserDefined,
+ bool $expectExisting,
+ bool $expectSuccess,
+ bool $expectTimeFactory,
+ bool $expectException,
+ ?string $expectedExceptionClass,
+ ?string $expectedExceptionMessage,
+ ): void {
+ $userStatus = new UserStatus();
+
+ if ($expectExisting) {
+ $userStatus->setId(42);
+ $userStatus->setUserId($userId);
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willReturn($userStatus);
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willThrowException(new DoesNotExistException(''));
+ }
+
+ if ($expectTimeFactory) {
+ $this->timeFactory
+ ->method('getTime')
+ ->willReturn(40);
+ }
+
+ if ($expectException) {
+ $this->expectException($expectedExceptionClass);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+
+ $this->service->setStatus($userId, $status, $statusTimestamp, $isUserDefined);
+ }
+
+ if ($expectSuccess) {
+ if ($expectExisting) {
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->willReturnArgument(0);
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('insert')
+ ->willReturnArgument(0);
+ }
+
+ $actual = $this->service->setStatus($userId, $status, $statusTimestamp, $isUserDefined);
+
+ $this->assertEquals('john.doe', $actual->getUserId());
+ $this->assertEquals($status, $actual->getStatus());
+ $this->assertEquals($statusTimestamp ?? 40, $actual->getStatusTimestamp());
+ $this->assertEquals($isUserDefined, $actual->getIsUserDefined());
+ }
+ }
+
+ public static function setStatusDataProvider(): array {
+ return [
+ ['john.doe', 'online', 50, true, true, true, false, false, null, null],
+ ['john.doe', 'online', 50, true, false, true, false, false, null, null],
+ ['john.doe', 'online', 50, false, true, true, false, false, null, null],
+ ['john.doe', 'online', 50, false, false, true, false, false, null, null],
+ ['john.doe', 'online', null, true, true, true, true, false, null, null],
+ ['john.doe', 'online', null, true, false, true, true, false, null, null],
+ ['john.doe', 'online', null, false, true, true, true, false, null, null],
+ ['john.doe', 'online', null, false, false, true, true, false, null, null],
+
+ ['john.doe', 'away', 50, true, true, true, false, false, null, null],
+ ['john.doe', 'away', 50, true, false, true, false, false, null, null],
+ ['john.doe', 'away', 50, false, true, true, false, false, null, null],
+ ['john.doe', 'away', 50, false, false, true, false, false, null, null],
+ ['john.doe', 'away', null, true, true, true, true, false, null, null],
+ ['john.doe', 'away', null, true, false, true, true, false, null, null],
+ ['john.doe', 'away', null, false, true, true, true, false, null, null],
+ ['john.doe', 'away', null, false, false, true, true, false, null, null],
+
+ ['john.doe', 'dnd', 50, true, true, true, false, false, null, null],
+ ['john.doe', 'dnd', 50, true, false, true, false, false, null, null],
+ ['john.doe', 'dnd', 50, false, true, true, false, false, null, null],
+ ['john.doe', 'dnd', 50, false, false, true, false, false, null, null],
+ ['john.doe', 'dnd', null, true, true, true, true, false, null, null],
+ ['john.doe', 'dnd', null, true, false, true, true, false, null, null],
+ ['john.doe', 'dnd', null, false, true, true, true, false, null, null],
+ ['john.doe', 'dnd', null, false, false, true, true, false, null, null],
+
+ ['john.doe', 'invisible', 50, true, true, true, false, false, null, null],
+ ['john.doe', 'invisible', 50, true, false, true, false, false, null, null],
+ ['john.doe', 'invisible', 50, false, true, true, false, false, null, null],
+ ['john.doe', 'invisible', 50, false, false, true, false, false, null, null],
+ ['john.doe', 'invisible', null, true, true, true, true, false, null, null],
+ ['john.doe', 'invisible', null, true, false, true, true, false, null, null],
+ ['john.doe', 'invisible', null, false, true, true, true, false, null, null],
+ ['john.doe', 'invisible', null, false, false, true, true, false, null, null],
+
+ ['john.doe', 'offline', 50, true, true, true, false, false, null, null],
+ ['john.doe', 'offline', 50, true, false, true, false, false, null, null],
+ ['john.doe', 'offline', 50, false, true, true, false, false, null, null],
+ ['john.doe', 'offline', 50, false, false, true, false, false, null, null],
+ ['john.doe', 'offline', null, true, true, true, true, false, null, null],
+ ['john.doe', 'offline', null, true, false, true, true, false, null, null],
+ ['john.doe', 'offline', null, false, true, true, true, false, null, null],
+ ['john.doe', 'offline', null, false, false, true, true, false, null, null],
+
+ ['john.doe', 'illegal-status', 50, true, true, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', 50, true, false, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', 50, false, true, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', 50, false, false, false, false, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', null, true, true, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', null, true, false, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', null, false, true, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ['john.doe', 'illegal-status', null, false, false, false, true, true, InvalidStatusTypeException::class, 'Status-type "illegal-status" is not supported'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('setPredefinedMessageDataProvider')]
+ public function testSetPredefinedMessage(
+ string $userId,
+ string $messageId,
+ bool $isValidMessageId,
+ ?int $clearAt,
+ bool $expectExisting,
+ bool $expectSuccess,
+ bool $expectException,
+ ?string $expectedExceptionClass,
+ ?string $expectedExceptionMessage,
+ ): void {
+ $userStatus = new UserStatus();
+
+ if ($expectExisting) {
+ $userStatus->setId(42);
+ $userStatus->setUserId($userId);
+ $userStatus->setStatus('offline');
+ $userStatus->setStatusTimestamp(0);
+ $userStatus->setIsUserDefined(false);
+ $userStatus->setCustomIcon('😀');
+ $userStatus->setCustomMessage('Foo');
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willReturn($userStatus);
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willThrowException(new DoesNotExistException(''));
+ }
+
+ $this->predefinedStatusService->expects($this->once())
+ ->method('isValidId')
+ ->with($messageId)
+ ->willReturn($isValidMessageId);
+
+ $this->timeFactory
+ ->method('getTime')
+ ->willReturn(40);
+
+ if ($expectException) {
+ $this->expectException($expectedExceptionClass);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+
+ $this->service->setPredefinedMessage($userId, $messageId, $clearAt);
+ }
+
+ if ($expectSuccess) {
+ if ($expectExisting) {
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->willReturnArgument(0);
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('insert')
+ ->willReturnArgument(0);
+ }
+
+ $actual = $this->service->setPredefinedMessage($userId, $messageId, $clearAt);
+
+ $this->assertEquals('john.doe', $actual->getUserId());
+ $this->assertEquals('offline', $actual->getStatus());
+ $this->assertEquals(0, $actual->getStatusTimestamp());
+ $this->assertEquals(false, $actual->getIsUserDefined());
+ $this->assertEquals($messageId, $actual->getMessageId());
+ $this->assertNull($actual->getCustomIcon());
+ $this->assertNull($actual->getCustomMessage());
+ $this->assertEquals($clearAt, $actual->getClearAt());
+ }
+ }
+
+ public static function setPredefinedMessageDataProvider(): array {
+ return [
+ ['john.doe', 'sick-leave', true, null, true, true, false, null, null],
+ ['john.doe', 'sick-leave', true, null, false, true, false, null, null],
+ ['john.doe', 'sick-leave', true, 20, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
+ ['john.doe', 'sick-leave', true, 20, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
+ ['john.doe', 'sick-leave', true, 60, true, true, false, null, null],
+ ['john.doe', 'sick-leave', true, 60, false, true, false, null, null],
+ ['john.doe', 'illegal-message-id', false, null, true, false, true, InvalidMessageIdException::class, 'Message-Id "illegal-message-id" is not supported'],
+ ['john.doe', 'illegal-message-id', false, null, false, false, true, InvalidMessageIdException::class, 'Message-Id "illegal-message-id" is not supported'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('setCustomMessageDataProvider')]
+ public function testSetCustomMessage(
+ string $userId,
+ ?string $statusIcon,
+ bool $supportsEmoji,
+ string $message,
+ ?int $clearAt,
+ bool $expectExisting,
+ bool $expectSuccess,
+ bool $expectException,
+ ?string $expectedExceptionClass,
+ ?string $expectedExceptionMessage,
+ ): void {
+ $userStatus = new UserStatus();
+
+ if ($expectExisting) {
+ $userStatus->setId(42);
+ $userStatus->setUserId($userId);
+ $userStatus->setStatus('offline');
+ $userStatus->setStatusTimestamp(0);
+ $userStatus->setIsUserDefined(false);
+ $userStatus->setMessageId('messageId-42');
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willReturn($userStatus);
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willThrowException(new DoesNotExistException(''));
+ }
+
+ $this->emojiHelper->method('isValidSingleEmoji')
+ ->with($statusIcon)
+ ->willReturn($supportsEmoji);
+
+ $this->timeFactory
+ ->method('getTime')
+ ->willReturn(40);
+
+ if ($expectException) {
+ $this->expectException($expectedExceptionClass);
+ $this->expectExceptionMessage($expectedExceptionMessage);
+
+ $this->service->setCustomMessage($userId, $statusIcon, $message, $clearAt);
+ }
+
+ if ($expectSuccess) {
+ if ($expectExisting) {
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->willReturnArgument(0);
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('insert')
+ ->willReturnArgument(0);
+ }
+
+ $actual = $this->service->setCustomMessage($userId, $statusIcon, $message, $clearAt);
+
+ $this->assertEquals('john.doe', $actual->getUserId());
+ $this->assertEquals('offline', $actual->getStatus());
+ $this->assertEquals(0, $actual->getStatusTimestamp());
+ $this->assertEquals(false, $actual->getIsUserDefined());
+ $this->assertNull($actual->getMessageId());
+ $this->assertEquals($statusIcon, $actual->getCustomIcon());
+ $this->assertEquals($message, $actual->getCustomMessage());
+ $this->assertEquals($clearAt, $actual->getClearAt());
+ }
+ }
+
+ public static function setCustomMessageDataProvider(): array {
+ return [
+ ['john.doe', '😁', true, 'Custom message', null, true, true, false, null, null],
+ ['john.doe', '😁', true, 'Custom message', null, false, true, false, null, null],
+ ['john.doe', null, false, 'Custom message', null, true, true, false, null, null],
+ ['john.doe', null, false, 'Custom message', null, false, true, false, null, null],
+ ['john.doe', '😁', false, 'Custom message', null, true, false, true, InvalidStatusIconException::class, 'Status-Icon is longer than one character'],
+ ['john.doe', '😁', false, 'Custom message', null, false, false, true, InvalidStatusIconException::class, 'Status-Icon is longer than one character'],
+ ['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'],
+ ['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'],
+ ['john.doe', '😁', true, 'Custom message', 80, true, true, false, null, null],
+ ['john.doe', '😁', true, 'Custom message', 80, false, true, false, null, null],
+ ['john.doe', '😁', true, 'Custom message', 20, true, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
+ ['john.doe', '😁', true, 'Custom message', 20, false, false, true, InvalidClearAtException::class, 'ClearAt is in the past'],
+ ];
+ }
+
+ public function testClearStatus(): void {
+ $status = new UserStatus();
+ $status->setId(1);
+ $status->setUserId('john.doe');
+ $status->setStatus('dnd');
+ $status->setStatusTimestamp(1337);
+ $status->setIsUserDefined(true);
+ $status->setMessageId('messageId-42');
+ $status->setCustomIcon('🙊');
+ $status->setCustomMessage('My custom status message');
+ $status->setClearAt(42);
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($status);
+
+ $actual = $this->service->clearStatus('john.doe');
+ $this->assertTrue($actual);
+ $this->assertEquals('offline', $status->getStatus());
+ $this->assertEquals(0, $status->getStatusTimestamp());
+ $this->assertFalse($status->getIsUserDefined());
+ }
+
+ public function testClearStatusDoesNotExist(): void {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willThrowException(new DoesNotExistException(''));
+
+ $this->mapper->expects($this->never())
+ ->method('update');
+
+ $actual = $this->service->clearStatus('john.doe');
+ $this->assertFalse($actual);
+ }
+
+ public function testClearMessage(): void {
+ $status = new UserStatus();
+ $status->setId(1);
+ $status->setUserId('john.doe');
+ $status->setStatus('dnd');
+ $status->setStatusTimestamp(1337);
+ $status->setIsUserDefined(true);
+ $status->setMessageId('messageId-42');
+ $status->setCustomIcon('🙊');
+ $status->setCustomMessage('My custom status message');
+ $status->setClearAt(42);
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($status);
+
+ $actual = $this->service->clearMessage('john.doe');
+ $this->assertTrue($actual);
+ $this->assertNull($status->getMessageId());
+ $this->assertNull($status->getCustomMessage());
+ $this->assertNull($status->getCustomIcon());
+ $this->assertNull($status->getClearAt());
+ }
+
+ public function testClearMessageDoesNotExist(): void {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willThrowException(new DoesNotExistException(''));
+
+ $this->mapper->expects($this->never())
+ ->method('update');
+
+ $actual = $this->service->clearMessage('john.doe');
+ $this->assertFalse($actual);
+ }
+
+ public function testRemoveUserStatus(): void {
+ $status = $this->createMock(UserStatus::class);
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+
+ $this->mapper->expects($this->once())
+ ->method('delete')
+ ->with($status);
+
+ $actual = $this->service->removeUserStatus('john.doe');
+ $this->assertTrue($actual);
+ }
+
+ public function testRemoveUserStatusDoesNotExist(): void {
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willThrowException(new DoesNotExistException(''));
+
+ $this->mapper->expects($this->never())
+ ->method('delete');
+
+ $actual = $this->service->removeUserStatus('john.doe');
+ $this->assertFalse($actual);
+ }
+
+ public function testCleanStatusAutomaticOnline(): void {
+ $status = new UserStatus();
+ $status->setStatus(IUserStatus::ONLINE);
+ $status->setStatusTimestamp(1337);
+ $status->setIsUserDefined(false);
+
+ $this->mapper->expects(self::once())
+ ->method('update')
+ ->with($status);
+
+ parent::invokePrivate($this->service, 'cleanStatus', [$status]);
+ }
+
+ public function testCleanStatusCustomOffline(): void {
+ $status = new UserStatus();
+ $status->setStatus(IUserStatus::OFFLINE);
+ $status->setStatusTimestamp(1337);
+ $status->setIsUserDefined(true);
+
+ $this->mapper->expects(self::once())
+ ->method('update')
+ ->with($status);
+
+ parent::invokePrivate($this->service, 'cleanStatus', [$status]);
+ }
+
+ public function testCleanStatusCleanedAlready(): void {
+ $status = new UserStatus();
+ $status->setStatus(IUserStatus::OFFLINE);
+ $status->setStatusTimestamp(1337);
+ $status->setIsUserDefined(false);
+
+ // Don't update the status again and again when no value changed
+ $this->mapper->expects(self::never())
+ ->method('update')
+ ->with($status);
+
+ parent::invokePrivate($this->service, 'cleanStatus', [$status]);
+ }
+
+ public function testBackupWorkingHasBackupAlready(): void {
+ $p = $this->createMock(UniqueConstraintViolationException::class);
+ $e = DbalException::wrap($p);
+ $this->mapper->expects($this->once())
+ ->method('createBackupStatus')
+ ->with('john')
+ ->willThrowException($e);
+
+ $this->assertFalse($this->service->backupCurrentStatus('john'));
+ }
+
+ public function testBackupThrowsOther(): void {
+ $e = new Exception('', Exception::REASON_CONNECTION_LOST);
+ $this->mapper->expects($this->once())
+ ->method('createBackupStatus')
+ ->with('john')
+ ->willThrowException($e);
+
+ $this->expectException(Exception::class);
+ $this->service->backupCurrentStatus('john');
+ }
+
+ public function testBackup(): void {
+ $this->mapper->expects($this->once())
+ ->method('createBackupStatus')
+ ->with('john')
+ ->willReturn(true);
+
+ $this->assertTrue($this->service->backupCurrentStatus('john'));
+ }
+
+ public function testRevertMultipleUserStatus(): void {
+ $john = new UserStatus();
+ $john->setId(1);
+ $john->setStatus(IUserStatus::AWAY);
+ $john->setStatusTimestamp(1337);
+ $john->setIsUserDefined(false);
+ $john->setMessageId('call');
+ $john->setUserId('john');
+ $john->setIsBackup(false);
+
+ $johnBackup = new UserStatus();
+ $johnBackup->setId(2);
+ $johnBackup->setStatus(IUserStatus::ONLINE);
+ $johnBackup->setStatusTimestamp(1337);
+ $johnBackup->setIsUserDefined(true);
+ $johnBackup->setMessageId('hello');
+ $johnBackup->setUserId('_john');
+ $johnBackup->setIsBackup(true);
+
+ $noBackup = new UserStatus();
+ $noBackup->setId(3);
+ $noBackup->setStatus(IUserStatus::AWAY);
+ $noBackup->setStatusTimestamp(1337);
+ $noBackup->setIsUserDefined(false);
+ $noBackup->setMessageId('call');
+ $noBackup->setUserId('nobackup');
+ $noBackup->setIsBackup(false);
+
+ $backupOnly = new UserStatus();
+ $backupOnly->setId(4);
+ $backupOnly->setStatus(IUserStatus::ONLINE);
+ $backupOnly->setStatusTimestamp(1337);
+ $backupOnly->setIsUserDefined(true);
+ $backupOnly->setMessageId('hello');
+ $backupOnly->setUserId('_backuponly');
+ $backupOnly->setIsBackup(true);
+
+ $noBackupDND = new UserStatus();
+ $noBackupDND->setId(5);
+ $noBackupDND->setStatus(IUserStatus::DND);
+ $noBackupDND->setStatusTimestamp(1337);
+ $noBackupDND->setIsUserDefined(false);
+ $noBackupDND->setMessageId('call');
+ $noBackupDND->setUserId('nobackupanddnd');
+ $noBackupDND->setIsBackup(false);
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserIds')
+ ->with(['john', 'nobackup', 'backuponly', 'nobackupanddnd', '_john', '_nobackup', '_backuponly', '_nobackupanddnd'])
+ ->willReturn([
+ $john,
+ $johnBackup,
+ $noBackup,
+ $backupOnly,
+ $noBackupDND,
+ ]);
+
+ $this->mapper->expects($this->once())
+ ->method('deleteByIds')
+ ->with([1, 3, 5]);
+
+ $this->mapper->expects($this->once())
+ ->method('restoreBackupStatuses')
+ ->with([2]);
+
+ $this->service->revertMultipleUserStatus(['john', 'nobackup', 'backuponly', 'nobackupanddnd'], 'call');
+ }
+
+ public static function dataSetUserStatus(): array {
+ return [
+ [IUserStatus::MESSAGE_CALENDAR_BUSY, '', false],
+
+ // Call > Meeting
+ [IUserStatus::MESSAGE_CALENDAR_BUSY, IUserStatus::MESSAGE_CALL, false],
+ [IUserStatus::MESSAGE_CALL, IUserStatus::MESSAGE_CALENDAR_BUSY, true],
+
+ // Availability > Call&Meeting
+ [IUserStatus::MESSAGE_CALENDAR_BUSY, IUserStatus::MESSAGE_AVAILABILITY, false],
+ [IUserStatus::MESSAGE_CALL, IUserStatus::MESSAGE_AVAILABILITY, false],
+ [IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::MESSAGE_CALENDAR_BUSY, true],
+ [IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::MESSAGE_CALL, true],
+
+ // Out-of-office > Availability&Call&Meeting
+ [IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::MESSAGE_OUT_OF_OFFICE, false],
+ [IUserStatus::MESSAGE_CALENDAR_BUSY, IUserStatus::MESSAGE_OUT_OF_OFFICE, false],
+ [IUserStatus::MESSAGE_CALL, IUserStatus::MESSAGE_OUT_OF_OFFICE, false],
+ [IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::MESSAGE_AVAILABILITY, true],
+ [IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::MESSAGE_CALENDAR_BUSY, true],
+ [IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::MESSAGE_CALL, true],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataSetUserStatus')]
+ public function testSetUserStatus(string $messageId, string $oldMessageId, bool $expectedUpdateShortcut): void {
+ $previous = new UserStatus();
+ $previous->setId(1);
+ $previous->setStatus(IUserStatus::AWAY);
+ $previous->setStatusTimestamp(1337);
+ $previous->setIsUserDefined(false);
+ $previous->setMessageId($oldMessageId);
+ $previous->setUserId('john');
+ $previous->setIsBackup(false);
+
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john')
+ ->willReturn($previous);
+
+ $e = DbalException::wrap($this->createMock(UniqueConstraintViolationException::class));
+ $this->mapper->expects($expectedUpdateShortcut ? $this->never() : $this->once())
+ ->method('createBackupStatus')
+ ->willThrowException($e);
+
+ $this->mapper->expects($this->any())
+ ->method('update')
+ ->willReturnArgument(0);
+
+ $this->predefinedStatusService->expects($this->once())
+ ->method('isValidId')
+ ->with($messageId)
+ ->willReturn(true);
+
+ $this->service->setUserStatus('john', IUserStatus::DND, $messageId, true);
+ }
+}