summaryrefslogtreecommitdiffstats
path: root/apps/user_status/tests
diff options
context:
space:
mode:
authorGeorg Ehrke <developer@georgehrke.com>2020-06-02 12:48:37 +0200
committerGeorg Ehrke <developer@georgehrke.com>2020-07-31 16:45:27 +0200
commit0fad921840eb801492522af6ef795231163cff20 (patch)
treeddab0d1567d81eeb8d956ec98196180ad296cabd /apps/user_status/tests
parentfce6df06e2bd1d68ee5614621ae7f92c6f7fa53d (diff)
downloadnextcloud-server-0fad921840eb801492522af6ef795231163cff20.tar.gz
nextcloud-server-0fad921840eb801492522af6ef795231163cff20.zip
Add user-status app
Signed-off-by: Georg Ehrke <developer@georgehrke.com>
Diffstat (limited to 'apps/user_status/tests')
-rw-r--r--apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php63
-rw-r--r--apps/user_status/tests/Unit/CapabilitiesTest.php71
-rw-r--r--apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php74
-rw-r--r--apps/user_status/tests/Unit/Controller/StatusesControllerTest.php114
-rw-r--r--apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php340
-rw-r--r--apps/user_status/tests/Unit/Db/UserStatusMapperTest.php168
-rw-r--r--apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php71
-rw-r--r--apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php162
-rw-r--r--apps/user_status/tests/Unit/Service/EmojiServiceTest.php100
-rw-r--r--apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php184
-rw-r--r--apps/user_status/tests/Unit/Service/StatusServiceTest.php592
-rw-r--r--apps/user_status/tests/bootstrap.php36
12 files changed, 1975 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..6c5f15d47e9
--- /dev/null
+++ b/apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php
@@ -0,0 +1,63 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\BackgroundJob;
+
+use OCA\UserStatus\BackgroundJob\ClearOldStatusesBackgroundJob;
+use OCA\UserStatus\Db\UserStatusMapper;
+use OCP\AppFramework\Utility\ITimeFactory;
+use Test\TestCase;
+
+class ClearOldStatusesBackgroundJobTest extends TestCase {
+
+ /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
+ private $time;
+
+ /** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */
+ private $mapper;
+
+ /** @var ClearOldStatusesBackgroundJob */
+ private $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() {
+ $this->mapper->expects($this->once())
+ ->method('clearOlderThan')
+ ->with(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..80f6765a694
--- /dev/null
+++ b/apps/user_status/tests/Unit/CapabilitiesTest.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests;
+
+use OCA\UserStatus\Capabilities;
+use OCA\UserStatus\Service\EmojiService;
+use Test\TestCase;
+
+class CapabilitiesTest extends TestCase {
+
+ /** @var EmojiService|\PHPUnit\Framework\MockObject\MockObject */
+ private $emojiService;
+
+ /** @var Capabilities */
+ private $capabilities;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->emojiService = $this->createMock(EmojiService::class);
+ $this->capabilities = new Capabilities($this->emojiService);
+ }
+
+ /**
+ * @param bool $supportsEmojis
+ *
+ * @dataProvider getCapabilitiesDataProvider
+ */
+ public function testGetCapabilities(bool $supportsEmojis): void {
+ $this->emojiService->expects($this->once())
+ ->method('doesPlatformSupportEmoji')
+ ->willReturn($supportsEmojis);
+
+ $this->assertEquals([
+ 'user_status' => [
+ 'enabled' => true,
+ 'supports_emoji' => $supportsEmojis,
+ ]
+ ], $this->capabilities->getCapabilities());
+ }
+
+ public function getCapabilitiesDataProvider(): array {
+ return [
+ [true],
+ [false],
+ ];
+ }
+}
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..44e3af5c0d2
--- /dev/null
+++ b/apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Controller;
+
+use OCA\UserStatus\Controller\PredefinedStatusController;
+use OCA\UserStatus\Service\PredefinedStatusService;
+use OCP\IRequest;
+use Test\TestCase;
+
+class PredefinedStatusControllerTest extends TestCase {
+
+ /** @var PredefinedStatusService|\PHPUnit\Framework\MockObject\MockObject */
+ private $service;
+
+ /** @var PredefinedStatusController */
+ private $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() {
+ $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..1dd6c0c4ae1
--- /dev/null
+++ b/apps/user_status/tests/Unit/Controller/StatusesControllerTest.php
@@ -0,0 +1,114 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+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 Test\TestCase;
+
+class StatusesControllerTest extends TestCase {
+
+ /** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
+ private $service;
+
+ /** @var StatusesController */
+ private $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..5d1e15b0d3e
--- /dev/null
+++ b/apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php
@@ -0,0 +1,340 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Controller;
+
+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\ILogger;
+use OCP\IRequest;
+use Test\TestCase;
+use Throwable;
+
+class UserStatusControllerTest extends TestCase {
+
+ /** @var ILogger|\PHPUnit\Framework\MockObject\MockObject */
+ private $logger;
+
+ /** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
+ private $service;
+
+ /** @var UserStatusController */
+ private $controller;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $request = $this->createMock(IRequest::class);
+ $userId = 'john.doe';
+ $this->logger = $this->createMock(ILogger::class);
+ $this->service = $this->createMock(StatusService::class);
+
+ $this->controller = new UserStatusController('user_status', $request, $userId, $this->logger, $this->service);
+ }
+
+ public function testGetStatus(): void {
+ $userStatus = $this->getUserStatus();
+
+ $this->service->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->service->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();
+ }
+
+ /**
+ * @param string $statusType
+ * @param string|null $statusIcon
+ * @param string|null $message
+ * @param int|null $clearAt
+ * @param bool $expectSuccess
+ * @param bool $expectException
+ * @param Throwable|null $exception
+ * @param bool $expectLogger
+ * @param string|null $expectedLogMessage
+ *
+ * @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->service->expects($this->once())
+ ->method('setStatus')
+ ->with('john.doe', $statusType, null, true)
+ ->willThrowException($exception);
+ } else {
+ $this->service->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 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"'],
+ ];
+ }
+
+ /**
+ * @param string $messageId
+ * @param int|null $clearAt
+ * @param bool $expectSuccess
+ * @param bool $expectException
+ * @param Throwable|null $exception
+ * @param bool $expectLogger
+ * @param string|null $expectedLogMessage
+ *
+ * @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->service->expects($this->once())
+ ->method('setPredefinedMessage')
+ ->with('john.doe', $messageId, $clearAt)
+ ->willThrowException($exception);
+ } else {
+ $this->service->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 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"'],
+ ];
+ }
+
+ /**
+ * @param string|null $statusIcon
+ * @param string $message
+ * @param int|null $clearAt
+ * @param bool $expectSuccess
+ * @param bool $expectException
+ * @param Throwable|null $exception
+ * @param bool $expectLogger
+ * @param string|null $expectedLogMessage
+ *
+ * @dataProvider setCustomMessageDataProvider
+ */
+ public function testSetCustomMessage(?string $statusIcon,
+ string $message,
+ ?int $clearAt,
+ bool $expectSuccess,
+ bool $expectException,
+ ?Throwable $exception,
+ bool $expectLogger,
+ ?string $expectedLogMessage): void {
+ $userStatus = $this->getUserStatus();
+
+ if ($expectException) {
+ $this->service->expects($this->once())
+ ->method('setCustomMessage')
+ ->with('john.doe', $statusIcon, $message, $clearAt)
+ ->willThrowException($exception);
+ } else {
+ $this->service->expects($this->once())
+ ->method('setCustomMessage')
+ ->with('john.doe', $statusIcon, $message, $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->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 function setCustomMessageDataProvider(): array {
+ return [
+ ['👨🏽‍💻', 'Busy developing the status feature', 500, true, false, null, false, null],
+ ['👨🏽‍💻', '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 testClearStatus(): void {
+ $this->service->expects($this->once())
+ ->method('clearStatus')
+ ->with('john.doe');
+
+ $response = $this->controller->clearStatus();
+ $this->assertEquals([], $response->getData());
+ }
+
+ public function testClearMessage(): void {
+ $this->service->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/Db/UserStatusMapperTest.php b/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php
new file mode 100644
index 00000000000..16699d2ae27
--- /dev/null
+++ b/apps/user_status/tests/Unit/Db/UserStatusMapperTest.php
@@ -0,0 +1,168 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Db;
+
+use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Db\UserStatusMapper;
+use Test\TestCase;
+
+class UserStatusMapperTest extends TestCase {
+
+ /** @var UserStatusMapper */
+ private $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 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(5000, $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(UniqueConstraintViolationException::class);
+
+ $this->mapper->insert($userStatus2);
+ }
+
+ public function testClearOlderThan(): void {
+ $this->insertSampleStatuses();
+
+ $this->mapper->clearOlderThan(55000);
+
+ $allStatuses = $this->mapper->findAll();
+ $this->assertCount(3, $allStatuses);
+
+ $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(null, $user1Status->getCustomIcon());
+ $this->assertEquals(null, $user1Status->getCustomMessage());
+ $this->assertEquals(null, $user1Status->getClearAt());
+ }
+
+ 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->setIsUserDefined(true);
+ $userStatus2->setCustomIcon('💩');
+ $userStatus2->setCustomMessage('Do not disturb');
+ $userStatus2->setClearAt(50000);
+
+ $userStatus3 = new UserStatus();
+ $userStatus3->setUserId('user2');
+ $userStatus3->setStatus('away');
+ $userStatus3->setStatusTimestamp(5000);
+ $userStatus3->setIsUserDefined(false);
+ $userStatus3->setCustomIcon('🏝');
+ $userStatus3->setCustomMessage('On vacation');
+ $userStatus3->setClearAt(60000);
+
+ $this->mapper->insert($userStatus1);
+ $this->mapper->insert($userStatus2);
+ $this->mapper->insert($userStatus3);
+ }
+}
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..6267bf1d185
--- /dev/null
+++ b/apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+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 Test\TestCase;
+
+class UserDeletedListenerTest extends TestCase {
+
+ /** @var StatusService|\PHPUnit\Framework\MockObject\MockObject */
+ private $service;
+
+ /** @var UserDeletedListener */
+ private $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..29f444ece0b
--- /dev/null
+++ b/apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php
@@ -0,0 +1,162 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Listener;
+
+use OCA\UserStatus\Db\UserStatus;
+use OCA\UserStatus\Db\UserStatusMapper;
+use OCA\UserStatus\Listener\UserDeletedListener;
+use OCA\UserStatus\Listener\UserLiveStatusListener;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\EventDispatcher\GenericEvent;
+use OCP\IUser;
+use OCP\User\Events\UserLiveStatusEvent;
+use Test\TestCase;
+
+class UserLiveStatusListenerTest extends TestCase {
+
+ /** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */
+ private $mapper;
+
+ /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
+ private $timeFactory;
+
+ /** @var UserDeletedListener */
+ private $listener;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->mapper = $this->createMock(UserStatusMapper::class);
+ $this->timeFactory = $this->createMock(ITimeFactory::class);
+ $this->listener = new UserLiveStatusListener($this->mapper, $this->timeFactory);
+ }
+
+ /**
+ * @param string $userId
+ * @param string $previousStatus
+ * @param int $previousTimestamp
+ * @param bool $previousIsUserDefined
+ * @param string $eventStatus
+ * @param int $eventTimestamp
+ * @param bool $expectExisting
+ * @param bool $expectUpdate
+ *
+ * @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->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with($userId)
+ ->willReturn($userStatus);
+ } else {
+ $this->mapper->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->once())
+ ->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 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],
+ ];
+ }
+
+ 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/EmojiServiceTest.php b/apps/user_status/tests/Unit/Service/EmojiServiceTest.php
new file mode 100644
index 00000000000..e622a7eabcd
--- /dev/null
+++ b/apps/user_status/tests/Unit/Service/EmojiServiceTest.php
@@ -0,0 +1,100 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Service;
+
+use OCA\UserStatus\Service\EmojiService;
+use OCP\IDBConnection;
+use Test\TestCase;
+
+class EmojiServiceTest extends TestCase {
+
+ /** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */
+ private $db;
+
+ /** @var EmojiService */
+ private $service;
+
+ protected function setUp(): void {
+ parent::setUp();
+
+ $this->db = $this->createMock(IDBConnection::class);
+ $this->service = new EmojiService($this->db);
+ }
+
+ /**
+ * @param bool $supports4ByteText
+ * @param bool $expected
+ *
+ * @dataProvider doesPlatformSupportEmojiDataProvider
+ */
+ public function testDoesPlatformSupportEmoji(bool $supports4ByteText, bool $expected): void {
+ $this->db->expects($this->once())
+ ->method('supports4ByteText')
+ ->willReturn($supports4ByteText);
+
+ $this->assertEquals($expected, $this->service->doesPlatformSupportEmoji());
+ }
+
+ /**
+ * @return array
+ */
+ public function doesPlatformSupportEmojiDataProvider(): array {
+ return [
+ [true, true],
+ [false, false],
+ ];
+ }
+
+ /**
+ * @param string $emoji
+ * @param bool $expected
+ *
+ * @dataProvider isValidEmojiDataProvider
+ */
+ public function testIsValidEmoji(string $emoji, bool $expected): void {
+ $actual = $this->service->isValidEmoji($emoji);
+
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function isValidEmojiDataProvider(): array {
+ return [
+ ['🏝', true],
+ ['📱', true],
+ ['🏢', true],
+ ['📱📠', false],
+ ['a', false],
+ ['0', false],
+ ['$', false],
+ // Test some more complex emojis with modifiers and zero-width-joiner
+ ['👩🏿‍💻', true],
+ ['🤷🏼‍♀️', true],
+ ['🏳️‍🌈', true],
+ ['👨‍👨‍👦‍👦', true],
+ ['👩‍❤️‍👩', true]
+ ];
+ }
+}
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..2f58b5b1df8
--- /dev/null
+++ b/apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php
@@ -0,0 +1,184 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Service;
+
+use OCA\UserStatus\Service\PredefinedStatusService;
+use OCP\IL10N;
+use Test\TestCase;
+
+class PredefinedStatusServiceTest extends TestCase {
+
+ /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */
+ protected $l10n;
+
+ /** @var PredefinedStatusService */
+ protected $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(5))
+ ->method('t')
+ ->withConsecutive(
+ ['In a meeting'],
+ ['Commuting'],
+ ['Working remotely'],
+ ['Out sick'],
+ ['Vacationing']
+ )
+ ->willReturnArgument(0);
+
+ $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' => '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,
+ ],
+ ], $actual);
+ }
+
+ /**
+ * @param string $id
+ * @param string|null $expectedIcon
+ *
+ * @dataProvider getIconForIdDataProvider
+ */
+ public function testGetIconForId(string $id, ?string $expectedIcon): void {
+ $actual = $this->service->getIconForId($id);
+ $this->assertEquals($expectedIcon, $actual);
+ }
+
+ /**
+ * @return array
+ */
+ public function getIconForIdDataProvider(): array {
+ return [
+ ['meeting', '📅'],
+ ['commuting', '🚌'],
+ ['sick-leave', '🤒'],
+ ['vacationing', '🌴'],
+ ['remote-work', '🏡'],
+ ['unknown-id', null],
+ ];
+ }
+
+ /**
+ * @param string $id
+ * @param string|null $expected
+ *
+ * @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);
+ }
+
+ /**
+ * @return array
+ */
+ public function getTranslatedStatusForIdDataProvider(): array {
+ return [
+ ['meeting', 'In a meeting'],
+ ['commuting', 'Commuting'],
+ ['sick-leave', 'Out sick'],
+ ['vacationing', 'Vacationing'],
+ ['remote-work', 'Working remotely'],
+ ['unknown-id', null],
+ ];
+ }
+
+ /**
+ * @param string $id
+ * @param bool $expected
+ *
+ * @dataProvider isValidIdDataProvider
+ */
+ public function testIsValidId(string $id, bool $expected): void {
+ $actual = $this->service->isValidId($id);
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * @return array
+ */
+ public function isValidIdDataProvider(): array {
+ return [
+ ['meeting', true],
+ ['commuting', true],
+ ['sick-leave', true],
+ ['vacationing', true],
+ ['remote-work', true],
+ ['unknown-id', false],
+ ];
+ }
+}
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..647c1089db8
--- /dev/null
+++ b/apps/user_status/tests/Unit/Service/StatusServiceTest.php
@@ -0,0 +1,592 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+namespace OCA\UserStatus\Tests\Service;
+
+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\EmojiService;
+use OCA\UserStatus\Service\PredefinedStatusService;
+use OCA\UserStatus\Service\StatusService;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Utility\ITimeFactory;
+use Test\TestCase;
+
+class StatusServiceTest extends TestCase {
+
+ /** @var UserStatusMapper|\PHPUnit\Framework\MockObject\MockObject */
+ private $mapper;
+
+ /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */
+ private $timeFactory;
+
+ /** @var PredefinedStatusService|\PHPUnit\Framework\MockObject\MockObject */
+ private $predefinedStatusService;
+
+ /** @var EmojiService|\PHPUnit\Framework\MockObject\MockObject */
+ private $emojiService;
+
+ /** @var StatusService */
+ private $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->emojiService = $this->createMock(EmojiService::class);
+ $this->service = new StatusService($this->mapper,
+ $this->timeFactory,
+ $this->predefinedStatusService,
+ $this->emojiService);
+ }
+
+ 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 testFindByUserId(): void {
+ $status = $this->createMock(UserStatus::class);
+ $this->mapper->expects($this->once())
+ ->method('findByUserId')
+ ->with('john.doe')
+ ->willReturn($status);
+
+ $this->assertEquals($status, $this->service->findByUserId('john.doe'));
+ }
+
+ 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->setClearAt(50);
+ $status->setMessageId('commuting');
+
+ $this->timeFactory->expects($this->once())
+ ->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());
+ }
+
+ /**
+ * @param string $userId
+ * @param string $status
+ * @param int|null $statusTimestamp
+ * @param bool $isUserDefined
+ * @param bool $expectExisting
+ * @param bool $expectSuccess
+ * @param bool $expectTimeFactory
+ * @param bool $expectException
+ * @param string|null $expectedExceptionClass
+ * @param string|null $expectedExceptionMessage
+ *
+ * @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 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'],
+ ];
+ }
+
+ /**
+ * @param string $userId
+ * @param string $messageId
+ * @param bool $isValidMessageId
+ * @param int|null $clearAt
+ * @param bool $expectExisting
+ * @param bool $expectSuccess
+ * @param bool $expectException
+ * @param string|null $expectedExceptionClass
+ * @param string|null $expectedExceptionMessage
+ *
+ * @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 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'],
+ ];
+ }
+
+ /**
+ * @param string $userId
+ * @param string|null $statusIcon
+ * @param bool $supportsEmoji
+ * @param string $message
+ * @param int|null $clearAt
+ * @param bool $expectExisting
+ * @param bool $expectSuccess
+ * @param bool $expectException
+ * @param string|null $expectedExceptionClass
+ * @param string|null $expectedExceptionMessage
+ *
+ * @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->emojiService->method('isValidEmoji')
+ ->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 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);
+ }
+}
diff --git a/apps/user_status/tests/bootstrap.php b/apps/user_status/tests/bootstrap.php
new file mode 100644
index 00000000000..5ef5008fed2
--- /dev/null
+++ b/apps/user_status/tests/bootstrap.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright Copyright (c) 2020, Georg Ehrke
+ *
+ * @author Georg Ehrke <oc.list@georgehrke.com>
+ *
+ * @license AGPL-3.0
+ *
+ * This code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License, version 3,
+ * along with this program. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+if (!defined('PHPUNIT_RUN')) {
+ define('PHPUNIT_RUN', 1);
+}
+
+require_once __DIR__.'/../../../lib/base.php';
+
+\OC::$composerAutoloader->addPsr4('Test\\', OC::$SERVERROOT . '/tests/lib/', true);
+
+\OC_App::loadApp('user_status');
+
+OC_Hook::clear();