From 0fad921840eb801492522af6ef795231163cff20 Mon Sep 17 00:00:00 2001 From: Georg Ehrke Date: Tue, 2 Jun 2020 12:48:37 +0200 Subject: Add user-status app Signed-off-by: Georg Ehrke --- .../ClearOldStatusesBackgroundJobTest.php | 63 +++ apps/user_status/tests/Unit/CapabilitiesTest.php | 71 +++ .../Controller/PredefinedStatusControllerTest.php | 74 +++ .../Unit/Controller/StatusesControllerTest.php | 114 ++++ .../Unit/Controller/UserStatusControllerTest.php | 340 ++++++++++++ .../tests/Unit/Db/UserStatusMapperTest.php | 168 ++++++ .../Unit/Listener/UserDeletedListenerTest.php | 71 +++ .../Unit/Listener/UserLiveStatusListenerTest.php | 162 ++++++ .../tests/Unit/Service/EmojiServiceTest.php | 100 ++++ .../Unit/Service/PredefinedStatusServiceTest.php | 184 +++++++ .../tests/Unit/Service/StatusServiceTest.php | 592 +++++++++++++++++++++ apps/user_status/tests/bootstrap.php | 36 ++ 12 files changed, 1975 insertions(+) create mode 100644 apps/user_status/tests/Unit/BackgroundJob/ClearOldStatusesBackgroundJobTest.php create mode 100644 apps/user_status/tests/Unit/CapabilitiesTest.php create mode 100644 apps/user_status/tests/Unit/Controller/PredefinedStatusControllerTest.php create mode 100644 apps/user_status/tests/Unit/Controller/StatusesControllerTest.php create mode 100644 apps/user_status/tests/Unit/Controller/UserStatusControllerTest.php create mode 100644 apps/user_status/tests/Unit/Db/UserStatusMapperTest.php create mode 100644 apps/user_status/tests/Unit/Listener/UserDeletedListenerTest.php create mode 100644 apps/user_status/tests/Unit/Listener/UserLiveStatusListenerTest.php create mode 100644 apps/user_status/tests/Unit/Service/EmojiServiceTest.php create mode 100644 apps/user_status/tests/Unit/Service/PredefinedStatusServiceTest.php create mode 100644 apps/user_status/tests/Unit/Service/StatusServiceTest.php create mode 100644 apps/user_status/tests/bootstrap.php (limited to 'apps/user_status/tests') 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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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 @@ + + * + * @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 + * + */ + +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(); -- cgit v1.2.3