aboutsummaryrefslogtreecommitdiffstats
path: root/apps/user_status/tests/Unit
diff options
context:
space:
mode:
Diffstat (limited to 'apps/user_status/tests/Unit')
-rw-r--r--apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php44
-rw-r--r--apps/user_status/tests/Unit/CapabilitiesTest.php49
-rw-r--r--apps/user_status/tests/Unit/Connector/UserStatusProviderTest.php73
-rw-r--r--apps/user_status/tests/Unit/Connector/UserStatusTest.php53
-rw-r--r--apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php53
-rw-r--r--apps/user_status/tests/Unit/Controller/StatusesControllerTest.php94
-rw-r--r--apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php313
-rw-r--r--apps/user_status/tests/Unit/Dashboard/UserStatusWidgetTest.php69
-rw-r--r--apps/user_status/tests/Unit/Db/UserStatusMapperTest.php332
-rw-r--r--apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php51
-rw-r--r--apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php149
-rw-r--r--apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php184
-rw-r--r--apps/user_status/tests/Unit/Service/StatusServiceTest.php828
13 files changed, 2292 insertions, 0 deletions
diff --git a/apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php b/apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php
new file mode 100644
index 00000000000..66142082343
--- /dev/null
+++ b/apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php
@@ -0,0 +1,44 @@
+<?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\BackgroundJob;
+
+use OCA\UserStatus\BackgroundJob\ClearOldStatusesBackgroundJob;
+use OCA\UserStatus\Db\UserStatusMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class ClearOldStatusesBackgroundJobTest extends TestCase {
+ private ITimeFactory&MockObject $time;
+ private UserStatusMapper&MockObject $mapper;
+ private ClearOldStatusesBackgroundJob $job;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->time = $this->createMock(ITimeFactory::class);
+ $this->mapper = $this->createMock(UserStatusMapper::class);
+
+ $this->job = new ClearOldStatusesBackgroundJob($this->time, $this->mapper);
+ }
+
+ public function testRun(): void {
+ $this->mapper->expects($this->once())
+ ->method('clearOlderThanClearAt')
+ ->with(1337);
+ $this->mapper->expects($this->once())
+ ->method('clearStatusesOlderThan')
+ ->with(437, 1337);
+
+ $this->time->method('getTime')
+ ->willReturn(1337);
+
+ self::invokePrivate($this->job, 'run', [[]]);
+ }
+}
diff --git a/apps/user_status/tests/Unit/CapabilitiesTest.php b/apps/user_status/tests/Unit/CapabilitiesTest.php
new file mode 100644
index 00000000000..601fb207df4
--- /dev/null
+++ b/apps/user_status/tests/Unit/CapabilitiesTest.php
@@ -0,0 +1,49 @@
+<?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;
+
+use OCA\UserStatus\Capabilities;
+use OCP\IEmojiHelper;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class CapabilitiesTest extends TestCase {
+ private IEmojiHelper&MockObject $emojiHelper;
+ private Capabilities $capabilities;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->emojiHelper = $this->createMock(IEmojiHelper::class);
+ $this->capabilities = new Capabilities($this->emojiHelper);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('getCapabilitiesDataProvider')]
+ public function testGetCapabilities(bool $supportsEmojis): void {
+ $this->emojiHelper->expects($this->once())
+ ->method('doesPlatformSupportEmoji')
+ ->willReturn($supportsEmojis);
+
+ $this->assertEquals([
+ 'user_status' => [
+ 'enabled' => true,
+ 'restore' => true,
+ 'supports_emoji' => $supportsEmojis,
+ 'supports_busy' => true,
+ ]
+ ], $this->capabilities->getCapabilities());
+ }
+
+ public static function getCapabilitiesDataProvider(): array {
+ return [
+ [true],
+ [false],
+ ];
+ }
+}
diff --git a/apps/user_status/tests/Unit/Connector/UserStatusProviderTest.php b/apps/user_status/tests/Unit/Connector/UserStatusProviderTest.php
new file mode 100644
index 00000000000..df6c55488d5
--- /dev/null
+++ b/apps/user_status/tests/Unit/Connector/UserStatusProviderTest.php
@@ -0,0 +1,73 @@
+<?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\Connector;
+
+use OCA\UserStatus\Connector\UserStatusProvider;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Service\StatusService;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class UserStatusProviderTest extends TestCase {
+ private StatusService&MockObject $service;
+ private UserStatusProvider $provider;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->service = $this->createMock(StatusService::class);
+ $this->provider = new UserStatusProvider($this->service);
+ }
+
+ public function testGetUserStatuses(): void {
+ $userStatus2 = new UserStatus();
+ $userStatus2->setUserId('userId2');
+ $userStatus2->setStatus('dnd');
+ $userStatus2->setStatusTimestamp(5000);
+ $userStatus2->setIsUserDefined(true);
+ $userStatus2->setCustomIcon('💩');
+ $userStatus2->setCustomMessage('Do not disturb');
+ $userStatus2->setClearAt(50000);
+
+ $userStatus3 = new UserStatus();
+ $userStatus3->setUserId('userId3');
+ $userStatus3->setStatus('away');
+ $userStatus3->setStatusTimestamp(5000);
+ $userStatus3->setIsUserDefined(false);
+ $userStatus3->setCustomIcon('🏝');
+ $userStatus3->setCustomMessage('On vacation');
+ $userStatus3->setClearAt(60000);
+
+ $this->service->expects($this->once())
+ ->method('findByUserIds')
+ ->with(['userId1', 'userId2', 'userId3'])
+ ->willReturn([$userStatus2, $userStatus3]);
+
+ $actual = $this->provider->getUserStatuses(['userId1', 'userId2', 'userId3']);
+
+ $this->assertCount(2, $actual);
+ $status2 = $actual['userId2'];
+ $this->assertEquals('userId2', $status2->getUserId());
+ $this->assertEquals('dnd', $status2->getStatus());
+ $this->assertEquals('Do not disturb', $status2->getMessage());
+ $this->assertEquals('💩', $status2->getIcon());
+ $dateTime2 = $status2->getClearAt();
+ $this->assertInstanceOf(\DateTimeImmutable::class, $dateTime2);
+ $this->assertEquals('50000', $dateTime2->format('U'));
+
+ $status3 = $actual['userId3'];
+ $this->assertEquals('userId3', $status3->getUserId());
+ $this->assertEquals('away', $status3->getStatus());
+ $this->assertEquals('On vacation', $status3->getMessage());
+ $this->assertEquals('🏝', $status3->getIcon());
+ $dateTime3 = $status3->getClearAt();
+ $this->assertInstanceOf(\DateTimeImmutable::class, $dateTime3);
+ $this->assertEquals('60000', $dateTime3->format('U'));
+ }
+}
diff --git a/apps/user_status/tests/Unit/Connector/UserStatusTest.php b/apps/user_status/tests/Unit/Connector/UserStatusTest.php
new file mode 100644
index 00000000000..fee9b4e4b89
--- /dev/null
+++ b/apps/user_status/tests/Unit/Connector/UserStatusTest.php
@@ -0,0 +1,53 @@
+<?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\Connector;
+
+use OCA\UserStatus\Connector\UserStatus;
+use OCA\UserStatus\Db;
+use Test\TestCase;
+
+class UserStatusTest extends TestCase {
+ public function testUserStatus(): void {
+ $status = new Db\UserStatus();
+ $status->setUserId('user2');
+ $status->setStatus('away');
+ $status->setStatusTimestamp(5000);
+ $status->setIsUserDefined(false);
+ $status->setCustomIcon('🏝');
+ $status->setCustomMessage('On vacation');
+ $status->setClearAt(60000);
+
+ $userStatus = new UserStatus($status);
+ $this->assertEquals('user2', $userStatus->getUserId());
+ $this->assertEquals('away', $userStatus->getStatus());
+ $this->assertEquals('On vacation', $userStatus->getMessage());
+ $this->assertEquals('🏝', $userStatus->getIcon());
+
+ $dateTime = $userStatus->getClearAt();
+ $this->assertInstanceOf(\DateTimeImmutable::class, $dateTime);
+ $this->assertEquals('60000', $dateTime->format('U'));
+ }
+
+ public function testUserStatusInvisible(): void {
+ $status = new Db\UserStatus();
+ $status->setUserId('user2');
+ $status->setStatus('invisible');
+ $status->setStatusTimestamp(5000);
+ $status->setIsUserDefined(false);
+ $status->setCustomIcon('🏝');
+ $status->setCustomMessage('On vacation');
+ $status->setClearAt(60000);
+
+ $userStatus = new UserStatus($status);
+ $this->assertEquals('user2', $userStatus->getUserId());
+ $this->assertEquals('offline', $userStatus->getStatus());
+ $this->assertEquals('On vacation', $userStatus->getMessage());
+ $this->assertEquals('🏝', $userStatus->getIcon());
+ }
+}
diff --git a/apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php b/apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php
new file mode 100644
index 00000000000..0f96f41a524
--- /dev/null
+++ b/apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php
@@ -0,0 +1,53 @@
+<?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\Controller;
+
+use OCA\UserStatus\Controller\PredefinedStatusController;
+use OCA\UserStatus\Service\PredefinedStatusService;
+use OCP\IRequest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class PredefinedStatusControllerTest extends TestCase {
+ private PredefinedStatusService&MockObject $service;
+ private PredefinedStatusController $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $request = $this->createMock(IRequest::class);
+ $this->service = $this->createMock(PredefinedStatusService::class);
+
+ $this->controller = new PredefinedStatusController('user_status', $request, $this->service);
+ }
+
+ public function testFindAll(): void {
+ $this->service->expects($this->once())
+ ->method('getDefaultStatuses')
+ ->with()
+ ->willReturn([
+ [
+ 'id' => 'predefined-status-one',
+ ],
+ [
+ 'id' => 'predefined-status-two',
+ ],
+ ]);
+
+ $actual = $this->controller->findAll();
+ $this->assertEquals([
+ [
+ 'id' => 'predefined-status-one',
+ ],
+ [
+ 'id' => 'predefined-status-two',
+ ],
+ ], $actual->getData());
+ }
+}
diff --git a/apps/user_status/tests/Unit/Controller/StatusesControllerTest.php b/apps/user_status/tests/Unit/Controller/StatusesControllerTest.php
new file mode 100644
index 00000000000..76d337879c3
--- /dev/null
+++ b/apps/user_status/tests/Unit/Controller/StatusesControllerTest.php
@@ -0,0 +1,94 @@
+<?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\Controller;
+
+use OCA\UserStatus\Controller\StatusesController;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Service\StatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\OCS\OCSNotFoundException;
+use OCP\IRequest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class StatusesControllerTest extends TestCase {
+ private StatusService&MockObject $service;
+ private StatusesController $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $request = $this->createMock(IRequest::class);
+ $this->service = $this->createMock(StatusService::class);
+
+ $this->controller = new StatusesController('user_status', $request, $this->service);
+ }
+
+ public function testFindAll(): void {
+ $userStatus = $this->getUserStatus();
+
+ $this->service->expects($this->once())
+ ->method('findAll')
+ ->with(20, 40)
+ ->willReturn([$userStatus]);
+
+ $response = $this->controller->findAll(20, 40);
+ $this->assertEquals([[
+ 'userId' => 'john.doe',
+ 'status' => 'offline',
+ 'icon' => '🏝',
+ 'message' => 'On vacation',
+ 'clearAt' => 60000,
+ ]], $response->getData());
+ }
+
+ public function testFind(): void {
+ $userStatus = $this->getUserStatus();
+
+ $this->service->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($userStatus);
+
+ $response = $this->controller->find('john.doe');
+ $this->assertEquals([
+ 'userId' => 'john.doe',
+ 'status' => 'offline',
+ 'icon' => '🏝',
+ 'message' => 'On vacation',
+ 'clearAt' => 60000,
+ ], $response->getData());
+ }
+
+ public function testFindDoesNotExist(): void {
+ $this->service->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willThrowException(new DoesNotExistException(''));
+
+ $this->expectException(OCSNotFoundException::class);
+ $this->expectExceptionMessage('No status for the requested userId');
+
+ $this->controller->find('john.doe');
+ }
+
+ private function getUserStatus(): UserStatus {
+ $userStatus = new UserStatus();
+ $userStatus->setId(1337);
+ $userStatus->setUserId('john.doe');
+ $userStatus->setStatus('invisible');
+ $userStatus->setStatusTimestamp(5000);
+ $userStatus->setIsUserDefined(true);
+ $userStatus->setCustomIcon('🏝');
+ $userStatus->setCustomMessage('On vacation');
+ $userStatus->setClearAt(60000);
+
+ return $userStatus;
+ }
+}
diff --git a/apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php b/apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php
new file mode 100644
index 00000000000..e99290319ed
--- /dev/null
+++ b/apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php
@@ -0,0 +1,313 @@
+<?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\Controller;
+
+use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
+use OCA\UserStatus\Controller\UserStatusController;
+use OCA\UserStatus\Db\UserStatus;
+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\StatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\OCS\OCSBadRequestException;
+use OCP\AppFramework\OCS\OCSNotFoundException;
+use OCP\IRequest;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+use Throwable;
+
+class UserStatusControllerTest extends TestCase {
+ private LoggerInterface&MockObject $logger;
+ private StatusService&MockObject $statusService;
+ private CalendarStatusService&MockObject $calendarStatusService;
+ private UserStatusController $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $request = $this->createMock(IRequest::class);
+ $userId = 'john.doe';
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->statusService = $this->createMock(StatusService::class);
+ $this->calendarStatusService = $this->createMock(CalendarStatusService::class);
+
+ $this->controller = new UserStatusController(
+ 'user_status',
+ $request,
+ $userId,
+ $this->logger,
+ $this->statusService,
+ $this->calendarStatusService,
+ );
+ }
+
+ public function testGetStatus(): void {
+ $userStatus = $this->getUserStatus();
+
+ $this->statusService->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($userStatus);
+
+ $response = $this->controller->getStatus();
+ $this->assertEquals([
+ 'userId' => 'john.doe',
+ 'status' => 'invisible',
+ 'icon' => '🏝',
+ 'message' => 'On vacation',
+ 'clearAt' => 60000,
+ 'statusIsUserDefined' => true,
+ 'messageIsPredefined' => false,
+ 'messageId' => null,
+ ], $response->getData());
+ }
+
+ public function testGetStatusDoesNotExist(): void {
+ $this->calendarStatusService->expects(self::once())
+ ->method('processCalendarStatus')
+ ->with('john.doe');
+ $this->statusService->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willThrowException(new DoesNotExistException(''));
+
+ $this->expectException(OCSNotFoundException::class);
+ $this->expectExceptionMessage('No status for the current user');
+
+ $this->controller->getStatus();
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('setStatusDataProvider')]
+ public function testSetStatus(
+ string $statusType,
+ ?string $statusIcon,
+ ?string $message,
+ ?int $clearAt,
+ bool $expectSuccess,
+ bool $expectException,
+ ?Throwable $exception,
+ bool $expectLogger,
+ ?string $expectedLogMessage,
+ ): void {
+ $userStatus = $this->getUserStatus();
+
+ if ($expectException) {
+ $this->statusService->expects($this->once())
+ ->method('setStatus')
+ ->with('john.doe', $statusType, null, true)
+ ->willThrowException($exception);
+ } else {
+ $this->statusService->expects($this->once())
+ ->method('setStatus')
+ ->with('john.doe', $statusType, null, true)
+ ->willReturn($userStatus);
+ }
+
+ if ($expectLogger) {
+ $this->logger->expects($this->once())
+ ->method('debug')
+ ->with($expectedLogMessage);
+ }
+ if ($expectException) {
+ $this->expectException(OCSBadRequestException::class);
+ $this->expectExceptionMessage('Original exception message');
+ }
+
+ $response = $this->controller->setStatus($statusType);
+
+ if ($expectSuccess) {
+ $this->assertEquals([
+ 'userId' => 'john.doe',
+ 'status' => 'invisible',
+ 'icon' => '🏝',
+ 'message' => 'On vacation',
+ 'clearAt' => 60000,
+ 'statusIsUserDefined' => true,
+ 'messageIsPredefined' => false,
+ 'messageId' => null,
+ ], $response->getData());
+ }
+ }
+
+ public static function setStatusDataProvider(): array {
+ return [
+ ['busy', '👨🏽‍💻', 'Busy developing the status feature', 500, true, false, null, false, null],
+ ['busy', '👨🏽‍💻', 'Busy developing the status feature', 500, false, true, new InvalidStatusTypeException('Original exception message'), true,
+ 'New user-status for "john.doe" was rejected due to an invalid status type "busy"'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('setPredefinedMessageDataProvider')]
+ public function testSetPredefinedMessage(
+ string $messageId,
+ ?int $clearAt,
+ bool $expectSuccess,
+ bool $expectException,
+ ?Throwable $exception,
+ bool $expectLogger,
+ ?string $expectedLogMessage,
+ ): void {
+ $userStatus = $this->getUserStatus();
+
+ if ($expectException) {
+ $this->statusService->expects($this->once())
+ ->method('setPredefinedMessage')
+ ->with('john.doe', $messageId, $clearAt)
+ ->willThrowException($exception);
+ } else {
+ $this->statusService->expects($this->once())
+ ->method('setPredefinedMessage')
+ ->with('john.doe', $messageId, $clearAt)
+ ->willReturn($userStatus);
+ }
+
+ if ($expectLogger) {
+ $this->logger->expects($this->once())
+ ->method('debug')
+ ->with($expectedLogMessage);
+ }
+ if ($expectException) {
+ $this->expectException(OCSBadRequestException::class);
+ $this->expectExceptionMessage('Original exception message');
+ }
+
+ $response = $this->controller->setPredefinedMessage($messageId, $clearAt);
+
+ if ($expectSuccess) {
+ $this->assertEquals([
+ 'userId' => 'john.doe',
+ 'status' => 'invisible',
+ 'icon' => '🏝',
+ 'message' => 'On vacation',
+ 'clearAt' => 60000,
+ 'statusIsUserDefined' => true,
+ 'messageIsPredefined' => false,
+ 'messageId' => null,
+ ], $response->getData());
+ }
+ }
+
+ public static function setPredefinedMessageDataProvider(): array {
+ return [
+ ['messageId-42', 500, true, false, null, false, null],
+ ['messageId-42', 500, false, true, new InvalidClearAtException('Original exception message'), true,
+ 'New user-status for "john.doe" was rejected due to an invalid clearAt value "500"'],
+ ['messageId-42', 500, false, true, new InvalidMessageIdException('Original exception message'), true,
+ 'New user-status for "john.doe" was rejected due to an invalid message-id "messageId-42"'],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('setCustomMessageDataProvider')]
+ public function testSetCustomMessage(
+ ?string $statusIcon,
+ string $message,
+ ?int $clearAt,
+ bool $expectSuccess,
+ bool $expectException,
+ ?Throwable $exception,
+ bool $expectLogger,
+ ?string $expectedLogMessage,
+ bool $expectSuccessAsReset = false,
+ ): void {
+ $userStatus = $this->getUserStatus();
+
+ if ($expectException) {
+ $this->statusService->expects($this->once())
+ ->method('setCustomMessage')
+ ->with('john.doe', $statusIcon, $message, $clearAt)
+ ->willThrowException($exception);
+ } else {
+ if ($expectSuccessAsReset) {
+ $this->statusService->expects($this->never())
+ ->method('setCustomMessage');
+ $this->statusService->expects($this->once())
+ ->method('clearMessage')
+ ->with('john.doe');
+ $this->statusService->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($userStatus);
+ } else {
+ $this->statusService->expects($this->once())
+ ->method('setCustomMessage')
+ ->with('john.doe', $statusIcon, $message, $clearAt)
+ ->willReturn($userStatus);
+
+ $this->statusService->expects($this->never())
+ ->method('clearMessage');
+ }
+ }
+
+ if ($expectLogger) {
+ $this->logger->expects($this->once())
+ ->method('debug')
+ ->with($expectedLogMessage);
+ }
+ if ($expectException) {
+ $this->expectException(OCSBadRequestException::class);
+ $this->expectExceptionMessage('Original exception message');
+ }
+
+ $response = $this->controller->setCustomMessage($statusIcon, $message, $clearAt);
+
+ if ($expectSuccess) {
+ $this->assertEquals([
+ 'userId' => 'john.doe',
+ 'status' => 'invisible',
+ 'icon' => '🏝',
+ 'message' => 'On vacation',
+ 'clearAt' => 60000,
+ 'statusIsUserDefined' => true,
+ 'messageIsPredefined' => false,
+ 'messageId' => null,
+ ], $response->getData());
+ }
+ }
+
+ public static function setCustomMessageDataProvider(): array {
+ return [
+ ['👨🏽‍💻', 'Busy developing the status feature', 500, true, false, null, false, null],
+ ['👨🏽‍💻', '', 500, true, false, null, false, null, false],
+ ['👨🏽‍💻', '', 0, true, false, null, false, null, false],
+ ['👨🏽‍💻', 'Busy developing the status feature', 500, false, true, new InvalidClearAtException('Original exception message'), true,
+ 'New user-status for "john.doe" was rejected due to an invalid clearAt value "500"'],
+ ['👨🏽‍💻', 'Busy developing the status feature', 500, false, true, new InvalidStatusIconException('Original exception message'), true,
+ 'New user-status for "john.doe" was rejected due to an invalid icon value "👨🏽‍💻"'],
+ ['👨🏽‍💻', 'Busy developing the status feature', 500, false, true, new StatusMessageTooLongException('Original exception message'), true,
+ 'New user-status for "john.doe" was rejected due to a too long status message.'],
+ ];
+ }
+
+ public function testClearMessage(): void {
+ $this->statusService->expects($this->once())
+ ->method('clearMessage')
+ ->with('john.doe');
+
+ $response = $this->controller->clearMessage();
+ $this->assertEquals([], $response->getData());
+ }
+
+ private function getUserStatus(): UserStatus {
+ $userStatus = new UserStatus();
+ $userStatus->setId(1337);
+ $userStatus->setUserId('john.doe');
+ $userStatus->setStatus('invisible');
+ $userStatus->setStatusTimestamp(5000);
+ $userStatus->setIsUserDefined(true);
+ $userStatus->setCustomIcon('🏝');
+ $userStatus->setCustomMessage('On vacation');
+ $userStatus->setClearAt(60000);
+
+ return $userStatus;
+ }
+}
diff --git a/apps/user_status/tests/Unit/Dashboard/UserStatusWidgetTest.php b/apps/user_status/tests/Unit/Dashboard/UserStatusWidgetTest.php
new file mode 100644
index 00000000000..8773b04c95f
--- /dev/null
+++ b/apps/user_status/tests/Unit/Dashboard/UserStatusWidgetTest.php
@@ -0,0 +1,69 @@
+<?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\Dashboard;
+
+use OCA\UserStatus\Dashboard\UserStatusWidget;
+use OCA\UserStatus\Service\StatusService;
+use OCP\AppFramework\Services\IInitialState;
+use OCP\IDateTimeFormatter;
+use OCP\IL10N;
+use OCP\IURLGenerator;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class UserStatusWidgetTest extends TestCase {
+ private IL10N&MockObject $l10n;
+ private IDateTimeFormatter&MockObject $dateTimeFormatter;
+ private IURLGenerator&MockObject $urlGenerator;
+ private IInitialState&MockObject $initialState;
+ private IUserManager&MockObject $userManager;
+ private IUserSession&MockObject $userSession;
+ private StatusService&MockObject $service;
+ private UserStatusWidget $widget;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+ $this->dateTimeFormatter = $this->createMock(IDateTimeFormatter::class);
+ $this->urlGenerator = $this->createMock(IURLGenerator::class);
+ $this->initialState = $this->createMock(IInitialState::class);
+ $this->userManager = $this->createMock(IUserManager::class);
+ $this->userSession = $this->createMock(IUserSession::class);
+ $this->service = $this->createMock(StatusService::class);
+
+ $this->widget = new UserStatusWidget($this->l10n, $this->dateTimeFormatter, $this->urlGenerator, $this->initialState, $this->userManager, $this->userSession, $this->service);
+ }
+
+ public function testGetId(): void {
+ $this->assertEquals('user_status', $this->widget->getId());
+ }
+
+ public function testGetTitle(): void {
+ $this->l10n->expects($this->exactly(1))
+ ->method('t')
+ ->willReturnArgument(0);
+
+ $this->assertEquals('Recent statuses', $this->widget->getTitle());
+ }
+
+ public function testGetOrder(): void {
+ $this->assertEquals(5, $this->widget->getOrder());
+ }
+
+ public function testGetIconClass(): void {
+ $this->assertEquals('icon-user-status-dark', $this->widget->getIconClass());
+ }
+
+ public function testGetUrl(): void {
+ $this->assertNull($this->widget->getUrl());
+ }
+}
diff --git a/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php b/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php
new file mode 100644
index 00000000000..ea4480489c7
--- /dev/null
+++ b/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php
@@ -0,0 +1,332 @@
+<?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\Db;
+
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Db\UserStatusMapper;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\DB\Exception;
+use Test\TestCase;
+
+class UserStatusMapperTest extends TestCase {
+ private UserStatusMapper $mapper;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ // make sure that DB is empty
+ $qb = self::$realDatabase->getQueryBuilder();
+ $qb->delete('user_status')->execute();
+
+ $this->mapper = new UserStatusMapper(self::$realDatabase);
+ }
+
+ public function testGetTableName(): void {
+ $this->assertEquals('user_status', $this->mapper->getTableName());
+ }
+
+ public function testGetFindAll(): void {
+ $this->insertSampleStatuses();
+
+ $allResults = $this->mapper->findAll();
+ $this->assertCount(3, $allResults);
+
+ $limitedResults = $this->mapper->findAll(2);
+ $this->assertCount(2, $limitedResults);
+ $this->assertEquals('admin', $limitedResults[0]->getUserId());
+ $this->assertEquals('user1', $limitedResults[1]->getUserId());
+
+ $offsetResults = $this->mapper->findAll(null, 2);
+ $this->assertCount(1, $offsetResults);
+ $this->assertEquals('user2', $offsetResults[0]->getUserId());
+ }
+
+ public function testFindAllRecent(): void {
+ $this->insertSampleStatuses();
+
+ $allResults = $this->mapper->findAllRecent(2, 0);
+ $this->assertCount(2, $allResults);
+ $this->assertEquals('user2', $allResults[0]->getUserId());
+ $this->assertEquals('user1', $allResults[1]->getUserId());
+ }
+
+ public function testGetFind(): void {
+ $this->insertSampleStatuses();
+
+ $adminStatus = $this->mapper->findByUserId('admin');
+ $this->assertEquals('admin', $adminStatus->getUserId());
+ $this->assertEquals('offline', $adminStatus->getStatus());
+ $this->assertEquals(0, $adminStatus->getStatusTimestamp());
+ $this->assertEquals(false, $adminStatus->getIsUserDefined());
+ $this->assertEquals(null, $adminStatus->getCustomIcon());
+ $this->assertEquals(null, $adminStatus->getCustomMessage());
+ $this->assertEquals(null, $adminStatus->getClearAt());
+
+ $user1Status = $this->mapper->findByUserId('user1');
+ $this->assertEquals('user1', $user1Status->getUserId());
+ $this->assertEquals('dnd', $user1Status->getStatus());
+ $this->assertEquals(5000, $user1Status->getStatusTimestamp());
+ $this->assertEquals(true, $user1Status->getIsUserDefined());
+ $this->assertEquals('💩', $user1Status->getCustomIcon());
+ $this->assertEquals('Do not disturb', $user1Status->getCustomMessage());
+ $this->assertEquals(50000, $user1Status->getClearAt());
+
+ $user2Status = $this->mapper->findByUserId('user2');
+ $this->assertEquals('user2', $user2Status->getUserId());
+ $this->assertEquals('away', $user2Status->getStatus());
+ $this->assertEquals(6000, $user2Status->getStatusTimestamp());
+ $this->assertEquals(false, $user2Status->getIsUserDefined());
+ $this->assertEquals('🏝', $user2Status->getCustomIcon());
+ $this->assertEquals('On vacation', $user2Status->getCustomMessage());
+ $this->assertEquals(60000, $user2Status->getClearAt());
+ }
+
+ public function testFindByUserIds(): void {
+ $this->insertSampleStatuses();
+
+ $statuses = $this->mapper->findByUserIds(['admin', 'user2']);
+ $this->assertCount(2, $statuses);
+
+ $adminStatus = $statuses[0];
+ $this->assertEquals('admin', $adminStatus->getUserId());
+ $this->assertEquals('offline', $adminStatus->getStatus());
+ $this->assertEquals(0, $adminStatus->getStatusTimestamp());
+ $this->assertEquals(false, $adminStatus->getIsUserDefined());
+ $this->assertEquals(null, $adminStatus->getCustomIcon());
+ $this->assertEquals(null, $adminStatus->getCustomMessage());
+ $this->assertEquals(null, $adminStatus->getClearAt());
+
+ $user2Status = $statuses[1];
+ $this->assertEquals('user2', $user2Status->getUserId());
+ $this->assertEquals('away', $user2Status->getStatus());
+ $this->assertEquals(6000, $user2Status->getStatusTimestamp());
+ $this->assertEquals(false, $user2Status->getIsUserDefined());
+ $this->assertEquals('🏝', $user2Status->getCustomIcon());
+ $this->assertEquals('On vacation', $user2Status->getCustomMessage());
+ $this->assertEquals(60000, $user2Status->getClearAt());
+ }
+
+ public function testUserIdUnique(): void {
+ // Test that inserting a second status for a user is throwing an exception
+
+ $userStatus1 = new UserStatus();
+ $userStatus1->setUserId('admin');
+ $userStatus1->setStatus('dnd');
+ $userStatus1->setStatusTimestamp(5000);
+ $userStatus1->setIsUserDefined(true);
+
+ $this->mapper->insert($userStatus1);
+
+ $userStatus2 = new UserStatus();
+ $userStatus2->setUserId('admin');
+ $userStatus2->setStatus('away');
+ $userStatus2->setStatusTimestamp(6000);
+ $userStatus2->setIsUserDefined(false);
+
+ $this->expectException(Exception::class);
+
+ $this->mapper->insert($userStatus2);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('clearStatusesOlderThanDataProvider')]
+ public function testClearStatusesOlderThan(string $status, bool $isUserDefined, int $timestamp, bool $expectsClean): void {
+ $oldStatus = UserStatus::fromParams([
+ 'userId' => 'john.doe',
+ 'status' => $status,
+ 'isUserDefined' => $isUserDefined,
+ 'statusTimestamp' => $timestamp,
+ ]);
+
+ $this->mapper->insert($oldStatus);
+
+ $this->mapper->clearStatusesOlderThan(5000, 8000);
+
+ $updatedStatus = $this->mapper->findAll()[0];
+
+ if ($expectsClean) {
+ $this->assertEquals('offline', $updatedStatus->getStatus());
+ $this->assertFalse($updatedStatus->getIsUserDefined());
+ $this->assertEquals(8000, $updatedStatus->getStatusTimestamp());
+ } else {
+ $this->assertEquals($status, $updatedStatus->getStatus());
+ $this->assertEquals($isUserDefined, $updatedStatus->getIsUserDefined());
+ $this->assertEquals($timestamp, $updatedStatus->getStatusTimestamp());
+ }
+ }
+
+ public static function clearStatusesOlderThanDataProvider(): array {
+ return [
+ ['offline', false, 6000, false],
+ ['online', true, 6000, false],
+ ['online', true, 4000, true],
+ ['online', false, 6000, false],
+ ['online', false, 4000, true],
+ ['away', true, 6000, false],
+ ['away', true, 4000, false],
+ ['away', false, 6000, false],
+ ['away', false, 4000, true],
+ ['dnd', true, 6000, false],
+ ['dnd', true, 4000, false],
+ ['invisible', true, 6000, false],
+ ['invisible', true, 4000, false],
+ ];
+ }
+
+ public function testClearOlderThanClearAt(): void {
+ $this->insertSampleStatuses();
+
+ $this->mapper->clearOlderThanClearAt(55000);
+
+ $allStatuses = $this->mapper->findAll();
+ $this->assertCount(2, $allStatuses);
+
+ $this->expectException(DoesNotExistException::class);
+ $this->mapper->findByUserId('user1');
+ }
+
+ private function insertSampleStatuses(): void {
+ $userStatus1 = new UserStatus();
+ $userStatus1->setUserId('admin');
+ $userStatus1->setStatus('offline');
+ $userStatus1->setStatusTimestamp(0);
+ $userStatus1->setIsUserDefined(false);
+
+ $userStatus2 = new UserStatus();
+ $userStatus2->setUserId('user1');
+ $userStatus2->setStatus('dnd');
+ $userStatus2->setStatusTimestamp(5000);
+ $userStatus2->setStatusMessageTimestamp(5000);
+ $userStatus2->setIsUserDefined(true);
+ $userStatus2->setCustomIcon('💩');
+ $userStatus2->setCustomMessage('Do not disturb');
+ $userStatus2->setClearAt(50000);
+
+ $userStatus3 = new UserStatus();
+ $userStatus3->setUserId('user2');
+ $userStatus3->setStatus('away');
+ $userStatus3->setStatusTimestamp(6000);
+ $userStatus3->setStatusMessageTimestamp(6000);
+ $userStatus3->setIsUserDefined(false);
+ $userStatus3->setCustomIcon('🏝');
+ $userStatus3->setCustomMessage('On vacation');
+ $userStatus3->setClearAt(60000);
+
+ $this->mapper->insert($userStatus1);
+ $this->mapper->insert($userStatus2);
+ $this->mapper->insert($userStatus3);
+ }
+
+ public static function dataCreateBackupStatus(): array {
+ return [
+ [false, false, false],
+ [true, false, true],
+ [false, true, false],
+ [true, true, false],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('dataCreateBackupStatus')]
+ public function testCreateBackupStatus(bool $hasStatus, bool $hasBackup, bool $backupCreated): void {
+ if ($hasStatus) {
+ $userStatus1 = new UserStatus();
+ $userStatus1->setUserId('user1');
+ $userStatus1->setStatus('online');
+ $userStatus1->setStatusTimestamp(5000);
+ $userStatus1->setIsUserDefined(true);
+ $userStatus1->setIsBackup(false);
+ $userStatus1->setCustomIcon('🚀');
+ $userStatus1->setCustomMessage('Current');
+ $userStatus1->setClearAt(50000);
+ $this->mapper->insert($userStatus1);
+ }
+
+ if ($hasBackup) {
+ $userStatus1 = new UserStatus();
+ $userStatus1->setUserId('_user1');
+ $userStatus1->setStatus('online');
+ $userStatus1->setStatusTimestamp(5000);
+ $userStatus1->setIsUserDefined(true);
+ $userStatus1->setIsBackup(true);
+ $userStatus1->setCustomIcon('🚀');
+ $userStatus1->setCustomMessage('Backup');
+ $userStatus1->setClearAt(50000);
+ $this->mapper->insert($userStatus1);
+ }
+
+ if ($hasStatus && $hasBackup) {
+ $this->expectException(Exception::class);
+ }
+
+ self::assertSame($backupCreated, $this->mapper->createBackupStatus('user1'));
+
+ if ($backupCreated) {
+ $user1Status = $this->mapper->findByUserId('user1', true);
+ $this->assertEquals('_user1', $user1Status->getUserId());
+ $this->assertEquals(true, $user1Status->getIsBackup());
+ $this->assertEquals('Current', $user1Status->getCustomMessage());
+ } elseif ($hasBackup) {
+ $user1Status = $this->mapper->findByUserId('user1', true);
+ $this->assertEquals('_user1', $user1Status->getUserId());
+ $this->assertEquals(true, $user1Status->getIsBackup());
+ $this->assertEquals('Backup', $user1Status->getCustomMessage());
+ }
+ }
+
+ public function testRestoreBackupStatuses(): void {
+ $userStatus1 = new UserStatus();
+ $userStatus1->setUserId('_user1');
+ $userStatus1->setStatus('online');
+ $userStatus1->setStatusTimestamp(5000);
+ $userStatus1->setIsUserDefined(true);
+ $userStatus1->setIsBackup(true);
+ $userStatus1->setCustomIcon('🚀');
+ $userStatus1->setCustomMessage('Releasing');
+ $userStatus1->setClearAt(50000);
+ $userStatus1 = $this->mapper->insert($userStatus1);
+
+ $userStatus2 = new UserStatus();
+ $userStatus2->setUserId('_user2');
+ $userStatus2->setStatus('away');
+ $userStatus2->setStatusTimestamp(5000);
+ $userStatus2->setIsUserDefined(true);
+ $userStatus2->setIsBackup(true);
+ $userStatus2->setCustomIcon('💩');
+ $userStatus2->setCustomMessage('Do not disturb');
+ $userStatus2->setClearAt(50000);
+ $userStatus2 = $this->mapper->insert($userStatus2);
+
+ $userStatus3 = new UserStatus();
+ $userStatus3->setUserId('_user3');
+ $userStatus3->setStatus('away');
+ $userStatus3->setStatusTimestamp(5000);
+ $userStatus3->setIsUserDefined(true);
+ $userStatus3->setIsBackup(true);
+ $userStatus3->setCustomIcon('🏝️');
+ $userStatus3->setCustomMessage('Vacationing');
+ $userStatus3->setClearAt(50000);
+ $this->mapper->insert($userStatus3);
+
+ $this->mapper->restoreBackupStatuses([$userStatus1->getId(), $userStatus2->getId()]);
+
+ $user1Status = $this->mapper->findByUserId('user1', false);
+ $this->assertEquals('user1', $user1Status->getUserId());
+ $this->assertEquals(false, $user1Status->getIsBackup());
+ $this->assertEquals('Releasing', $user1Status->getCustomMessage());
+
+ $user2Status = $this->mapper->findByUserId('user2', false);
+ $this->assertEquals('user2', $user2Status->getUserId());
+ $this->assertEquals(false, $user2Status->getIsBackup());
+ $this->assertEquals('Do not disturb', $user2Status->getCustomMessage());
+
+ $user3Status = $this->mapper->findByUserId('user3', true);
+ $this->assertEquals('_user3', $user3Status->getUserId());
+ $this->assertEquals(true, $user3Status->getIsBackup());
+ $this->assertEquals('Vacationing', $user3Status->getCustomMessage());
+ }
+}
diff --git a/apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php b/apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php
new file mode 100644
index 00000000000..fbcea23338d
--- /dev/null
+++ b/apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php
@@ -0,0 +1,51 @@
+<?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\Listener;
+
+use OCA\UserStatus\Listener\UserDeletedListener;
+use OCA\UserStatus\Service\StatusService;
+use OCP\EventDispatcher\GenericEvent;
+use OCP\IUser;
+use OCP\User\Events\UserDeletedEvent;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class UserDeletedListenerTest extends TestCase {
+ private StatusService&MockObject $service;
+ private UserDeletedListener $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->service = $this->createMock(StatusService::class);
+ $this->listener = new UserDeletedListener($this->service);
+ }
+
+ public function testHandleWithCorrectEvent(): void {
+ $user = $this->createMock(IUser::class);
+ $user->expects($this->once())
+ ->method('getUID')
+ ->willReturn('john.doe');
+
+ $this->service->expects($this->once())
+ ->method('removeUserStatus')
+ ->with('john.doe');
+
+ $event = new UserDeletedEvent($user);
+ $this->listener->handle($event);
+ }
+
+ public function testHandleWithWrongEvent(): void {
+ $this->service->expects($this->never())
+ ->method('removeUserStatus');
+
+ $event = new GenericEvent();
+ $this->listener->handle($event);
+ }
+}
diff --git a/apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php b/apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php
new file mode 100644
index 00000000000..c03eed0089e
--- /dev/null
+++ b/apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php
@@ -0,0 +1,149 @@
+<?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\Listener;
+
+use OCA\DAV\CalDAV\Status\StatusService as CalendarStatusService;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Db\UserStatusMapper;
+use OCA\UserStatus\Listener\UserLiveStatusListener;
+use OCA\UserStatus\Service\StatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\GenericEvent;
+use OCP\IUser;
+use OCP\User\Events\UserLiveStatusEvent;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
+use Test\TestCase;
+
+class UserLiveStatusListenerTest extends TestCase {
+ private UserStatusMapper&MockObject $mapper;
+ private StatusService&MockObject $statusService;
+ private ITimeFactory&MockObject $timeFactory;
+ private CalendarStatusService&MockObject $calendarStatusService;
+
+ private LoggerInterface&MockObject $logger;
+ private UserLiveStatusListener $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mapper = $this->createMock(UserStatusMapper::class);
+ $this->statusService = $this->createMock(StatusService::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->calendarStatusService = $this->createMock(CalendarStatusService::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+
+ $this->listener = new UserLiveStatusListener(
+ $this->mapper,
+ $this->statusService,
+ $this->timeFactory,
+ $this->calendarStatusService,
+ $this->logger,
+ );
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('handleEventWithCorrectEventDataProvider')]
+ public function testHandleWithCorrectEvent(
+ string $userId,
+ string $previousStatus,
+ int $previousTimestamp,
+ bool $previousIsUserDefined,
+ string $eventStatus,
+ int $eventTimestamp,
+ bool $expectExisting,
+ bool $expectUpdate,
+ ): void {
+ $userStatus = new UserStatus();
+
+ if ($expectExisting) {
+ $userStatus->setId(42);
+ $userStatus->setUserId($userId);
+ $userStatus->setStatus($previousStatus);
+ $userStatus->setStatusTimestamp($previousTimestamp);
+ $userStatus->setIsUserDefined($previousIsUserDefined);
+
+ $this->statusService->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willReturn($userStatus);
+ } else {
+ $this->statusService->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willThrowException(new DoesNotExistException(''));
+ }
+
+ $user = $this->createMock(IUser::class);
+ $user->method('getUID')->willReturn($userId);
+ $event = new UserLiveStatusEvent($user, $eventStatus, $eventTimestamp);
+
+ $this->timeFactory->expects($this->atMost(1))
+ ->method('getTime')
+ ->willReturn(5000);
+
+ if ($expectUpdate) {
+ if ($expectExisting) {
+ $this->mapper->expects($this->never())
+ ->method('insert');
+ $this->mapper->expects($this->once())
+ ->method('update')
+ ->with($this->callback(function ($userStatus) use ($eventStatus, $eventTimestamp) {
+ $this->assertEquals($eventStatus, $userStatus->getStatus());
+ $this->assertEquals($eventTimestamp, $userStatus->getStatusTimestamp());
+ $this->assertFalse($userStatus->getIsUserDefined());
+
+ return true;
+ }));
+ } else {
+ $this->mapper->expects($this->once())
+ ->method('insert')
+ ->with($this->callback(function ($userStatus) use ($eventStatus, $eventTimestamp) {
+ $this->assertEquals($eventStatus, $userStatus->getStatus());
+ $this->assertEquals($eventTimestamp, $userStatus->getStatusTimestamp());
+ $this->assertFalse($userStatus->getIsUserDefined());
+
+ return true;
+ }));
+ $this->mapper->expects($this->never())
+ ->method('update');
+ }
+
+ $this->listener->handle($event);
+ } else {
+ $this->mapper->expects($this->never())
+ ->method('insert');
+ $this->mapper->expects($this->never())
+ ->method('update');
+
+ $this->listener->handle($event);
+ }
+ }
+
+ public static function handleEventWithCorrectEventDataProvider(): array {
+ return [
+ ['john.doe', 'offline', 0, false, 'online', 5000, true, true],
+ ['john.doe', 'offline', 0, false, 'online', 5000, false, true],
+ ['john.doe', 'online', 5000, false, 'online', 5000, true, false],
+ ['john.doe', 'online', 5000, false, 'online', 5000, false, true],
+ ['john.doe', 'away', 5000, false, 'online', 5000, true, true],
+ ['john.doe', 'online', 5000, false, 'away', 5000, true, false],
+ ['john.doe', 'away', 5000, true, 'online', 5000, true, false],
+ ['john.doe', 'online', 5000, true, 'away', 5000, true, false],
+ ];
+ }
+
+ public function testHandleWithWrongEvent(): void {
+ $this->mapper->expects($this->never())
+ ->method('insertOrUpdate');
+
+ $event = new GenericEvent();
+ $this->listener->handle($event);
+ }
+}
diff --git a/apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php b/apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php
new file mode 100644
index 00000000000..78e4a18d9f1
--- /dev/null
+++ b/apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php
@@ -0,0 +1,184 @@
+<?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 OCA\UserStatus\Service\PredefinedStatusService;
+use OCP\IL10N;
+use PHPUnit\Framework\MockObject\MockObject;
+use Test\TestCase;
+
+class PredefinedStatusServiceTest extends TestCase {
+ protected IL10N&MockObject $l10n;
+ protected PredefinedStatusService $service;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->l10n = $this->createMock(IL10N::class);
+
+ $this->service = new PredefinedStatusService($this->l10n);
+ }
+
+ public function testGetDefaultStatuses(): void {
+ $this->l10n->expects($this->exactly(8))
+ ->method('t')
+ ->willReturnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ });
+
+ $actual = $this->service->getDefaultStatuses();
+ $this->assertEquals([
+ [
+ 'id' => 'meeting',
+ 'icon' => '📅',
+ 'message' => 'In a meeting',
+ 'clearAt' => [
+ 'type' => 'period',
+ 'time' => 3600,
+ ],
+ ],
+ [
+ 'id' => 'commuting',
+ 'icon' => '🚌',
+ 'message' => 'Commuting',
+ 'clearAt' => [
+ 'type' => 'period',
+ 'time' => 1800,
+ ],
+ ],
+ [
+ 'id' => 'be-right-back',
+ 'icon' => '⏳',
+ 'message' => 'Be right back',
+ 'clearAt' => [
+ 'type' => 'period',
+ 'time' => 900,
+ ],
+ ],
+ [
+ 'id' => 'remote-work',
+ 'icon' => '🏡',
+ 'message' => 'Working remotely',
+ 'clearAt' => [
+ 'type' => 'end-of',
+ 'time' => 'day',
+ ],
+ ],
+ [
+ 'id' => 'sick-leave',
+ 'icon' => '🤒',
+ 'message' => 'Out sick',
+ 'clearAt' => [
+ 'type' => 'end-of',
+ 'time' => 'day',
+ ],
+ ],
+ [
+ 'id' => 'vacationing',
+ 'icon' => '🌴',
+ 'message' => 'Vacationing',
+ 'clearAt' => null,
+ ],
+ [
+ 'id' => 'call',
+ 'icon' => '💬',
+ 'message' => 'In a call',
+ 'clearAt' => null,
+ 'visible' => false,
+ ],
+ [
+ 'id' => 'out-of-office',
+ 'icon' => '🛑',
+ 'message' => 'Out of office',
+ 'clearAt' => null,
+ 'visible' => false,
+ ],
+ ], $actual);
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('getIconForIdDataProvider')]
+ public function testGetIconForId(string $id, ?string $expectedIcon): void {
+ $actual = $this->service->getIconForId($id);
+ $this->assertEquals($expectedIcon, $actual);
+ }
+
+ public static function getIconForIdDataProvider(): array {
+ return [
+ ['meeting', '📅'],
+ ['commuting', '🚌'],
+ ['sick-leave', '🤒'],
+ ['vacationing', '🌴'],
+ ['remote-work', '🏡'],
+ ['be-right-back', '⏳'],
+ ['call', '💬'],
+ ['unknown-id', null],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('getTranslatedStatusForIdDataProvider')]
+ public function testGetTranslatedStatusForId(string $id, ?string $expected): void {
+ $this->l10n->method('t')
+ ->willReturnArgument(0);
+
+ $actual = $this->service->getTranslatedStatusForId($id);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public static function getTranslatedStatusForIdDataProvider(): array {
+ return [
+ ['meeting', 'In a meeting'],
+ ['commuting', 'Commuting'],
+ ['sick-leave', 'Out sick'],
+ ['vacationing', 'Vacationing'],
+ ['remote-work', 'Working remotely'],
+ ['be-right-back', 'Be right back'],
+ ['call', 'In a call'],
+ ['unknown-id', null],
+ ];
+ }
+
+ #[\PHPUnit\Framework\Attributes\DataProvider('isValidIdDataProvider')]
+ public function testIsValidId(string $id, bool $expected): void {
+ $actual = $this->service->isValidId($id);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public static function isValidIdDataProvider(): array {
+ return [
+ ['meeting', true],
+ ['commuting', true],
+ ['sick-leave', true],
+ ['vacationing', true],
+ ['remote-work', true],
+ ['be-right-back', true],
+ ['call', true],
+ ['unknown-id', false],
+ ];
+ }
+
+ public function testGetDefaultStatusById(): void {
+ $this->l10n->expects($this->exactly(8))
+ ->method('t')
+ ->willReturnCallback(function ($text, $parameters = []) {
+ return vsprintf($text, $parameters);
+ });
+
+ $this->assertEquals([
+ 'id' => 'call',
+ 'icon' => '💬',
+ 'message' => 'In a call',
+ 'clearAt' => null,
+ 'visible' => false,
+ ], $this->service->getDefaultStatusById('call'));
+ }
+
+ public function testGetDefaultStatusByUnknownId(): void {
+ $this->assertNull($this->service->getDefaultStatusById('unknown'));
+ }
+}
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);
+ }
+}