diff options
Diffstat (limited to 'apps/dav/tests/unit')
202 files changed, 12008 insertions, 6860 deletions
diff --git a/apps/dav/tests/unit/AppInfo/ApplicationTest.php b/apps/dav/tests/unit/AppInfo/ApplicationTest.php index f8ddd9bb821..336f487e0b8 100644 --- a/apps/dav/tests/unit/AppInfo/ApplicationTest.php +++ b/apps/dav/tests/unit/AppInfo/ApplicationTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. diff --git a/apps/dav/tests/unit/AppInfo/PluginManagerTest.php b/apps/dav/tests/unit/AppInfo/PluginManagerTest.php index 8211cdfc02c..0082aa45286 100644 --- a/apps/dav/tests/unit/AppInfo/PluginManagerTest.php +++ b/apps/dav/tests/unit/AppInfo/PluginManagerTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 ownCloud GmbH. * SPDX-License-Identifier: AGPL-3.0-only @@ -24,7 +26,7 @@ class PluginManagerTest extends TestCase { $server = $this->createMock(ServerContainer::class); $appManager = $this->createMock(AppManager::class); - $appManager->method('getInstalledApps') + $appManager->method('getEnabledApps') ->willReturn(['adavapp', 'adavapp2']); $appInfo1 = [ diff --git a/apps/dav/tests/unit/Avatars/AvatarHomeTest.php b/apps/dav/tests/unit/Avatars/AvatarHomeTest.php index 9699c146c8a..7117637a000 100644 --- a/apps/dav/tests/unit/Avatars/AvatarHomeTest.php +++ b/apps/dav/tests/unit/Avatars/AvatarHomeTest.php @@ -1,27 +1,25 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2017 ownCloud GmbH * SPDX-License-Identifier: AGPL-3.0-only */ -namespace OCA\DAV\Tests\Unit\Avatars; +namespace OCA\DAV\Tests\unit\Avatars; use OCA\DAV\Avatars\AvatarHome; use OCA\DAV\Avatars\AvatarNode; use OCP\IAvatar; use OCP\IAvatarManager; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; use Test\TestCase; class AvatarHomeTest extends TestCase { - - /** @var AvatarHome */ - private $home; - - /** @var IAvatarManager | \PHPUnit\Framework\MockObject\MockObject */ - private $avatarManager; + private AvatarHome $home; + private IAvatarManager&MockObject $avatarManager; protected function setUp(): void { parent::setUp(); @@ -29,16 +27,14 @@ class AvatarHomeTest extends TestCase { $this->home = new AvatarHome(['uri' => 'principals/users/admin'], $this->avatarManager); } - /** - * @dataProvider providesForbiddenMethods - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesForbiddenMethods')] public function testForbiddenMethods($method): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->home->$method(''); } - public function providesForbiddenMethods() { + public static function providesForbiddenMethods(): array { return [ ['createFile'], ['createDirectory'], @@ -52,7 +48,7 @@ class AvatarHomeTest extends TestCase { self::assertEquals('admin', $n); } - public function providesTestGetChild() { + public static function providesTestGetChild(): array { return [ [MethodNotAllowed::class, false, ''], [MethodNotAllowed::class, false, 'bla.foo'], @@ -62,10 +58,8 @@ class AvatarHomeTest extends TestCase { ]; } - /** - * @dataProvider providesTestGetChild - */ - public function testGetChild($expectedException, $hasAvatar, $path): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesTestGetChild')] + public function testGetChild(?string $expectedException, bool $hasAvatar, string $path): void { if ($expectedException !== null) { $this->expectException($expectedException); } @@ -89,10 +83,8 @@ class AvatarHomeTest extends TestCase { self::assertEquals(1, count($avatarNodes)); } - /** - * @dataProvider providesTestGetChild - */ - public function testChildExists($expectedException, $hasAvatar, $path): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesTestGetChild')] + public function testChildExists(?string $expectedException, bool $hasAvatar, string $path): void { $avatar = $this->createMock(IAvatar::class); $avatar->method('exists')->willReturn($hasAvatar); diff --git a/apps/dav/tests/unit/Avatars/AvatarNodeTest.php b/apps/dav/tests/unit/Avatars/AvatarNodeTest.php index 92c02e17ff8..0ca147a1f3b 100644 --- a/apps/dav/tests/unit/Avatars/AvatarNodeTest.php +++ b/apps/dav/tests/unit/Avatars/AvatarNodeTest.php @@ -1,26 +1,28 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2017 ownCloud GmbH * SPDX-License-Identifier: AGPL-3.0-only */ -namespace OCA\DAV\Tests\Unit\Avatars; +namespace OCA\DAV\Tests\unit\Avatars; use OCA\DAV\Avatars\AvatarNode; use OCP\IAvatar; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class AvatarNodeTest extends TestCase { public function testGetName(): void { - /** @var IAvatar | \PHPUnit\Framework\MockObject\MockObject $a */ + /** @var IAvatar&MockObject $a */ $a = $this->createMock(IAvatar::class); $n = new AvatarNode(1024, 'png', $a); $this->assertEquals('1024.png', $n->getName()); } public function testGetContentType(): void { - /** @var IAvatar | \PHPUnit\Framework\MockObject\MockObject $a */ + /** @var IAvatar&MockObject $a */ $a = $this->createMock(IAvatar::class); $n = new AvatarNode(1024, 'png', $a); $this->assertEquals('image/png', $n->getContentType()); diff --git a/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php b/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php index 85000aba6d3..b2199e3e657 100644 --- a/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/CleanupInvitationTokenJobTest.php @@ -10,19 +10,16 @@ namespace OCA\DAV\Tests\unit\BackgroundJob; use OCA\DAV\BackgroundJob\CleanupInvitationTokenJob; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\DB\QueryBuilder\IExpressionBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class CleanupInvitationTokenJobTest extends TestCase { - /** @var IDBConnection | \PHPUnit\Framework\MockObject\MockObject */ - private $dbConnection; - - /** @var ITimeFactory | \PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; - - /** @var \OCA\DAV\BackgroundJob\CleanupInvitationTokenJob */ - private $backgroundJob; + private IDBConnection&MockObject $dbConnection; + private ITimeFactory&MockObject $timeFactory; + private CleanupInvitationTokenJob $backgroundJob; protected function setUp(): void { parent::setUp(); @@ -41,7 +38,7 @@ class CleanupInvitationTokenJobTest extends TestCase { ->willReturn(1337); $queryBuilder = $this->createMock(IQueryBuilder::class); - $expr = $this->createMock(\OCP\DB\QueryBuilder\IExpressionBuilder::class); + $expr = $this->createMock(IExpressionBuilder::class); $stmt = $this->createMock(\Doctrine\DBAL\Driver\Statement::class); $this->dbConnection->expects($this->once()) diff --git a/apps/dav/tests/unit/BackgroundJob/CleanupOrphanedChildrenJobTest.php b/apps/dav/tests/unit/BackgroundJob/CleanupOrphanedChildrenJobTest.php new file mode 100644 index 00000000000..2065b8fe946 --- /dev/null +++ b/apps/dav/tests/unit/BackgroundJob/CleanupOrphanedChildrenJobTest.php @@ -0,0 +1,170 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\BackgroundJob; + +use OCA\DAV\BackgroundJob\CleanupOrphanedChildrenJob; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\BackgroundJob\IJobList; +use OCP\DB\IResult; +use OCP\DB\QueryBuilder\IExpressionBuilder; +use OCP\DB\QueryBuilder\IQueryBuilder; +use OCP\IDBConnection; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class CleanupOrphanedChildrenJobTest extends TestCase { + private CleanupOrphanedChildrenJob $job; + + private ITimeFactory&MockObject $timeFactory; + private IDBConnection&MockObject $connection; + private LoggerInterface&MockObject $logger; + private IJobList&MockObject $jobList; + + protected function setUp(): void { + parent::setUp(); + + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->connection = $this->createMock(IDBConnection::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->jobList = $this->createMock(IJobList::class); + + $this->job = new CleanupOrphanedChildrenJob( + $this->timeFactory, + $this->connection, + $this->logger, + $this->jobList, + ); + } + + private function getArgument(): array { + return [ + 'childTable' => 'childTable', + 'parentTable' => 'parentTable', + 'parentId' => 'parentId', + 'logMessage' => 'logMessage', + ]; + } + + private function getMockQueryBuilder(): IQueryBuilder&MockObject { + $expr = $this->createMock(IExpressionBuilder::class); + $qb = $this->createMock(IQueryBuilder::class); + $qb->method('select') + ->willReturnSelf(); + $qb->method('from') + ->willReturnSelf(); + $qb->method('leftJoin') + ->willReturnSelf(); + $qb->method('where') + ->willReturnSelf(); + $qb->method('setMaxResults') + ->willReturnSelf(); + $qb->method('andWhere') + ->willReturnSelf(); + $qb->method('expr') + ->willReturn($expr); + $qb->method('delete') + ->willReturnSelf(); + return $qb; + } + + public function testRunWithoutOrphans(): void { + $argument = $this->getArgument(); + $selectQb = $this->getMockQueryBuilder(); + $result = $this->createMock(IResult::class); + + $this->connection->expects(self::once()) + ->method('getQueryBuilder') + ->willReturn($selectQb); + $selectQb->expects(self::once()) + ->method('executeQuery') + ->willReturn($result); + $result->expects(self::once()) + ->method('fetchAll') + ->willReturn([]); + $result->expects(self::once()) + ->method('closeCursor'); + $this->jobList->expects(self::never()) + ->method('add'); + + self::invokePrivate($this->job, 'run', [$argument]); + } + + public function testRunWithPartialBatch(): void { + $argument = $this->getArgument(); + $selectQb = $this->getMockQueryBuilder(); + $deleteQb = $this->getMockQueryBuilder(); + $result = $this->createMock(IResult::class); + + $calls = [ + $selectQb, + $deleteQb, + ]; + $this->connection->method('getQueryBuilder') + ->willReturnCallback(function () use (&$calls) { + return array_shift($calls); + }); + $selectQb->expects(self::once()) + ->method('executeQuery') + ->willReturn($result); + $result->expects(self::once()) + ->method('fetchAll') + ->willReturn([ + ['id' => 42], + ['id' => 43], + ]); + $result->expects(self::once()) + ->method('closeCursor'); + $deleteQb->expects(self::once()) + ->method('delete') + ->willReturnSelf(); + $deleteQb->expects(self::once()) + ->method('executeStatement'); + $this->jobList->expects(self::never()) + ->method('add'); + + self::invokePrivate($this->job, 'run', [$argument]); + } + + public function testRunWithFullBatch(): void { + $argument = $this->getArgument(); + $selectQb = $this->getMockQueryBuilder(); + $deleteQb = $this->getMockQueryBuilder(); + $result = $this->createMock(IResult::class); + + $calls = [ + $selectQb, + $deleteQb, + ]; + $this->connection->method('getQueryBuilder') + ->willReturnCallback(function () use (&$calls) { + return array_shift($calls); + }); + + $selectQb->expects(self::once()) + ->method('executeQuery') + ->willReturn($result); + $result->expects(self::once()) + ->method('fetchAll') + ->willReturn(array_map(static fn ($i) => ['id' => 42 + $i], range(0, 999))); + $result->expects(self::once()) + ->method('closeCursor'); + $deleteQb->expects(self::once()) + ->method('delete') + ->willReturnSelf(); + $deleteQb->expects(self::once()) + ->method('executeStatement'); + $this->jobList->expects(self::once()) + ->method('add') + ->with(CleanupOrphanedChildrenJob::class, $argument); + + self::invokePrivate($this->job, 'run', [$argument]); + } +} diff --git a/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php index 1173e516a22..a46a1e5e5b0 100644 --- a/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/EventReminderJobTest.php @@ -16,17 +16,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class EventReminderJobTest extends TestCase { - /** @var ITimeFactory|MockObject */ - private $time; - - /** @var ReminderService|MockObject */ - private $reminderService; - - /** @var IConfig|MockObject */ - private $config; - - /** @var EventReminderJob|MockObject */ - private $backgroundJob; + private ITimeFactory&MockObject $time; + private ReminderService&MockObject $reminderService; + private IConfig&MockObject $config; + private EventReminderJob $backgroundJob; protected function setUp(): void { parent::setUp(); @@ -42,7 +35,7 @@ class EventReminderJobTest extends TestCase { ); } - public function data(): array { + public static function data(): array { return [ [true, true, true], [true, false, false], @@ -52,23 +45,19 @@ class EventReminderJobTest extends TestCase { } /** - * @dataProvider data * * @param bool $sendEventReminders * @param bool $sendEventRemindersMode * @param bool $expectCall */ + #[\PHPUnit\Framework\Attributes\DataProvider('data')] public function testRun(bool $sendEventReminders, bool $sendEventRemindersMode, bool $expectCall): void { $this->config->expects($this->exactly($sendEventReminders ? 2 : 1)) ->method('getAppValue') - ->withConsecutive( - ['dav', 'sendEventReminders', 'yes'], - ['dav', 'sendEventRemindersMode', 'backgroundjob'], - ) - ->willReturnOnConsecutiveCalls( - $sendEventReminders ? 'yes' : 'no', - $sendEventRemindersMode ? 'backgroundjob' : 'cron' - ); + ->willReturnMap([ + ['dav', 'sendEventReminders', 'yes', ($sendEventReminders ? 'yes' : 'no')], + ['dav', 'sendEventRemindersMode', 'backgroundjob', ($sendEventRemindersMode ? 'backgroundjob' : 'cron')], + ]); if ($expectCall) { $this->reminderService->expects($this->once()) diff --git a/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php b/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php index 4874e79b9a2..88a76ae1332 100644 --- a/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/GenerateBirthdayCalendarBackgroundJobTest.php @@ -16,18 +16,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class GenerateBirthdayCalendarBackgroundJobTest extends TestCase { - - /** @var ITimeFactory|MockObject */ - private $time; - - /** @var BirthdayService | MockObject */ - private $birthdayService; - - /** @var IConfig | MockObject */ - private $config; - - /** @var \OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob */ - private $backgroundJob; + private ITimeFactory&MockObject $time; + private BirthdayService&MockObject $birthdayService; + private IConfig&MockObject $config; + private GenerateBirthdayCalendarBackgroundJob $backgroundJob; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/BackgroundJob/OutOfOfficeEventDispatcherJobTest.php b/apps/dav/tests/unit/BackgroundJob/OutOfOfficeEventDispatcherJobTest.php index b42334523f8..6135fd00fdc 100644 --- a/apps/dav/tests/unit/BackgroundJob/OutOfOfficeEventDispatcherJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/OutOfOfficeEventDispatcherJobTest.php @@ -25,21 +25,11 @@ use Test\TestCase; class OutOfOfficeEventDispatcherJobTest extends TestCase { private OutOfOfficeEventDispatcherJob $job; - - /** @var MockObject|ITimeFactory */ - private $timeFactory; - - /** @var MockObject|AbsenceMapper */ - private $absenceMapper; - - /** @var MockObject|LoggerInterface */ - private $logger; - - /** @var MockObject|IEventDispatcher */ - private $eventDispatcher; - - /** @var MockObject|IUserManager */ - private $userManager; + private ITimeFactory&MockObject $timeFactory; + private AbsenceMapper&MockObject $absenceMapper; + private LoggerInterface&MockObject $logger; + private IEventDispatcher&MockObject $eventDispatcher; + private IUserManager&MockObject $userManager; private MockObject|TimezoneService $timezoneService; protected function setUp(): void { @@ -62,7 +52,7 @@ class OutOfOfficeEventDispatcherJobTest extends TestCase { ); } - public function testDispatchStartEvent() { + public function testDispatchStartEvent(): void { $this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); $absence = new Absence(); @@ -94,7 +84,7 @@ class OutOfOfficeEventDispatcherJobTest extends TestCase { ]); } - public function testDispatchStopEvent() { + public function testDispatchStopEvent(): void { $this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); $absence = new Absence(); @@ -126,7 +116,7 @@ class OutOfOfficeEventDispatcherJobTest extends TestCase { ]); } - public function testDoesntDispatchUnknownEvent() { + public function testDoesntDispatchUnknownEvent(): void { $this->timezoneService->method('getUserTimezone')->with('user')->willReturn('Europe/Berlin'); $absence = new Absence(); diff --git a/apps/dav/tests/unit/BackgroundJob/PruneOutdatedSyncTokensJobTest.php b/apps/dav/tests/unit/BackgroundJob/PruneOutdatedSyncTokensJobTest.php index 9cd75445232..1838fb2537d 100644 --- a/apps/dav/tests/unit/BackgroundJob/PruneOutdatedSyncTokensJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/PruneOutdatedSyncTokensJobTest.php @@ -20,21 +20,11 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class PruneOutdatedSyncTokensJobTest extends TestCase { - /** @var ITimeFactory | MockObject */ - private $timeFactory; - - /** @var CalDavBackend | MockObject */ - private $calDavBackend; - - /** @var CardDavBackend | MockObject */ - private $cardDavBackend; - - /** @var IConfig|MockObject */ - private $config; - - /** @var LoggerInterface|MockObject*/ - private $logger; - + private ITimeFactory&MockObject $timeFactory; + private CalDavBackend&MockObject $calDavBackend; + private CardDavBackend&MockObject $cardDavBackend; + private IConfig&MockObject $config; + private LoggerInterface&MockObject $logger; private PruneOutdatedSyncTokensJob $backgroundJob; protected function setUp(): void { @@ -49,9 +39,7 @@ class PruneOutdatedSyncTokensJobTest extends TestCase { $this->backgroundJob = new PruneOutdatedSyncTokensJob($this->timeFactory, $this->calDavBackend, $this->cardDavBackend, $this->config, $this->logger); } - /** - * @dataProvider dataForTestRun - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataForTestRun')] public function testRun(string $configToKeep, string $configRetentionDays, int $actualLimit, int $retentionDays, int $deletedCalendarSyncTokens, int $deletedAddressBookSyncTokens): void { $this->config->expects($this->exactly(2)) ->method('getAppValue') @@ -84,7 +72,7 @@ class PruneOutdatedSyncTokensJobTest extends TestCase { $this->backgroundJob->run(null); } - public function dataForTestRun(): array { + public static function dataForTestRun(): array { return [ ['100', '2', 100, 7 * 24 * 3600, 2, 3], ['100', '14', 100, 14 * 24 * 3600, 2, 3], diff --git a/apps/dav/tests/unit/BackgroundJob/RefreshWebcalJobTest.php b/apps/dav/tests/unit/BackgroundJob/RefreshWebcalJobTest.php index f97626a6a73..7713ef2945a 100644 --- a/apps/dav/tests/unit/BackgroundJob/RefreshWebcalJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/RefreshWebcalJobTest.php @@ -19,20 +19,11 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class RefreshWebcalJobTest extends TestCase { - - /** @var RefreshWebcalService | MockObject */ - private $refreshWebcalService; - - /** @var IConfig | MockObject */ - private $config; - + private RefreshWebcalService&MockObject $refreshWebcalService; + private IConfig&MockObject $config; private LoggerInterface $logger; - - /** @var ITimeFactory | MockObject */ - private $timeFactory; - - /** @var IJobList | MockObject */ - private $jobList; + private ITimeFactory&MockObject $timeFactory; + private IJobList&MockObject $jobList; protected function setUp(): void { parent::setUp(); @@ -50,9 +41,8 @@ class RefreshWebcalJobTest extends TestCase { * @param int $lastRun * @param int $time * @param bool $process - * - * @dataProvider runDataProvider */ + #[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')] public function testRun(int $lastRun, int $time, bool $process): void { $backgroundJob = new RefreshWebcalJob($this->refreshWebcalService, $this->config, $this->logger, $this->timeFactory); $backgroundJob->setId(42); @@ -78,7 +68,7 @@ class RefreshWebcalJobTest extends TestCase { $this->config->expects($this->once()) ->method('getAppValue') - ->with('dav', 'calendarSubscriptionRefreshRate', 'P1W') + ->with('dav', 'calendarSubscriptionRefreshRate', 'P1D') ->willReturn('P1W'); $this->timeFactory->method('getTime') @@ -97,10 +87,7 @@ class RefreshWebcalJobTest extends TestCase { $backgroundJob->start($this->jobList); } - /** - * @return array - */ - public function runDataProvider():array { + public static function runDataProvider():array { return [ [0, 100000, true], [100000, 100000, false] diff --git a/apps/dav/tests/unit/BackgroundJob/RegisterRegenerateBirthdayCalendarsTest.php b/apps/dav/tests/unit/BackgroundJob/RegisterRegenerateBirthdayCalendarsTest.php index 88493d91d9b..6c9214d0268 100644 --- a/apps/dav/tests/unit/BackgroundJob/RegisterRegenerateBirthdayCalendarsTest.php +++ b/apps/dav/tests/unit/BackgroundJob/RegisterRegenerateBirthdayCalendarsTest.php @@ -14,20 +14,14 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\IUser; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class RegisterRegenerateBirthdayCalendarsTest extends TestCase { - /** @var ITimeFactory | \PHPUnit\Framework\MockObject\MockObject */ - private $time; - - /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject */ - private $userManager; - - /** @var IJobList | \PHPUnit\Framework\MockObject\MockObject */ - private $jobList; - - /** @var RegisterRegenerateBirthdayCalendars */ - private $backgroundJob; + private ITimeFactory&MockObject $time; + private IUserManager&MockObject $userManager; + private IJobList&MockObject $jobList; + private RegisterRegenerateBirthdayCalendars $backgroundJob; protected function setUp(): void { parent::setUp(); @@ -59,22 +53,26 @@ class RegisterRegenerateBirthdayCalendarsTest extends TestCase { $closure($user3); }); + $calls = [ + 'uid1', + 'uid2', + 'uid3', + ]; $this->jobList->expects($this->exactly(3)) ->method('add') - ->withConsecutive( - [GenerateBirthdayCalendarBackgroundJob::class, [ - 'userId' => 'uid1', - 'purgeBeforeGenerating' => true - ]], - [GenerateBirthdayCalendarBackgroundJob::class, [ - 'userId' => 'uid2', - 'purgeBeforeGenerating' => true - ]], - [GenerateBirthdayCalendarBackgroundJob::class, [ - 'userId' => 'uid3', - 'purgeBeforeGenerating' => true - ]], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals( + [ + GenerateBirthdayCalendarBackgroundJob::class, + [ + 'userId' => $expected, + 'purgeBeforeGenerating' => true + ] + ], + func_get_args() + ); + }); $this->backgroundJob->run([]); } diff --git a/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php b/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php index 7e81616a3e2..38a981787cd 100644 --- a/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php +++ b/apps/dav/tests/unit/BackgroundJob/UpdateCalendarResourcesRoomsBackgroundJobTest.php @@ -10,36 +10,17 @@ namespace OCA\DAV\Tests\unit\BackgroundJob; use OCA\DAV\BackgroundJob\UpdateCalendarResourcesRoomsBackgroundJob; -use OCA\DAV\CalDAV\CalDavBackend; use OCP\AppFramework\Utility\ITimeFactory; -use OCP\Calendar\BackendTemporarilyUnavailableException; -use OCP\Calendar\IMetadataProvider; -use OCP\Calendar\Resource\IBackend; use OCP\Calendar\Resource\IManager as IResourceManager; -use OCP\Calendar\Resource\IResource; use OCP\Calendar\Room\IManager as IRoomManager; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; -interface tmpI extends IResource, IMetadataProvider { -} - class UpdateCalendarResourcesRoomsBackgroundJobTest extends TestCase { - - /** @var ITimeFactory|MockObject */ - private $time; - - /** @var IResourceManager|MockObject */ - private $resourceManager; - - /** @var IRoomManager|MockObject */ - private $roomManager; - - /** @var CalDavBackend|MockObject */ - private $calDavBackend; - - /** @var UpdateCalendarResourcesRoomsBackgroundJob */ - private $backgroundJob; + private UpdateCalendarResourcesRoomsBackgroundJob $backgroundJob; + private ITimeFactory&MockObject $time; + private IResourceManager&MockObject $resourceManager; + private IRoomManager&MockObject $roomManager; protected function setUp(): void { parent::setUp(); @@ -47,390 +28,20 @@ class UpdateCalendarResourcesRoomsBackgroundJobTest extends TestCase { $this->time = $this->createMock(ITimeFactory::class); $this->resourceManager = $this->createMock(IResourceManager::class); $this->roomManager = $this->createMock(IRoomManager::class); - $this->calDavBackend = $this->createMock(CalDavBackend::class); $this->backgroundJob = new UpdateCalendarResourcesRoomsBackgroundJob( $this->time, $this->resourceManager, $this->roomManager, - self::$realDatabase, - $this->calDavBackend ); } - protected function tearDown(): void { - $query = self::$realDatabase->getQueryBuilder(); - $query->delete('calendar_resources')->execute(); - $query->delete('calendar_resources_md')->execute(); - $query->delete('calendar_rooms')->execute(); - $query->delete('calendar_rooms_md')->execute(); - } - - /** - * Data in Cache: - * resources: - * [backend1, res1, Beamer1, {}] - [] - * [backend1, res2, TV1, {}] - [] - * [backend2, res3, Beamer2, {}] - ['meta1' => 'value1', 'meta2' => 'value2'] - * [backend2, res4, TV2, {}] - ['meta1' => 'value1', 'meta3' => 'value3-old'] - * [backend3, res5, Beamer3, {}] - [] - * [backend3, res6, Pointer, {foo, bar}] - ['meta99' => 'value99'] - * - * Data in Backend: - * backend1 gone - * backend2 throws BackendTemporarilyUnavailableException - * [backend3, res6, Pointer123, {foo, biz}] - ['meta99' => 'value99-new', 'meta123' => 'meta456'] - * [backend3, res7, Resource4, {biz}] - ['meta1' => 'value1'] - * [backend4, res8, Beamer, {}] - ['meta2' => 'value2'] - * [backend4, res9, Beamer2, {}] - [] - * - * Expected after run: - * [backend1, res1, Beamer1, {}] - [] - * [backend1, res2, TV1, {}] - [] - * [backend2, res3, Beamer2, {}] - ['meta1' => 'value1', 'meta2' => 'value2'] - * [backend2, res4, TV2, {}] - ['meta1' => 'value1', 'meta3' => 'value3-old'] - * [backend3, res6, Pointer123, {foo, biz}] - ['meta99' => 'value99-new', 'meta123' => 'meta456'] - * [backend3, res7, Resource4, {biz}] - ['meta1' => 'value1'] - * [backend4, res8, Beamer, {}] - ['meta2' => 'value2'] - * [backend4, res9, Beamer2, {}] - [] - */ - public function testRun(): void { - $this->createTestResourcesInCache(); - - $backend2 = $this->createMock(IBackend::class); - $backend3 = $this->createMock(IBackend::class); - $backend4 = $this->createMock(IBackend::class); - - $res6 = $this->createMock(tmpI::class); - $res7 = $this->createMock(tmpI::class); - $res8 = $this->createMock(tmpI::class); - $res9 = $this->createMock(IResource::class); - - $backend2->method('getBackendIdentifier') - ->willReturn('backend2'); - $backend2->method('listAllResources') - ->will($this->throwException(new BackendTemporarilyUnavailableException())); - $backend2->method('getResource') - ->will($this->throwException(new BackendTemporarilyUnavailableException())); - $backend2->method('getAllResources') - ->will($this->throwException(new BackendTemporarilyUnavailableException())); - $backend3->method('getBackendIdentifier') - ->willReturn('backend3'); - $backend3->method('listAllResources') - ->willReturn(['res6', 'res7']); - $backend3->method('getResource') - ->willReturnMap([ - ['res6', $res6], - ['res7', $res7], - ]); - $backend4->method('getBackendIdentifier') - ->willReturn('backend4'); - $backend4->method('listAllResources') - ->willReturn(['res8', 'res9']); - $backend4->method('getResource') - ->willReturnMap([ - ['res8', $res8], - ['res9', $res9], - ]); - - $res6->method('getId')->willReturn('res6'); - $res6->method('getDisplayName')->willReturn('Pointer123'); - $res6->method('getGroupRestrictions')->willReturn(['foo', 'biz']); - $res6->method('getEMail')->willReturn('res6@foo.bar'); - $res6->method('getBackend')->willReturn($backend3); - - $res6->method('getAllAvailableMetadataKeys')->willReturn(['meta99', 'meta123']); - $res6->method('getMetadataForKey')->willReturnCallback(function ($key) { - switch ($key) { - case 'meta99': - return 'value99-new'; - - case 'meta123': - return 'meta456'; - - default: - return null; - } - }); - - $res7->method('getId')->willReturn('res7'); - $res7->method('getDisplayName')->willReturn('Resource4'); - $res7->method('getGroupRestrictions')->willReturn(['biz']); - $res7->method('getEMail')->willReturn('res7@foo.bar'); - $res7->method('getBackend')->willReturn($backend3); - $res7->method('getAllAvailableMetadataKeys')->willReturn(['meta1']); - $res7->method('getMetadataForKey')->willReturnCallback(function ($key) { - switch ($key) { - case 'meta1': - return 'value1'; - - default: - return null; - } - }); - - $res8->method('getId')->willReturn('res8'); - $res8->method('getDisplayName')->willReturn('Beamer'); - $res8->method('getGroupRestrictions')->willReturn([]); - $res8->method('getEMail')->willReturn('res8@foo.bar'); - $res8->method('getBackend')->willReturn($backend4); - $res8->method('getAllAvailableMetadataKeys')->willReturn(['meta2']); - $res8->method('getMetadataForKey')->willReturnCallback(function ($key) { - switch ($key) { - case 'meta2': - return 'value2'; - - default: - return null; - } - }); - - $res9->method('getId')->willReturn('res9'); - $res9->method('getDisplayName')->willReturn('Beamer2'); - $res9->method('getGroupRestrictions')->willReturn([]); - $res9->method('getEMail')->willReturn('res9@foo.bar'); - $res9->method('getBackend')->willReturn($backend4); - - $this->resourceManager - ->method('getBackends') - ->willReturn([ - $backend2, $backend3, $backend4 - ]); - $this->resourceManager - ->method('getBackend') - ->willReturnMap([ - ['backend2', $backend2], - ['backend3', $backend3], - ['backend4', $backend4], - ]); + $this->resourceManager->expects(self::once()) + ->method('update'); + $this->roomManager->expects(self::once()) + ->method('update'); $this->backgroundJob->run([]); - - $query = self::$realDatabase->getQueryBuilder(); - $query->select('*')->from('calendar_resources'); - - $rows = []; - $ids = []; - $stmt = $query->execute(); - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - $ids[$row['backend_id'] . '::' . $row['resource_id']] = $row['id']; - unset($row['id']); - $rows[] = $row; - } - - $this->assertEquals([ - [ - 'backend_id' => 'backend1', - 'resource_id' => 'res1', - 'displayname' => 'Beamer1', - 'email' => 'res1@foo.bar', - 'group_restrictions' => '[]', - ], - [ - 'backend_id' => 'backend1', - 'resource_id' => 'res2', - 'displayname' => 'TV1', - 'email' => 'res2@foo.bar', - 'group_restrictions' => '[]', - ], - [ - 'backend_id' => 'backend2', - 'resource_id' => 'res3', - 'displayname' => 'Beamer2', - 'email' => 'res3@foo.bar', - 'group_restrictions' => '[]', - ], - [ - 'backend_id' => 'backend2', - 'resource_id' => 'res4', - 'displayname' => 'TV2', - 'email' => 'res4@foo.bar', - 'group_restrictions' => '[]', - ], - [ - 'backend_id' => 'backend3', - 'resource_id' => 'res6', - 'displayname' => 'Pointer123', - 'email' => 'res6@foo.bar', - 'group_restrictions' => '["foo","biz"]', - ], - [ - 'backend_id' => 'backend3', - 'resource_id' => 'res7', - 'displayname' => 'Resource4', - 'email' => 'res7@foo.bar', - 'group_restrictions' => '["biz"]', - ], - [ - 'backend_id' => 'backend4', - 'resource_id' => 'res8', - 'displayname' => 'Beamer', - 'email' => 'res8@foo.bar', - 'group_restrictions' => '[]', - ], - [ - 'backend_id' => 'backend4', - 'resource_id' => 'res9', - 'displayname' => 'Beamer2', - 'email' => 'res9@foo.bar', - 'group_restrictions' => '[]', - ], - ], $rows); - - $query2 = self::$realDatabase->getQueryBuilder(); - $query2->select('*')->from('calendar_resources_md'); - - $rows2 = []; - $stmt = $query2->execute(); - while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { - unset($row['id']); - $rows2[] = $row; - } - - $this->assertEquals([ - [ - 'resource_id' => $ids['backend2::res3'], - 'key' => 'meta1', - 'value' => 'value1', - ], - [ - 'resource_id' => $ids['backend2::res3'], - 'key' => 'meta2', - 'value' => 'value2', - ], - [ - 'resource_id' => $ids['backend2::res4'], - 'key' => 'meta1', - 'value' => 'value1', - ], - [ - 'resource_id' => $ids['backend2::res4'], - 'key' => 'meta3', - 'value' => 'value3-old', - ], - [ - 'resource_id' => $ids['backend3::res6'], - 'key' => 'meta99', - 'value' => 'value99-new', - ], - [ - 'resource_id' => $ids['backend3::res7'], - 'key' => 'meta1', - 'value' => 'value1', - ], - [ - 'resource_id' => $ids['backend3::res6'], - 'key' => 'meta123', - 'value' => 'meta456', - ], - [ - 'resource_id' => $ids['backend4::res8'], - 'key' => 'meta2', - 'value' => 'value2', - ] - ], $rows2); - } - - protected function createTestResourcesInCache() { - $query = self::$realDatabase->getQueryBuilder(); - $query->insert('calendar_resources') - ->values([ - 'backend_id' => $query->createNamedParameter('backend1'), - 'resource_id' => $query->createNamedParameter('res1'), - 'email' => $query->createNamedParameter('res1@foo.bar'), - 'displayname' => $query->createNamedParameter('Beamer1'), - 'group_restrictions' => $query->createNamedParameter('[]'), - ]) - ->execute(); - - $query->insert('calendar_resources') - ->values([ - 'backend_id' => $query->createNamedParameter('backend1'), - 'resource_id' => $query->createNamedParameter('res2'), - 'email' => $query->createNamedParameter('res2@foo.bar'), - 'displayname' => $query->createNamedParameter('TV1'), - 'group_restrictions' => $query->createNamedParameter('[]'), - ]) - ->execute(); - - $query->insert('calendar_resources') - ->values([ - 'backend_id' => $query->createNamedParameter('backend2'), - 'resource_id' => $query->createNamedParameter('res3'), - 'email' => $query->createNamedParameter('res3@foo.bar'), - 'displayname' => $query->createNamedParameter('Beamer2'), - 'group_restrictions' => $query->createNamedParameter('[]'), - ]) - ->execute(); - $id3 = $query->getLastInsertId(); - - $query->insert('calendar_resources') - ->values([ - 'backend_id' => $query->createNamedParameter('backend2'), - 'resource_id' => $query->createNamedParameter('res4'), - 'email' => $query->createNamedParameter('res4@foo.bar'), - 'displayname' => $query->createNamedParameter('TV2'), - 'group_restrictions' => $query->createNamedParameter('[]'), - ]) - ->execute(); - $id4 = $query->getLastInsertId(); - - $query->insert('calendar_resources') - ->values([ - 'backend_id' => $query->createNamedParameter('backend3'), - 'resource_id' => $query->createNamedParameter('res5'), - 'email' => $query->createNamedParameter('res5@foo.bar'), - 'displayname' => $query->createNamedParameter('Beamer3'), - 'group_restrictions' => $query->createNamedParameter('[]'), - ]) - ->execute(); - - $query->insert('calendar_resources') - ->values([ - 'backend_id' => $query->createNamedParameter('backend3'), - 'resource_id' => $query->createNamedParameter('res6'), - 'email' => $query->createNamedParameter('res6@foo.bar'), - 'displayname' => $query->createNamedParameter('Pointer'), - 'group_restrictions' => $query->createNamedParameter('["foo", "bar"]'), - ]) - ->execute(); - $id6 = $query->getLastInsertId(); - - $query->insert('calendar_resources_md') - ->values([ - 'resource_id' => $query->createNamedParameter($id3), - 'key' => $query->createNamedParameter('meta1'), - 'value' => $query->createNamedParameter('value1') - ]) - ->execute(); - $query->insert('calendar_resources_md') - ->values([ - 'resource_id' => $query->createNamedParameter($id3), - 'key' => $query->createNamedParameter('meta2'), - 'value' => $query->createNamedParameter('value2') - ]) - ->execute(); - $query->insert('calendar_resources_md') - ->values([ - 'resource_id' => $query->createNamedParameter($id4), - 'key' => $query->createNamedParameter('meta1'), - 'value' => $query->createNamedParameter('value1') - ]) - ->execute(); - $query->insert('calendar_resources_md') - ->values([ - 'resource_id' => $query->createNamedParameter($id4), - 'key' => $query->createNamedParameter('meta3'), - 'value' => $query->createNamedParameter('value3-old') - ]) - ->execute(); - $query->insert('calendar_resources_md') - ->values([ - 'resource_id' => $query->createNamedParameter($id6), - 'key' => $query->createNamedParameter('meta99'), - 'value' => $query->createNamedParameter('value99') - ]) - ->execute(); } } diff --git a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php index fb3f9255f1a..d49d20180d9 100644 --- a/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php +++ b/apps/dav/tests/unit/BackgroundJob/UserStatusAutomationTest.php @@ -14,8 +14,10 @@ use OCA\DAV\BackgroundJob\UserStatusAutomation; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; +use OCP\Server; use OCP\User\IAvailabilityCoordinator; use OCP\UserStatus\IManager; use OCP\UserStatus\IUserStatus; @@ -27,14 +29,13 @@ use Test\TestCase; * @group DB */ class UserStatusAutomationTest extends TestCase { - - protected MockObject|ITimeFactory $time; - protected MockObject|IJobList $jobList; - protected MockObject|LoggerInterface $logger; - protected MockObject|IManager $statusManager; - protected MockObject|IConfig $config; - private IAvailabilityCoordinator|MockObject $coordinator; - private IUserManager|MockObject $userManager; + protected ITimeFactory&MockObject $time; + protected IJobList&MockObject $jobList; + protected LoggerInterface&MockObject $logger; + protected IManager&MockObject $statusManager; + protected IConfig&MockObject $config; + private IAvailabilityCoordinator&MockObject $coordinator; + private IUserManager&MockObject $userManager; protected function setUp(): void { parent::setUp(); @@ -53,7 +54,7 @@ class UserStatusAutomationTest extends TestCase { if (empty($methods)) { return new UserStatusAutomation( $this->time, - \OC::$server->getDatabaseConnection(), + Server::get(IDBConnection::class), $this->jobList, $this->logger, $this->statusManager, @@ -66,7 +67,7 @@ class UserStatusAutomationTest extends TestCase { return $this->getMockBuilder(UserStatusAutomation::class) ->setConstructorArgs([ $this->time, - \OC::$server->getDatabaseConnection(), + Server::get(IDBConnection::class), $this->jobList, $this->logger, $this->statusManager, @@ -74,11 +75,11 @@ class UserStatusAutomationTest extends TestCase { $this->coordinator, $this->userManager, ]) - ->setMethods($methods) + ->onlyMethods($methods) ->getMock(); } - public function dataRun(): array { + public static function dataRun(): array { return [ ['20230217', '2023-02-24 10:49:36.613834', true], ['20230224', '2023-02-24 10:49:36.613834', true], @@ -87,9 +88,7 @@ class UserStatusAutomationTest extends TestCase { ]; } - /** - * @dataProvider dataRun - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataRun')] public function testRunNoOOO(string $ruleDay, string $currentTime, bool $isAvailable): void { $user = $this->createConfiguredMock(IUser::class, [ 'getUID' => 'user' @@ -108,8 +107,6 @@ class UserStatusAutomationTest extends TestCase { ->willReturn(new \DateTime($currentTime, new \DateTimeZone('UTC'))); $this->logger->expects(self::exactly(4)) ->method('debug'); - $this->statusManager->expects(self::exactly(2)) - ->method('revertUserStatus'); if (!$isAvailable) { $this->statusManager->expects(self::once()) ->method('setUserStatus') @@ -172,8 +169,6 @@ END:VCALENDAR'); ->willReturn('yes'); $this->time->method('getDateTime') ->willReturn(new \DateTime('2023-02-24 13:58:24.479357', new \DateTimeZone('UTC'))); - $this->statusManager->expects($this->exactly(3)) - ->method('revertUserStatus'); $this->jobList->expects($this->once()) ->method('remove') ->with(UserStatusAutomation::class, ['userId' => 'user']); @@ -207,8 +202,6 @@ END:VCALENDAR'); $this->coordinator->expects(self::once()) ->method('isInEffect') ->willReturn(true); - $this->statusManager->expects($this->exactly(2)) - ->method('revertUserStatus'); $this->statusManager->expects(self::once()) ->method('setUserStatus') ->with('user', IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND, true, $ooo->getShortMessage()); diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php index de0684d1320..45937d86873 100644 --- a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackend.php @@ -25,6 +25,7 @@ use OCP\IUserManager; use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; +use OCP\Server; use OCP\Share\IManager as ShareManager; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; @@ -43,12 +44,12 @@ abstract class AbstractCalDavBackend extends TestCase { protected CalDavBackend $backend; - protected Principal|MockObject $principal; - protected IUserManager|MockObject $userManager; - protected IGroupManager|MockObject $groupManager; - protected IEventDispatcher|MockObject $dispatcher; - private LoggerInterface|MockObject $logger; - private IConfig|MockObject $config; + protected Principal&MockObject $principal; + protected IUserManager&MockObject $userManager; + protected IGroupManager&MockObject $groupManager; + protected IEventDispatcher&MockObject $dispatcher; + private LoggerInterface&MockObject $logger; + private IConfig&MockObject $config; private ISecureRandom $random; protected SharingBackend $sharingBackend; protected IDBConnection $db; @@ -76,7 +77,7 @@ abstract class AbstractCalDavBackend extends TestCase { $this->createMock(IConfig::class), $this->createMock(IFactory::class) ]) - ->setMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri']) + ->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri']) ->getMock(); $this->principal->expects($this->any())->method('getPrincipalByPath') ->willReturn([ @@ -87,8 +88,8 @@ abstract class AbstractCalDavBackend extends TestCase { ->withAnyParameters() ->willReturn([self::UNIT_TEST_GROUP, self::UNIT_TEST_GROUP2]); - $this->db = \OC::$server->getDatabaseConnection(); - $this->random = \OC::$server->getSecureRandom(); + $this->db = Server::get(IDBConnection::class); + $this->random = Server::get(ISecureRandom::class); $this->logger = $this->createMock(LoggerInterface::class); $this->config = $this->createMock(IConfig::class); $this->sharingBackend = new SharingBackend( @@ -142,7 +143,7 @@ abstract class AbstractCalDavBackend extends TestCase { } } - protected function createTestCalendar() { + protected function createTestCalendar(): int { $this->dispatcher->expects(self::any()) ->method('dispatchTyped'); @@ -154,14 +155,12 @@ abstract class AbstractCalDavBackend extends TestCase { $this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']); /** @var SupportedCalendarComponentSet $components */ $components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']; - $this->assertEquals(['VEVENT','VTODO'], $components->getValue()); + $this->assertEquals(['VEVENT','VTODO','VJOURNAL'], $components->getValue()); $color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color']; $this->assertEquals('#1C4587FF', $color); $this->assertEquals('Example', $calendars[0]['uri']); $this->assertEquals('Example', $calendars[0]['{DAV:}displayname']); - $calendarId = $calendars[0]['id']; - - return $calendarId; + return (int)$calendars[0]['id']; } protected function createTestSubscription() { @@ -207,6 +206,33 @@ EOD; return $uri0; } + protected function modifyEvent($calendarId, $objectId, $start = '20130912T130000Z', $end = '20130912T140000Z') { + $randomPart = self::getUniqueID(); + + $calData = <<<EOD +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:ownCloud Calendar +BEGIN:VEVENT +CREATED;VALUE=DATE-TIME:20130910T125139Z +UID:47d15e3ec8-$randomPart +LAST-MODIFIED;VALUE=DATE-TIME:20130910T125139Z +DTSTAMP;VALUE=DATE-TIME:20130910T125139Z +SUMMARY:Test Event +DTSTART;VALUE=DATE-TIME:$start +DTEND;VALUE=DATE-TIME:$end +CLASS:PUBLIC +END:VEVENT +END:VCALENDAR +EOD; + + $this->backend->updateCalendarObject($calendarId, $objectId, $calData); + } + + protected function deleteEvent($calendarId, $objectId) { + $this->backend->deleteCalendarObject($calendarId, $objectId); + } + protected function assertAcl($principal, $privilege, $acl) { foreach ($acl as $a) { if ($a['principal'] === $principal && $a['privilege'] === $privilege) { diff --git a/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php b/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php index 6ace633b072..4848a01f6b9 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/BackendTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -19,21 +21,11 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class BackendTest extends TestCase { - - /** @var IManager|MockObject */ - protected $activityManager; - - /** @var IGroupManager|MockObject */ - protected $groupManager; - - /** @var IUserSession|MockObject */ - protected $userSession; - - /** @var IAppManager|MockObject */ - protected $appManager; - - /** @var IUserManager|MockObject */ - protected $userManager; + protected IManager&MockObject $activityManager; + protected IGroupManager&MockObject $groupManager; + protected IUserSession&MockObject $userSession; + protected IAppManager&MockObject $appManager; + protected IUserManager&MockObject $userManager; protected function setUp(): void { parent::setUp(); @@ -45,10 +37,9 @@ class BackendTest extends TestCase { } /** - * @param array $methods - * @return Backend|MockObject + * @return Backend|(Backend&MockObject) */ - protected function getBackend(array $methods = []) { + protected function getBackend(array $methods = []): Backend { if (empty($methods)) { return new Backend( $this->activityManager, @@ -71,7 +62,7 @@ class BackendTest extends TestCase { } } - public function dataCallTriggerCalendarActivity() { + public static function dataCallTriggerCalendarActivity(): array { return [ ['onCalendarAdd', [['data']], Calendar::SUBJECT_ADD, [['data'], [], []]], ['onCalendarUpdate', [['data'], ['shares'], ['changed-properties']], Calendar::SUBJECT_UPDATE, [['data'], ['shares'], ['changed-properties']]], @@ -80,15 +71,8 @@ class BackendTest extends TestCase { ]; } - /** - * @dataProvider dataCallTriggerCalendarActivity - * - * @param string $method - * @param array $payload - * @param string $expectedSubject - * @param array $expectedPayload - */ - public function testCallTriggerCalendarActivity($method, array $payload, $expectedSubject, array $expectedPayload): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataCallTriggerCalendarActivity')] + public function testCallTriggerCalendarActivity(string $method, array $payload, string $expectedSubject, array $expectedPayload): void { $backend = $this->getBackend(['triggerCalendarActivity']); $backend->expects($this->once()) ->method('triggerCalendarActivity') @@ -101,7 +85,7 @@ class BackendTest extends TestCase { call_user_func_array([$backend, $method], $payload); } - public function dataTriggerCalendarActivity() { + public static function dataTriggerCalendarActivity(): array { return [ // Add calendar [Calendar::SUBJECT_ADD, [], [], [], '', '', null, []], @@ -182,18 +166,8 @@ class BackendTest extends TestCase { ]; } - /** - * @dataProvider dataTriggerCalendarActivity - * @param string $action - * @param array $data - * @param array $shares - * @param array $changedProperties - * @param string $currentUser - * @param string $author - * @param string[]|null $shareUsers - * @param string[] $users - */ - public function testTriggerCalendarActivity($action, array $data, array $shares, array $changedProperties, $currentUser, $author, $shareUsers, array $users): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataTriggerCalendarActivity')] + public function testTriggerCalendarActivity(string $action, array $data, array $shares, array $changedProperties, string $currentUser, string $author, ?array $shareUsers, array $users): void { $backend = $this->getBackend(['getUsersForShares']); if ($shareUsers === null) { @@ -278,7 +252,7 @@ class BackendTest extends TestCase { ], [], []]); } - public function dataGetUsersForShares() { + public static function dataGetUsersForShares(): array { return [ [ [], @@ -321,12 +295,7 @@ class BackendTest extends TestCase { ]; } - /** - * @dataProvider dataGetUsersForShares - * @param array $shares - * @param array $groups - * @param array $expected - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetUsersForShares')] public function testGetUsersForShares(array $shares, array $groups, array $expected): void { $backend = $this->getBackend(); @@ -356,7 +325,7 @@ class BackendTest extends TestCase { /** * @param string[] $users - * @return IUser[]|MockObject[] + * @return IUser[]&MockObject[] */ protected function getUsers(array $users) { $list = []; @@ -368,7 +337,7 @@ class BackendTest extends TestCase { /** * @param string $uid - * @return IUser|MockObject + * @return IUser&MockObject */ protected function getUserMock($uid) { $user = $this->createMock(IUser::class); diff --git a/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php b/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php index a7c84260f21..b4c4e14fe7d 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Filter/CalendarTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,15 +11,12 @@ use OCA\DAV\CalDAV\Activity\Filter\Calendar; use OCP\Activity\IFilter; use OCP\IL10N; use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class CalendarTest extends TestCase { - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $url; - - /** @var IFilter|\PHPUnit\Framework\MockObject\MockObject */ - protected $filter; + protected IURLGenerator&MockObject $url; + protected IFilter $filter; protected function setUp(): void { parent::setUp(); @@ -48,7 +47,7 @@ class CalendarTest extends TestCase { $this->assertEquals('absolute-path-to-icon', $this->filter->getIcon()); } - public function dataFilterTypes() { + public static function dataFilterTypes(): array { return [ [[], []], [['calendar', 'calendar_event'], ['calendar', 'calendar_event']], @@ -58,11 +57,11 @@ class CalendarTest extends TestCase { } /** - * @dataProvider dataFilterTypes * @param string[] $types * @param string[] $expected */ - public function testFilterTypes($types, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilterTypes')] + public function testFilterTypes(array $types, array $expected): void { $this->assertEquals($expected, $this->filter->filterTypes($types)); } } diff --git a/apps/dav/tests/unit/CalDAV/Activity/Filter/GenericTest.php b/apps/dav/tests/unit/CalDAV/Activity/Filter/GenericTest.php index fcfd1f81517..87b55f14bcc 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Filter/GenericTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Filter/GenericTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -8,89 +10,69 @@ namespace OCA\DAV\Tests\unit\CalDAV\Activity\Filter; use OCA\DAV\CalDAV\Activity\Filter\Calendar; use OCA\DAV\CalDAV\Activity\Filter\Todo; use OCP\Activity\IFilter; +use OCP\Server; use Test\TestCase; /** * @group DB */ class GenericTest extends TestCase { - public function dataFilters() { + public static function dataFilters(): array { return [ [Calendar::class], [Todo::class], ]; } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testImplementsInterface($filterClass): void { - $filter = \OC::$server->query($filterClass); + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testImplementsInterface(string $filterClass): void { + $filter = Server::get($filterClass); $this->assertInstanceOf(IFilter::class, $filter); } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testGetIdentifier($filterClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testGetIdentifier(string $filterClass): void { /** @var IFilter $filter */ - $filter = \OC::$server->query($filterClass); + $filter = Server::get($filterClass); $this->assertIsString($filter->getIdentifier()); } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testGetName($filterClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testGetName(string $filterClass): void { /** @var IFilter $filter */ - $filter = \OC::$server->query($filterClass); + $filter = Server::get($filterClass); $this->assertIsString($filter->getName()); } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testGetPriority($filterClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testGetPriority(string $filterClass): void { /** @var IFilter $filter */ - $filter = \OC::$server->query($filterClass); + $filter = Server::get($filterClass); $priority = $filter->getPriority(); $this->assertIsInt($filter->getPriority()); $this->assertGreaterThanOrEqual(0, $priority); $this->assertLessThanOrEqual(100, $priority); } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testGetIcon($filterClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testGetIcon(string $filterClass): void { /** @var IFilter $filter */ - $filter = \OC::$server->query($filterClass); + $filter = Server::get($filterClass); $this->assertIsString($filter->getIcon()); $this->assertStringStartsWith('http', $filter->getIcon()); } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testFilterTypes($filterClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testFilterTypes(string $filterClass): void { /** @var IFilter $filter */ - $filter = \OC::$server->query($filterClass); + $filter = Server::get($filterClass); $this->assertIsArray($filter->filterTypes([])); } - /** - * @dataProvider dataFilters - * @param string $filterClass - */ - public function testAllowedApps($filterClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilters')] + public function testAllowedApps(string $filterClass): void { /** @var IFilter $filter */ - $filter = \OC::$server->query($filterClass); + $filter = Server::get($filterClass); $this->assertIsArray($filter->allowedApps()); } } diff --git a/apps/dav/tests/unit/CalDAV/Activity/Filter/TodoTest.php b/apps/dav/tests/unit/CalDAV/Activity/Filter/TodoTest.php index 6aa47f33750..f18d66b9774 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Filter/TodoTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Filter/TodoTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,15 +11,12 @@ use OCA\DAV\CalDAV\Activity\Filter\Todo; use OCP\Activity\IFilter; use OCP\IL10N; use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class TodoTest extends TestCase { - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $url; - - /** @var IFilter|\PHPUnit\Framework\MockObject\MockObject */ - protected $filter; + protected IURLGenerator&MockObject $url; + protected IFilter $filter; protected function setUp(): void { parent::setUp(); @@ -48,7 +47,7 @@ class TodoTest extends TestCase { $this->assertEquals('absolute-path-to-icon', $this->filter->getIcon()); } - public function dataFilterTypes() { + public static function dataFilterTypes(): array { return [ [[], []], [['calendar_todo'], ['calendar_todo']], @@ -58,11 +57,11 @@ class TodoTest extends TestCase { } /** - * @dataProvider dataFilterTypes * @param string[] $types * @param string[] $expected */ - public function testFilterTypes($types, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataFilterTypes')] + public function testFilterTypes(array $types, array $expected): void { $this->assertEquals($expected, $this->filter->filterTypes($types)); } } diff --git a/apps/dav/tests/unit/CalDAV/Activity/Provider/BaseTest.php b/apps/dav/tests/unit/CalDAV/Activity/Provider/BaseTest.php index ba97c888b0c..3e6219beef8 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Provider/BaseTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Provider/BaseTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,7 +9,6 @@ namespace OCA\DAV\Tests\unit\CalDAV\Activity\Provider; use OCA\DAV\CalDAV\Activity\Provider\Base; use OCP\Activity\IEvent; -use OCP\Activity\IProvider; use OCP\IGroupManager; use OCP\IL10N; use OCP\IURLGenerator; @@ -16,17 +17,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class BaseTest extends TestCase { - /** @var IUserManager|MockObject */ - protected $userManager; - - /** @var IGroupManager|MockObject */ - protected $groupManager; - - /** @var IURLGenerator|MockObject */ - protected $url; - - /** @var IProvider|Base|MockObject */ - protected $provider; + protected IUserManager&MockObject $userManager; + protected IGroupManager&MockObject $groupManager; + protected IURLGenerator&MockObject $url; + protected Base&MockObject $provider; protected function setUp(): void { parent::setUp(); @@ -39,24 +33,19 @@ class BaseTest extends TestCase { $this->groupManager, $this->url, ]) - ->setMethods(['parse']) + ->onlyMethods(['parse']) ->getMock(); } - public function dataSetSubjects() { + public static function dataSetSubjects(): array { return [ - ['abc', [], 'abc'], - ['{actor} created {calendar}', ['actor' => ['name' => 'abc'], 'calendar' => ['name' => 'xyz']], 'abc created xyz'], + ['abc', []], + ['{actor} created {calendar}', ['actor' => ['name' => 'abc'], 'calendar' => ['name' => 'xyz']]], ]; } - /** - * @dataProvider dataSetSubjects - * @param string $subject - * @param array $parameters - * @param string $parsedSubject - */ - public function testSetSubjects(string $subject, array $parameters, string $parsedSubject): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSetSubjects')] + public function testSetSubjects(string $subject, array $parameters): void { $event = $this->createMock(IEvent::class); $event->expects($this->once()) ->method('setRichSubject') @@ -68,7 +57,7 @@ class BaseTest extends TestCase { $this->invokePrivate($this->provider, 'setSubjects', [$event, $subject, $parameters]); } - public function dataGenerateCalendarParameter() { + public static function dataGenerateCalendarParameter(): array { return [ [['id' => 23, 'uri' => 'foo', 'name' => 'bar'], 'bar'], [['id' => 42, 'uri' => 'foo', 'name' => 'Personal'], 'Personal'], @@ -77,11 +66,7 @@ class BaseTest extends TestCase { ]; } - /** - * @dataProvider dataGenerateCalendarParameter - * @param array $data - * @param string $name - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGenerateCalendarParameter')] public function testGenerateCalendarParameter(array $data, string $name): void { $l = $this->createMock(IL10N::class); $l->expects($this->any()) @@ -97,18 +82,14 @@ class BaseTest extends TestCase { ], $this->invokePrivate($this->provider, 'generateCalendarParameter', [$data, $l])); } - public function dataGenerateLegacyCalendarParameter() { + public static function dataGenerateLegacyCalendarParameter(): array { return [ [23, 'c1'], [42, 'c2'], ]; } - /** - * @dataProvider dataGenerateLegacyCalendarParameter - * @param int $id - * @param string $name - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGenerateLegacyCalendarParameter')] public function testGenerateLegacyCalendarParameter(int $id, string $name): void { $this->assertEquals([ 'type' => 'calendar', @@ -117,17 +98,14 @@ class BaseTest extends TestCase { ], $this->invokePrivate($this->provider, 'generateLegacyCalendarParameter', [$id, $name])); } - public function dataGenerateGroupParameter() { + public static function dataGenerateGroupParameter(): array { return [ ['g1'], ['g2'], ]; } - /** - * @dataProvider dataGenerateGroupParameter - * @param string $gid - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGenerateGroupParameter')] public function testGenerateGroupParameter(string $gid): void { $this->assertEquals([ 'type' => 'user-group', diff --git a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php index b0b2cc936a5..4fd38c1aed2 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Provider/EventTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -6,11 +8,9 @@ namespace OCA\DAV\Tests\unit\CalDAV\Activity\Provider; use InvalidArgumentException; -use OCA\DAV\CalDAV\Activity\Provider\Base; use OCA\DAV\CalDAV\Activity\Provider\Event; use OCP\Activity\IEventMerger; use OCP\Activity\IManager; -use OCP\Activity\IProvider; use OCP\App\IAppManager; use OCP\IGroupManager; use OCP\IURLGenerator; @@ -21,30 +21,14 @@ use Test\TestCase; use TypeError; class EventTest extends TestCase { - - /** @var IUserManager|MockObject */ - protected $userManager; - - /** @var IGroupManager|MockObject */ - protected $groupManager; - - /** @var IURLGenerator|MockObject */ - protected $url; - - /** @var IProvider|Base|MockObject */ - protected $provider; - - /** @var IAppManager|MockObject */ - protected $appManager; - - /** @var IFactory|MockObject */ - protected $i10nFactory; - - /** @var IManager|MockObject */ - protected $activityManager; - - /** @var IEventMerger|MockObject */ - protected $eventMerger; + protected IUserManager&MockObject $userManager; + protected IGroupManager&MockObject $groupManager; + protected IURLGenerator&MockObject $url; + protected IAppManager&MockObject $appManager; + protected IFactory&MockObject $i10nFactory; + protected IManager&MockObject $activityManager; + protected IEventMerger&MockObject $eventMerger; + protected Event&MockObject $provider; protected function setUp(): void { parent::setUp(); @@ -65,11 +49,11 @@ class EventTest extends TestCase { $this->eventMerger, $this->appManager ]) - ->setMethods(['parse']) + ->onlyMethods(['parse']) ->getMock(); } - public function dataGenerateObjectParameter() { + public static function dataGenerateObjectParameter(): array { $link = [ 'object_uri' => 'someuuid.ics', 'calendar_uri' => 'personal', @@ -83,23 +67,13 @@ class EventTest extends TestCase { ]; } - /** - * @dataProvider dataGenerateObjectParameter - * @param int $id - * @param string $name - * @param array|null $link - * @param bool $calendarAppEnabled - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGenerateObjectParameter')] public function testGenerateObjectParameter(int $id, string $name, ?array $link, bool $calendarAppEnabled = true): void { $affectedUser = 'otheruser'; if ($link) { $affectedUser = $link['owner']; $generatedLink = [ - 'view' => 'dayGridMonth', - 'timeRange' => 'now', - 'mode' => 'sidebar', 'objectId' => base64_encode('/remote.php/dav/calendars/' . $link['owner'] . '/' . $link['calendar_uri'] . '/' . $link['object_uri']), - 'recurrenceId' => 'next' ]; $this->appManager->expects($this->once()) ->method('isEnabledForUser') @@ -110,7 +84,7 @@ class EventTest extends TestCase { ->method('getWebroot'); $this->url->expects($this->once()) ->method('linkToRouteAbsolute') - ->with('calendar.view.indexview.timerange.edit', $generatedLink) + ->with('calendar.view.indexdirect.edit', $generatedLink) ->willReturn('fullLink'); } } @@ -129,18 +103,55 @@ class EventTest extends TestCase { $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, $affectedUser])); } - public function testGenerateObjectParameterWithSharedCalendar(): void { - $link = [ - 'object_uri' => 'someuuid.ics', - 'calendar_uri' => 'personal', - 'owner' => 'sharer' + public static function generateObjectParameterLinkEncodingDataProvider(): array { + return [ + [ // Shared calendar + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'personal', + 'owner' => 'sharer' + ], + base64_encode('/remote.php/dav/calendars/sharee/personal_shared_by_sharer/someuuid.ics'), + ], + [ // Shared calendar with umlauts + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'umlaut_äüöß', + 'owner' => 'sharer' + ], + base64_encode('/remote.php/dav/calendars/sharee/umlaut_%c3%a4%c3%bc%c3%b6%c3%9f_shared_by_sharer/someuuid.ics'), + ], + [ // Shared calendar with umlauts and mixed casing + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'Umlaut_äüöß', + 'owner' => 'sharer' + ], + base64_encode('/remote.php/dav/calendars/sharee/Umlaut_%c3%a4%c3%bc%c3%b6%c3%9f_shared_by_sharer/someuuid.ics'), + ], + [ // Owned calendar with umlauts + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'umlaut_äüöß', + 'owner' => 'sharee' + ], + base64_encode('/remote.php/dav/calendars/sharee/umlaut_%c3%a4%c3%bc%c3%b6%c3%9f/someuuid.ics'), + ], + [ // Owned calendar with umlauts and mixed casing + [ + 'object_uri' => 'someuuid.ics', + 'calendar_uri' => 'Umlaut_äüöß', + 'owner' => 'sharee' + ], + base64_encode('/remote.php/dav/calendars/sharee/Umlaut_%c3%a4%c3%bc%c3%b6%c3%9f/someuuid.ics'), + ], ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('generateObjectParameterLinkEncodingDataProvider')] + public function testGenerateObjectParameterLinkEncoding(array $link, string $objectId): void { $generatedLink = [ - 'view' => 'dayGridMonth', - 'timeRange' => 'now', - 'mode' => 'sidebar', - 'objectId' => base64_encode('/remote.php/dav/calendars/sharee/' . $link['calendar_uri'] . '_shared_by_sharer/' . $link['object_uri']), - 'recurrenceId' => 'next' + 'objectId' => $objectId, ]; $this->appManager->expects($this->once()) ->method('isEnabledForUser') @@ -150,7 +161,7 @@ class EventTest extends TestCase { ->method('getWebroot'); $this->url->expects($this->once()) ->method('linkToRouteAbsolute') - ->with('calendar.view.indexview.timerange.edit', $generatedLink) + ->with('calendar.view.indexdirect.edit', $generatedLink) ->willReturn('fullLink'); $objectParameter = ['id' => 42, 'name' => 'calendar', 'link' => $link]; $result = [ @@ -162,7 +173,7 @@ class EventTest extends TestCase { $this->assertEquals($result, $this->invokePrivate($this->provider, 'generateObjectParameter', [$objectParameter, 'sharee'])); } - public function dataGenerateObjectParameterThrows() { + public static function dataGenerateObjectParameterThrows(): array { return [ ['event', TypeError::class], [['name' => 'event']], @@ -170,12 +181,8 @@ class EventTest extends TestCase { ]; } - /** - * @dataProvider dataGenerateObjectParameterThrows - * @param mixed $eventData - * @param string $exception - */ - public function testGenerateObjectParameterThrows($eventData, string $exception = InvalidArgumentException::class): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataGenerateObjectParameterThrows')] + public function testGenerateObjectParameterThrows(string|array $eventData, string $exception = InvalidArgumentException::class): void { $this->expectException($exception); $this->invokePrivate($this->provider, 'generateObjectParameter', [$eventData, 'no_user']); diff --git a/apps/dav/tests/unit/CalDAV/Activity/Setting/GenericTest.php b/apps/dav/tests/unit/CalDAV/Activity/Setting/GenericTest.php index 678b815999c..23126b6bbcf 100644 --- a/apps/dav/tests/unit/CalDAV/Activity/Setting/GenericTest.php +++ b/apps/dav/tests/unit/CalDAV/Activity/Setting/GenericTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,10 +11,11 @@ use OCA\DAV\CalDAV\Activity\Setting\Calendar; use OCA\DAV\CalDAV\Activity\Setting\Event; use OCA\DAV\CalDAV\Activity\Setting\Todo; use OCP\Activity\ISetting; +use OCP\Server; use Test\TestCase; class GenericTest extends TestCase { - public function dataSettings() { + public static function dataSettings(): array { return [ [Calendar::class], [Event::class], @@ -20,85 +23,61 @@ class GenericTest extends TestCase { ]; } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testImplementsInterface($settingClass): void { - $setting = \OC::$server->query($settingClass); + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testImplementsInterface(string $settingClass): void { + $setting = Server::get($settingClass); $this->assertInstanceOf(ISetting::class, $setting); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testGetIdentifier($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testGetIdentifier(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $this->assertIsString($setting->getIdentifier()); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testGetName($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testGetName(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $this->assertIsString($setting->getName()); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testGetPriority($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testGetPriority(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $priority = $setting->getPriority(); $this->assertIsInt($setting->getPriority()); $this->assertGreaterThanOrEqual(0, $priority); $this->assertLessThanOrEqual(100, $priority); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testCanChangeStream($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testCanChangeStream(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $this->assertIsBool($setting->canChangeStream()); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testIsDefaultEnabledStream($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testIsDefaultEnabledStream(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $this->assertIsBool($setting->isDefaultEnabledStream()); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testCanChangeMail($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testCanChangeMail(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $this->assertIsBool($setting->canChangeMail()); } - /** - * @dataProvider dataSettings - * @param string $settingClass - */ - public function testIsDefaultEnabledMail($settingClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataSettings')] + public function testIsDefaultEnabledMail(string $settingClass): void { /** @var ISetting $setting */ - $setting = \OC::$server->query($settingClass); + $setting = Server::get($settingClass); $this->assertIsBool($setting->isDefaultEnabledMail()); } } diff --git a/apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php b/apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php index b8983350348..84879e87238 100644 --- a/apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/AppCalendar/AppCalendarTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -16,13 +17,13 @@ use Test\TestCase; use function rewind; class AppCalendarTest extends TestCase { - private $principal = 'principals/users/foo'; + private string $principal = 'principals/users/foo'; private AppCalendar $appCalendar; private AppCalendar $writeableAppCalendar; - private ICalendar|MockObject $calendar; - private ICalendar|MockObject $writeableCalendar; + private ICalendar&MockObject $calendar; + private ICalendar&MockObject $writeableCalendar; protected function setUp(): void { parent::setUp(); @@ -52,10 +53,18 @@ class AppCalendarTest extends TestCase { $this->appCalendar->delete(); } - public function testCreateFile() { + public function testCreateFile(): void { + $calls = [ + ['some-name', 'data'], + ['other-name', ''], + ['name', 'some data'], + ]; $this->writeableCalendar->expects($this->exactly(3)) ->method('createFromString') - ->withConsecutive(['some-name', 'data'], ['other-name', ''], ['name', 'some data']); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); // pass data $this->assertNull($this->writeableAppCalendar->createFile('some-name', 'data')); @@ -69,7 +78,7 @@ class AppCalendarTest extends TestCase { fclose($fp); } - public function testCreateFile_readOnly() { + public function testCreateFile_readOnly(): void { // If writing is not supported $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->expectExceptionMessage('Creating a new entry is not allowed'); diff --git a/apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php b/apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php index 92288f5dfb0..3d72d5c97b8 100644 --- a/apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php +++ b/apps/dav/tests/unit/CalDAV/AppCalendar/CalendarObjectTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -17,9 +18,9 @@ use Test\TestCase; class CalendarObjectTest extends TestCase { private CalendarObject $calendarObject; - private AppCalendar|MockObject $calendar; - private ICalendar|MockObject $backend; - private VCalendar|MockObject $vobject; + private AppCalendar&MockObject $calendar; + private ICalendar&MockObject $backend; + private VCalendar&MockObject $vobject; protected function setUp(): void { parent::setUp(); @@ -33,15 +34,15 @@ class CalendarObjectTest extends TestCase { $this->calendarObject = new CalendarObject($this->calendar, $this->backend, $this->vobject); } - public function testGetOwner() { + public function testGetOwner(): void { $this->assertEquals($this->calendarObject->getOwner(), 'owner'); } - public function testGetGroup() { + public function testGetGroup(): void { $this->assertEquals($this->calendarObject->getGroup(), 'group'); } - public function testGetACL() { + public function testGetACL(): void { $this->calendar->expects($this->exactly(2)) ->method('getPermissions') ->willReturnOnConsecutiveCalls(Constants::PERMISSION_READ, Constants::PERMISSION_ALL); @@ -70,17 +71,17 @@ class CalendarObjectTest extends TestCase { ]); } - public function testSetACL() { + public function testSetACL(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->calendarObject->setACL([]); } - public function testPut_readOnlyBackend() { + public function testPut_readOnlyBackend(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->calendarObject->put('foo'); } - public function testPut_noPermissions() { + public function testPut_noPermissions(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $backend = $this->createMock(ICreateFromString::class); @@ -93,7 +94,7 @@ class CalendarObjectTest extends TestCase { $calendarObject->put('foo'); } - public function testPut() { + public function testPut(): void { $backend = $this->createMock(ICreateFromString::class); $calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); @@ -110,19 +111,19 @@ class CalendarObjectTest extends TestCase { $calendarObject->put('foo'); } - public function testGet() { + public function testGet(): void { $this->vobject->expects($this->once()) ->method('serialize') ->willReturn('foo'); $this->assertEquals($this->calendarObject->get(), 'foo'); } - public function testDelete_notWriteable() { + public function testDelete_notWriteable(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->calendarObject->delete(); } - public function testDelete_noPermission() { + public function testDelete_noPermission(): void { $backend = $this->createMock(ICreateFromString::class); $calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); @@ -130,7 +131,7 @@ class CalendarObjectTest extends TestCase { $calendarObject->delete(); } - public function testDelete() { + public function testDelete(): void { $backend = $this->createMock(ICreateFromString::class); $calendarObject = new CalendarObject($this->calendar, $backend, $this->vobject); @@ -153,7 +154,7 @@ class CalendarObjectTest extends TestCase { $calendarObject->delete(); } - public function testGetName() { + public function testGetName(): void { $this->vobject->expects($this->exactly(2)) ->method('getBaseComponent') ->willReturnOnConsecutiveCalls((object)['UID' => 'someid'], (object)['UID' => 'someid', 'X-FILENAME' => 'real-filename.ics']); @@ -162,7 +163,7 @@ class CalendarObjectTest extends TestCase { $this->assertEquals($this->calendarObject->getName(), 'real-filename.ics'); } - public function testSetName() { + public function testSetName(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->calendarObject->setName('Some name'); } diff --git a/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php b/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php index e7de7d0a55e..a5811271ce2 100644 --- a/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php +++ b/apps/dav/tests/unit/CalDAV/BirthdayCalendar/EnablePluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -11,24 +13,15 @@ use OCA\DAV\CalDAV\Calendar; use OCA\DAV\CalDAV\CalendarHome; use OCP\IConfig; use OCP\IUser; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class EnablePluginTest extends TestCase { - - /** @var \Sabre\DAV\Server|\PHPUnit\Framework\MockObject\MockObject */ - protected $server; - - /** @var \OCP\IConfig|\PHPUnit\Framework\MockObject\MockObject */ - protected $config; - - /** @var BirthdayService |\PHPUnit\Framework\MockObject\MockObject */ - protected $birthdayService; - - /** @var IUser|\PHPUnit\Framework\MockObject\MockObject */ - protected $user; - - /** @var \OCA\DAV\CalDAV\BirthdayCalendar\EnablePlugin $plugin */ - protected $plugin; + protected \Sabre\DAV\Server&MockObject $server; + protected IConfig&MockObject $config; + protected BirthdayService&MockObject $birthdayService; + protected IUser&MockObject $user; + protected EnablePlugin $plugin; protected $request; diff --git a/apps/dav/tests/unit/CalDAV/CachedSubscriptionImplTest.php b/apps/dav/tests/unit/CalDAV/CachedSubscriptionImplTest.php index 2378a75a7d5..935d8314f29 100644 --- a/apps/dav/tests/unit/CalDAV/CachedSubscriptionImplTest.php +++ b/apps/dav/tests/unit/CalDAV/CachedSubscriptionImplTest.php @@ -12,13 +12,14 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\CachedSubscription; use OCA\DAV\CalDAV\CachedSubscriptionImpl; use OCA\DAV\CalDAV\CalDavBackend; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class CachedSubscriptionImplTest extends TestCase { - private CachedSubscription $cachedSubscription; + private CachedSubscription&MockObject $cachedSubscription; private array $cachedSubscriptionInfo; + private CalDavBackend&MockObject $backend; private CachedSubscriptionImpl $cachedSubscriptionImpl; - private CalDavBackend $backend; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/CachedSubscriptionObjectTest.php b/apps/dav/tests/unit/CalDAV/CachedSubscriptionObjectTest.php index 56e4930d3b3..03a2c9f20ee 100644 --- a/apps/dav/tests/unit/CalDAV/CachedSubscriptionObjectTest.php +++ b/apps/dav/tests/unit/CalDAV/CachedSubscriptionObjectTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -32,7 +34,7 @@ class CachedSubscriptionObjectTest extends \Test\TestCase { $this->assertEquals('BEGIN...', $calendarObject->get()); } - + public function testPut(): void { $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); $this->expectExceptionMessage('Creating objects in a cached subscription is not allowed'); @@ -52,7 +54,7 @@ class CachedSubscriptionObjectTest extends \Test\TestCase { $calendarObject->put(''); } - + public function testDelete(): void { $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); $this->expectExceptionMessage('Deleting objects in a cached subscription is not allowed'); diff --git a/apps/dav/tests/unit/CalDAV/CachedSubscriptionProviderTest.php b/apps/dav/tests/unit/CalDAV/CachedSubscriptionProviderTest.php index 22998eec3d9..58d5ca7835c 100644 --- a/apps/dav/tests/unit/CalDAV/CachedSubscriptionProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/CachedSubscriptionProviderTest.php @@ -12,11 +12,12 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\CachedSubscriptionImpl; use OCA\DAV\CalDAV\CachedSubscriptionProvider; use OCA\DAV\CalDAV\CalDavBackend; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class CachedSubscriptionProviderTest extends TestCase { - private CalDavBackend $backend; + private CalDavBackend&MockObject $backend; private CachedSubscriptionProvider $provider; protected function setUp(): void { @@ -47,7 +48,7 @@ class CachedSubscriptionProviderTest extends TestCase { $this->provider = new CachedSubscriptionProvider($this->backend); } - public function testGetCalendars() { + public function testGetCalendars(): void { $calendars = $this->provider->getCalendars( 'user-principal-123', [] @@ -58,7 +59,7 @@ class CachedSubscriptionProviderTest extends TestCase { $this->assertInstanceOf(CachedSubscriptionImpl::class, $calendars[1]); } - public function testGetCalendarsFilterByUri() { + public function testGetCalendarsFilterByUri(): void { $calendars = $this->provider->getCalendars( 'user-principal-123', ['subscription-1'] diff --git a/apps/dav/tests/unit/CalDAV/CachedSubscriptionTest.php b/apps/dav/tests/unit/CalDAV/CachedSubscriptionTest.php index e1d22bc3e7b..ba0da422290 100644 --- a/apps/dav/tests/unit/CalDAV/CachedSubscriptionTest.php +++ b/apps/dav/tests/unit/CalDAV/CachedSubscriptionTest.php @@ -1,4 +1,5 @@ <?php + /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -140,19 +141,21 @@ class CachedSubscriptionTest extends \Test\TestCase { 'uri' => 'cal', ]; + $calls = [ + [666, 'foo1', 1, [ + 'id' => 99, + 'uri' => 'foo1' + ]], + [666, 'foo2', 1, null], + ]; $backend->expects($this->exactly(2)) ->method('getCalendarObject') - ->withConsecutive( - [666, 'foo1', 1], - [666, 'foo2', 1], - ) - ->willReturnOnConsecutiveCalls( - [ - 'id' => 99, - 'uri' => 'foo1' - ], - null - ); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $return = array_pop($expected); + $this->assertEquals($expected, func_get_args()); + return $return; + }); $calendar = new CachedSubscription($backend, $calendarInfo); @@ -250,19 +253,21 @@ class CachedSubscriptionTest extends \Test\TestCase { 'uri' => 'cal', ]; + $calls = [ + [666, 'foo1', 1, [ + 'id' => 99, + 'uri' => 'foo1' + ]], + [666, 'foo2', 1, null], + ]; $backend->expects($this->exactly(2)) ->method('getCalendarObject') - ->withConsecutive( - [666, 'foo1', 1], - [666, 'foo2', 1], - ) - ->willReturnOnConsecutiveCalls( - [ - 'id' => 99, - 'uri' => 'foo1' - ], - null - ); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $return = array_pop($expected); + $this->assertEquals($expected, func_get_args()); + return $return; + }); $calendar = new CachedSubscription($backend, $calendarInfo); diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index 94d55336444..f9205d5d322 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -17,6 +19,7 @@ use OCA\DAV\DAV\Sharing\Plugin as SharingPlugin; use OCA\DAV\Events\CalendarDeletedEvent; use OCP\IConfig; use OCP\IL10N; +use Psr\Log\NullLogger; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; use Sabre\DAV\Xml\Property\Href; @@ -27,8 +30,6 @@ use function time; * Class CalDavBackendTest * * @group DB - * - * @package OCA\DAV\Tests\unit\CalDAV */ class CalDavBackendTest extends AbstractCalDavBackend { public function testCalendarOperations(): void { @@ -58,7 +59,7 @@ class CalDavBackendTest extends AbstractCalDavBackend { self::assertEmpty($calendars); } - public function providesSharingData() { + public static function providesSharingData(): array { return [ [true, true, true, false, [ [ @@ -111,13 +112,11 @@ class CalDavBackendTest extends AbstractCalDavBackend { ]; } - /** - * @dataProvider providesSharingData - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesSharingData')] public function testCalendarSharing($userCanRead, $userCanWrite, $groupCanRead, $groupCanWrite, $add, $principals): void { $logger = $this->createMock(\Psr\Log\LoggerInterface::class); $config = $this->createMock(IConfig::class); - /** @var IL10N|MockObject $l10n */ + $l10n = $this->createMock(IL10N::class); $l10n->expects($this->any()) ->method('t') @@ -402,9 +401,7 @@ EOD; $this->assertCount(0, $calendarObjects); } - /** - * @dataProvider providesCalendarQueryParameters - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesCalendarQueryParameters')] public function testCalendarQuery($expectedEventsInResult, $propFilters, $compFilter): void { $calendarId = $this->createTestCalendar(); $events = []; @@ -457,7 +454,7 @@ EOD; $this->assertNotNull($co); } - public function providesCalendarQueryParameters() { + public static function providesCalendarQueryParameters(): array { return [ 'all' => [[0, 1, 2, 3], [], []], 'only-todos' => [[], ['name' => 'VTODO'], []], @@ -468,19 +465,91 @@ EOD; ]; } - public function testSyncSupport(): void { - $calendarId = $this->createTestCalendar(); + public function testCalendarSynchronization(): void { - // fist call without synctoken - $changes = $this->backend->getChangesForCalendar($calendarId, '', 1); - $syncToken = $changes['syncToken']; + // construct calendar for testing + $calendarId = $this->createTestCalendar(); - // add a change - $event = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z'); + /** test fresh sync state with NO events in calendar */ + // construct test state + $stateTest = ['syncToken' => 1, 'added' => [], 'modified' => [], 'deleted' => []]; + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with NO events in calendar'); + + /** test delta sync state with NO events in calendar */ + // construct test state + $stateTest = ['syncToken' => 1, 'added' => [], 'modified' => [], 'deleted' => []]; + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '2', 1); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with NO events in calendar'); + + /** add events to calendar */ + $event1 = $this->createEvent($calendarId, '20240701T130000Z', '20240701T140000Z'); + $event2 = $this->createEvent($calendarId, '20240701T140000Z', '20240701T150000Z'); + $event3 = $this->createEvent($calendarId, '20240701T150000Z', '20240701T160000Z'); + + /** test fresh sync state with events in calendar */ + // construct expected state + $stateTest = ['syncToken' => 4, 'added' => [$event1, $event2, $event3], 'modified' => [], 'deleted' => []]; + sort($stateTest['added']); + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1); + // sort live state results + sort($stateLive['added']); + sort($stateLive['modified']); + sort($stateLive['deleted']); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with events in calendar'); + + /** test delta sync state with events in calendar */ + // construct expected state + $stateTest = ['syncToken' => 4, 'added' => [$event2, $event3], 'modified' => [], 'deleted' => []]; + sort($stateTest['added']); + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '2', 1); + // sort live state results + sort($stateLive['added']); + sort($stateLive['modified']); + sort($stateLive['deleted']); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with events in calendar'); + + /** modify/delete events in calendar */ + $this->deleteEvent($calendarId, $event1); + $this->modifyEvent($calendarId, $event2, '20250701T140000Z', '20250701T150000Z'); + + /** test fresh sync state with modified/deleted events in calendar */ + // construct expected state + $stateTest = ['syncToken' => 6, 'added' => [$event2, $event3], 'modified' => [], 'deleted' => []]; + sort($stateTest['added']); + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '', 1); + // sort live state results + sort($stateLive['added']); + sort($stateLive['modified']); + sort($stateLive['deleted']); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test fresh sync state with modified/deleted events in calendar'); + + /** test delta sync state with modified/deleted events in calendar */ + // construct expected state + $stateTest = ['syncToken' => 6, 'added' => [$event3], 'modified' => [$event2], 'deleted' => [$event1]]; + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '3', 1); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with modified/deleted events in calendar'); + + /** test delta sync state with modified/deleted events in calendar and invalid token */ + // construct expected state + $stateTest = ['syncToken' => 6, 'added' => [], 'modified' => [], 'deleted' => []]; + // retrieve live state + $stateLive = $this->backend->getChangesForCalendar($calendarId, '6', 1); + // test live state + $this->assertEquals($stateTest, $stateLive, 'Failed test delta sync state with modified/deleted events in calendar and invalid token'); - // look for changes - $changes = $this->backend->getChangesForCalendar($calendarId, $syncToken, 1); - $this->assertEquals($event, $changes['added'][0]); } public function testPublications(): void { @@ -546,7 +615,7 @@ EOD; $this->assertCount(0, $subscriptions); } - public function providesSchedulingData() { + public static function providesSchedulingData(): array { $data = <<<EOS BEGIN:VCALENDAR VERSION:2.0 @@ -619,9 +688,9 @@ EOS; } /** - * @dataProvider providesSchedulingData * @param $objectData */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesSchedulingData')] public function testScheduling($objectData): void { $this->backend->createSchedulingObject(self::UNIT_TEST_USER, 'Sample Schedule', $objectData); @@ -637,9 +706,7 @@ EOS; $this->assertCount(0, $sos); } - /** - * @dataProvider providesCalDataForGetDenormalizedData - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesCalDataForGetDenormalizedData')] public function testGetDenormalizedData($expected, $key, $calData): void { try { $actual = $this->backend->getDenormalizedData($calData); @@ -652,7 +719,7 @@ EOS; } } - public function providesCalDataForGetDenormalizedData(): array { + public static function providesCalDataForGetDenormalizedData(): array { return [ 'first occurrence before unix epoch starts' => [0, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nDTSTART;VALUE=DATE:16040222\r\nDTEND;VALUE=DATE:16040223\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], 'no first occurrence because yearly' => [null, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], @@ -661,7 +728,7 @@ EOS; 'last occurrence before unix epoch starts' => [0, 'lastOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.3.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTART;VALUE=DATE:19110324\r\nDTEND;VALUE=DATE:19110325\r\nDTSTAMP:20200927T180638Z\r\nUID:asdfasdfasdf@google.com\r\nCREATED:20200626T181848Z\r\nDESCRIPTION:Very old event\r\nLAST-MODIFIED:20200922T192707Z\r\nSUMMARY:Some old event\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], - 'first occurrence is found when not first VEVENT in group' => [(new DateTime('2020-09-01T110000', new DateTimeZone("America/Los_Angeles")))->getTimestamp(), 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.3.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20201013T110000\r\nDTEND;TZID=America/Los_Angeles:20201013T120000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdf0000@google.com\r\nRECURRENCE-ID;TZID=America/Los_Angeles:20201013T110000\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200925T042014Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20200901T110000\r\nDTEND;TZID=America/Los_Angeles:20200901T120000\r\nRRULE:FREQ=WEEKLY;BYDAY=TU\r\nEXDATE;TZID=America/Los_Angeles:20200922T110000\r\nEXDATE;TZID=America/Los_Angeles:20200915T110000\r\nEXDATE;TZID=America/Los_Angeles:20200908T110000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdf0000@google.com\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200915T162810Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], + 'first occurrence is found when not first VEVENT in group' => [(new DateTime('2020-09-01T110000', new DateTimeZone('America/Los_Angeles')))->getTimestamp(), 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.3.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20201013T110000\r\nDTEND;TZID=America/Los_Angeles:20201013T120000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdf0000@google.com\r\nRECURRENCE-ID;TZID=America/Los_Angeles:20201013T110000\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200925T042014Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nBEGIN:VEVENT\r\nDTSTART;TZID=America/Los_Angeles:20200901T110000\r\nDTEND;TZID=America/Los_Angeles:20200901T120000\r\nRRULE:FREQ=WEEKLY;BYDAY=TU\r\nEXDATE;TZID=America/Los_Angeles:20200922T110000\r\nEXDATE;TZID=America/Los_Angeles:20200915T110000\r\nEXDATE;TZID=America/Los_Angeles:20200908T110000\r\nDTSTAMP:20200927T180638Z\r\nUID:asdf0000@google.com\r\nCREATED:20160330T034726Z\r\nLAST-MODIFIED:20200915T162810Z\r\nSTATUS:CONFIRMED\r\nTRANSP:OPAQUE\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], 'CLASS:PRIVATE' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PRIVATE\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], @@ -805,9 +872,7 @@ EOD; $this->assertEquals(count($search5), 0); } - /** - * @dataProvider searchDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')] public function testSearch(bool $isShared, array $searchOptions, int $count): void { $calendarId = $this->createTestCalendar(); @@ -907,7 +972,7 @@ EOD; $this->assertCount($count, $result); } - public function searchDataProvider() { + public static function searchDataProvider(): array { return [ [false, [], 4], [true, ['timerange' => ['start' => new DateTime('2013-09-12 13:00:00'), 'end' => new DateTime('2013-09-12 14:00:00')]], 2], @@ -1418,7 +1483,7 @@ EOD; self::assertSame(0, $deleted); } - public function testSearchAndExpandRecurrences() { + public function testSearchAndExpandRecurrences(): void { $calendarId = $this->createTestCalendar(); $calendarInfo = [ 'id' => $calendarId, @@ -1574,7 +1639,7 @@ EOD; self::assertEquals([$uri2], $changesAfter['deleted']); } - public function testSearchWithLimitAndTimeRange() { + public function testSearchWithLimitAndTimeRange(): void { $calendarId = $this->createTestCalendar(); $calendarInfo = [ 'id' => $calendarId, @@ -1583,12 +1648,12 @@ EOD; ]; $testFiles = [ - __DIR__ . '/../../misc/caldav-search-limit-timerange-1.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-2.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-3.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-4.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-5.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-6.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', ]; foreach ($testFiles as $testFile) { @@ -1631,7 +1696,7 @@ EOD; ); } - public function testSearchWithLimitAndTimeRangeShouldNotReturnMoreObjectsThenLimit() { + public function testSearchWithLimitAndTimeRangeShouldNotReturnMoreObjectsThenLimit(): void { $calendarId = $this->createTestCalendar(); $calendarInfo = [ 'id' => $calendarId, @@ -1640,12 +1705,12 @@ EOD; ]; $testFiles = [ - __DIR__ . '/../../misc/caldav-search-limit-timerange-1.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-2.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-3.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-4.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-5.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-6.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', ]; foreach ($testFiles as $testFile) { @@ -1681,7 +1746,7 @@ EOD; ); } - public function testSearchWithLimitAndTimeRangeShouldReturnObjectsInTheSameOrder() { + public function testSearchWithLimitAndTimeRangeShouldReturnObjectsInTheSameOrder(): void { $calendarId = $this->createTestCalendar(); $calendarInfo = [ 'id' => $calendarId, @@ -1690,12 +1755,12 @@ EOD; ]; $testFiles = [ - __DIR__ . '/../../misc/caldav-search-limit-timerange-1.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-2.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-3.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-4.ics', - __DIR__ . '/../../misc/caldav-search-limit-timerange-6.ics', // <-- intentional! - __DIR__ . '/../../misc/caldav-search-limit-timerange-5.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-1.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-2.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-3.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-4.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', // <-- intentional! + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics', ]; foreach ($testFiles as $testFile) { @@ -1738,7 +1803,7 @@ EOD; ); } - public function testSearchShouldReturnObjectsInTheSameOrderMissingDate() { + public function testSearchShouldReturnObjectsInTheSameOrderMissingDate(): void { $calendarId = $this->createTestCalendar(); $calendarInfo = [ 'id' => $calendarId, @@ -1747,10 +1812,10 @@ EOD; ]; $testFiles = [ - __DIR__ . '/../../misc/caldav-search-limit-timerange-6.ics', // <-- intentional! - __DIR__ . '/../../misc/caldav-search-limit-timerange-5.ics', - __DIR__ . '/../../misc/caldav-search-missing-start-1.ics', - __DIR__ . '/../../misc/caldav-search-missing-start-2.ics', + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-6.ics', // <-- intentional! + __DIR__ . '/../test_fixtures/caldav-search-limit-timerange-5.ics', + __DIR__ . '/../test_fixtures/caldav-search-missing-start-1.ics', + __DIR__ . '/../test_fixtures/caldav-search-missing-start-2.ics', ]; foreach ($testFiles as $testFile) { @@ -1775,4 +1840,46 @@ EOD; $this->assertEquals('Missing DTSTART 1', $results[2]['objects'][0]['SUMMARY'][0]); $this->assertEquals('Missing DTSTART 2', $results[3]['objects'][0]['SUMMARY'][0]); } + + public function testUnshare(): void { + $principalGroup = 'principal:' . self::UNIT_TEST_GROUP; + $principalUser = 'principal:' . self::UNIT_TEST_USER; + + $l10n = $this->createMock(IL10N::class); + $l10n->method('t') + ->willReturnCallback(fn ($text, $parameters = []) => vsprintf($text, $parameters)); + $config = $this->createMock(IConfig::class); + $logger = new NullLogger(); + + $this->principal->expects($this->exactly(2)) + ->method('findByUri') + ->willReturnMap([ + [$principalGroup, '', self::UNIT_TEST_GROUP], + [$principalUser, '', self::UNIT_TEST_USER], + ]); + $this->groupManager->expects($this->once()) + ->method('groupExists') + ->willReturn(true); + $this->dispatcher->expects($this->exactly(2)) + ->method('dispatchTyped'); + + $calendarId = $this->createTestCalendar(); + $calendarInfo = $this->backend->getCalendarById($calendarId); + + $calendar = new Calendar($this->backend, $calendarInfo, $l10n, $config, $logger); + + $this->backend->updateShares( + shareable: $calendar, + add: [ + ['href' => $principalGroup, 'readOnly' => false] + ], + remove: [] + ); + + $this->backend->unshare( + shareable: $calendar, + principal: $principalUser + ); + + } } diff --git a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php index 9956c17fff3..e25cc099bd6 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarHomeTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -22,21 +24,11 @@ use Sabre\DAV\MkCol; use Test\TestCase; class CalendarHomeTest extends TestCase { - - /** @var CalDavBackend | MockObject */ - private $backend; - - /** @var array */ - private $principalInfo = []; - - /** @var PluginManager */ - private $pluginManager; - - /** @var CalendarHome */ - private $calendarHome; - - /** @var MockObject|LoggerInterface */ - private $logger; + private CalDavBackend&MockObject $backend; + private array $principalInfo = []; + private PluginManager&MockObject $pluginManager; + private LoggerInterface&MockObject $logger; + private CalendarHome $calendarHome; protected function setUp(): void { parent::setUp(); @@ -62,7 +54,7 @@ class CalendarHomeTest extends TestCase { } public function testCreateCalendarValidName(): void { - /** @var MkCol | MockObject $mkCol */ + /** @var MkCol&MockObject $mkCol */ $mkCol = $this->createMock(MkCol::class); $mkCol->method('getResourceType') @@ -82,7 +74,7 @@ class CalendarHomeTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); $this->expectExceptionMessage('The resource you tried to create has a reserved name'); - /** @var MkCol | MockObject $mkCol */ + /** @var MkCol&MockObject $mkCol */ $mkCol = $this->createMock(MkCol::class); $this->calendarHome->createExtendedCollection('contact_birthdays', $mkCol); @@ -92,7 +84,7 @@ class CalendarHomeTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\MethodNotAllowed::class); $this->expectExceptionMessage('The resource you tried to create has a reserved name'); - /** @var MkCol | MockObject $mkCol */ + /** @var MkCol&MockObject $mkCol */ $mkCol = $this->createMock(MkCol::class); $this->calendarHome->createExtendedCollection('app-generated--example--foo-1', $mkCol); diff --git a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php index 85545854918..d6a8f3b910e 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarImplTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarImplTest.php @@ -1,10 +1,13 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\DAV\Tests\unit\CalDAV; +use Generator; use OCA\DAV\CalDAV\Auth\CustomPrincipalPlugin; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; @@ -20,37 +23,35 @@ use Sabre\VObject\ITip\Message; use Sabre\VObject\Reader; class CalendarImplTest extends \Test\TestCase { - /** @var CalendarImpl */ - private $calendarImpl; - - /** @var Calendar | \PHPUnit\Framework\MockObject\MockObject */ - private $calendar; - - /** @var array */ - private $calendarInfo; - - /** @var CalDavBackend | \PHPUnit\Framework\MockObject\MockObject */ - private $backend; + private Calendar&MockObject $calendar; + private array $calendarInfo; + private CalDavBackend&MockObject $backend; + private CalendarImpl $calendarImpl; + private array $mockExportCollection; protected function setUp(): void { parent::setUp(); $this->calendar = $this->createMock(Calendar::class); $this->calendarInfo = [ - 'id' => 'fancy_id_123', + 'id' => 1, '{DAV:}displayname' => 'user readable name 123', '{http://apple.com/ns/ical/}calendar-color' => '#AABBCC', - 'uri' => '/this/is/a/uri' + 'uri' => '/this/is/a/uri', + 'principaluri' => 'principal/users/foobar' ]; $this->backend = $this->createMock(CalDavBackend::class); - $this->calendarImpl = new CalendarImpl($this->calendar, - $this->calendarInfo, $this->backend); + $this->calendarImpl = new CalendarImpl( + $this->calendar, + $this->calendarInfo, + $this->backend + ); } public function testGetKey(): void { - $this->assertEquals($this->calendarImpl->getKey(), 'fancy_id_123'); + $this->assertEquals($this->calendarImpl->getKey(), 1); } public function testGetDisplayname(): void { @@ -76,7 +77,10 @@ class CalendarImplTest extends \Test\TestCase { ->method('getACL') ->with() ->willReturn([ - ['privilege' => '{DAV:}read'] + ['privilege' => '{DAV:}read', 'principal' => 'principal/users/foobar'], + ['privilege' => '{DAV:}read', 'principal' => 'principal/users/other'], + ['privilege' => '{DAV:}write', 'principal' => 'principal/users/other'], + ['privilege' => '{DAV:}all', 'principal' => 'principal/users/other'], ]); $this->assertEquals(1, $this->calendarImpl->getPermissions()); @@ -87,7 +91,9 @@ class CalendarImplTest extends \Test\TestCase { ->method('getACL') ->with() ->willReturn([ - ['privilege' => '{DAV:}write'] + ['privilege' => '{DAV:}write', 'principal' => 'principal/users/foobar'], + ['privilege' => '{DAV:}read', 'principal' => 'principal/users/other'], + ['privilege' => '{DAV:}all', 'principal' => 'principal/users/other'], ]); $this->assertEquals(6, $this->calendarImpl->getPermissions()); @@ -98,8 +104,9 @@ class CalendarImplTest extends \Test\TestCase { ->method('getACL') ->with() ->willReturn([ - ['privilege' => '{DAV:}read'], - ['privilege' => '{DAV:}write'] + ['privilege' => '{DAV:}write', 'principal' => 'principal/users/foobar'], + ['privilege' => '{DAV:}read', 'principal' => 'principal/users/foobar'], + ['privilege' => '{DAV:}all', 'principal' => 'principal/users/other'], ]); $this->assertEquals(7, $this->calendarImpl->getPermissions()); @@ -110,7 +117,7 @@ class CalendarImplTest extends \Test\TestCase { ->method('getACL') ->with() ->willReturn([ - ['privilege' => '{DAV:}all'] + ['privilege' => '{DAV:}all', 'principal' => 'principal/users/foobar'], ]); $this->assertEquals(31, $this->calendarImpl->getPermissions()); @@ -138,13 +145,13 @@ EOF; ->method('setCurrentPrincipal') ->with($this->calendar->getPrincipalURI()); - /** @var \Sabre\DAVACL\Plugin|MockObject $aclPlugin*/ + /** @var \Sabre\DAVACL\Plugin|MockObject $aclPlugin */ $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class); /** @var Plugin|MockObject $schedulingPlugin */ $schedulingPlugin = $this->createMock(Plugin::class); $iTipMessage = $this->getITipMessage($message); - $iTipMessage->recipient = "mailto:lewis@stardew-tent-living.com"; + $iTipMessage->recipient = 'mailto:lewis@stardew-tent-living.com'; $server = $this->createMock(Server::class); $server->expects($this->any()) @@ -191,8 +198,8 @@ EOF; /** @var \Sabre\DAVACL\Plugin|MockObject $schedulingPlugin */ $aclPlugin = $this->createMock(\Sabre\DAVACL\Plugin::class); - $server = - $this->createMock(Server::class); + $server + = $this->createMock(Server::class); $server->expects($this->any()) ->method('getPlugin') ->willReturnMap([ @@ -254,4 +261,48 @@ EOF; $iTipMessage->message = $vObject; return $iTipMessage; } + + protected function mockExportGenerator(): Generator { + foreach ($this->mockExportCollection as $entry) { + yield $entry; + } + } + + public function testExport(): void { + // Arrange + // construct calendar with a 1 hour event and same start/end time zones + $vCalendar = new VCalendar(); + /** @var VEvent $vEvent */ + $vEvent = $vCalendar->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + // construct data store return + $this->mockExportCollection[] = [ + 'id' => 1, + 'calendardata' => $vCalendar->serialize() + ]; + $this->backend->expects($this->once()) + ->method('exportCalendar') + ->with(1, $this->backend::CALENDAR_TYPE_CALENDAR, null) + ->willReturn($this->mockExportGenerator()); + + // Act + foreach ($this->calendarImpl->export(null) as $entry) { + $exported[] = $entry; + } + + // Assert + $this->assertCount(1, $exported, 'Invalid exported items count'); + } + } diff --git a/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php b/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php index 63d92dff40d..e8159ffe07c 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarManagerTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -16,20 +18,11 @@ use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; class CalendarManagerTest extends \Test\TestCase { - /** @var CalDavBackend | MockObject */ - private $backend; - - /** @var IL10N | MockObject */ - private $l10n; - - /** @var IConfig|MockObject */ - private $config; - - /** @var CalendarManager */ - private $manager; - - /** @var MockObject|LoggerInterface */ - private $logger; + private CalDavBackend&MockObject $backend; + private IL10N&MockObject $l10n; + private IConfig&MockObject $config; + private LoggerInterface&MockObject $logger; + private CalendarManager $manager; protected function setUp(): void { parent::setUp(); @@ -54,7 +47,7 @@ class CalendarManagerTest extends \Test\TestCase { ['id' => 456, 'uri' => 'blablub2'], ]); - /** @var IManager | MockObject $calendarManager */ + /** @var IManager&MockObject $calendarManager */ $calendarManager = $this->createMock(Manager::class); $registeredIds = []; $calendarManager->expects($this->exactly(2)) diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php index ca6b3191df4..b0d3c35bfe7 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -18,20 +20,13 @@ use Sabre\VObject\Reader; use Test\TestCase; class CalendarTest extends TestCase { - - /** @var IL10N */ - protected $l10n; - - /** @var IConfig */ - protected $config; - - /** @var MockObject|LoggerInterface */ - protected $logger; + protected IL10N&MockObject $l10n; + protected IConfig&MockObject $config; + protected LoggerInterface&MockObject $logger; protected function setUp(): void { parent::setUp(); - $this->l10n = $this->getMockBuilder(IL10N::class) - ->disableOriginalConstructor()->getMock(); + $this->l10n = $this->createMock(IL10N::class); $this->config = $this->createMock(IConfig::class); $this->logger = $this->createMock(LoggerInterface::class); $this->l10n @@ -43,12 +38,13 @@ class CalendarTest extends TestCase { } public function testDelete(): void { - /** @var MockObject | CalDavBackend $backend */ - $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); - $backend->expects($this->once())->method('updateShares'); - $backend->expects($this->any())->method('getShares')->willReturn([ - ['href' => 'principal:user2'] - ]); + /** @var CalDavBackend&MockObject $backend */ + $backend = $this->createMock(CalDavBackend::class); + $backend->expects($this->never()) + ->method('updateShares'); + $backend->expects($this->once()) + ->method('unshare'); + $calendarInfo = [ '{http://owncloud.org/ns}owner-principal' => 'user1', 'principaluri' => 'user2', @@ -61,12 +57,13 @@ class CalendarTest extends TestCase { public function testDeleteFromGroup(): void { - /** @var MockObject | CalDavBackend $backend */ - $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); - $backend->expects($this->once())->method('updateShares'); - $backend->expects($this->any())->method('getShares')->willReturn([ - ['href' => 'principal:group2'] - ]); + /** @var CalDavBackend&MockObject $backend */ + $backend = $this->createMock(CalDavBackend::class); + $backend->expects($this->never()) + ->method('updateShares'); + $backend->expects($this->once()) + ->method('unshare'); + $calendarInfo = [ '{http://owncloud.org/ns}owner-principal' => 'user1', 'principaluri' => 'user2', @@ -78,7 +75,7 @@ class CalendarTest extends TestCase { } public function testDeleteOwn(): void { - /** @var MockObject | CalDavBackend $backend */ + /** @var CalDavBackend&MockObject $backend */ $backend = $this->createMock(CalDavBackend::class); $backend->expects($this->never())->method('updateShares'); $backend->expects($this->never())->method('getShares'); @@ -99,7 +96,7 @@ class CalendarTest extends TestCase { } public function testDeleteBirthdayCalendar(): void { - /** @var MockObject | CalDavBackend $backend */ + /** @var CalDavBackend&MockObject $backend */ $backend = $this->createMock(CalDavBackend::class); $backend->expects($this->once())->method('deleteCalendar') ->with(666); @@ -113,13 +110,14 @@ class CalendarTest extends TestCase { 'principaluri' => 'principals/users/user1', 'id' => 666, 'uri' => 'contact_birthdays', + '{DAV:}displayname' => 'Test', ]; $c = new Calendar($backend, $calendarInfo, $this->l10n, $this->config, $this->logger); $c->delete(); } - public function dataPropPatch() { + public static function dataPropPatch(): array { return [ ['user1', 'user2', [], true], ['user1', 'user2', [ @@ -146,12 +144,10 @@ class CalendarTest extends TestCase { ]; } - /** - * @dataProvider dataPropPatch - */ - public function testPropPatch($ownerPrincipal, $principalUri, $mutations, $shared): void { - /** @var MockObject | CalDavBackend $backend */ - $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('dataPropPatch')] + public function testPropPatch(string $ownerPrincipal, string $principalUri, array $mutations, bool $shared): void { + /** @var CalDavBackend&MockObject $backend */ + $backend = $this->createMock(CalDavBackend::class); $calendarInfo = [ '{http://owncloud.org/ns}owner-principal' => $ownerPrincipal, 'principaluri' => $principalUri, @@ -170,18 +166,17 @@ class CalendarTest extends TestCase { $this->addToAssertionCount(1); } - /** - * @dataProvider providesReadOnlyInfo - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesReadOnlyInfo')] public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet, $uri = 'default'): void { - /** @var MockObject | CalDavBackend $backend */ - $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); + /** @var CalDavBackend&MockObject $backend */ + $backend = $this->createMock(CalDavBackend::class); $backend->expects($this->any())->method('applyShareAcl')->willReturnArgument(1); $calendarInfo = [ 'principaluri' => 'user2', 'id' => 666, 'uri' => $uri ]; + $calendarInfo['{DAV:}displayname'] = 'Test'; if (!is_null($readOnlyValue)) { $calendarInfo['{http://owncloud.org/ns}read-only'] = $readOnlyValue; } @@ -259,7 +254,7 @@ class CalendarTest extends TestCase { $this->assertEquals($expectedAcl, $childAcl); } - public function providesReadOnlyInfo() { + public static function providesReadOnlyInfo(): array { return [ 'read-only property not set' => [true, null, true], 'read-only property is false' => [true, false, true], @@ -271,18 +266,14 @@ class CalendarTest extends TestCase { ]; } - /** - * @dataProvider providesConfidentialClassificationData - * @param int $expectedChildren - * @param bool $isShared - */ - public function testPrivateClassification($expectedChildren, $isShared): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesConfidentialClassificationData')] + public function testPrivateClassification(int $expectedChildren, bool $isShared): void { $calObject0 = ['uri' => 'event-0', 'classification' => CalDavBackend::CLASSIFICATION_PUBLIC]; $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL]; $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; - /** @var MockObject | CalDavBackend $backend */ - $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); + /** @var CalDavBackend&MockObject $backend */ + $backend = $this->createMock(CalDavBackend::class); $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ $calObject0, $calObject1, $calObject2 ]); @@ -313,12 +304,8 @@ class CalendarTest extends TestCase { $this->assertEquals(!$isShared, $c->childExists('event-2')); } - /** - * @dataProvider providesConfidentialClassificationData - * @param int $expectedChildren - * @param bool $isShared - */ - public function testConfidentialClassification($expectedChildren, $isShared): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesConfidentialClassificationData')] + public function testConfidentialClassification(int $expectedChildren, bool $isShared): void { $start = '20160609'; $end = '20160610'; @@ -368,8 +355,8 @@ EOD; $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'calendardata' => $calData]; $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; - /** @var MockObject | CalDavBackend $backend */ - $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); + /** @var CalDavBackend&MockObject $backend */ + $backend = $this->createMock(CalDavBackend::class); $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ $calObject0, $calObject1, $calObject2 ]); @@ -416,7 +403,7 @@ EOD; $l10n->expects($this->once()) ->method('t') ->with('Busy') - ->willReturn("Translated busy"); + ->willReturn('Translated busy'); } else { $l10n->expects($this->never()) ->method('t'); @@ -433,14 +420,14 @@ EOD; } } - public function providesConfidentialClassificationData() { + public static function providesConfidentialClassificationData(): array { return [ [3, false], [2, true] ]; } - public function testRemoveVAlarms() { + public function testRemoveVAlarms(): void { $publicObjectData = <<<EOD BEGIN:VCALENDAR VERSION:2.0 @@ -536,7 +523,7 @@ EOD; 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'calendardata' => $confidentialObjectData]; - /** @var MockObject | CalDavBackend $backend */ + /** @var CalDavBackend&MockObject $backend */ $backend = $this->createMock(CalDavBackend::class); $backend->expects($this->any()) ->method('getCalendarObjects') @@ -615,7 +602,7 @@ EOD; $this->fixLinebreak($confidentialObjectCleaned)); } - private function fixLinebreak($str) { + private function fixLinebreak(string $str): string { return preg_replace('~(*BSR_ANYCRLF)\R~', "\r\n", $str); } } diff --git a/apps/dav/tests/unit/CalDAV/DefaultCalendarValidatorTest.php b/apps/dav/tests/unit/CalDAV/DefaultCalendarValidatorTest.php new file mode 100644 index 00000000000..194009827da --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/DefaultCalendarValidatorTest.php @@ -0,0 +1,171 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\CalDAV; + +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\DefaultCalendarValidator; +use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; +use Test\TestCase; + +class DefaultCalendarValidatorTest extends TestCase { + private DefaultCalendarValidator $validator; + + protected function setUp(): void { + parent::setUp(); + + $this->validator = new DefaultCalendarValidator(); + } + + public function testValidateScheduleDefaultCalendar(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(false); + $node->expects(self::once()) + ->method('canWrite') + ->willReturn(true); + $node->expects(self::once()) + ->method('isShared') + ->willReturn(false); + $node->expects(self::once()) + ->method('isDeleted') + ->willReturn(false); + $node->expects(self::once()) + ->method('getProperties') + ->willReturn([ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VEVENT']), + ]); + + $this->validator->validateScheduleDefaultCalendar($node); + } + + public function testValidateScheduleDefaultCalendarWithEmptyProperties(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(false); + $node->expects(self::once()) + ->method('canWrite') + ->willReturn(true); + $node->expects(self::once()) + ->method('isShared') + ->willReturn(false); + $node->expects(self::once()) + ->method('isDeleted') + ->willReturn(false); + $node->expects(self::once()) + ->method('getProperties') + ->willReturn([]); + + $this->validator->validateScheduleDefaultCalendar($node); + } + + public function testValidateScheduleDefaultCalendarWithSubscription(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(true); + $node->expects(self::never()) + ->method('canWrite'); + $node->expects(self::never()) + ->method('isShared'); + $node->expects(self::never()) + ->method('isDeleted'); + $node->expects(self::never()) + ->method('getProperties'); + + $this->expectException(\Sabre\DAV\Exception::class); + $this->validator->validateScheduleDefaultCalendar($node); + } + + public function testValidateScheduleDefaultCalendarWithoutWrite(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(false); + $node->expects(self::once()) + ->method('canWrite') + ->willReturn(false); + $node->expects(self::never()) + ->method('isShared'); + $node->expects(self::never()) + ->method('isDeleted'); + $node->expects(self::never()) + ->method('getProperties'); + + $this->expectException(\Sabre\DAV\Exception::class); + $this->validator->validateScheduleDefaultCalendar($node); + } + + public function testValidateScheduleDefaultCalendarWithShared(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(false); + $node->expects(self::once()) + ->method('canWrite') + ->willReturn(true); + $node->expects(self::once()) + ->method('isShared') + ->willReturn(true); + $node->expects(self::never()) + ->method('isDeleted'); + $node->expects(self::never()) + ->method('getProperties'); + + $this->expectException(\Sabre\DAV\Exception::class); + $this->validator->validateScheduleDefaultCalendar($node); + } + + public function testValidateScheduleDefaultCalendarWithDeleted(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(false); + $node->expects(self::once()) + ->method('canWrite') + ->willReturn(true); + $node->expects(self::once()) + ->method('isShared') + ->willReturn(false); + $node->expects(self::once()) + ->method('isDeleted') + ->willReturn(true); + $node->expects(self::never()) + ->method('getProperties'); + + $this->expectException(\Sabre\DAV\Exception::class); + $this->validator->validateScheduleDefaultCalendar($node); + } + + public function testValidateScheduleDefaultCalendarWithoutVeventSupport(): void { + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('isSubscription') + ->willReturn(false); + $node->expects(self::once()) + ->method('canWrite') + ->willReturn(true); + $node->expects(self::once()) + ->method('isShared') + ->willReturn(false); + $node->expects(self::once()) + ->method('isDeleted') + ->willReturn(false); + $node->expects(self::once()) + ->method('getProperties') + ->willReturn([ + '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO']), + ]); + + $this->expectException(\Sabre\DAV\Exception::class); + $this->validator->validateScheduleDefaultCalendar($node); + } +} diff --git a/apps/dav/tests/unit/CalDAV/EventComparisonServiceTest.php b/apps/dav/tests/unit/CalDAV/EventComparisonServiceTest.php index 43a7180647f..90b6f9ec0db 100644 --- a/apps/dav/tests/unit/CalDAV/EventComparisonServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/EventComparisonServiceTest.php @@ -14,8 +14,7 @@ use Sabre\VObject\Component\VCalendar; use Test\TestCase; class EventComparisonServiceTest extends TestCase { - /** @var EventComparisonService */ - private $eventComparisonService; + private EventComparisonService $eventComparisonService; protected function setUp(): void { $this->eventComparisonService = new EventComparisonService(); diff --git a/apps/dav/tests/unit/CalDAV/EventReaderTest.php b/apps/dav/tests/unit/CalDAV/EventReaderTest.php new file mode 100644 index 00000000000..3bd4f9d85c2 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/EventReaderTest.php @@ -0,0 +1,1087 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\CalDAV; + +use DateTimeZone; +use OCA\DAV\CalDAV\EventReader; +use Sabre\VObject\Component\VCalendar; +use Test\TestCase; + +class EventReaderTest extends TestCase { + + private VCalendar $vCalendar1a; + private VCalendar $vCalendar1b; + private VCalendar $vCalendar1c; + private VCalendar $vCalendar1d; + private VCalendar $vCalendar1e; + private VCalendar $vCalendar2; + private VCalendar $vCalendar3; + + protected function setUp(): void { + + parent::setUp(); + + // construct calendar with a 1 hour event and same start/end time zones + $this->vCalendar1a = new VCalendar(); + $vEvent = $this->vCalendar1a->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a 1 hour event and different start/end time zones + $this->vCalendar1b = new VCalendar(); + $vEvent = $this->vCalendar1b->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Vancouver']); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a 1 hour event and global time zone + $this->vCalendar1c = new VCalendar(); + // time zone component + $vTimeZone = $this->vCalendar1c->add('VTIMEZONE'); + $vTimeZone->add('TZID', 'America/Toronto'); + // event component + $vEvent = $this->vCalendar1c->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000'); + $vEvent->add('DTEND', '20240701T090000'); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a 1 hour event and no time zone + $this->vCalendar1d = new VCalendar(); + $vEvent = $this->vCalendar1d->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000'); + $vEvent->add('DTEND', '20240701T090000'); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a 1 hour event and Microsoft time zone + $this->vCalendar1e = new VCalendar(); + $vEvent = $this->vCalendar1e->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'Eastern Standard Time']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'Eastern Standard Time']); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a full day event + $this->vCalendar2 = new VCalendar(); + // time zone component + $vTimeZone = $this->vCalendar2->add('VTIMEZONE'); + $vTimeZone->add('TZID', 'America/Toronto'); + // event component + $vEvent = $this->vCalendar2->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701'); + $vEvent->add('DTEND', '20240702'); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a multi day event + $this->vCalendar3 = new VCalendar(); + // time zone component + $vTimeZone = $this->vCalendar3->add('VTIMEZONE'); + $vTimeZone->add('TZID', 'America/Toronto'); + // event component + $vEvent = $this->vCalendar3->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701'); + $vEvent->add('DTEND', '20240706'); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + } + + public function testConstructFromCalendarString(): void { + + // construct event reader + $er = new EventReader($this->vCalendar1a->serialize(), '96a0e6b1-d886-4a55-a60d-152b31401dcc'); + // test object creation + $this->assertInstanceOf(EventReader::class, $er); + + } + + public function testConstructFromCalendarObject(): void { + + // construct event reader + $er = new EventReader($this->vCalendar1a, '96a0e6b1-d886-4a55-a60d-152b31401dcc'); + // test object creation + $this->assertInstanceOf(EventReader::class, $er); + + } + + public function testConstructFromEventObject(): void { + + // construct event reader + $er = new EventReader($this->vCalendar1a->VEVENT[0]); + // test object creation + $this->assertInstanceOf(EventReader::class, $er); + + } + + public function testStartDateTime(): void { + + /** test day part event with same start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1a, $this->vCalendar1a->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->startDateTime()); + + /** test day part event with different start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1b, $this->vCalendar1b->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->startDateTime()); + + /** test day part event with global time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1c, $this->vCalendar1c->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->startDateTime()); + + /** test day part event with no time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1d, $this->vCalendar1d->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('UTC')))), $er->startDateTime()); + + /** test day part event with microsoft time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1e, $this->vCalendar1e->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->startDateTime()); + + /** test full day event */ + // construct event reader + $er = new EventReader($this->vCalendar2, $this->vCalendar2->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T000000', (new DateTimeZone('America/Toronto')))), $er->startDateTime()); + + /** test multi day event */ + // construct event reader + $er = new EventReader($this->vCalendar3, $this->vCalendar3->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T000000', (new DateTimeZone('America/Toronto')))), $er->startDateTime()); + + } + + public function testStartTimeZone(): void { + + /** test day part event with same start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1a, $this->vCalendar1a->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->startTimeZone()); + + /** test day part event with different start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1b, $this->vCalendar1b->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->startTimeZone()); + + /** test day part event with global time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1c, $this->vCalendar1c->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->startTimeZone()); + + /** test day part event with no time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1d, $this->vCalendar1d->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('UTC')), $er->startTimeZone()); + + /** test day part event with microsoft time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1e, $this->vCalendar1e->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->startTimeZone()); + + /** test full day event */ + // construct event reader + $er = new EventReader($this->vCalendar2, $this->vCalendar2->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->startTimeZone()); + + /** test multi day event */ + // construct event reader + $er = new EventReader($this->vCalendar3, $this->vCalendar3->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->startTimeZone()); + + } + + public function testEndDate(): void { + + /** test day part event with same start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1a, $this->vCalendar1a->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T090000', (new DateTimeZone('America/Toronto')))), $er->endDateTime()); + + /** test day part event with different start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1b, $this->vCalendar1b->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T090000', (new DateTimeZone('America/Vancouver')))), $er->endDateTime()); + + /** test day part event with global time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1c, $this->vCalendar1c->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T090000', (new DateTimeZone('America/Toronto')))), $er->endDateTime()); + + /** test day part event with no time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1d, $this->vCalendar1d->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T090000', (new DateTimeZone('UTC')))), $er->endDateTime()); + + /** test day part event with microsoft time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1e, $this->vCalendar1e->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240701T090000', (new DateTimeZone('America/Toronto')))), $er->endDateTime()); + + /** test full day event */ + // construct event reader + $er = new EventReader($this->vCalendar2, $this->vCalendar2->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240702T000000', (new DateTimeZone('America/Toronto')))), $er->endDateTime()); + + /** test multi day event */ + // construct event reader + $er = new EventReader($this->vCalendar3, $this->vCalendar3->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240706T000000', (new DateTimeZone('America/Toronto')))), $er->endDateTime()); + + } + + public function testEndTimeZone(): void { + + /** test day part event with same start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1a, $this->vCalendar1a->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->endTimeZone()); + + /** test day part event with different start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1b, $this->vCalendar1b->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Vancouver')), $er->endTimeZone()); + + /** test day part event with global time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1c, $this->vCalendar1c->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->endTimeZone()); + + /** test day part event with no time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1d, $this->vCalendar1d->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('UTC')), $er->endTimeZone()); + + /** test day part event with microsoft time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1e, $this->vCalendar1e->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->endTimeZone()); + + /** test full day event */ + // construct event reader + $er = new EventReader($this->vCalendar2, $this->vCalendar2->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->endTimeZone()); + + /** test multi day event */ + // construct event reader + $er = new EventReader($this->vCalendar3, $this->vCalendar3->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new DateTimeZone('America/Toronto')), $er->endTimeZone()); + + } + + public function testEntireDay(): void { + + /** test day part event with same start/end time zone */ + // construct event reader + $er = new EventReader($this->vCalendar1a, $this->vCalendar1a->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertFalse($er->entireDay()); + + /** test full day event */ + // construct event reader + $er = new EventReader($this->vCalendar2, $this->vCalendar2->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->entireDay()); + + /** test multi day event */ + // construct event reader + $er = new EventReader($this->vCalendar3, $this->vCalendar3->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->entireDay()); + + } + + public function testRecurs(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertFalse($er->recurs()); + + /** test rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurs()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurs()); + + } + + public function testRecurringPattern(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringPattern()); + + /** test absolute rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('A', $er->recurringPattern()); + + /** test relative rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=MO;BYSETPOS=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('R', $er->recurringPattern()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('A', $er->recurringPattern()); + + } + + public function testRecurringPrecision(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringPrecision()); + + /** test daily rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('daily', $er->recurringPrecision()); + + /** test weekly rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('weekly', $er->recurringPrecision()); + + /** test monthly rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8,15'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('monthly', $er->recurringPrecision()); + + /** test yearly rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYMONTHDAY=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('yearly', $er->recurringPrecision()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals('fixed', $er->recurringPrecision()); + + } + + public function testRecurringInterval(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringInterval()); + + /** test daily rrule recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(2, $er->recurringInterval()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringInterval()); + + } + + public function testRecurringConcludes(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertFalse($er->recurringConcludes()); + + /** test rrule recurrance with no end */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertFalse($er->recurringConcludes()); + + /** test rrule recurrance with until date end */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;UNTIL=20240712T080000Z;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + + /** test rrule recurrance with iteration end */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + + /** test rdate (multiple property instances) recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + + /** test rrule and rdate recurrance with rdate as last date */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + $vCalendar->VEVENT[0]->add('RDATE', '20240706,20240715'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + + /** test rrule and rdate recurrance with rrule as last date */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=7;BYDAY=MO,WE,FR'); + $vCalendar->VEVENT[0]->add('RDATE', '20240706,20240713'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertTrue($er->recurringConcludes()); + + } + + public function testRecurringConcludesAfter(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringConcludesAfter()); + + /** test rrule recurrance with count */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(6, $er->recurringConcludesAfter()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(2, $er->recurringConcludesAfter()); + + /** test rdate (multiple property instances) recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(2, $er->recurringConcludesAfter()); + + /** test rrule and rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + $vCalendar->VEVENT[0]->add('RDATE', '20240706,20240715'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(8, $er->recurringConcludesAfter()); + + } + + public function testRecurringConcludesOn(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringConcludesOn()); + + /** test rrule recurrance with no end */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertNull($er->recurringConcludesOn()); + + /** test rrule recurrance with until date end */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;UNTIL=20240712T080000Z;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + + // TODO: Fix until time zone + //$this->assertEquals((new \DateTime('20240712T080000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + + /** test rdate recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703,20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240705T000000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + + /** test rdate (multiple property instances) recurrance */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240705T000000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + + /** test rrule and rdate recurrance with rdate as last date */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=6;BYDAY=MO,WE,FR'); + $vCalendar->VEVENT[0]->add('RDATE', '20240706,20240715'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240715T000000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + + /** test rrule and rdate recurrance with rrule as last date */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;COUNT=7;BYDAY=MO,WE,FR'); + $vCalendar->VEVENT[0]->add('RDATE', '20240706,20240713'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals((new \DateTime('20240715T080000', (new DateTimeZone('America/Toronto')))), $er->recurringConcludesOn()); + + } + + public function testRecurringDaysOfWeek(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringDaysOfWeek()); + + /** test rrule recurrance with weekly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;UNTIL=20240712T080000Z;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(['MO','WE','FR'], $er->recurringDaysOfWeek()); + + } + + public function testRecurringDaysOfWeekNamed(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringDaysOfWeekNamed()); + + /** test rrule recurrance with weekly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;UNTIL=20240712T080000Z;BYDAY=MO,WE,FR'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(['Monday','Wednesday','Friday'], $er->recurringDaysOfWeekNamed()); + + } + + public function testRecurringDaysOfMonth(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringDaysOfMonth()); + + /** test rrule recurrance with monthly absolute dates*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=6,13,20,27'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([6,13,20,27], $er->recurringDaysOfMonth()); + + } + + public function testRecurringDaysOfYear(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringDaysOfYear()); + + /** test rrule recurrance with monthly absolute dates*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYYEARDAY=1,30,180,365'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([1,30,180,365], $er->recurringDaysOfYear()); + + } + + public function testRecurringWeeksOfMonth(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringWeeksOfMonth()); + + /** test rrule recurrance with monthly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=MO;BYSETPOS=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([1], $er->recurringWeeksOfMonth()); + + } + + public function testRecurringWeeksOfMonthNamed(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringWeeksOfMonthNamed()); + + /** test rrule recurrance with weekly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=MO;BYSETPOS=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(['First'], $er->recurringWeeksOfMonthNamed()); + + } + + public function testRecurringWeeksOfYear(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringWeeksOfYear()); + + /** test rrule recurrance with monthly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;INTERVAL=1;BYWEEKNO=35,42;BYDAY=TU'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([35,42], $er->recurringWeeksOfYear()); + + } + + public function testRecurringMonthsOfYear(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringMonthsOfYear()); + + /** test rrule recurrance with monthly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;INTERVAL=1;BYMONTH=7;BYMONTHDAY=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([7], $er->recurringMonthsOfYear()); + + } + + public function testRecurringMonthsOfYearNamed(): void { + + /** test no recurrance */ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals([], $er->recurringMonthsOfYearNamed()); + + /** test rrule recurrance with weekly days*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;INTERVAL=1;BYMONTH=7;BYMONTHDAY=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test set by constructor + $this->assertEquals(['July'], $er->recurringMonthsOfYearNamed()); + + } + + public function testRecurringIterationDaily(): void { + + /** test rrule recurrance with daily frequency*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240714T040000Z'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240704T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240707T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240710T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240713T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20240709T080000'))); + $this->assertEquals((new \DateTime('20240710T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + + public function testRecurringIterationWeekly(): void { + + /** test rrule recurrance with weekly frequency*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240713T040000Z'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240703T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240705T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240708T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240710T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240712T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20240709T080000'))); + $this->assertEquals((new \DateTime('20240710T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + + public function testRecurringIterationMonthlyAbsolute(): void { + + /** test rrule recurrance with monthly absolute frequency on the 1st of each month*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;COUNT=3;BYMONTHDAY=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240801T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240901T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20240809T080000'))); + $this->assertEquals((new \DateTime('20240901T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + + public function testRecurringIterationMonthlyRelative(): void { + + /** test rrule recurrance with monthly relative frequency on the first monday of each month*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;COUNT=3;BYDAY=MO;BYSETPOS=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240805T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240902T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20240809T080000'))); + $this->assertEquals((new \DateTime('20240902T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + + public function testRecurringIterationYearlyAbsolute(): void { + + /** test rrule recurrance with yearly absolute frequency on the 1st of july*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;COUNT=3;BYMONTH=7'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20250701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20260701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20250809T080000'))); + $this->assertEquals((new \DateTime('20260701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + + public function testRecurringIterationYearlyRelative(): void { + + /** test rrule recurrance with yearly relative frequency on the first monday of july*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;COUNT=3;BYMONTH=7;BYDAY=MO;BYSETPOS=1'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20250707T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20260706T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20250809T080000'))); + $this->assertEquals((new \DateTime('20260706T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + + public function testRecurringIterationFixed(): void { + + /** test rrule recurrance with yearly relative frequency on the first monday of july*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240905T080000,20241231T080000'); + // construct event reader + $er = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test initial recurrance + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240703T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20240905T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvance(); + $this->assertEquals((new \DateTime('20241231T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance (This is past the last recurrance and should return null) + $er->recurrenceAdvance(); + $this->assertNull($er->recurrenceDate()); + // test rewind to initial recurrance + $er->recurrenceRewind(); + $this->assertEquals((new \DateTime('20240701T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + // test next recurrance + $er->recurrenceAdvanceTo((new \DateTime('20240809T080000'))); + $this->assertEquals((new \DateTime('20240905T080000', (new DateTimeZone('America/Toronto')))), $er->recurrenceDate()); + + } + +} diff --git a/apps/dav/tests/unit/CalDAV/Export/ExportServiceTest.php b/apps/dav/tests/unit/CalDAV/Export/ExportServiceTest.php new file mode 100644 index 00000000000..838dfc18f2f --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Export/ExportServiceTest.php @@ -0,0 +1,81 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\CalDAV\Export; + +use Generator; +use OCA\DAV\CalDAV\Export\ExportService; +use OCP\Calendar\CalendarExportOptions; +use OCP\Calendar\ICalendarExport; +use OCP\ServerVersion; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\VObject\Component\VCalendar; + +class ExportServiceTest extends \Test\TestCase { + private ServerVersion&MockObject $serverVersion; + private ExportService $service; + private ICalendarExport&MockObject $calendar; + private array $mockExportCollection; + + protected function setUp(): void { + parent::setUp(); + + $this->serverVersion = $this->createMock(ServerVersion::class); + $this->serverVersion->method('getVersionString') + ->willReturn('32.0.0.0'); + $this->service = new ExportService($this->serverVersion); + $this->calendar = $this->createMock(ICalendarExport::class); + + } + + protected function mockGenerator(): Generator { + foreach ($this->mockExportCollection as $entry) { + yield $entry; + } + } + + public function testExport(): void { + // Arrange + // construct calendar with a 1 hour event and same start/end time zones + $vCalendar = new VCalendar(); + /** @var \Sabre\VObject\Component\VEvent $vEvent */ + $vEvent = $vCalendar->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Recurrence Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + // construct calendar return + $options = new CalendarExportOptions(); + $this->mockExportCollection[] = $vCalendar; + $this->calendar->expects($this->once()) + ->method('export') + ->with($options) + ->willReturn($this->mockGenerator()); + + // Act + $document = ''; + foreach ($this->service->export($this->calendar, $options) as $chunk) { + $document .= $chunk; + } + + // Assert + $this->assertStringContainsString('BEGIN:VCALENDAR', $document, 'Exported document calendar start missing'); + $this->assertStringContainsString('BEGIN:VEVENT', $document, 'Exported document event start missing'); + $this->assertStringContainsString('END:VEVENT', $document, 'Exported document event end missing'); + $this->assertStringContainsString('END:VCALENDAR', $document, 'Exported document calendar end missing'); + + } + +} diff --git a/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php b/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php index 778df5697f0..b2f479ac0e3 100644 --- a/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/Integration/ExternalCalendarTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -6,16 +8,17 @@ namespace OCA\DAV\Tests\unit\CalDAV\Integration; use OCA\DAV\CalDAV\Integration\ExternalCalendar; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ExternalCalendarTest extends TestCase { - private $abstractExternalCalendar; + private ExternalCalendar&MockObject $abstractExternalCalendar; protected function setUp(): void { parent::setUp(); - $this->abstractExternalCalendar = - $this->getMockForAbstractClass(ExternalCalendar::class, ['example-app-id', 'calendar-uri-in-backend']); + $this->abstractExternalCalendar + = $this->getMockForAbstractClass(ExternalCalendar::class, ['example-app-id', 'calendar-uri-in-backend']); } public function testGetName():void { @@ -39,7 +42,7 @@ class ExternalCalendarTest extends TestCase { $this->abstractExternalCalendar->setName('other-name'); } - public function createDirectory():void { + public function createDirectory(): void { // Check that the method is final and can't be overridden by other classes $reflectionMethod = new \ReflectionMethod(ExternalCalendar::class, 'createDirectory'); $this->assertTrue($reflectionMethod->isFinal()); @@ -63,9 +66,7 @@ class ExternalCalendarTest extends TestCase { $this->assertTrue(ExternalCalendar::isAppGeneratedCalendar('app-generated--example--foo--2')); } - /** - * @dataProvider splitAppGeneratedCalendarUriDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('splitAppGeneratedCalendarUriDataProvider')] public function testSplitAppGeneratedCalendarUriInvalid(string $name):void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Provided calendar uri was not app-generated'); @@ -73,7 +74,7 @@ class ExternalCalendarTest extends TestCase { ExternalCalendar::splitAppGeneratedCalendarUri($name); } - public function splitAppGeneratedCalendarUriDataProvider():array { + public static function splitAppGeneratedCalendarUriDataProvider():array { return [ ['personal'], ['foo_shared_by_admin'], diff --git a/apps/dav/tests/unit/CalDAV/Listener/CalendarPublicationListenerTest.php b/apps/dav/tests/unit/CalDAV/Listener/CalendarPublicationListenerTest.php index b55359cd208..3ba0b832593 100644 --- a/apps/dav/tests/unit/CalDAV/Listener/CalendarPublicationListenerTest.php +++ b/apps/dav/tests/unit/CalDAV/Listener/CalendarPublicationListenerTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -15,20 +17,11 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class CalendarPublicationListenerTest extends TestCase { - - /** @var Backend|MockObject */ - private $activityBackend; - - /** @var LoggerInterface|MockObject */ - private $logger; - + private Backend&MockObject $activityBackend; + private LoggerInterface&MockObject $logger; private CalendarPublicationListener $calendarPublicationListener; - - /** @var CalendarPublishedEvent|MockObject */ - private $publicationEvent; - - /** @var CalendarUnpublishedEvent|MockObject */ - private $unpublicationEvent; + private CalendarPublishedEvent&MockObject $publicationEvent; + private CalendarUnpublishedEvent&MockObject $unpublicationEvent; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/Listener/CalendarShareUpdateListenerTest.php b/apps/dav/tests/unit/CalDAV/Listener/CalendarShareUpdateListenerTest.php index b8414ecd695..d5697a862db 100644 --- a/apps/dav/tests/unit/CalDAV/Listener/CalendarShareUpdateListenerTest.php +++ b/apps/dav/tests/unit/CalDAV/Listener/CalendarShareUpdateListenerTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,17 +16,10 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class CalendarShareUpdateListenerTest extends TestCase { - - /** @var Backend|MockObject */ - private $activityBackend; - - /** @var LoggerInterface|MockObject */ - private $logger; - + private Backend&MockObject $activityBackend; + private LoggerInterface&MockObject $logger; private CalendarShareUpdateListener $calendarPublicationListener; - - /** @var CalendarShareUpdatedEvent|MockObject */ - private $event; + private CalendarShareUpdatedEvent&MockObject $event; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/Listener/SubscriptionListenerTest.php b/apps/dav/tests/unit/CalDAV/Listener/SubscriptionListenerTest.php index 589e659b9ea..cbfdfd6b9b7 100644 --- a/apps/dav/tests/unit/CalDAV/Listener/SubscriptionListenerTest.php +++ b/apps/dav/tests/unit/CalDAV/Listener/SubscriptionListenerTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -18,26 +20,13 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class SubscriptionListenerTest extends TestCase { - - /** @var RefreshWebcalService|MockObject */ - private $refreshWebcalService; - - /** @var Backend|MockObject */ - private $reminderBackend; - - /** @var IJobList|MockObject */ - private $jobList; - - /** @var LoggerInterface|MockObject */ - private $logger; - + private RefreshWebcalService&MockObject $refreshWebcalService; + private Backend&MockObject $reminderBackend; + private IJobList&MockObject $jobList; + private LoggerInterface&MockObject $logger; private SubscriptionListener $calendarPublicationListener; - - /** @var SubscriptionCreatedEvent|MockObject */ - private $subscriptionCreatedEvent; - - /** @var SubscriptionDeletedEvent|MockObject */ - private $subscriptionDeletedEvent; + private SubscriptionCreatedEvent&MockObject $subscriptionCreatedEvent; + private SubscriptionDeletedEvent&MockObject $subscriptionDeletedEvent; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/OutboxTest.php b/apps/dav/tests/unit/CalDAV/OutboxTest.php index def2bd80157..cc0a3f0405f 100644 --- a/apps/dav/tests/unit/CalDAV/OutboxTest.php +++ b/apps/dav/tests/unit/CalDAV/OutboxTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,15 +9,12 @@ namespace OCA\DAV\Tests\unit\CalDAV; use OCA\DAV\CalDAV\Outbox; use OCP\IConfig; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class OutboxTest extends TestCase { - - /** @var IConfig */ - private $config; - - /** @var Outbox */ - private $outbox; + private IConfig&MockObject $config; + private Outbox $outbox; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/PluginTest.php b/apps/dav/tests/unit/CalDAV/PluginTest.php index 0915fdf2646..c5725a1fa81 100644 --- a/apps/dav/tests/unit/CalDAV/PluginTest.php +++ b/apps/dav/tests/unit/CalDAV/PluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -9,8 +11,7 @@ use OCA\DAV\CalDAV\Plugin; use Test\TestCase; class PluginTest extends TestCase { - /** @var Plugin */ - private $plugin; + private Plugin $plugin; protected function setUp(): void { parent::setUp(); @@ -18,7 +19,7 @@ class PluginTest extends TestCase { $this->plugin = new Plugin(); } - public function linkProvider() { + public static function linkProvider(): array { return [ [ 'principals/users/MyUserName', @@ -35,13 +36,8 @@ class PluginTest extends TestCase { ]; } - /** - * @dataProvider linkProvider - * - * @param $input - * @param $expected - */ - public function testGetCalendarHomeForPrincipal($input, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('linkProvider')] + public function testGetCalendarHomeForPrincipal(string $input, string $expected): void { $this->assertSame($expected, $this->plugin->getCalendarHomeForPrincipal($input)); } diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php index 4333754222b..6acceed6f64 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarRootTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -12,10 +14,13 @@ use OCA\DAV\CalDAV\PublicCalendarRoot; use OCA\DAV\Connector\Sabre\Principal; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; +use OCP\IDBConnection; use OCP\IGroupManager; use OCP\IL10N; use OCP\IUserManager; use OCP\Security\ISecureRandom; +use OCP\Server; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Test\TestCase; @@ -28,34 +33,24 @@ use Test\TestCase; */ class PublicCalendarRootTest extends TestCase { public const UNIT_TEST_USER = ''; - /** @var CalDavBackend */ - private $backend; - /** @var PublicCalendarRoot */ - private $publicCalendarRoot; - /** @var IL10N */ - private $l10n; - /** @var Principal|\PHPUnit\Framework\MockObject\MockObject */ - private $principal; - /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $userManager; - /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $groupManager; - /** @var IConfig */ - protected $config; - - /** @var ISecureRandom */ - private $random; - /** @var LoggerInterface */ - private $logger; + private CalDavBackend $backend; + private PublicCalendarRoot $publicCalendarRoot; + private IL10N&MockObject $l10n; + private Principal&MockObject $principal; + protected IUserManager&MockObject $userManager; + protected IGroupManager&MockObject $groupManager; + protected IConfig&MockObject $config; + private ISecureRandom $random; + private LoggerInterface&MockObject $logger; protected function setUp(): void { parent::setUp(); - $db = \OC::$server->getDatabaseConnection(); + $db = Server::get(IDBConnection::class); $this->principal = $this->createMock('OCA\DAV\Connector\Sabre\Principal'); $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); - $this->random = \OC::$server->getSecureRandom(); + $this->random = Server::get(ISecureRandom::class); $this->logger = $this->createMock(LoggerInterface::class); $dispatcher = $this->createMock(IEventDispatcher::class); $config = $this->createMock(IConfig::class); @@ -80,8 +75,7 @@ class PublicCalendarRootTest extends TestCase { $sharingBackend, false, ); - $this->l10n = $this->getMockBuilder(IL10N::class) - ->disableOriginalConstructor()->getMock(); + $this->l10n = $this->createMock(IL10N::class); $this->config = $this->createMock(IConfig::class); $this->publicCalendarRoot = new PublicCalendarRoot($this->backend, @@ -132,10 +126,7 @@ class PublicCalendarRootTest extends TestCase { $this->assertSame([], $calendarResults); } - /** - * @return Calendar - */ - protected function createPublicCalendar() { + protected function createPublicCalendar(): Calendar { $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', []); $calendarInfo = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER)[0]; diff --git a/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php b/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php index 04b99b70124..98153a067fb 100644 --- a/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/PublicCalendarTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,17 +16,13 @@ use Sabre\VObject\Reader; class PublicCalendarTest extends CalendarTest { - /** - * @dataProvider providesConfidentialClassificationData - * @param int $expectedChildren - * @param bool $isShared - */ - public function testPrivateClassification($expectedChildren, $isShared): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesConfidentialClassificationData')] + public function testPrivateClassification(int $expectedChildren, bool $isShared): void { $calObject0 = ['uri' => 'event-0', 'classification' => CalDavBackend::CLASSIFICATION_PUBLIC]; $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL]; $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; - /** @var MockObject | CalDavBackend $backend */ + /** @var CalDavBackend&MockObject $backend */ $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ $calObject0, $calObject1, $calObject2 @@ -44,9 +42,9 @@ class PublicCalendarTest extends CalendarTest { 'id' => 666, 'uri' => 'cal', ]; - /** @var MockObject | IConfig $config */ + /** @var IConfig&MockObject $config */ $config = $this->createMock(IConfig::class); - /** @var MockObject | LoggerInterface $logger */ + /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $c = new PublicCalendar($backend, $calendarInfo, $this->l10n, $config, $logger); $children = $c->getChildren(); @@ -57,12 +55,8 @@ class PublicCalendarTest extends CalendarTest { $this->assertFalse($c->childExists('event-2')); } - /** - * @dataProvider providesConfidentialClassificationData - * @param int $expectedChildren - * @param bool $isShared - */ - public function testConfidentialClassification($expectedChildren, $isShared): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesConfidentialClassificationData')] + public function testConfidentialClassification(int $expectedChildren, bool $isShared): void { $start = '20160609'; $end = '20160610'; @@ -112,7 +106,7 @@ EOD; $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'calendardata' => $calData]; $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; - /** @var MockObject | CalDavBackend $backend */ + /** @var CalDavBackend&MockObject $backend */ $backend = $this->getMockBuilder(CalDavBackend::class)->disableOriginalConstructor()->getMock(); $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ $calObject0, $calObject1, $calObject2 @@ -132,9 +126,9 @@ EOD; 'id' => 666, 'uri' => 'cal', ]; - /** @var MockObject | IConfig $config */ + /** @var IConfig&MockObject $config */ $config = $this->createMock(IConfig::class); - /** @var MockObject | LoggerInterface $logger */ + /** @var LoggerInterface&MockObject $logger */ $logger = $this->createMock(LoggerInterface::class); $c = new PublicCalendar($backend, $calendarInfo, $this->l10n, $config, $logger); diff --git a/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php b/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php index 769e1537646..5344ec5d7cd 100644 --- a/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php +++ b/apps/dav/tests/unit/CalDAV/Publishing/PublisherTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -43,9 +45,9 @@ class PublisherTest extends TestCase { } - protected $elementMap = []; - protected $namespaceMap = ['DAV:' => 'd']; - protected $contextUri = '/'; + protected array $elementMap = []; + protected array $namespaceMap = ['DAV:' => 'd']; + protected string $contextUri = '/'; private function write($input) { $writer = new Writer(); diff --git a/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php b/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php index 8aecdf7f0dd..ec2ae37a929 100644 --- a/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php +++ b/apps/dav/tests/unit/CalDAV/Publishing/PublishingTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -10,6 +12,7 @@ use OCA\DAV\CalDAV\Publishing\PublishPlugin; use OCP\IConfig; use OCP\IRequest; use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Server; use Sabre\DAV\SimpleCollection; use Sabre\HTTP\Request; @@ -17,31 +20,21 @@ use Sabre\HTTP\Response; use Test\TestCase; class PublishingTest extends TestCase { - - /** @var PublishPlugin */ - private $plugin; - /** @var Server */ - private $server; - /** @var Calendar | \PHPUnit\Framework\MockObject\MockObject */ - private $book; - /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ - private $config; - /** @var IURLGenerator | \PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; + private PublishPlugin $plugin; + private Server $server; + private Calendar&MockObject $book; + private IConfig&MockObject $config; + private IURLGenerator&MockObject $urlGenerator; protected function setUp(): void { parent::setUp(); - $this->config = $this->getMockBuilder(IConfig::class)-> - disableOriginalConstructor()-> - getMock(); + $this->config = $this->createMock(IConfig::class); $this->config->expects($this->any())->method('getSystemValue') ->with($this->equalTo('secret')) ->willReturn('mysecret'); - $this->urlGenerator = $this->getMockBuilder(IURLGenerator::class)-> - disableOriginalConstructor()-> - getMock(); + $this->urlGenerator = $this->createMock(IURLGenerator::class); /** @var IRequest $request */ $this->plugin = new PublishPlugin($this->config, $this->urlGenerator); @@ -49,9 +42,9 @@ class PublishingTest extends TestCase { $root = new SimpleCollection('calendars'); $this->server = new Server($root); /** @var SimpleCollection $node */ - $this->book = $this->getMockBuilder(Calendar::class)-> - disableOriginalConstructor()-> - getMock(); + $this->book = $this->getMockBuilder(Calendar::class) + ->disableOriginalConstructor() + ->getMock(); $this->book->method('getName')->willReturn('cal1'); $root->addChild($this->book); $this->plugin->initialize($this->server); diff --git a/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php index ffba1a2fc96..356acf2dd7f 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/BackendTest.php @@ -10,27 +10,20 @@ namespace OCA\DAV\Tests\unit\CalDAV\Reminder; use OCA\DAV\CalDAV\Reminder\Backend as ReminderBackend; use OCP\AppFramework\Utility\ITimeFactory; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class BackendTest extends TestCase { - - /** - * Reminder Backend - * - * @var ReminderBackend|\PHPUnit\Framework\MockObject\MockObject - */ - private $reminderBackend; - - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; + private ReminderBackend $reminderBackend; + private ITimeFactory&MockObject $timeFactory; protected function setUp(): void { parent::setUp(); $query = self::$realDatabase->getQueryBuilder(); - $query->delete('calendar_reminders')->execute(); - $query->delete('calendarobjects')->execute(); - $query->delete('calendars')->execute(); + $query->delete('calendar_reminders')->executeStatement(); + $query->delete('calendarobjects')->executeStatement(); + $query->delete('calendars')->executeStatement(); $this->timeFactory = $this->createMock(ITimeFactory::class); $this->reminderBackend = new ReminderBackend(self::$realDatabase, $this->timeFactory); @@ -40,9 +33,11 @@ class BackendTest extends TestCase { protected function tearDown(): void { $query = self::$realDatabase->getQueryBuilder(); - $query->delete('calendar_reminders')->execute(); - $query->delete('calendarobjects')->execute(); - $query->delete('calendars')->execute(); + $query->delete('calendar_reminders')->executeStatement(); + $query->delete('calendarobjects')->executeStatement(); + $query->delete('calendars')->executeStatement(); + + parent::tearDown(); } @@ -95,7 +90,7 @@ class BackendTest extends TestCase { $this->assertCount(4, $rows); - $this->reminderBackend->removeReminder((int) $rows[3]['id']); + $this->reminderBackend->removeReminder((int)$rows[3]['id']); $query = self::$realDatabase->getQueryBuilder(); $rows = $query->select('*') @@ -118,7 +113,7 @@ class BackendTest extends TestCase { unset($rows[0]['id']); unset($rows[1]['id']); - $this->assertEquals($rows[0], [ + $expected1 = [ 'calendar_id' => 1, 'object_id' => 1, 'uid' => 'asd', @@ -134,8 +129,8 @@ class BackendTest extends TestCase { 'calendardata' => 'Calendar data 123', 'displayname' => 'Displayname 123', 'principaluri' => 'principals/users/user001', - ]); - $this->assertEquals($rows[1], [ + ]; + $expected2 = [ 'calendar_id' => 1, 'object_id' => 1, 'uid' => 'asd', @@ -151,7 +146,9 @@ class BackendTest extends TestCase { 'calendardata' => 'Calendar data 123', 'displayname' => 'Displayname 123', 'principaluri' => 'principals/users/user001', - ]); + ]; + + $this->assertEqualsCanonicalizing([$rows[0],$rows[1]], [$expected1,$expected2]); } public function testGetAllScheduledRemindersForEvent(): void { @@ -233,14 +230,14 @@ class BackendTest extends TestCase { $query = self::$realDatabase->getQueryBuilder(); $rows = $query->select('*') ->from('calendar_reminders') - ->execute() + ->executeQuery() ->fetchAll(); $this->assertCount(4, $rows); $this->assertEquals($rows[3]['notification_date'], 123600); - $reminderId = (int) $rows[3]['id']; + $reminderId = (int)$rows[3]['id']; $newNotificationDate = 123700; $this->reminderBackend->updateReminder($reminderId, $newNotificationDate); @@ -249,10 +246,10 @@ class BackendTest extends TestCase { $row = $query->select('notification_date') ->from('calendar_reminders') ->where($query->expr()->eq('id', $query->createNamedParameter($reminderId))) - ->execute() + ->executeQuery() ->fetch(); - $this->assertEquals((int) $row['notification_date'], 123700); + $this->assertEquals((int)$row['notification_date'], 123700); } @@ -264,7 +261,7 @@ class BackendTest extends TestCase { 'principaluri' => $query->createNamedParameter('principals/users/user001'), 'displayname' => $query->createNamedParameter('Displayname 123'), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendars') @@ -273,7 +270,7 @@ class BackendTest extends TestCase { 'principaluri' => $query->createNamedParameter('principals/users/user002'), 'displayname' => $query->createNamedParameter('Displayname 99'), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendarobjects') @@ -283,7 +280,7 @@ class BackendTest extends TestCase { 'calendarid' => $query->createNamedParameter(1), 'size' => $query->createNamedParameter(42), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendarobjects') @@ -293,7 +290,7 @@ class BackendTest extends TestCase { 'calendarid' => $query->createNamedParameter(1), 'size' => $query->createNamedParameter(42), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendarobjects') @@ -303,7 +300,7 @@ class BackendTest extends TestCase { 'calendarid' => $query->createNamedParameter(99), 'size' => $query->createNamedParameter(42), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendar_reminders') @@ -321,7 +318,7 @@ class BackendTest extends TestCase { 'notification_date' => $query->createNamedParameter(123456), 'is_repeat_based' => $query->createNamedParameter(0), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendar_reminders') @@ -339,7 +336,7 @@ class BackendTest extends TestCase { 'notification_date' => $query->createNamedParameter(123456), 'is_repeat_based' => $query->createNamedParameter(0), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendar_reminders') @@ -357,7 +354,7 @@ class BackendTest extends TestCase { 'notification_date' => $query->createNamedParameter(123499), 'is_repeat_based' => $query->createNamedParameter(0), ]) - ->execute(); + ->executeStatement(); $query = self::$realDatabase->getQueryBuilder(); $query->insert('calendar_reminders') @@ -375,6 +372,6 @@ class BackendTest extends TestCase { 'notification_date' => $query->createNamedParameter(123600), 'is_repeat_based' => $query->createNamedParameter(0), ]) - ->execute(); + ->executeStatement(); } } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTestCase.php index 60ef1df43d5..70b374298ea 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/AbstractNotificationProviderTestCase.php @@ -14,44 +14,21 @@ use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUser; use OCP\L10N\IFactory as L10NFactory; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\VObject\Component\VCalendar; use Test\TestCase; -abstract class AbstractNotificationProviderTest extends TestCase { - - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; - - /** @var L10NFactory|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10nFactory; - - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10n; - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $urlGenerator; - - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - protected $config; - - /** @var AbstractProvider|\PHPUnit\Framework\MockObject\MockObject */ - protected $provider; - - /** - * @var VCalendar - */ - protected $vcalendar; - - /** - * @var string - */ - protected $calendarDisplayName; - - /** - * @var IUser|\PHPUnit\Framework\MockObject\MockObject - */ - protected $user; +abstract class AbstractNotificationProviderTestCase extends TestCase { + protected LoggerInterface&MockObject $logger; + protected L10NFactory&MockObject $l10nFactory; + protected IL10N&MockObject $l10n; + protected IURLGenerator&MockObject $urlGenerator; + protected IConfig&MockObject $config; + protected AbstractProvider $provider; + protected VCalendar $vcalendar; + protected string $calendarDisplayName; + protected IUser&MockObject $user; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php index 708581f9b6d..f7fbac2c407 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/EmailProviderTest.php @@ -14,14 +14,13 @@ use OCP\IUser; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Mail\IMessage; +use OCP\Util; use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Component\VCalendar; -class EmailProviderTest extends AbstractNotificationProviderTest { +class EmailProviderTest extends AbstractNotificationProviderTestCase { public const USER_EMAIL = 'frodo@hobb.it'; - - /** @var IMailer|MockObject */ - private $mailer; + private IMailer&MockObject $mailer; protected function setUp(): void { parent::setUp(); @@ -96,18 +95,12 @@ class EmailProviderTest extends AbstractNotificationProviderTest { $this->mailer->expects($this->exactly(4)) ->method('validateMailAddress') - ->withConsecutive( - ['uid1@example.com'], - ['uid2@example.com'], - ['uid3@example.com'], - ['invalid'], - ) - ->willReturnOnConsecutiveCalls( - true, - true, - true, - false, - ); + ->willReturnMap([ + ['uid1@example.com', true], + ['uid2@example.com', true], + ['uid3@example.com', true], + ['invalid', false], + ]); $this->mailer->expects($this->exactly(3)) ->method('createMessage') @@ -118,14 +111,18 @@ class EmailProviderTest extends AbstractNotificationProviderTest { $message22 ); - $this->mailer->expects($this->exactly(3)) + $calls = [ + [$message11], + [$message21], + [$message22], + ]; + $this->mailer->expects($this->exactly(count($calls))) ->method('send') - ->withConsecutive( - [$message11], - [$message21], - [$message22], - ) - ->willReturn([]); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return []; + }); $this->setupURLGeneratorMock(2); @@ -214,16 +211,22 @@ class EmailProviderTest extends AbstractNotificationProviderTest { $message22, $message23, ); - $this->mailer->expects($this->exactly(6)) + + $calls = [ + [$message11], + [$message12], + [$message13], + [$message21], + [$message22], + [$message23], + ]; + $this->mailer->expects($this->exactly(count($calls))) ->method('send') - ->withConsecutive( - [$message11], - [$message12], - [$message13], - [$message21], - [$message22], - [$message23], - )->willReturn([]); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return []; + }); $this->setupURLGeneratorMock(2); $vcalendar = $this->getAttendeeVCalendar(); @@ -292,12 +295,18 @@ class EmailProviderTest extends AbstractNotificationProviderTest { $message12, $message13, ); - $this->mailer->expects($this->exactly(2)) + + $calls = [ + [$message12], + [$message13], + ]; + $this->mailer->expects($this->exactly(count($calls))) ->method('send') - ->withConsecutive( - [$message12], - [$message13], - )->willReturn([]); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return []; + }); $this->setupURLGeneratorMock(1); $vcalendar = $this->getAttendeeVCalendar(); @@ -350,7 +359,7 @@ class EmailProviderTest extends AbstractNotificationProviderTest { $message->expects($this->once()) ->method('setFrom') - ->with([\OCP\Util::getDefaultEmailAddress('reminders-noreply')]) + ->with([Util::getDefaultEmailAddress('reminders-noreply')]) ->willReturn($message); if ($replyTo) { diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php index b090fa0e5e7..5034af49cae 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProvider/PushProviderTest.php @@ -13,13 +13,11 @@ use OCP\AppFramework\Utility\ITimeFactory; use OCP\IUser; use OCP\Notification\IManager; use OCP\Notification\INotification; +use PHPUnit\Framework\MockObject\MockObject; -class PushProviderTest extends AbstractNotificationProviderTest { - /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ - private $manager; - - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; +class PushProviderTest extends AbstractNotificationProviderTestCase { + private IManager&MockObject $manager; + private ITimeFactory&MockObject $timeFactory; protected function setUp(): void { parent::setUp(); @@ -96,20 +94,23 @@ class PushProviderTest extends AbstractNotificationProviderTest { $this->manager->expects($this->exactly(3)) ->method('createNotification') - ->with() ->willReturnOnConsecutiveCalls( $notification1, $notification2, $notification3 ); + $calls = [ + $notification1, + $notification2, + $notification3, + ]; $this->manager->expects($this->exactly(3)) ->method('notify') - ->withConsecutive( - [$notification1], - [$notification2], - [$notification3], - ); + ->willReturnCallback(function ($notification) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, $notification); + }); $this->provider->send($this->vcalendar->VEVENT, $this->calendarDisplayName, [], $users); } diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php index 98d49552b02..6b813ed0228 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotificationProviderManagerTest.php @@ -14,15 +14,17 @@ use OCA\DAV\CalDAV\Reminder\NotificationProvider\PushProvider; use OCA\DAV\CalDAV\Reminder\NotificationProviderManager; use OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException; use OCA\DAV\Capabilities; +use OCP\AppFramework\QueryException; use Test\TestCase; +/** + * @group DB + */ class NotificationProviderManagerTest extends TestCase { - - /** @var NotificationProviderManager|\PHPUnit\Framework\MockObject\MockObject */ - private $providerManager; + private NotificationProviderManager $providerManager; /** - * @throws \OCP\AppFramework\QueryException + * @throws QueryException */ protected function setUp(): void { parent::setUp(); @@ -36,7 +38,7 @@ class NotificationProviderManagerTest extends TestCase { * @throws NotificationTypeDoesNotExistException */ public function testGetProviderForUnknownType(): void { - $this->expectException(\OCA\DAV\CalDAV\Reminder\NotificationTypeDoesNotExistException::class); + $this->expectException(NotificationTypeDoesNotExistException::class); $this->expectExceptionMessage('Type NOT EXISTENT is not an accepted type of notification'); $this->providerManager->getProvider('NOT EXISTENT'); @@ -47,7 +49,7 @@ class NotificationProviderManagerTest extends TestCase { * @throws ProviderNotAvailableException */ public function testGetProviderForUnRegisteredType(): void { - $this->expectException(\OCA\DAV\CalDAV\Reminder\NotificationProvider\ProviderNotAvailableException::class); + $this->expectException(ProviderNotAvailableException::class); $this->expectExceptionMessage('No notification provider for type AUDIO available'); $this->providerManager->getProvider('AUDIO'); @@ -65,7 +67,7 @@ class NotificationProviderManagerTest extends TestCase { } /** - * @throws \OCP\AppFramework\QueryException + * @throws QueryException */ public function testRegisterBadProvider(): void { $this->expectException(\InvalidArgumentException::class); diff --git a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php index 6f319766d21..c091f590711 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/NotifierTest.php @@ -16,24 +16,16 @@ use OCP\IURLGenerator; use OCP\L10N\IFactory; use OCP\Notification\AlreadyProcessedException; use OCP\Notification\INotification; +use OCP\Notification\UnknownNotificationException; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class NotifierTest extends TestCase { - /** @var Notifier */ - protected $notifier; - - /** @var IFactory|MockObject */ - protected $factory; - - /** @var IURLGenerator|MockObject */ - protected $urlGenerator; - - /** @var IL10N|MockObject */ - protected $l10n; - - /** @var ITimeFactory|MockObject */ - protected $timeFactory; + protected IFactory&MockObject $factory; + protected IURLGenerator&MockObject $urlGenerator; + protected IL10N&MockObject $l10n; + protected ITimeFactory&MockObject $timeFactory; + protected Notifier $notifier; protected function setUp(): void { parent::setUp(); @@ -88,10 +80,10 @@ class NotifierTest extends TestCase { public function testPrepareWrongApp(): void { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(UnknownNotificationException::class); $this->expectExceptionMessage('Notification not from this app'); - /** @var INotification|MockObject $notification */ + /** @var INotification&MockObject $notification */ $notification = $this->createMock(INotification::class); $notification->expects($this->once()) @@ -105,10 +97,10 @@ class NotifierTest extends TestCase { public function testPrepareWrongSubject(): void { - $this->expectException(\InvalidArgumentException::class); + $this->expectException(UnknownNotificationException::class); $this->expectExceptionMessage('Unknown subject'); - /** @var INotification|MockObject $notification */ + /** @var INotification&MockObject $notification */ $notification = $this->createMock(INotification::class); $notification->expects($this->once()) @@ -129,7 +121,7 @@ class NotifierTest extends TestCase { return $d1->diff($d2)->y < 0; } - public function dataPrepare(): array { + public static function dataPrepare(): array { return [ [ 'calendar_reminder', @@ -178,18 +170,9 @@ class NotifierTest extends TestCase { ]; } - /** - * @dataProvider dataPrepare - * - * @param string $subjectType - * @param array $subjectParams - * @param string $subject - * @param array $messageParams - * @param string $message - * @throws \Exception - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataPrepare')] public function testPrepare(string $subjectType, array $subjectParams, string $subject, array $messageParams, string $message): void { - /** @var INotification|MockObject $notification */ + /** @var INotification&MockObject $notification */ $notification = $this->createMock(INotification::class); $notification->expects($this->once()) @@ -234,7 +217,7 @@ class NotifierTest extends TestCase { } public function testPassedEvent(): void { - /** @var INotification|MockObject $notification */ + /** @var INotification&MockObject $notification */ $notification = $this->createMock(INotification::class); $notification->expects($this->once()) diff --git a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php index 593131ba5dd..c18901c5f58 100644 --- a/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Reminder/ReminderServiceTest.php @@ -26,35 +26,16 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class ReminderServiceTest extends TestCase { - /** @var Backend|MockObject */ - private $backend; - - /** @var NotificationProviderManager|MockObject */ - private $notificationProviderManager; - - /** @var IUserManager|MockObject */ - private $userManager; - - /** @var IGroupManager|MockObject*/ - private $groupManager; - - /** @var CalDavBackend|MockObject */ - private $caldavBackend; - - /** @var ITimeFactory|MockObject */ - private $timeFactory; - - /** @var IConfig|MockObject */ - private $config; - - /** @var ReminderService */ - private $reminderService; - - /** @var MockObject|LoggerInterface */ - private $logger; - - /** @var MockObject|Principal */ - private $principalConnector; + private Backend&MockObject $backend; + private NotificationProviderManager&MockObject $notificationProviderManager; + private IUserManager&MockObject $userManager; + private IGroupManager&MockObject $groupManager; + private CalDavBackend&MockObject $caldavBackend; + private ITimeFactory&MockObject $timeFactory; + private IConfig&MockObject $config; + private LoggerInterface&MockObject $logger; + private Principal&MockObject $principalConnector; + private ReminderService $reminderService; public const CALENDAR_DATA = <<<EOD BEGIN:VCALENDAR @@ -252,9 +233,7 @@ END:VTIMEZONE END:VCALENDAR ICS; - - /** @var null|string */ - private $oldTimezone; + private ?string $oldTimezone; protected function setUp(): void { parent::setUp(); @@ -305,13 +284,17 @@ ICS; 'component' => 'vevent', ]; - $this->backend->expects($this->exactly(2)) + $calls = [ + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1465429500, false], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', '35b3eae8e792aa2209f0b4e1a302f105', 'DISPLAY', false, 1465344000, false] + ]; + $this->backend->expects($this->exactly(count($calls))) ->method('insertReminder') - ->withConsecutive( - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1465429500, false], - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', '35b3eae8e792aa2209f0b4e1a302f105', 'DISPLAY', false, 1465344000, false] - ) - ->willReturn(1); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return 1; + }); $this->timeFactory->expects($this->once()) ->method('getDateTime') @@ -353,12 +336,7 @@ EOD; ]; $this->backend->expects($this->never()) - ->method('insertReminder') - ->withConsecutive( - [1337, 42, 'wej2z68l9h', false, null, false, '5c70531aab15c92b52518ae10a2f78a4', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1465429500, false], - [1337, 42, 'wej2z68l9h', false, null, false, '5c70531aab15c92b52518ae10a2f78a4', '35b3eae8e792aa2209f0b4e1a302f105', 'DISPLAY', false, 1465344000, false] - ) - ->willReturn(1); + ->method('insertReminder'); $this->reminderService->onCalendarObjectCreate($objectData); } @@ -371,16 +349,20 @@ EOD; 'component' => 'vevent', ]; - $this->backend->expects($this->exactly(5)) + $calls = [ + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429500, false], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429620, true], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429740, true], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429860, true], + [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429980, true] + ]; + $this->backend->expects($this->exactly(count($calls))) ->method('insertReminder') - ->withConsecutive( - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429500, false], - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429620, true], - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429740, true], - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429860, true], - [1337, 42, 'wej2z68l9h', false, 1465430400, false, '5c70531aab15c92b52518ae10a2f78a4', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1465429980, true] - ) - ->willReturn(1); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return 1; + }); $this->timeFactory->expects($this->once()) ->method('getDateTime') @@ -398,13 +380,17 @@ EOD; 'component' => 'vevent', ]; - $this->backend->expects($this->exactly(2)) + $calls = [ + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1467243900, false], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false] + ]; + $this->backend->expects($this->exactly(count($calls))) ->method('insertReminder') - ->withConsecutive( - [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'de919af7429d3b5c11e8b9d289b411a6', 'EMAIL', true, 1467243900, false], - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false] - ) - ->willReturn(1); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return 1; + }); $this->timeFactory->expects($this->once()) ->method('getDateTime') @@ -427,7 +413,7 @@ EOD; $this->reminderService->onCalendarObjectCreate($objectData); } - public function testOnCalendarObjectCreateAllDayWithoutTimezone(): void { + public function testOnCalendarObjectCreateAllDayWithNullTimezone(): void { $objectData = [ 'calendardata' => self::CALENDAR_DATA_ALL_DAY, 'id' => '42', @@ -454,6 +440,33 @@ EOD; $this->reminderService->onCalendarObjectCreate($objectData); } + public function testOnCalendarObjectCreateAllDayWithBlankTimezone(): void { + $objectData = [ + 'calendardata' => self::CALENDAR_DATA_ALL_DAY, + 'id' => '42', + 'calendarid' => '1337', + 'component' => 'vevent', + ]; + $this->timeFactory->expects($this->once()) + ->method('getDateTime') + ->with() + ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2023-02-03T13:28:00+00:00')); + $this->caldavBackend->expects(self::once()) + ->method('getCalendarById') + ->with(1337) + ->willReturn([ + '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => '', + ]); + + // One hour before midnight relative to the server's time + $expectedReminderTimstamp = (new DateTime('2023-02-03T23:00:00'))->getTimestamp(); + $this->backend->expects(self::once()) + ->method('insertReminder') + ->with(1337, 42, self::anything(), false, 1675468800, false, self::anything(), self::anything(), 'EMAIL', true, $expectedReminderTimstamp, false); + + $this->reminderService->onCalendarObjectCreate($objectData); + } + public function testOnCalendarObjectCreateAllDayWithTimezone(): void { $objectData = [ 'calendardata' => self::CALENDAR_DATA_ALL_DAY, @@ -494,17 +507,23 @@ EOD; ->willReturn([ '{urn:ietf:params:xml:ns:caldav}calendar-timezone' => null, ]); - $this->backend->expects($this->exactly(6)) + + $calls = [ + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467243900, false], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244020, true], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244140, true], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244260, true], + [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244380, true], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false] + ]; + $this->backend->expects($this->exactly(count($calls))) ->method('insertReminder') - ->withConsecutive( - [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467243900, false], - [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244020, true], - [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244140, true], - [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244260, true], - [1337, 42, 'wej2z68l9h', true, 1467244800, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467244380, true], - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467158400, false] - ) - ->willReturn(1); + ->willReturnCallback(function () use (&$calls) { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + return 1; + }); + $this->timeFactory->expects($this->once()) ->method('getDateTime') ->with() @@ -529,9 +548,7 @@ EOD; $expectedReminderTimstamp = (new DateTime('2023-02-04T08:00:00', new DateTimeZone('Europe/Vienna')))->getTimestamp(); $this->backend->expects(self::once()) ->method('insertReminder') - ->withConsecutive( - [1337, 42, self::anything(), false, self::anything(), false, self::anything(), self::anything(), self::anything(), true, $expectedReminderTimstamp, false], - ) + ->with(1337, 42, self::anything(), false, self::anything(), false, self::anything(), self::anything(), self::anything(), true, $expectedReminderTimstamp, false) ->willReturn(1); $this->caldavBackend->expects(self::once()) ->method('getCalendarById') @@ -657,22 +674,22 @@ EOD; $provider3 = $this->createMock(INotificationProvider::class); $provider4 = $this->createMock(INotificationProvider::class); $provider5 = $this->createMock(INotificationProvider::class); - $this->notificationProviderManager->expects($this->exactly(5)) + + $getProviderCalls = [ + ['EMAIL', $provider1], + ['EMAIL', $provider2], + ['DISPLAY', $provider3], + ['EMAIL', $provider4], + ['EMAIL', $provider5], + ]; + $this->notificationProviderManager->expects($this->exactly(count($getProviderCalls))) ->method('getProvider') - ->withConsecutive( - ['EMAIL'], - ['EMAIL'], - ['DISPLAY'], - ['EMAIL'], - ['EMAIL'], - ) - ->willReturnOnConsecutiveCalls( - $provider1, - $provider2, - $provider3, - $provider4, - $provider5, - ); + ->willReturnCallback(function () use (&$getProviderCalls) { + $expected = array_shift($getProviderCalls); + $return = array_pop($expected); + $this->assertEquals($expected, func_get_args()); + return $return; + }); $user = $this->createMock(IUser::class); $this->userManager->expects($this->exactly(5)) @@ -721,20 +738,36 @@ EOD; return true; }, 'Displayname 123', $user)); + $removeReminderCalls = [ + [1], + [2], + [3], + [4], + [5], + ]; $this->backend->expects($this->exactly(5)) ->method('removeReminder') - ->withConsecutive([1], [2], [3], [4], [5]); - $this->backend->expects($this->exactly(6)) + ->willReturnCallback(function () use (&$removeReminderCalls): void { + $expected = array_shift($removeReminderCalls); + $this->assertEquals($expected, func_get_args()); + }); + + + $insertReminderCalls = [ + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848700, false], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848820, true], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848940, true], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849060, true], + [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849180, true], + [1337, 42, 'wej2z68l9h', true, 1468454400, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467763200, false], + ]; + $this->backend->expects($this->exactly(count($insertReminderCalls))) ->method('insertReminder') - ->withConsecutive( - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848700, false], - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848820, true], - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467848940, true], - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849060, true], - [1337, 42, 'wej2z68l9h', true, 1467849600, false, 'fbdb2726bc0f7dfacac1d881c1453e20', 'ecacbf07d413c3c78d1ac7ad8c469602', 'EMAIL', true, 1467849180, true], - [1337, 42, 'wej2z68l9h', true, 1468454400, false, 'fbdb2726bc0f7dfacac1d881c1453e20', '8996992118817f9f311ac5cc56d1cc97', 'EMAIL', true, 1467763200, false], - ) - ->willReturn(99); + ->willReturnCallback(function () use (&$insertReminderCalls) { + $expected = array_shift($insertReminderCalls); + $this->assertEquals($expected, func_get_args()); + return 99; + }); $this->timeFactory->method('getDateTime') ->willReturn(DateTime::createFromFormat(DateTime::ATOM, '2016-06-08T00:00:00+00:00')); diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTestCase.php index ed39129fa56..364bc74de49 100644 --- a/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/AbstractPrincipalBackendTestCase.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,43 +9,27 @@ namespace OCA\DAV\Tests\unit\CalDAV\ResourceBooking; use OCA\DAV\CalDAV\Proxy\Proxy; use OCA\DAV\CalDAV\Proxy\ProxyMapper; +use OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend; +use OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\PropPatch; use Test\TestCase; -abstract class AbstractPrincipalBackendTest extends TestCase { - /** @var \OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend|\OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend */ - protected $principalBackend; - - /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ - protected $userSession; - - /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $groupManager; - - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; - - /** @var ProxyMapper|\PHPUnit\Framework\MockObject\MockObject */ - protected $proxyMapper; - - /** @var string */ - protected $mainDbTable; - - /** @var string */ - protected $metadataDbTable; - - /** @var string */ - protected $foreignKey; - - /** @var string */ - protected $principalPrefix; - - /** @var string */ - protected $expectedCUType; +abstract class AbstractPrincipalBackendTestCase extends TestCase { + protected ResourcePrincipalBackend|RoomPrincipalBackend $principalBackend; + protected IUserSession&MockObject $userSession; + protected IGroupManager&MockObject $groupManager; + protected LoggerInterface&MockObject $logger; + protected ProxyMapper&MockObject $proxyMapper; + protected string $mainDbTable; + protected string $metadataDbTable; + protected string $foreignKey; + protected string $principalPrefix; + protected string $expectedCUType; protected function setUp(): void { parent::setUp(); @@ -57,10 +43,10 @@ abstract class AbstractPrincipalBackendTest extends TestCase { protected function tearDown(): void { $query = self::$realDatabase->getQueryBuilder(); - $query->delete('calendar_resources')->execute(); - $query->delete('calendar_resources_md')->execute(); - $query->delete('calendar_rooms')->execute(); - $query->delete('calendar_rooms_md')->execute(); + $query->delete('calendar_resources')->executeStatement(); + $query->delete('calendar_resources_md')->executeStatement(); + $query->delete('calendar_rooms')->executeStatement(); + $query->delete('calendar_rooms_md')->executeStatement(); } public function testGetPrincipalsByPrefix(): void { @@ -212,38 +198,43 @@ abstract class AbstractPrincipalBackendTest extends TestCase { ->with($this->principalPrefix . '/backend1-res1') ->willReturn([]); + $calls = [ + function ($proxy) { + /** @var Proxy $proxy */ + if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') { + return false; + } + if ($proxy->getProxyId() !== $this->principalPrefix . '/backend1-res2') { + return false; + } + if ($proxy->getPermissions() !== 3) { + return false; + } + + return true; + }, + function ($proxy) { + /** @var Proxy $proxy */ + if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') { + return false; + } + if ($proxy->getProxyId() !== $this->principalPrefix . '/backend2-res3') { + return false; + } + if ($proxy->getPermissions() !== 3) { + return false; + } + + return true; + } + ]; $this->proxyMapper->expects($this->exactly(2)) ->method('insert') - ->withConsecutive( - [$this->callback(function ($proxy) { - /** @var Proxy $proxy */ - if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') { - return false; - } - if ($proxy->getProxyId() !== $this->principalPrefix . '/backend1-res2') { - return false; - } - if ($proxy->getPermissions() !== 3) { - return false; - } - - return true; - })], - [$this->callback(function ($proxy) { - /** @var Proxy $proxy */ - if ($proxy->getOwnerId() !== $this->principalPrefix . '/backend1-res1') { - return false; - } - if ($proxy->getProxyId() !== $this->principalPrefix . '/backend2-res3') { - return false; - } - if ($proxy->getPermissions() !== 3) { - return false; - } - - return true; - })], - ); + ->willReturnCallback(function ($proxy) use (&$calls) { + $expected = array_shift($calls); + $this->assertTrue($expected($proxy)); + return $proxy; + }); $this->principalBackend->setGroupMemberSet($this->principalPrefix . '/backend1-res1/calendar-proxy-write', [$this->principalPrefix . '/backend1-res2', $this->principalPrefix . '/backend2-res3']); } @@ -255,9 +246,7 @@ abstract class AbstractPrincipalBackendTest extends TestCase { $this->assertEquals(0, $actual); } - /** - * @dataProvider dataSearchPrincipals - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataSearchPrincipals')] public function testSearchPrincipals($expected, $test): void { $user = $this->createMock(IUser::class); $this->userSession->expects($this->once()) @@ -279,7 +268,7 @@ abstract class AbstractPrincipalBackendTest extends TestCase { $actual); } - public function dataSearchPrincipals() { + public static function dataSearchPrincipals(): array { // data providers are called before we subclass // this class, $this->principalPrefix is null // at that point, so we need this hack diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php index d430afb0b01..168e21c3a91 100644 --- a/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/ResourcePrincipalBackendTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,7 +9,7 @@ namespace OCA\DAV\Tests\unit\CalDAV\ResourceBooking; use OCA\DAV\CalDAV\ResourceBooking\ResourcePrincipalBackend; -class ResourcePrincipalBackendTest extends AbstractPrincipalBackendTest { +class ResourcePrincipalBackendTest extends AbstractPrincipalBackendTestCase { protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php b/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php index cd63a3512ae..8a53b0ee25e 100644 --- a/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/ResourceBooking/RoomPrincipalBackendTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,7 +9,7 @@ namespace OCA\DAV\Tests\unit\CalDAV\ResourceBooking; use OCA\DAV\CalDAV\ResourceBooking\RoomPrincipalBackend; -class RoomPrincipalBackendTest extends AbstractPrincipalBackendTest { +class RoomPrincipalBackendTest extends AbstractPrincipalBackendTestCase { protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginCharsetTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginCharsetTest.php new file mode 100644 index 00000000000..fa52d5319c9 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginCharsetTest.php @@ -0,0 +1,193 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\CalDAV\Schedule; + +use OC\L10N\L10N; +use OC\URLGenerator; +use OCA\DAV\CalDAV\EventComparisonService; +use OCA\DAV\CalDAV\Schedule\IMipPlugin; +use OCA\DAV\CalDAV\Schedule\IMipService; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Defaults; +use OCP\IAppConfig; +use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IURLGenerator; +use OCP\IUser; +use OCP\IUserSession; +use OCP\L10N\IFactory; +use OCP\Mail\IMailer; +use OCP\Mail\IMessage; +use OCP\Mail\Provider\IManager; +use OCP\Mail\Provider\IMessageSend; +use OCP\Mail\Provider\IService; +use OCP\Mail\Provider\Message as MailProviderMessage; +use OCP\Security\ISecureRandom; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\Component\VEvent; +use Sabre\VObject\ITip\Message; +use Sabre\VObject\Property\ICalendar\CalAddress; +use Symfony\Component\Mime\Email; +use Test\TestCase; + +class IMipPluginCharsetTest extends TestCase { + // Dependencies + private Defaults&MockObject $defaults; + private IAppConfig&MockObject $appConfig; + private IConfig&MockObject $config; + private IDBConnection&MockObject $db; + private IFactory $l10nFactory; + private IManager&MockObject $mailManager; + private IMailer&MockObject $mailer; + private ISecureRandom&MockObject $random; + private ITimeFactory&MockObject $timeFactory; + private IUrlGenerator&MockObject $urlGenerator; + private IUserSession&MockObject $userSession; + private LoggerInterface $logger; + + // Services + private EventComparisonService $eventComparisonService; + private IMipPlugin $imipPlugin; + private IMipService $imipService; + + // ITip Message + private Message $itipMessage; + + protected function setUp(): void { + // Used by IMipService and IMipPlugin + $today = new \DateTime('2025-06-15 14:30'); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->timeFactory->method('getTime') + ->willReturn($today->getTimestamp()); + $this->timeFactory->method('getDateTime') + ->willReturn($today); + + // IMipService + $this->urlGenerator = $this->createMock(URLGenerator::class); + $this->config = $this->createMock(IConfig::class); + $this->db = $this->createMock(IDBConnection::class); + $this->random = $this->createMock(ISecureRandom::class); + $l10n = $this->createMock(L10N::class); + $this->l10nFactory = $this->createMock(IFactory::class); + $this->l10nFactory->method('findGenericLanguage') + ->willReturn('en'); + $this->l10nFactory->method('findLocale') + ->willReturn('en_US'); + $this->l10nFactory->method('get') + ->willReturn($l10n); + $this->imipService = new IMipService( + $this->urlGenerator, + $this->config, + $this->db, + $this->random, + $this->l10nFactory, + $this->timeFactory, + ); + + // EventComparisonService + $this->eventComparisonService = new EventComparisonService(); + + // IMipPlugin + $this->appConfig = $this->createMock(IAppConfig::class); + $message = new \OC\Mail\Message(new Email(), false); + $this->mailer = $this->createMock(IMailer::class); + $this->mailer->method('createMessage') + ->willReturn($message); + $this->mailer->method('validateMailAddress') + ->willReturn(true); + $this->logger = new NullLogger(); + $this->defaults = $this->createMock(Defaults::class); + $this->defaults->method('getName') + ->willReturn('Instance Name 123'); + $user = $this->createMock(IUser::class); + $user->method('getUID') + ->willReturn('luigi'); + $this->userSession = $this->createMock(IUserSession::class); + $this->userSession->method('getUser') + ->willReturn($user); + $this->mailManager = $this->createMock(IManager::class); + $this->imipPlugin = new IMipPlugin( + $this->appConfig, + $this->mailer, + $this->logger, + $this->timeFactory, + $this->defaults, + $this->userSession, + $this->imipService, + $this->eventComparisonService, + $this->mailManager, + ); + + // ITipMessage + $calendar = new VCalendar(); + $event = new VEvent($calendar, 'VEVENT'); + $event->UID = 'uid-1234'; + $event->SEQUENCE = 1; + $event->SUMMARY = 'Lunch'; + $event->DTSTART = new \DateTime('2025-06-20 12:30:00'); + $organizer = new CalAddress($calendar, 'ORGANIZER', 'mailto:luigi@example.org'); + $event->add($organizer); + $attendee = new CalAddress($calendar, 'ATTENDEE', 'mailto:jose@example.org', ['RSVP' => 'TRUE', 'CN' => 'José']); + $event->add($attendee); + $calendar->add($event); + $this->itipMessage = new Message(); + $this->itipMessage->method = 'REQUEST'; + $this->itipMessage->message = $calendar; + $this->itipMessage->sender = 'mailto:luigi@example.org'; + $this->itipMessage->senderName = 'Luigi'; + $this->itipMessage->recipient = 'mailto:' . 'jose@example.org'; + } + + public function testCharsetMailer(): void { + // Arrange + $symfonyEmail = null; + $this->mailer->expects(self::once()) + ->method('send') + ->willReturnCallback(function (IMessage $message) use (&$symfonyEmail): array { + if ($message instanceof \OC\Mail\Message) { + $symfonyEmail = $message->getSymfonyEmail(); + } + return []; + }); + + // Act + $this->imipPlugin->schedule($this->itipMessage); + + // Assert + $this->assertNotNull($symfonyEmail); + $body = $symfonyEmail->getBody()->toString(); + $this->assertStringContainsString('Content-Type: text/calendar; method=REQUEST; charset="utf-8"; name=event.ics', $body); + } + + public function testCharsetMailProvider(): void { + // Arrange + $this->appConfig->method('getValueBool') + ->with('core', 'mail_providers_enabled', true) + ->willReturn(true); + $mailMessage = new MailProviderMessage(); + $mailService = $this->createStubForIntersectionOfInterfaces([IService::class, IMessageSend::class]); + $mailService->method('initiateMessage') + ->willReturn($mailMessage); + $mailService->expects(self::once()) + ->method('sendMessage'); + $this->mailManager->method('findServiceByAddress') + ->willReturn($mailService); + + // Act + $this->imipPlugin->schedule($this->itipMessage); + + // Assert + $attachments = $mailMessage->getAttachments(); + $this->assertCount(1, $attachments); + $this->assertStringContainsString('text/calendar; method=REQUEST; charset="utf-8"; name=event.ics', $attachments[0]->getType()); + } +} diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php index 433f3a87c59..8e71bfa6edf 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipPluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -11,13 +13,17 @@ use OCA\DAV\CalDAV\Schedule\IMipPlugin; use OCA\DAV\CalDAV\Schedule\IMipService; use OCP\AppFramework\Utility\ITimeFactory; use OCP\Defaults; -use OCP\IConfig; +use OCP\IAppConfig; use OCP\IUser; use OCP\IUserSession; use OCP\Mail\IAttachment; use OCP\Mail\IEMailTemplate; use OCP\Mail\IMailer; use OCP\Mail\IMessage; +use OCP\Mail\Provider\IManager as IMailManager; +use OCP\Mail\Provider\IMessage as IMailMessageNew; +use OCP\Mail\Provider\IMessageSend as IMailMessageSend; +use OCP\Mail\Provider\IService as IMailService; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\VObject\Component\VCalendar; @@ -26,46 +32,28 @@ use Sabre\VObject\ITip\Message; use Test\TestCase; use function array_merge; -class IMipPluginTest extends TestCase { - - /** @var IMessage|MockObject */ - private $mailMessage; - - /** @var IMailer|MockObject */ - private $mailer; - - /** @var IEMailTemplate|MockObject */ - private $emailTemplate; - - /** @var IAttachment|MockObject */ - private $emailAttachment; - - /** @var ITimeFactory|MockObject */ - private $timeFactory; - - /** @var IConfig|MockObject */ - private $config; - - /** @var IUserSession|MockObject */ - private $userSession; - - /** @var IUser|MockObject */ - private $user; - - /** @var IMipPlugin */ - private $plugin; - - /** @var IMipService|MockObject */ - private $service; - - /** @var Defaults|MockObject */ - private $defaults; - - /** @var LoggerInterface|MockObject */ - private $logger; +interface IMailServiceMock extends IMailService, IMailMessageSend { + // workaround for creating mock class with multiple interfaces + // TODO: remove after phpUnit 10 is supported. +} - /** @var EventComparisonService|MockObject */ - private $eventComparisonService; +class IMipPluginTest extends TestCase { + private IMessage&MockObject $mailMessage; + private IMailer&MockObject $mailer; + private IEMailTemplate&MockObject $emailTemplate; + private IAttachment&MockObject $emailAttachment; + private ITimeFactory&MockObject $timeFactory; + private IAppConfig&MockObject $config; + private IUserSession&MockObject $userSession; + private IUser&MockObject $user; + private IMipPlugin $plugin; + private IMipService&MockObject $service; + private Defaults&MockObject $defaults; + private LoggerInterface&MockObject $logger; + private EventComparisonService&MockObject $eventComparisonService; + private IMailManager&MockObject $mailManager; + private IMailServiceMock&MockObject $mailService; + private IMailMessageNew&MockObject $mailMessageNew; protected function setUp(): void { $this->mailMessage = $this->createMock(IMessage::class); @@ -87,13 +75,9 @@ class IMipPluginTest extends TestCase { $this->timeFactory = $this->createMock(ITimeFactory::class); $this->timeFactory->method('getTime')->willReturn(1496912528); // 2017-01-01 - $this->config = $this->createMock(IConfig::class); - + $this->config = $this->createMock(IAppConfig::class); + $this->user = $this->createMock(IUser::class); - /* - $this->user->method('getUID'); - $this->user->method('getDisplayName'); - */ $this->userSession = $this->createMock(IUserSession::class); $this->userSession->method('getUser') @@ -107,6 +91,12 @@ class IMipPluginTest extends TestCase { $this->eventComparisonService = $this->createMock(EventComparisonService::class); + $this->mailManager = $this->createMock(IMailManager::class); + + $this->mailService = $this->createMock(IMailServiceMock::class); + + $this->mailMessageNew = $this->createMock(IMailMessageNew::class); + $this->plugin = new IMipPlugin( $this->config, $this->mailer, @@ -115,7 +105,8 @@ class IMipPluginTest extends TestCase { $this->defaults, $this->userSession, $this->service, - $this->eventComparisonService + $this->eventComparisonService, + $this->mailManager, ); } @@ -181,7 +172,7 @@ class IMipPluginTest extends TestCase { $this->plugin->setVCalendar($oldVCalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -198,6 +189,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, $oldVEvent) ->willReturn($data); @@ -222,12 +217,12 @@ class IMipPluginTest extends TestCase { ->method('getAttendeeRsvpOrReqForParticipant') ->willReturn(true); $this->config->expects(self::once()) - ->method('getAppValue') + ->method('getValueString') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); $this->service->expects(self::once()) ->method('createInvitationToken') - ->with($message, $newVevent, '1496912700') + ->with($message, $newVevent, 1496912700) ->willReturn('token'); $this->service->expects(self::once()) ->method('addResponseButtons') @@ -284,7 +279,7 @@ class IMipPluginTest extends TestCase { $this->plugin->setVCalendar($oldVCalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('the-shire@hobb.it') @@ -301,6 +296,88 @@ class IMipPluginTest extends TestCase { ->with($room) ->willReturn(true); $this->service->expects(self::never()) + ->method('isCircle'); + $this->service->expects(self::never()) + ->method('buildBodyData'); + $this->user->expects(self::any()) + ->method('getUID') + ->willReturn('user1'); + $this->user->expects(self::any()) + ->method('getDisplayName') + ->willReturn('Mr. Wizard'); + $this->userSession->expects(self::any()) + ->method('getUser') + ->willReturn($this->user); + $this->service->expects(self::never()) + ->method('getFrom'); + $this->service->expects(self::never()) + ->method('addSubjectAndHeading'); + $this->service->expects(self::never()) + ->method('addBulletList'); + $this->service->expects(self::never()) + ->method('getAttendeeRsvpOrReqForParticipant'); + $this->config->expects(self::never()) + ->method('getValueString'); + $this->service->expects(self::never()) + ->method('createInvitationToken'); + $this->service->expects(self::never()) + ->method('addResponseButtons'); + $this->service->expects(self::never()) + ->method('addMoreOptionsButton'); + $this->mailer->expects(self::never()) + ->method('send'); + $this->plugin->schedule($message); + $this->assertEquals('1.0', $message->getScheduleStatus()); + } + + public function testAttendeeIsCircle(): void { + $message = new Message(); + $message->method = 'REQUEST'; + $newVCalendar = new VCalendar(); + $newVevent = new VEvent($newVCalendar, 'one', array_merge([ + 'UID' => 'uid-1234', + 'SEQUENCE' => 1, + 'SUMMARY' => 'Fellowship meeting without (!) Boromir', + 'DTSTART' => new \DateTime('2016-01-01 00:00:00') + ], [])); + $newVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard'); + $newVevent->add('ATTENDEE', 'mailto:' . 'circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth', ['RSVP' => 'TRUE', 'CN' => 'The Fellowship', 'CUTYPE' => 'GROUP']); + $newVevent->add('ATTENDEE', 'mailto:' . 'boromir@tra.it.or', ['RSVP' => 'TRUE', 'MEMBER' => 'circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth']); + $message->message = $newVCalendar; + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->senderName = 'Mr. Wizard'; + $message->recipient = 'mailto:' . 'circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth'; + $attendees = $newVevent->select('ATTENDEE'); + $circle = ''; + foreach ($attendees as $attendee) { + if (strcasecmp($attendee->getValue(), $message->recipient) === 0) { + $circle = $attendee; + } + } + $this->assertNotEmpty($circle, 'Failed to find attendee belonging to the circle'); + $this->service->expects(self::once()) + ->method('getLastOccurrence') + ->willReturn(1496912700); + $this->mailer->expects(self::once()) + ->method('validateMailAddress') + ->with('circle+82utEV1Fle8wvxndZLK5TVAPtxj8IIe@middle.earth') + ->willReturn(true); + $this->eventComparisonService->expects(self::once()) + ->method('findModified') + ->willReturn(['new' => [$newVevent], 'old' => null]); + $this->service->expects(self::once()) + ->method('getCurrentAttendee') + ->with($message) + ->willReturn($circle); + $this->service->expects(self::once()) + ->method('isRoomOrResource') + ->with($circle) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('isCircle') + ->with($circle) + ->willReturn(true); + $this->service->expects(self::never()) ->method('buildBodyData'); $this->user->expects(self::any()) ->method('getUID') @@ -320,7 +397,7 @@ class IMipPluginTest extends TestCase { $this->service->expects(self::never()) ->method('getAttendeeRsvpOrReqForParticipant'); $this->config->expects(self::never()) - ->method('getAppValue'); + ->method('getValueString'); $this->service->expects(self::never()) ->method('createInvitationToken'); $this->service->expects(self::never()) @@ -385,7 +462,7 @@ class IMipPluginTest extends TestCase { $this->plugin->setVCalendar($oldVCalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -402,6 +479,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -426,12 +507,12 @@ class IMipPluginTest extends TestCase { ->method('getAttendeeRsvpOrReqForParticipant') ->willReturn(true); $this->config->expects(self::once()) - ->method('getAppValue') + ->method('getValueString') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); $this->service->expects(self::once()) ->method('createInvitationToken') - ->with($message, $newVevent, '1496912700') + ->with($message, $newVevent, 1496912700) ->willReturn('token'); $this->service->expects(self::once()) ->method('addResponseButtons') @@ -446,7 +527,7 @@ class IMipPluginTest extends TestCase { $this->assertEquals('1.1', $message->getScheduleStatus()); } - public function testEmailValidationFailed() { + public function testEmailValidationFailed(): void { $message = new Message(); $message->method = 'REQUEST'; $message->message = new VCalendar(); @@ -464,7 +545,7 @@ class IMipPluginTest extends TestCase { $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -516,7 +597,7 @@ class IMipPluginTest extends TestCase { $this->plugin->setVCalendar($oldVcalendar); $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -533,6 +614,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -557,12 +642,12 @@ class IMipPluginTest extends TestCase { ->method('getAttendeeRsvpOrReqForParticipant') ->willReturn(true); $this->config->expects(self::once()) - ->method('getAppValue') + ->method('getValueString') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); $this->service->expects(self::once()) ->method('createInvitationToken') - ->with($message, $newVevent, '1496912700') + ->with($message, $newVevent, 1496912700) ->willReturn('token'); $this->service->expects(self::once()) ->method('addResponseButtons') @@ -582,6 +667,226 @@ class IMipPluginTest extends TestCase { $this->assertEquals('5.0', $message->getScheduleStatus()); } + public function testMailProviderSend(): void { + // construct iTip message with event and attendees + $message = new Message(); + $message->method = 'REQUEST'; + $calendar = new VCalendar(); + $event = new VEvent($calendar, 'one', array_merge([ + 'UID' => 'uid-1234', + 'SEQUENCE' => 1, + 'SUMMARY' => 'Fellowship meeting without (!) Boromir', + 'DTSTART' => new \DateTime('2016-01-01 00:00:00') + ], [])); + $event->add('ORGANIZER', 'mailto:gandalf@wiz.ard'); + $event->add('ATTENDEE', 'mailto:' . 'frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']); + $message->message = $calendar; + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->senderName = 'Mr. Wizard'; + $message->recipient = 'mailto:' . 'frodo@hobb.it'; + // construct + foreach ($event->select('ATTENDEE') as $entry) { + if (strcasecmp($entry->getValue(), $message->recipient) === 0) { + $attendee = $entry; + } + } + // construct body data return + $data = ['invitee_name' => 'Mr. Wizard', + 'meeting_title' => 'Fellowship meeting without (!) Boromir', + 'attendee_name' => 'frodo@hobb.it' + ]; + // construct system config mock returns + $this->config->expects(self::once()) + ->method('getValueString') + ->with('dav', 'invitation_link_recipients', 'yes') + ->willReturn('yes'); + // construct user mock returns + $this->user->expects(self::any()) + ->method('getUID') + ->willReturn('user1'); + $this->user->expects(self::any()) + ->method('getDisplayName') + ->willReturn('Mr. Wizard'); + // construct user session mock returns + $this->userSession->expects(self::any()) + ->method('getUser') + ->willReturn($this->user); + // construct service mock returns + $this->service->expects(self::once()) + ->method('getLastOccurrence') + ->willReturn(1496912700); + $this->service->expects(self::once()) + ->method('getCurrentAttendee') + ->with($message) + ->willReturn($attendee); + $this->service->expects(self::once()) + ->method('isRoomOrResource') + ->with($attendee) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('isCircle') + ->with($attendee) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('buildBodyData') + ->with($event, null) + ->willReturn($data); + $this->service->expects(self::once()) + ->method('getFrom'); + $this->service->expects(self::once()) + ->method('addSubjectAndHeading') + ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', false); + $this->service->expects(self::once()) + ->method('addBulletList') + ->with($this->emailTemplate, $event, $data); + $this->service->expects(self::once()) + ->method('getAttendeeRsvpOrReqForParticipant') + ->willReturn(true); + $this->service->expects(self::once()) + ->method('createInvitationToken') + ->with($message, $event, 1496912700) + ->willReturn('token'); + $this->service->expects(self::once()) + ->method('addResponseButtons') + ->with($this->emailTemplate, 'token'); + $this->service->expects(self::once()) + ->method('addMoreOptionsButton') + ->with($this->emailTemplate, 'token'); + $this->eventComparisonService->expects(self::once()) + ->method('findModified') + ->willReturn(['old' => [] ,'new' => [$event]]); + // construct mail mock returns + $this->mailer->expects(self::once()) + ->method('validateMailAddress') + ->with('frodo@hobb.it') + ->willReturn(true); + // construct mail provider mock returns + $this->mailService + ->method('initiateMessage') + ->willReturn($this->mailMessageNew); + $this->mailService + ->method('sendMessage') + ->with($this->mailMessageNew); + $this->mailManager + ->method('findServiceByAddress') + ->with('user1', 'gandalf@wiz.ard') + ->willReturn($this->mailService); + + $this->plugin->schedule($message); + $this->assertEquals('1.1', $message->getScheduleStatus()); + } + + public function testMailProviderDisabled(): void { + $message = new Message(); + $message->method = 'REQUEST'; + $newVCalendar = new VCalendar(); + $newVevent = new VEvent($newVCalendar, 'one', array_merge([ + 'UID' => 'uid-1234', + 'SEQUENCE' => 1, + 'SUMMARY' => 'Fellowship meeting without (!) Boromir', + 'DTSTART' => new \DateTime('2016-01-01 00:00:00') + ], [])); + $newVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard'); + $newVevent->add('ATTENDEE', 'mailto:' . 'frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']); + $message->message = $newVCalendar; + $message->sender = 'mailto:gandalf@wiz.ard'; + $message->senderName = 'Mr. Wizard'; + $message->recipient = 'mailto:' . 'frodo@hobb.it'; + // save the old copy in the plugin + $oldVCalendar = new VCalendar(); + $oldVEvent = new VEvent($oldVCalendar, 'one', [ + 'UID' => 'uid-1234', + 'SEQUENCE' => 0, + 'SUMMARY' => 'Fellowship meeting', + 'DTSTART' => new \DateTime('2016-01-01 00:00:00') + ]); + $oldVEvent->add('ORGANIZER', 'mailto:gandalf@wiz.ard'); + $oldVEvent->add('ATTENDEE', 'mailto:' . 'frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']); + $oldVEvent->add('ATTENDEE', 'mailto:' . 'boromir@tra.it.or', ['RSVP' => 'TRUE']); + $oldVCalendar->add($oldVEvent); + $data = ['invitee_name' => 'Mr. Wizard', + 'meeting_title' => 'Fellowship meeting without (!) Boromir', + 'attendee_name' => 'frodo@hobb.it' + ]; + $attendees = $newVevent->select('ATTENDEE'); + $atnd = ''; + foreach ($attendees as $attendee) { + if (strcasecmp($attendee->getValue(), $message->recipient) === 0) { + $atnd = $attendee; + } + } + $this->plugin->setVCalendar($oldVCalendar); + $this->service->expects(self::once()) + ->method('getLastOccurrence') + ->willReturn(1496912700); + $this->mailer->expects(self::once()) + ->method('validateMailAddress') + ->with('frodo@hobb.it') + ->willReturn(true); + $this->eventComparisonService->expects(self::once()) + ->method('findModified') + ->willReturn(['new' => [$newVevent], 'old' => [$oldVEvent]]); + $this->service->expects(self::once()) + ->method('getCurrentAttendee') + ->with($message) + ->willReturn($atnd); + $this->service->expects(self::once()) + ->method('isRoomOrResource') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('buildBodyData') + ->with($newVevent, $oldVEvent) + ->willReturn($data); + $this->user->expects(self::any()) + ->method('getUID') + ->willReturn('user1'); + $this->user->expects(self::any()) + ->method('getDisplayName') + ->willReturn('Mr. Wizard'); + $this->userSession->expects(self::any()) + ->method('getUser') + ->willReturn($this->user); + $this->service->expects(self::once()) + ->method('getFrom'); + $this->service->expects(self::once()) + ->method('addSubjectAndHeading') + ->with($this->emailTemplate, 'request', 'Mr. Wizard', 'Fellowship meeting without (!) Boromir', true); + $this->service->expects(self::once()) + ->method('addBulletList') + ->with($this->emailTemplate, $newVevent, $data); + $this->service->expects(self::once()) + ->method('getAttendeeRsvpOrReqForParticipant') + ->willReturn(true); + $this->config->expects(self::once()) + ->method('getValueString') + ->with('dav', 'invitation_link_recipients', 'yes') + ->willReturn('yes'); + $this->config->expects(self::once()) + ->method('getValueBool') + ->with('core', 'mail_providers_enabled', true) + ->willReturn(false); + $this->service->expects(self::once()) + ->method('createInvitationToken') + ->with($message, $newVevent, 1496912700) + ->willReturn('token'); + $this->service->expects(self::once()) + ->method('addResponseButtons') + ->with($this->emailTemplate, 'token'); + $this->service->expects(self::once()) + ->method('addMoreOptionsButton') + ->with($this->emailTemplate, 'token'); + $this->mailer->expects(self::once()) + ->method('send') + ->willReturn([]); + $this->plugin->schedule($message); + $this->assertEquals('1.1', $message->getScheduleStatus()); + } + public function testNoOldEvent(): void { $message = new Message(); $message->method = 'REQUEST'; @@ -611,7 +916,7 @@ class IMipPluginTest extends TestCase { } $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -629,6 +934,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -653,12 +962,12 @@ class IMipPluginTest extends TestCase { ->method('getAttendeeRsvpOrReqForParticipant') ->willReturn(true); $this->config->expects(self::once()) - ->method('getAppValue') + ->method('getValueString') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('yes'); $this->service->expects(self::once()) ->method('createInvitationToken') - ->with($message, $newVevent, '1496912700') + ->with($message, $newVevent, 1496912700) ->willReturn('token'); $this->service->expects(self::once()) ->method('addResponseButtons') @@ -704,7 +1013,7 @@ class IMipPluginTest extends TestCase { } $this->service->expects(self::once()) ->method('getLastOccurrence') - ->willReturn('1496912700'); + ->willReturn(1496912700); $this->mailer->expects(self::once()) ->method('validateMailAddress') ->with('frodo@hobb.it') @@ -722,6 +1031,10 @@ class IMipPluginTest extends TestCase { ->with($atnd) ->willReturn(false); $this->service->expects(self::once()) + ->method('isCircle') + ->with($atnd) + ->willReturn(false); + $this->service->expects(self::once()) ->method('buildBodyData') ->with($newVevent, null) ->willReturn($data); @@ -746,7 +1059,7 @@ class IMipPluginTest extends TestCase { ->method('getAttendeeRsvpOrReqForParticipant') ->willReturn(true); $this->config->expects(self::once()) - ->method('getAppValue') + ->method('getValueString') ->with('dav', 'invitation_link_recipients', 'yes') ->willReturn('no'); $this->service->expects(self::never()) diff --git a/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php b/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php index d4f1dd66897..2be6a1cf8b1 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/IMipServiceTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,49 +9,48 @@ namespace OCA\DAV\Tests\unit\CalDAV\Schedule; -use OC\L10N\L10N; -use OC\L10N\LazyL10N; use OC\URLGenerator; +use OCA\DAV\CalDAV\EventReader; use OCA\DAV\CalDAV\Schedule\IMipService; +use OCP\AppFramework\Utility\ITimeFactory; use OCP\IConfig; use OCP\IDBConnection; -use OCP\L10N\IFactory as L10NFactory; +use OCP\IL10N; +use OCP\L10N\IFactory; use OCP\Security\ISecureRandom; use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Component\VCalendar; -use Sabre\VObject\Component\VEvent; use Sabre\VObject\Property\ICalendar\DateTime; use Test\TestCase; class IMipServiceTest extends TestCase { - /** @var URLGenerator|MockObject */ - private $urlGenerator; - - /** @var IConfig|MockObject */ - private $config; - - /** @var IDBConnection|MockObject */ - private $db; - - /** @var ISecureRandom|MockObject */ - private $random; - - /** @var L10NFactory|MockObject */ - private $l10nFactory; + private URLGenerator&MockObject $urlGenerator; + private IConfig&MockObject $config; + private IDBConnection&MockObject $db; + private ISecureRandom&MockObject $random; + private IFactory&MockObject $l10nFactory; + private IL10N&MockObject $l10n; + private ITimeFactory&MockObject $timeFactory; + private IMipService $service; - /** @var L10N|MockObject */ - private $l10n; - /** @var IMipService */ - private $service; + private VCalendar $vCalendar1a; + private VCalendar $vCalendar1b; + private VCalendar $vCalendar2; + private VCalendar $vCalendar3; + /** @var DateTime DateTime object that will be returned by DateTime() or DateTime('now') */ + public static $datetimeNow; protected function setUp(): void { + parent::setUp(); + $this->urlGenerator = $this->createMock(URLGenerator::class); $this->config = $this->createMock(IConfig::class); $this->db = $this->createMock(IDBConnection::class); $this->random = $this->createMock(ISecureRandom::class); - $this->l10nFactory = $this->createMock(L10NFactory::class); - $this->l10n = $this->createMock(LazyL10N::class); + $this->l10nFactory = $this->createMock(IFactory::class); + $this->l10n = $this->createMock(IL10N::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); $this->l10nFactory->expects(self::once()) ->method('findGenericLanguage') ->willReturn('en'); @@ -62,14 +63,87 @@ class IMipServiceTest extends TestCase { $this->config, $this->db, $this->random, - $this->l10nFactory + $this->l10nFactory, + $this->timeFactory ); + + // construct calendar with a 1 hour event and same start/end time zones + $this->vCalendar1a = new VCalendar(); + $vEvent = $this->vCalendar1a->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Testing Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a 1 hour event and different start/end time zones + $this->vCalendar1b = new VCalendar(); + $vEvent = $this->vCalendar1b->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Vancouver']); + $vEvent->add('SUMMARY', 'Testing Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a full day event + $this->vCalendar2 = new VCalendar(); + // time zone component + $vTimeZone = $this->vCalendar2->add('VTIMEZONE'); + $vTimeZone->add('TZID', 'America/Toronto'); + // event component + $vEvent = $this->vCalendar2->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701'); + $vEvent->add('DTEND', '20240702'); + $vEvent->add('SUMMARY', 'Testing Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + + // construct calendar with a multi day event + $this->vCalendar3 = new VCalendar(); + // time zone component + $vTimeZone = $this->vCalendar3->add('VTIMEZONE'); + $vTimeZone->add('TZID', 'America/Toronto'); + // event component + $vEvent = $this->vCalendar3->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701'); + $vEvent->add('DTEND', '20240706'); + $vEvent->add('SUMMARY', 'Testing Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); } public function testGetFrom(): void { - $senderName = "Detective McQueen"; - $default = "Twin Lakes Police Department - Darkside Division"; - $expected = "Detective McQueen via Twin Lakes Police Department - Darkside Division"; + $senderName = 'Detective McQueen'; + $default = 'Twin Lakes Police Department - Darkside Division'; + $expected = 'Detective McQueen via Twin Lakes Police Department - Darkside Division'; $this->l10n->expects(self::once()) ->method('t') @@ -80,96 +154,105 @@ class IMipServiceTest extends TestCase { } public function testBuildBodyDataCreated(): void { - $vCalendar = new VCalendar(); - $oldVevent = null; - $newVevent = new VEvent($vCalendar, 'two', [ - 'UID' => 'uid-1234', - 'SEQUENCE' => 3, - 'LAST-MODIFIED' => 789456, - 'SUMMARY' => 'Second Breakfast', - 'DTSTART' => new \DateTime('2016-01-01 00:00:00'), - 'RECURRENCE-ID' => new \DateTime('2016-01-01 00:00:00') - ]); + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024' + }; + } + ); + $this->l10n->method('n')->willReturnMap([ + [ + 'In a day on %1$s between %2$s - %3$s', + 'In %n days on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ] + ]); + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnCallback( + function ($v1, $v2) { + return match (true) { + $v1 == 'now' && $v2 == null => (new \DateTime('20240630T000000')) + }; + } + ); + /** test singleton partial day event*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // define expected output $expected = [ - 'meeting_when' => $this->service->generateWhenString($newVevent), + 'meeting_when' => $this->service->generateWhenString($eventReader), 'meeting_description' => '', - 'meeting_title' => 'Second Breakfast', + 'meeting_title' => 'Testing Event', 'meeting_location' => '', 'meeting_url' => '', 'meeting_url_html' => '', ]; - - $actual = $this->service->buildBodyData($newVevent, $oldVevent); - + // generate actual output + $actual = $this->service->buildBodyData($vCalendar->VEVENT[0], null); + // test output $this->assertEquals($expected, $actual); } public function testBuildBodyDataUpdate(): void { - $vCalendar = new VCalendar(); - $oldVevent = new VEvent($vCalendar, 'two', [ - 'UID' => 'uid-1234', - 'SEQUENCE' => 1, - 'LAST-MODIFIED' => 456789, - 'SUMMARY' => 'Elevenses', - 'DTSTART' => new \DateTime('2016-01-01 00:00:00'), - 'RECURRENCE-ID' => new \DateTime('2016-01-01 00:00:00') - ]); - $oldVevent->add('ORGANIZER', 'mailto:gandalf@wiz.ard'); - $oldVevent->add('ATTENDEE', 'mailto:' . 'frodo@hobb.it', ['RSVP' => 'TRUE', 'CN' => 'Frodo']); - $newVevent = new VEvent($vCalendar, 'two', [ - 'UID' => 'uid-1234', - 'SEQUENCE' => 3, - 'LAST-MODIFIED' => 789456, - 'SUMMARY' => 'Second Breakfast', - 'DTSTART' => new \DateTime('2016-01-01 00:00:00'), - 'RECURRENCE-ID' => new \DateTime('2016-01-01 00:00:00') - ]); + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024' + }; + } + ); + $this->l10n->method('n')->willReturnMap([ + [ + 'In a day on %1$s between %2$s - %3$s', + 'In %n days on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ] + ]); + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnCallback( + function ($v1, $v2) { + return match (true) { + $v1 == 'now' && $v2 == null => (new \DateTime('20240630T000000')) + }; + } + ); + /** test singleton partial day event*/ + $vCalendarNew = clone $this->vCalendar1a; + $vCalendarOld = clone $this->vCalendar1a; + // construct event reader + $eventReaderNew = new EventReader($vCalendarNew, $vCalendarNew->VEVENT[0]->UID->getValue()); + // alter old event label/title + $vCalendarOld->VEVENT[0]->SUMMARY->setValue('Testing Singleton Event'); + // define expected output $expected = [ - 'meeting_when' => $this->service->generateWhenString($newVevent), + 'meeting_when' => $this->service->generateWhenString($eventReaderNew), 'meeting_description' => '', - 'meeting_title' => 'Second Breakfast', + 'meeting_title' => 'Testing Event', 'meeting_location' => '', 'meeting_url' => '', 'meeting_url_html' => '', - 'meeting_when_html' => $this->service->generateWhenString($newVevent), - 'meeting_title_html' => sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", 'Elevenses', 'Second Breakfast'), + 'meeting_when_html' => $this->service->generateWhenString($eventReaderNew), + 'meeting_title_html' => sprintf("<span style='text-decoration: line-through'>%s</span><br />%s", 'Testing Singleton Event', 'Testing Event'), 'meeting_description_html' => '', 'meeting_location_html' => '' ]; - - $actual = $this->service->buildBodyData($newVevent, $oldVevent); - - $this->assertEquals($expected, $actual); - } - - public function testGenerateWhenStringHourlyEvent(): void { - $vCalendar = new VCalendar(); - $vevent = new VEvent($vCalendar, 'two', [ - 'UID' => 'uid-1234', - 'SEQUENCE' => 1, - 'LAST-MODIFIED' => 456789, - 'SUMMARY' => 'Elevenses', - 'TZID' => 'Europe/Vienna', - 'DTSTART' => (new \DateTime('2016-01-01 08:00:00'))->setTimezone(new \DateTimeZone('Europe/Vienna')), - 'DTEND' => (new \DateTime('2016-01-01 09:00:00'))->setTimezone(new \DateTimeZone('Europe/Vienna')), - ]); - - $this->l10n->expects(self::exactly(3)) - ->method('l') - ->withConsecutive( - ['weekdayName', (new \DateTime('2016-01-01 08:00:00'))->setTimezone(new \DateTimeZone('Europe/Vienna')), ['width' => 'abbreviated']], - ['datetime', (new \DateTime('2016-01-01 08:00:00'))->setTimezone(new \DateTimeZone('Europe/Vienna')), ['width' => 'medium|short']], - ['time', (new \DateTime('2016-01-01 09:00:00'))->setTimezone(new \DateTimeZone('Europe/Vienna')), ['width' => 'short']] - )->willReturnOnConsecutiveCalls( - 'Fr.', - '01.01. 08:00', - '09:00' - ); - - $expected = 'Fr., 01.01. 08:00 - 09:00 (Europe/Vienna)'; - $actual = $this->service->generateWhenString($vevent); + // generate actual output + $actual = $this->service->buildBodyData($vCalendarNew->VEVENT[0], $vCalendarOld->VEVENT[0]); + // test output $this->assertEquals($expected, $actual); } @@ -248,4 +331,1870 @@ class IMipServiceTest extends TestCase { $occurrence = $this->service->getLastOccurrence($vCalendar); $this->assertEquals(1451606400, $occurrence); } + + public function testGenerateWhenStringSingular(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240701T000000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'full'] => 'July 1, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + [ + 'In the past on %1$s for the entire day', + ['July 1, 2024'], + 'In the past on July 1, 2024 for the entire day' + ], + [ + 'In the past on %1$s between %2$s - %3$s', + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + ]); + $this->l10n->method('n')->willReturnMap([ + // singular entire day + [ + 'In a minute on %1$s for the entire day', + 'In %n minutes on %1$s for the entire day', + 1, + ['July 1, 2024'], + 'In a minute on July 1, 2024 for the entire day' + ], + [ + 'In a hour on %1$s for the entire day', + 'In %n hours on %1$s for the entire day', + 1, + ['July 1, 2024'], + 'In a hour on July 1, 2024 for the entire day' + ], + [ + 'In a day on %1$s for the entire day', + 'In %n days on %1$s for the entire day', + 1, + ['July 1, 2024'], + 'In a day on July 1, 2024 for the entire day' + ], + [ + 'In a week on %1$s for the entire day', + 'In %n weeks on %1$s for the entire day', + 1, + ['July 1, 2024'], + 'In a week on July 1, 2024 for the entire day' + ], + [ + 'In a month on %1$s for the entire day', + 'In %n months on %1$s for the entire day', + 1, + ['July 1, 2024'], + 'In a month on July 1, 2024 for the entire day' + ], + [ + 'In a year on %1$s for the entire day', + 'In %n years on %1$s for the entire day', + 1, + ['July 1, 2024'], + 'In a year on July 1, 2024 for the entire day' + ], + // plural entire day + [ + 'In a minute on %1$s for the entire day', + 'In %n minutes on %1$s for the entire day', + 2, + ['July 1, 2024'], + 'In 2 minutes on July 1, 2024 for the entire day' + ], + [ + 'In a hour on %1$s for the entire day', + 'In %n hours on %1$s for the entire day', + 2, + ['July 1, 2024'], + 'In 2 hours on July 1, 2024 for the entire day' + ], + [ + 'In a day on %1$s for the entire day', + 'In %n days on %1$s for the entire day', + 2, + ['July 1, 2024'], + 'In 2 days on July 1, 2024 for the entire day' + ], + [ + 'In a week on %1$s for the entire day', + 'In %n weeks on %1$s for the entire day', + 2, + ['July 1, 2024'], + 'In 2 weeks on July 1, 2024 for the entire day' + ], + [ + 'In a month on %1$s for the entire day', + 'In %n months on %1$s for the entire day', + 2, + ['July 1, 2024'], + 'In 2 months on July 1, 2024 for the entire day' + ], + [ + 'In a year on %1$s for the entire day', + 'In %n years on %1$s for the entire day', + 2, + ['July 1, 2024'], + 'In 2 years on July 1, 2024 for the entire day' + ], + // singular partial day + [ + 'In a minute on %1$s between %2$s - %3$s', + 'In %n minutes on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a minute on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a hour on %1$s between %2$s - %3$s', + 'In %n hours on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a hour on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a day on %1$s between %2$s - %3$s', + 'In %n days on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a week on %1$s between %2$s - %3$s', + 'In %n weeks on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a week on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a month on %1$s between %2$s - %3$s', + 'In %n months on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a month on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a year on %1$s between %2$s - %3$s', + 'In %n years on %1$s between %2$s - %3$s', + 1, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In a year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + // plural partial day + [ + 'In a minute on %1$s between %2$s - %3$s', + 'In %n minutes on %1$s between %2$s - %3$s', + 2, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In 2 minutes on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a hour on %1$s between %2$s - %3$s', + 'In %n hours on %1$s between %2$s - %3$s', + 2, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In 2 hours on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a day on %1$s between %2$s - %3$s', + 'In %n days on %1$s between %2$s - %3$s', + 2, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In 2 days on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a week on %1$s between %2$s - %3$s', + 'In %n weeks on %1$s between %2$s - %3$s', + 2, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In 2 weeks on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a month on %1$s between %2$s - %3$s', + 'In %n months on %1$s between %2$s - %3$s', + 2, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In 2 months on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + [ + 'In a year on %1$s between %2$s - %3$s', + 'In %n years on %1$s between %2$s - %3$s', + 2, + ['July 1, 2024', '8:00 AM', '9:00 AM (America/Toronto)'], + 'In 2 years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)' + ], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + // past interval test dates + (new \DateTime('20240702T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240703T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240702T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240703T170000', (new \DateTimeZone('America/Toronto')))), + // minute interval test dates + (new \DateTime('20240701T075900', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240630T235900', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240701T075800', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240630T235800', (new \DateTimeZone('America/Toronto')))), + // hour interval test dates + (new \DateTime('20240701T070000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240630T230000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240701T060000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240630T220000', (new \DateTimeZone('America/Toronto')))), + // day interval test dates + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + // week interval test dates + (new \DateTime('20240621T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240621T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240614T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240614T170000', (new \DateTimeZone('America/Toronto')))), + // month interval test dates + (new \DateTime('20240530T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240530T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240430T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240430T170000', (new \DateTimeZone('America/Toronto')))), + // year interval test dates + (new \DateTime('20230630T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20230630T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20220630T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20220630T170000', (new \DateTimeZone('America/Toronto')))) + ); + + /** test partial day event in 1 day in the past*/ + $vCalendar = clone $this->vCalendar1a; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 day in the past*/ + $vCalendar = clone $this->vCalendar2; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In the past on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event in 2 days in the past*/ + $vCalendar = clone $this->vCalendar1a; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In the past on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 days in the past*/ + $vCalendar = clone $this->vCalendar2; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In the past on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event in 1 minute*/ + $vCalendar = clone $this->vCalendar1a; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In a minute on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 minute*/ + $vCalendar = clone $this->vCalendar2; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In a minute on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event in 2 minutes*/ + $vCalendar = clone $this->vCalendar1a; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In 2 minutes on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 minutes*/ + $vCalendar = clone $this->vCalendar2; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In 2 minutes on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event in 1 hour*/ + $vCalendar = clone $this->vCalendar1a; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In a hour on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 hour*/ + $vCalendar = clone $this->vCalendar2; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In a hour on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event in 2 hours*/ + $vCalendar = clone $this->vCalendar1a; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In 2 hours on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 hours*/ + $vCalendar = clone $this->vCalendar2; + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + $this->assertEquals( + 'In 2 hours on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 1 day*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 day*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 2 days*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 days*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 1 week*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a week on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 week*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a week on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 2 weeks*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 weeks on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 weeks*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 weeks on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 1 month*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a month on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 month*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a month on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 2 months*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 months on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 months*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 months on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 1 year*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a year on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 1 year*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a year on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test patrial day event in 2 years*/ + $vCalendar = clone $this->vCalendar1a; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 years on July 1, 2024 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event in 2 years*/ + $vCalendar = clone $this->vCalendar2; + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 years on July 1, 2024 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + } + + public function testGenerateWhenStringRecurringDaily(): void { + + // construct l10n return maps + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20240713T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 13, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['Every Day for the entire day', [], 'Every Day for the entire day'], + ['Every Day for the entire day until %1$s', ['July 13, 2024'], 'Every Day for the entire day until July 13, 2024'], + ['Every Day between %1$s - %2$s', ['8:00 AM', '9:00 AM (America/Toronto)'], 'Every Day between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every Day between %1$s - %2$s until %3$s', ['8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'], + ['Every %1$d Days for the entire day', [3], 'Every 3 Days for the entire day'], + ['Every %1$d Days for the entire day until %2$s', [3, 'July 13, 2024'], 'Every 3 Days for the entire day until July 13, 2024'], + ['Every %1$d Days between %2$s - %3$s', [3, '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every %1$d Days between %2$s - %3$s until %4$s', [3, '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'], + ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'], + ]); + + /** test partial day event with every day interval and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Day between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event with every day interval and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;UNTIL=20240713T080000Z'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Day between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event every 3rd day interval and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event with every 3rd day interval and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240713T080000Z'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 3 Days between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every day interval and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Day for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every day interval and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=1;UNTIL=20240713T080000Z'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Day for the entire day until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every 3rd day interval and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 3 Days for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every 3rd day interval and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=3;UNTIL=20240713T080000Z'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 3 Days for the entire day until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + } + + public function testGenerateWhenStringRecurringWeekly(): void { + + // construct l10n return maps + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20240722T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 13, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['Every Week on %1$s for the entire day', ['Monday, Wednesday, Friday'], 'Every Week on Monday, Wednesday, Friday for the entire day'], + ['Every Week on %1$s for the entire day until %2$s', ['Monday, Wednesday, Friday', 'July 13, 2024'], 'Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024'], + ['Every Week on %1$s between %2$s - %3$s', ['Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every Week on %1$s between %2$s - %3$s until %4$s', ['Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'], + ['Every %1$d Weeks on %2$s for the entire day', [2, 'Monday, Wednesday, Friday'], 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day'], + ['Every %1$d Weeks on %2$s for the entire day until %3$s', [2, 'Monday, Wednesday, Friday', 'July 13, 2024'], 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024'], + ['Every %1$d Weeks on %2$s between %3$s - %4$s', [2, 'Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every %1$d Weeks on %2$s between %3$s - %4$s until %5$s', [2, 'Monday, Wednesday, Friday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'], + ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'], + ['Monday', [], 'Monday'], + ['Wednesday', [], 'Wednesday'], + ['Friday', [], 'Friday'], + ]); + + /** test partial day event with every week interval on Mon, Wed, Fri and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event with every week interval on Mon, Wed, Fri and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240722T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Week on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event with every 2nd week interval on Mon, Wed, Fri and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test partial day event with every 2nd week interval on Mon, Wed, Fri and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;UNTIL=20240722T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Weeks on Monday, Wednesday, Friday between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every week interval on Mon, Wed, Fri and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Week on Monday, Wednesday, Friday for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every week interval on Mon, Wed, Fri and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;UNTIL=20240722T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Week on Monday, Wednesday, Friday for the entire day until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every 2nd week interval on Mon, Wed, Fri and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every 2nd week interval on Mon, Wed, Fri and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=WEEKLY;BYDAY=MO,WE,FR;INTERVAL=2;UNTIL=20240722T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Weeks on Monday, Wednesday, Friday for the entire day until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + } + + public function testGenerateWhenStringRecurringMonthly(): void { + + // construct l10n return maps + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20241231T080000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'December 31, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['Every Month on the %1$s for the entire day', ['1, 8'], 'Every Month on the 1, 8 for the entire day'], + ['Every Month on the %1$s for the entire day until %2$s', ['1, 8', 'December 31, 2024'], 'Every Month on the 1, 8 for the entire day until December 31, 2024'], + ['Every Month on the %1$s between %2$s - %3$s', ['1, 8', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every Month on the %1$s between %2$s - %3$s until %4$s', ['1, 8', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'], + ['Every %1$d Months on the %2$s for the entire day', [2, '1, 8'], 'Every 2 Months on the 1, 8 for the entire day'], + ['Every %1$d Months on the %2$s for the entire day until %3$s', [2, '1, 8', 'December 31, 2024'], 'Every 2 Months on the 1, 8 for the entire day until December 31, 2024'], + ['Every %1$d Months on the %2$s between %3$s - %4$s', [2, '1, 8', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [2, '1, 8', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'], + ['Every Month on the %1$s for the entire day', ['First Sunday, Saturday'], 'Every Month on the First Sunday, Saturday for the entire day'], + ['Every Month on the %1$s for the entire day until %2$s', ['First Sunday, Saturday', 'December 31, 2024'], 'Every Month on the First Sunday, Saturday for the entire day until December 31, 2024'], + ['Every Month on the %1$s between %2$s - %3$s', ['First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every Month on the %1$s between %2$s - %3$s until %4$s', ['First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'], + ['Every %1$d Months on the %2$s for the entire day', [2, 'First Sunday, Saturday'], 'Every 2 Months on the First Sunday, Saturday for the entire day'], + ['Every %1$d Months on the %2$s for the entire day until %3$s', [2, 'First Sunday, Saturday', 'December 31, 2024'], 'Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024'], + ['Every %1$d Months on the %2$s between %3$s - %4$s', [2, 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every %1$d Months on the %2$s between %3$s - %4$s until %5$s', [2, 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'December 31, 2024'], 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024'], + ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'], + ['Saturday', [], 'Saturday'], + ['Sunday', [], 'Sunday'], + ['First', [], 'First'], + ]); + + /** test absolute partial day event with every month interval on 1st, 8th and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute partial day event with every Month interval on 1st, 8th and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute partial day event with every 2nd Month interval on 1st, 8th and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute partial day event with every 2nd Month interval on 1st, 8th and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the 1, 8 between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every Month interval on 1st, 8th and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the 1, 8 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every Month interval on 1st, 8th and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the 1, 8 for the entire day until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every 2nd Month interval on 1st, 8th and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the 1, 8 for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every 2nd Month interval on 1st, 8th and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYMONTHDAY=1,8;INTERVAL=2;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the 1, 8 for the entire day until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every month interval on the 1st Saturday, Sunday and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every Month interval on the 1st Saturday, Sunday and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every 2nd Month interval on the 1st Saturday, Sunday and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every 2nd Month interval on the 1st Saturday, Sunday and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every Month interval on the 1st Saturday, Sunday and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the First Sunday, Saturday for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every Month interval on the 1st Saturday, Sunday and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Month on the First Sunday, Saturday for the entire day until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every 2nd Month interval on the 1st Saturday, Sunday and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the First Sunday, Saturday for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every 2nd Month interval on the 1st Saturday, Sunday and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=MONTHLY;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20241231T080000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Months on the First Sunday, Saturday for the entire day until December 31, 2024', + $this->service->generateWhenString($eventReader) + ); + + } + + public function testGenerateWhenStringRecurringYearly(): void { + + // construct l10n return maps + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20260731T040000', (new \DateTimeZone('UTC')))) && $v3 == ['width' => 'long'] => 'July 31, 2026' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['Every Year in %1$s on the %2$s for the entire day', ['July', '1st'], 'Every Year in July on the 1st for the entire day'], + ['Every Year in %1$s on the %2$s for the entire day until %3$s', ['July', '1st', 'July 31, 2026'], 'Every Year in July on the 1st for the entire day until July 31, 2026'], + ['Every Year in %1$s on the %2$s between %3$s - %4$s', ['July', '1st', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', ['July', '1st', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'], + ['Every %1$d Years in %2$s on the %3$s for the entire day', [2, 'July', '1st'], 'Every 2 Years in July on the 1st for the entire day'], + ['Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [2, 'July', '1st', 'July 31, 2026'], 'Every 2 Years in July on the 1st for the entire day until July 31, 2026'], + ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [2, 'July', '1st', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [2, 'July', '1st', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'], + ['Every Year in %1$s on the %2$s for the entire day', ['July', 'First Sunday, Saturday'], 'Every Year in July on the First Sunday, Saturday for the entire day'], + ['Every Year in %1$s on the %2$s for the entire day until %3$s', ['July', 'First Sunday, Saturday', 'July 31, 2026'], 'Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026'], + ['Every Year in %1$s on the %2$s between %3$s - %4$s', ['July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every Year in %1$s on the %2$s between %3$s - %4$s until %5$s', ['July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'], + ['Every %1$d Years in %2$s on the %3$s for the entire day', [2, 'July', 'First Sunday, Saturday'], 'Every 2 Years in July on the First Sunday, Saturday for the entire day'], + ['Every %1$d Years in %2$s on the %3$s for the entire day until %4$s', [2, 'July', 'First Sunday, Saturday', 'July 31, 2026'], 'Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026'], + ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s', [2, 'July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)'], 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)'], + ['Every %1$d Years in %2$s on the %3$s between %4$s - %5$s until %6$s', [2, 'July', 'First Sunday, Saturday', '8:00 AM', '9:00 AM (America/Toronto)', 'July 31, 2026'], 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026'], + ['Could not generate event recurrence statement', [], 'Could not generate event recurrence statement'], + ['July', [], 'July'], + ['Saturday', [], 'Saturday'], + ['Sunday', [], 'Sunday'], + ['First', [], 'First'], + ]); + + /** test absolute partial day event with every year interval on July 1 and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute partial day event with every year interval on July 1 and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;UNTIL=20260731T040000Z'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute partial day event with every 2nd year interval on July 1 and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute partial day event with every 2nd year interval on July 1 and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the 1st between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every year interval on July 1 and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the 1st for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every year interval on July 1 and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the 1st for the entire day until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every 2nd year interval on July 1 and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the 1st for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test absolute entire day event with every 2nd year interval on July 1 and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;INTERVAL=2;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the 1st for the entire day until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every year interval on the 1st Saturday, Sunday in July and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every year interval on the 1st Saturday, Sunday in July and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every 2nd year interval on the 1st Saturday, Sunday in July and no conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto)', + $this->service->generateWhenString($eventReader) + ); + + /** test relative partial day event with every 2nd year interval on the 1st Saturday, Sunday in July and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the First Sunday, Saturday between 8:00 AM - 9:00 AM (America/Toronto) until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every year interval on the 1st Saturday, Sunday in July and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the First Sunday, Saturday for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every year interval on the 1st Saturday, Sunday in July and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every Year in July on the First Sunday, Saturday for the entire day until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every 2nd year interval on the 1st Saturday, Sunday in July and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the First Sunday, Saturday for the entire day', + $this->service->generateWhenString($eventReader) + ); + + /** test relative entire day event with every 2nd year interval on the 1st Saturday, Sunday in July and conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=YEARLY;BYMONTH=7;BYDAY=SU,SA;BYSETPOS=1;INTERVAL=2;UNTIL=20260731T040000Z;'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'Every 2 Years in July on the First Sunday, Saturday for the entire day until July 31, 2026', + $this->service->generateWhenString($eventReader) + ); + + } + + public function testGenerateWhenStringRecurringFixed(): void { + + // construct l10n return maps + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'time' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '8:00 AM', + $v1 === 'time' && $v2 == (new \DateTime('20240701T090000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'short'] => '9:00 AM', + $v1 === 'date' && $v2 == (new \DateTime('20240713T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 13, 2024' + }; + } + ); + $this->l10n->method('t')->willReturnMap([ + ['On specific dates for the entire day until %1$s', ['July 13, 2024'], 'On specific dates for the entire day until July 13, 2024'], + ['On specific dates between %1$s - %2$s until %3$s', ['8:00 AM', '9:00 AM (America/Toronto)', 'July 13, 2024'], 'On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024'], + ]); + + /** test partial day event with every day interval and conclusion*/ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240709T080000,20240713T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'On specific dates between 8:00 AM - 9:00 AM (America/Toronto) until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + /** test entire day event with every day interval and no conclusion*/ + $vCalendar = clone $this->vCalendar2; + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000,20240709T080000,20240713T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'On specific dates for the entire day until July 13, 2024', + $this->service->generateWhenString($eventReader) + ); + + } + + public function testGenerateOccurringStringWithRrule(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024' + }; + } + ); + $this->l10n->method('n')->willReturnMap([ + // singular + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 1, + ['July 1, 2024'], + 'In a day on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 1, + ['July 1, 2024', 'July 3, 2024'], + 'In a day on July 1, 2024 then on July 3, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 1, + ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'], + 'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024' + ], + // plural + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 2, + ['July 1, 2024'], + 'In 2 days on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 2, + ['July 1, 2024', 'July 3, 2024'], + 'In 2 days on July 1, 2024 then on July 3, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 2, + ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'], + 'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024' + ], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader) + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 3, 2024', + $this->service->generateOccurringString($eventReader) + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024', + $this->service->generateOccurringString($eventReader) + ); + + /** test patrial day recurring event in 2 days with single occurrence remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader) + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 3, 2024', + $this->service->generateOccurringString($eventReader) + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024', + $this->service->generateOccurringString($eventReader) + ); + } + + public function testGenerateOccurringStringWithRdate(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240703T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 3, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024' + }; + } + ); + $this->l10n->method('n')->willReturnMap([ + // singular + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 1, + ['July 1, 2024'], + 'In a day on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 1, + ['July 1, 2024', 'July 3, 2024'], + 'In a day on July 1, 2024 then on July 3, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 1, + ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'], + 'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024' + ], + // plural + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 2, + ['July 1, 2024'], + 'In 2 days on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 2, + ['July 1, 2024', 'July 3, 2024'], + 'In 2 days on July 1, 2024 then on July 3, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 2, + ['July 1, 2024', 'July 3, 2024', 'July 5, 2024'], + 'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024' + ], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with single occurrence remaining' + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 3, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with two occurrences remaining' + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000,20240703T080000,20240705T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 3, 2024 and July 5, 2024', + $this->service->generateOccurringString($eventReader), + '' + ); + + /** test patrial day recurring event in 2 days with single occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + '' + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 3, 2024', + $this->service->generateOccurringString($eventReader), + '' + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RDATE', '20240701T080000'); + $vCalendar->VEVENT[0]->add('RDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('RDATE', '20240705T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 3, 2024 and July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with three occurrences remaining' + ); + } + + public function testGenerateOccurringStringWithOneExdate(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240707T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 7, 2024' + }; + } + ); + $this->l10n->method('n')->willReturnMap([ + // singular + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 1, + ['July 1, 2024'], + 'In a day on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 1, + ['July 1, 2024', 'July 5, 2024'], + 'In a day on July 1, 2024 then on July 5, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 1, + ['July 1, 2024', 'July 5, 2024', 'July 7, 2024'], + 'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024' + ], + // plural + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 2, + ['July 1, 2024'], + 'In 2 days on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 2, + ['July 1, 2024', 'July 5, 2024'], + 'In 2 days on July 1, 2024 then on July 5, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 2, + ['July 1, 2024', 'July 5, 2024', 'July 7, 2024'], + 'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024' + ], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with single occurrence remaining and one exception' + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with two occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with three occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 1 day with four occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024 and July 7, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with four occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with single occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with single occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with two occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with three occurrences remaining and one exception' + ); + + /** test patrial day recurring event in 2 days with four occurrences remaining and one exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=4'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024 and July 7, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with four occurrences remaining and one exception' + ); + } + + public function testGenerateOccurringStringWithTwoExdate(): void { + + // construct l10n return(s) + $this->l10n->method('l')->willReturnCallback( + function ($v1, $v2, $v3) { + return match (true) { + $v1 === 'date' && $v2 == (new \DateTime('20240701T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 1, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240705T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 5, 2024', + $v1 === 'date' && $v2 == (new \DateTime('20240709T080000', (new \DateTimeZone('America/Toronto')))) && $v3 == ['width' => 'long'] => 'July 9, 2024' + }; + } + ); + $this->l10n->method('n')->willReturnMap([ + // singular + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 1, + ['July 1, 2024'], + 'In a day on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 1, + ['July 1, 2024', 'July 5, 2024'], + 'In a day on July 1, 2024 then on July 5, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 1, + ['July 1, 2024', 'July 5, 2024', 'July 9, 2024'], + 'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024' + ], + // plural + [ + 'In a day on %1$s', + 'In %n days on %1$s', + 2, + ['July 1, 2024'], + 'In 2 days on July 1, 2024' + ], + [ + 'In a day on %1$s then on %2$s', + 'In %n days on %1$s then on %2$s', + 2, + ['July 1, 2024', 'July 5, 2024'], + 'In 2 days on July 1, 2024 then on July 5, 2024' + ], + [ + 'In a day on %1$s then on %2$s and %3$s', + 'In %n days on %1$s then on %2$s and %3$s', + 2, + ['July 1, 2024', 'July 5, 2024', 'July 9, 2024'], + 'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024' + ], + ]); + + // construct time factory return(s) + $this->timeFactory->method('getDateTime')->willReturnOnConsecutiveCalls( + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240629T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + (new \DateTime('20240628T170000', (new \DateTimeZone('America/Toronto')))), + ); + + /** test patrial day recurring event in 1 day with single occurrence remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with single occurrence remaining and two exception' + ); + + /** test patrial day recurring event in 1 day with two occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with two occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 1 day with three occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with three occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 1 day with four occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In a day on July 1, 2024 then on July 5, 2024 and July 9, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 1 day with four occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with single occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=1'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with single occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with two occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=2'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with two occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with three occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=3'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with three occurrences remaining and two exception' + ); + + /** test patrial day recurring event in 2 days with five occurrences remaining and two exception */ + $vCalendar = clone $this->vCalendar1a; + $vCalendar->VEVENT[0]->add('RRULE', 'FREQ=DAILY;INTERVAL=2;COUNT=5'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240703T080000'); + $vCalendar->VEVENT[0]->add('EXDATE', '20240707T080000'); + // construct event reader + $eventReader = new EventReader($vCalendar, $vCalendar->VEVENT[0]->UID->getValue()); + // test output + $this->assertEquals( + 'In 2 days on July 1, 2024 then on July 5, 2024 and July 9, 2024', + $this->service->generateOccurringString($eventReader), + 'test patrial day recurring event in 2 days with five occurrences remaining and two exception' + ); + } + } diff --git a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php index c727079a367..524ac556e19 100644 --- a/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Schedule/PluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -8,6 +10,7 @@ namespace OCA\DAV\Tests\unit\CalDAV\Schedule; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; use OCA\DAV\CalDAV\CalendarHome; +use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\CalDAV\Plugin as CalDAVPlugin; use OCA\DAV\CalDAV\Schedule\Plugin; use OCA\DAV\CalDAV\Trashbin\Plugin as TrashbinPlugin; @@ -15,70 +18,68 @@ use OCP\IConfig; use OCP\IL10N; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; +use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\PropFind; use Sabre\DAV\Server; use Sabre\DAV\Tree; use Sabre\DAV\Xml\Property\Href; use Sabre\DAV\Xml\Property\LocalHref; use Sabre\DAVACL\IPrincipal; +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; use Sabre\HTTP\ResponseInterface; +use Sabre\VObject\Component\VCalendar; +use Sabre\VObject\ITip\Message; use Sabre\VObject\Parameter; use Sabre\VObject\Property\ICalendar\CalAddress; use Sabre\Xml\Service; use Test\TestCase; class PluginTest extends TestCase { - /** @var Plugin */ - private $plugin; - /** @var Server|MockObject */ - private $server; - - /** @var IConfig|MockObject */ - private $config; - - /** @var MockObject|LoggerInterface */ - private $logger; + private Plugin $plugin; + private Server&MockObject $server; + private IConfig&MockObject $config; + private LoggerInterface&MockObject $logger; + private DefaultCalendarValidator $calendarValidator; protected function setUp(): void { parent::setUp(); - $this->server = $this->createMock(Server::class); $this->config = $this->createMock(IConfig::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->calendarValidator = new DefaultCalendarValidator(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->server->httpResponse = $response; + $this->server = $this->createMock(Server::class); + $this->server->httpResponse = $this->createMock(ResponseInterface::class); $this->server->xml = new Service(); - $this->logger = $this->createMock(LoggerInterface::class); - - $this->plugin = new Plugin($this->config, $this->logger); + $this->plugin = new Plugin($this->config, $this->logger, $this->calendarValidator); $this->plugin->initialize($this->server); } public function testInitialize(): void { - $plugin = new Plugin($this->config, $this->logger); - - $this->server->expects($this->exactly(10)) + $calls = [ + // Sabre\CalDAV\Schedule\Plugin events + ['method:POST', [$this->plugin, 'httpPost'], 100], + ['propFind', [$this->plugin, 'propFind'], 100], + ['propPatch', [$this->plugin, 'propPatch'], 100], + ['calendarObjectChange', [$this->plugin, 'calendarObjectChange'], 100], + ['beforeUnbind', [$this->plugin, 'beforeUnbind'], 100], + ['schedule', [$this->plugin, 'scheduleLocalDelivery'], 100], + ['getSupportedPrivilegeSet', [$this->plugin, 'getSupportedPrivilegeSet'], 100], + // OCA\DAV\CalDAV\Schedule\Plugin events + ['propFind', [$this->plugin, 'propFindDefaultCalendarUrl'], 90], + ['afterWriteContent', [$this->plugin, 'dispatchSchedulingResponses'], 100], + ['afterCreateFile', [$this->plugin, 'dispatchSchedulingResponses'], 100], + ]; + $this->server->expects($this->exactly(count($calls))) ->method('on') - ->withConsecutive( - // Sabre\CalDAV\Schedule\Plugin events - ['method:POST', [$plugin, 'httpPost']], - ['propFind', [$plugin, 'propFind']], - ['propPatch', [$plugin, 'propPatch']], - ['calendarObjectChange', [$plugin, 'calendarObjectChange']], - ['beforeUnbind', [$plugin, 'beforeUnbind']], - ['schedule', [$plugin, 'scheduleLocalDelivery']], - ['getSupportedPrivilegeSet', [$plugin, 'getSupportedPrivilegeSet']], - // OCA\DAV\CalDAV\Schedule\Plugin events - ['propFind', [$plugin, 'propFindDefaultCalendarUrl'], 90], - ['afterWriteContent', [$plugin, 'dispatchSchedulingResponses']], - ['afterCreateFile', [$plugin, 'dispatchSchedulingResponses']] - ); - - $plugin->initialize($this->server); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); + + $this->plugin->initialize($this->server); } public function testGetAddressesForPrincipal(): void { @@ -104,7 +105,6 @@ class PluginTest extends TestCase { $this->assertSame(['lukas@nextcloud.com', 'rullzer@nextcloud.com'], $result); } - public function testGetAddressesForPrincipalEmpty(): void { $this->server ->expects($this->once()) @@ -160,7 +160,7 @@ class PluginTest extends TestCase { $this->assertFalse($this->invokePrivate($this->plugin, 'getAttendeeRSVP', [$property3])); } - public function propFindDefaultCalendarUrlProvider(): array { + public static function propFindDefaultCalendarUrlProvider(): array { return [ [ 'principals/users/myuser', @@ -251,9 +251,7 @@ class PluginTest extends TestCase { ]; } - /** - * @dataProvider propFindDefaultCalendarUrlProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('propFindDefaultCalendarUrlProvider')] public function testPropFindDefaultCalendarUrl(string $principalUri, ?string $calendarHome, bool $isResource, string $calendarUri, string $displayName, bool $exists, bool $deleted = false, bool $hasExistingCalendars = false, bool $propertiesForPath = true): void { $propFind = new PropFind( $principalUri, @@ -262,7 +260,7 @@ class PluginTest extends TestCase { ], 0 ); - /** @var IPrincipal|MockObject $node */ + /** @var IPrincipal&MockObject $node */ $node = $this->getMockBuilder(IPrincipal::class) ->disableOriginalConstructor() ->getMock(); @@ -340,12 +338,12 @@ class PluginTest extends TestCase { if (!$exists || $deleted) { if (!$hasExistingCalendars) { $calendarBackend->expects($this->once()) - ->method('createCalendar') - ->with($principalUri, $calendarUri, [ - '{DAV:}displayname' => $displayName, - ]); + ->method('createCalendar') + ->with($principalUri, $calendarUri, [ + '{DAV:}displayname' => $displayName, + ]); - $calendarHomeObject->expects($this->once()) + $calendarHomeObject->expects($this->exactly($deleted ? 2 : 1)) ->method('getCalDAVBackend') ->with() ->willReturn($calendarBackend); @@ -359,7 +357,7 @@ class PluginTest extends TestCase { } } - /** @var Tree|MockObject $tree */ + /** @var Tree&MockObject $tree */ $tree = $this->createMock(Tree::class); $tree->expects($this->once()) ->method('getNodeForPath') @@ -373,7 +371,7 @@ class PluginTest extends TestCase { $this->server->expects($this->once()) ->method('getPropertiesForPath') - ->with($calendarHome .'/' . $calendarUri, [], 1) + ->with($calendarHome . '/' . $calendarUri, [], 1) ->willReturn($properties); $this->plugin->propFindDefaultCalendarUrl($propFind, $node); @@ -385,6 +383,388 @@ class PluginTest extends TestCase { /** @var LocalHref $result */ $result = $propFind->get(Plugin::SCHEDULE_DEFAULT_CALENDAR_URL); - $this->assertEquals('/remote.php/dav/'. $calendarHome . '/' . $calendarUri, $result->getHref()); + $this->assertEquals('/remote.php/dav/' . $calendarHome . '/' . $calendarUri, $result->getHref()); + } + + /** + * Test Calendar Event Creation for Personal Calendar + * + * Should generate 2 messages for attendees User 2 and User External + */ + public function testCalendarObjectChangePersonalCalendarCreate(): void { + + // define place holders + /** @var Message[] $iTipMessages */ + $iTipMessages = []; + // construct calendar node + $calendarNode = new Calendar( + $this->createMock(BackendInterface::class), + [ + 'uri' => 'personal', + 'principaluri' => 'principals/users/user1', + '{DAV:}displayname' => 'Calendar Shared By User1', + ], + $this->createMock(IL10N::class), + $this->config, + $this->logger + ); + // construct server request object + $request = new Request( + 'PUT', + '/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics' + ); + $request->setBaseUrl('/remote.php/dav/'); + // construct server response object + $response = new Response(); + // construct server tree object + $tree = $this->createMock(Tree::class); + $tree->expects($this->once()) + ->method('getNodeForPath') + ->with('calendars/user1/personal') + ->willReturn($calendarNode); + // construct server properties and returns + $this->server->httpRequest = $request; + $this->server->tree = $tree; + $this->server->expects($this->exactly(1))->method('getProperties') + ->willReturnMap([ + [ + 'principals/users/user1', + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'], + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref( + ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/'] + )] + ] + ]); + $this->server->expects($this->exactly(2))->method('emit')->willReturnCallback( + function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) { + $this->assertEquals('schedule', $eventName); + $this->assertCount(1, $arguments); + $iTipMessages[] = $arguments[0]; + return true; + } + ); + // construct calendar with a 1 hour event and same start/end time zones + $vCalendar = new VCalendar(); + $vEvent = $vCalendar->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Recurring Event'); + $vEvent->add('ORGANIZER', 'mailto:user1@testing.local', ['CN' => 'User One']); + $vEvent->add('ATTENDEE', 'mailto:user2@testing.local', [ + 'CN' => 'User Two', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $vEvent->add('ATTENDEE', 'mailto:user@external.local', [ + 'CN' => 'User External', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + // define flags + $newFlag = true; + $modifiedFlag = false; + // execute method + $this->plugin->calendarObjectChange( + $request, + $response, + $vCalendar, + 'calendars/user1/personal', + $modifiedFlag, + $newFlag + ); + // test for correct iTip message count + $this->assertCount(2, $iTipMessages); + // test for Sharer Attendee + $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->sender); + $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->recipient); + $this->assertTrue($iTipMessages[0]->significantChange); + // test for External Attendee + $this->assertEquals('mailto:user1@testing.local', $iTipMessages[1]->sender); + $this->assertEquals('mailto:user@external.local', $iTipMessages[1]->recipient); + $this->assertTrue($iTipMessages[1]->significantChange); + + } + + /** + * Test Calendar Event Creation for Shared Calendar as Sharer/Owner + * + * Should generate 3 messages for attendees User 2 (Sharee), User 3 (Non-Sharee) and User External + */ + public function testCalendarObjectChangeSharedCalendarSharerCreate(): void { + + // define place holders + /** @var Message[] $iTipMessages */ + $iTipMessages = []; + // construct calendar node + $calendarNode = new Calendar( + $this->createMock(BackendInterface::class), + [ + 'uri' => 'calendar_shared_by_user1', + 'principaluri' => 'principals/users/user1', + '{DAV:}displayname' => 'Calendar Shared By User1', + '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1' + ], + $this->createMock(IL10N::class), + $this->config, + $this->logger + ); + // construct server request object + $request = new Request( + 'PUT', + '/remote.php/dav/calendars/user1/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics' + ); + $request->setBaseUrl('/remote.php/dav/'); + // construct server response object + $response = new Response(); + // construct server tree object + $tree = $this->createMock(Tree::class); + $tree->expects($this->once()) + ->method('getNodeForPath') + ->with('calendars/user1/calendar_shared_by_user1') + ->willReturn($calendarNode); + // construct server properties and returns + $this->server->httpRequest = $request; + $this->server->tree = $tree; + $this->server->expects($this->exactly(1))->method('getProperties') + ->willReturnMap([ + [ + 'principals/users/user1', + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'], + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref( + ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/'] + )] + ] + ]); + $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback( + function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) { + $this->assertEquals('schedule', $eventName); + $this->assertCount(1, $arguments); + $iTipMessages[] = $arguments[0]; + return true; + } + ); + // construct calendar with a 1 hour event and same start/end time zones + $vCalendar = new VCalendar(); + $vEvent = $vCalendar->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Recurring Event'); + $vEvent->add('ORGANIZER', 'mailto:user1@testing.local', ['CN' => 'User One']); + $vEvent->add('ATTENDEE', 'mailto:user2@testing.local', [ + 'CN' => 'User Two', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $vEvent->add('ATTENDEE', 'mailto:user3@testing.local', [ + 'CN' => 'User Three', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $vEvent->add('ATTENDEE', 'mailto:user@external.local', [ + 'CN' => 'User External', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + // define flags + $newFlag = true; + $modifiedFlag = false; + // execute method + $this->plugin->calendarObjectChange( + $request, + $response, + $vCalendar, + 'calendars/user1/calendar_shared_by_user1', + $modifiedFlag, + $newFlag + ); + // test for correct iTip message count + $this->assertCount(3, $iTipMessages); + // test for Sharer Attendee + $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->sender); + $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->recipient); + $this->assertTrue($iTipMessages[0]->significantChange); + // test for Non Shee Attendee + $this->assertEquals('mailto:user1@testing.local', $iTipMessages[1]->sender); + $this->assertEquals('mailto:user3@testing.local', $iTipMessages[1]->recipient); + $this->assertTrue($iTipMessages[1]->significantChange); + // test for External Attendee + $this->assertEquals('mailto:user1@testing.local', $iTipMessages[2]->sender); + $this->assertEquals('mailto:user@external.local', $iTipMessages[2]->recipient); + $this->assertTrue($iTipMessages[2]->significantChange); + + } + + /** + * Test Calendar Event Creation for Shared Calendar as Shree + * + * Should generate 3 messages for attendees User 1 (Sharer/Owner), User 3 (Non-Sharee) and User External + */ + public function testCalendarObjectChangeSharedCalendarShreeCreate(): void { + + // define place holders + /** @var Message[] $iTipMessages */ + $iTipMessages = []; + // construct calendar node + $calendarNode = new Calendar( + $this->createMock(BackendInterface::class), + [ + 'uri' => 'calendar_shared_by_user1', + 'principaluri' => 'principals/users/user2', + '{DAV:}displayname' => 'Calendar Shared By User1', + '{http://owncloud.org/ns}owner-principal' => 'principals/users/user1' + ], + $this->createMock(IL10N::class), + $this->config, + $this->logger + ); + // construct server request object + $request = new Request( + 'PUT', + '/remote.php/dav/calendars/user2/calendar_shared_by_user1/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics' + ); + $request->setBaseUrl('/remote.php/dav/'); + // construct server response object + $response = new Response(); + // construct server tree object + $tree = $this->createMock(Tree::class); + $tree->expects($this->once()) + ->method('getNodeForPath') + ->with('calendars/user2/calendar_shared_by_user1') + ->willReturn($calendarNode); + // construct server properties and returns + $this->server->httpRequest = $request; + $this->server->tree = $tree; + $this->server->expects($this->exactly(2))->method('getProperties') + ->willReturnMap([ + [ + 'principals/users/user1', + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'], + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref( + ['mailto:user1@testing.local','/remote.php/dav/principals/users/user1/'] + )] + ], + [ + 'principals/users/user2', + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set'], + ['{urn:ietf:params:xml:ns:caldav}calendar-user-address-set' => new LocalHref( + ['mailto:user2@testing.local','/remote.php/dav/principals/users/user2/'] + )] + ] + ]); + $this->server->expects($this->exactly(3))->method('emit')->willReturnCallback( + function (string $eventName, array $arguments = [], ?callable $continueCallBack = null) use (&$iTipMessages) { + $this->assertEquals('schedule', $eventName); + $this->assertCount(1, $arguments); + $iTipMessages[] = $arguments[0]; + return true; + } + ); + // construct calendar with a 1 hour event and same start/end time zones + $vCalendar = new VCalendar(); + $vEvent = $vCalendar->add('VEVENT', []); + $vEvent->UID->setValue('96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Recurring Event'); + $vEvent->add('ORGANIZER', 'mailto:user2@testing.local', ['CN' => 'User Two']); + $vEvent->add('ATTENDEE', 'mailto:user1@testing.local', [ + 'CN' => 'User One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $vEvent->add('ATTENDEE', 'mailto:user3@testing.local', [ + 'CN' => 'User Three', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $vEvent->add('ATTENDEE', 'mailto:user@external.local', [ + 'CN' => 'User External', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + // define flags + $newFlag = true; + $modifiedFlag = false; + // execute method + $this->plugin->calendarObjectChange( + $request, + $response, + $vCalendar, + 'calendars/user2/calendar_shared_by_user1', + $modifiedFlag, + $newFlag + ); + // test for correct iTip message count + $this->assertCount(3, $iTipMessages); + // test for Sharer Attendee + $this->assertEquals('mailto:user2@testing.local', $iTipMessages[0]->sender); + $this->assertEquals('mailto:user1@testing.local', $iTipMessages[0]->recipient); + $this->assertTrue($iTipMessages[0]->significantChange); + // test for Non Shee Attendee + $this->assertEquals('mailto:user2@testing.local', $iTipMessages[1]->sender); + $this->assertEquals('mailto:user3@testing.local', $iTipMessages[1]->recipient); + $this->assertTrue($iTipMessages[1]->significantChange); + // test for External Attendee + $this->assertEquals('mailto:user2@testing.local', $iTipMessages[2]->sender); + $this->assertEquals('mailto:user@external.local', $iTipMessages[2]->recipient); + $this->assertTrue($iTipMessages[2]->significantChange); + + } + + /** + * Test Calendar Event Creation with iTip and iMip disabled + * + * Should generate 2 messages for attendees User 2 and User External + */ + public function testCalendarObjectChangeWithSchedulingDisabled(): void { + // construct server request + $request = new Request( + 'PUT', + '/remote.php/dav/calendars/user1/personal/B0DC78AE-6DD7-47E3-80BE-89F23E6D5383.ics', + ['x-nc-scheduling' => 'false'] + ); + $request->setBaseUrl('/remote.php/dav/'); + // construct server response + $response = new Response(); + // construct server tree + $tree = $this->createMock(Tree::class); + $tree->expects($this->never()) + ->method('getNodeForPath'); + // construct server properties and returns + $this->server->httpRequest = $request; + $this->server->tree = $tree; + // construct empty calendar event + $vCalendar = new VCalendar(); + $vEvent = $vCalendar->add('VEVENT', []); + // define flags + $newFlag = true; + $modifiedFlag = false; + // execute method + $this->plugin->calendarObjectChange( + $request, + $response, + $vCalendar, + 'calendars/user1/personal', + $modifiedFlag, + $newFlag + ); } } diff --git a/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php b/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php index cbfd4639ed7..02ae504bce0 100644 --- a/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php +++ b/apps/dav/tests/unit/CalDAV/Search/Request/CalendarSearchReportTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -10,9 +12,9 @@ use Sabre\Xml\Reader; use Test\TestCase; class CalendarSearchReportTest extends TestCase { - private $elementMap = [ - '{http://nextcloud.com/ns}calendar-search' => - 'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport', + private array $elementMap = [ + '{http://nextcloud.com/ns}calendar-search' + => 'OCA\\DAV\\CalDAV\\Search\\Xml\\Request\\CalendarSearchReport', ]; public function testFoo(): void { @@ -112,7 +114,7 @@ XML; ); } - + public function testRequiresCompFilter(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('{http://nextcloud.com/ns}prop-filter or {http://nextcloud.com/ns}param-filter given without any {http://nextcloud.com/ns}comp-filter'); @@ -139,7 +141,7 @@ XML; $this->parse($xml); } - + public function testRequiresFilter(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('The {http://nextcloud.com/ns}filter element is required for this request'); @@ -157,7 +159,7 @@ XML; $this->parse($xml); } - + public function testNoSearchTerm(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('{http://nextcloud.com/ns}search-term is required for this request'); @@ -185,7 +187,7 @@ XML; $this->parse($xml); } - + public function testCompOnly(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('At least one{http://nextcloud.com/ns}prop-filter or {http://nextcloud.com/ns}param-filter is required for this request'); @@ -313,7 +315,7 @@ XML; ); } - private function parse($xml, array $elementMap = []) { + private function parse(string $xml, array $elementMap = []): array { $reader = new Reader(); $reader->elementMap = array_merge($this->elementMap, $elementMap); $reader->xml($xml); diff --git a/apps/dav/tests/unit/CalDAV/Search/SearchPluginTest.php b/apps/dav/tests/unit/CalDAV/Search/SearchPluginTest.php index ae4519e2542..e576fbae34c 100644 --- a/apps/dav/tests/unit/CalDAV/Search/SearchPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Search/SearchPluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -14,7 +16,7 @@ use Test\TestCase; class SearchPluginTest extends TestCase { protected $server; - /** @var \OCA\DAV\CalDAV\Search\SearchPlugin $plugin */ + /** @var SearchPlugin $plugin */ protected $plugin; protected function setUp(): void { diff --git a/apps/dav/tests/unit/CalDAV/Security/RateLimitingPluginTest.php b/apps/dav/tests/unit/CalDAV/Security/RateLimitingPluginTest.php index fc0bd1502b2..a5cf6a23c66 100644 --- a/apps/dav/tests/unit/CalDAV/Security/RateLimitingPluginTest.php +++ b/apps/dav/tests/unit/CalDAV/Security/RateLimitingPluginTest.php @@ -24,11 +24,11 @@ use Test\TestCase; class RateLimitingPluginTest extends TestCase { - private Limiter|MockObject $limiter; - private CalDavBackend|MockObject $caldavBackend; - private IUserManager|MockObject $userManager; - private LoggerInterface|MockObject $logger; - private IAppConfig|MockObject $config; + private Limiter&MockObject $limiter; + private CalDavBackend&MockObject $caldavBackend; + private IUserManager&MockObject $userManager; + private LoggerInterface&MockObject $logger; + private IAppConfig&MockObject $config; private string $userId = 'user123'; private RateLimitingPlugin $plugin; diff --git a/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php b/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php index 78e76cf507d..ee0ef2334ec 100644 --- a/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/Status/StatusServiceTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -25,14 +27,14 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class StatusServiceTest extends TestCase { - private ITimeFactory|MockObject $timeFactory; - private IManager|MockObject $calendarManager; - private IUserManager|MockObject $userManager; - private UserStatusService|MockObject $userStatusService; - private IAvailabilityCoordinator|MockObject $availabilityCoordinator; - private ICacheFactory|MockObject $cacheFactory; - private LoggerInterface|MockObject $logger; - private ICache|MockObject $cache; + private ITimeFactory&MockObject $timeFactory; + private IManager&MockObject $calendarManager; + private IUserManager&MockObject $userManager; + private UserStatusService&MockObject $userStatusService; + private IAvailabilityCoordinator&MockObject $availabilityCoordinator; + private ICacheFactory&MockObject $cacheFactory; + private LoggerInterface&MockObject $logger; + private ICache&MockObject $cache; private StatusService $service; protected function setUp(): void { diff --git a/apps/dav/tests/unit/CalDAV/TimeZoneFactoryTest.php b/apps/dav/tests/unit/CalDAV/TimeZoneFactoryTest.php new file mode 100644 index 00000000000..2d6d0e86358 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/TimeZoneFactoryTest.php @@ -0,0 +1,51 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\CalDAV; + +use DateTimeZone; +use OCA\DAV\CalDAV\TimeZoneFactory; +use Test\TestCase; + +class TimeZoneFactoryTest extends TestCase { + + private TimeZoneFactory $factory; + + protected function setUp(): void { + parent::setUp(); + + $this->factory = new TimeZoneFactory(); + } + + public function testIsMS(): void { + // test Microsoft time zone + $this->assertTrue(TimeZoneFactory::isMS('Eastern Standard Time')); + // test IANA time zone + $this->assertFalse(TimeZoneFactory::isMS('America/Toronto')); + // test Fake time zone + $this->assertFalse(TimeZoneFactory::isMS('Fake Eastern Time')); + } + + public function testToIana(): void { + // test Microsoft time zone + $this->assertEquals('America/Toronto', TimeZoneFactory::toIANA('Eastern Standard Time')); + // test IANA time zone + $this->assertEquals(null, TimeZoneFactory::toIANA('America/Toronto')); + // test Fake time zone + $this->assertEquals(null, TimeZoneFactory::toIANA('Fake Eastern Time')); + } + + public function testFromName(): void { + // test Microsoft time zone + $this->assertEquals(new DateTimeZone('America/Toronto'), $this->factory->fromName('Eastern Standard Time')); + // test IANA time zone + $this->assertEquals(new DateTimeZone('America/Toronto'), $this->factory->fromName('America/Toronto')); + // test Fake time zone + $this->assertEquals(null, $this->factory->fromName('Fake Eastern Time')); + } + +} diff --git a/apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php b/apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php index b01139e4093..5bb87be67c1 100644 --- a/apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/TimezoneServiceTest.php @@ -1,11 +1,6 @@ <?php -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ declare(strict_types=1); - /** * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -26,10 +21,9 @@ use Sabre\VObject\Component\VTimeZone; use Test\TestCase; class TimezoneServiceTest extends TestCase { - - private IConfig|MockObject $config; - private PropertyMapper|MockObject $propertyMapper; - private IManager|MockObject $calendarManager; + private IConfig&MockObject $config; + private PropertyMapper&MockObject $propertyMapper; + private IManager&MockObject $calendarManager; private TimezoneService $service; protected function setUp(): void { diff --git a/apps/dav/tests/unit/CalDAV/TipBrokerTest.php b/apps/dav/tests/unit/CalDAV/TipBrokerTest.php new file mode 100644 index 00000000000..ddf992767d6 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/TipBrokerTest.php @@ -0,0 +1,180 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\CalDAV; + +use OCA\DAV\CalDAV\TipBroker; +use Sabre\VObject\Component\VCalendar; +use Test\TestCase; + +class TipBrokerTest extends TestCase { + + private TipBroker $broker; + private VCalendar $vCalendar1a; + + protected function setUp(): void { + parent::setUp(); + + $this->broker = new TipBroker(); + // construct calendar with a 1 hour event and same start/end time zones + $this->vCalendar1a = new VCalendar(); + /** @var VEvent $vEvent */ + $vEvent = $this->vCalendar1a->add('VEVENT', []); + $vEvent->add('UID', '96a0e6b1-d886-4a55-a60d-152b31401dcc'); + $vEvent->add('DTSTAMP', '20240701T000000Z'); + $vEvent->add('CREATED', '20240701T000000Z'); + $vEvent->add('LAST-MODIFIED', '20240701T000000Z'); + $vEvent->add('SEQUENCE', '1'); + $vEvent->add('STATUS', 'CONFIRMED'); + $vEvent->add('DTSTART', '20240701T080000', ['TZID' => 'America/Toronto']); + $vEvent->add('DTEND', '20240701T090000', ['TZID' => 'America/Toronto']); + $vEvent->add('SUMMARY', 'Test Event'); + $vEvent->add('ORGANIZER', 'mailto:organizer@testing.com', ['CN' => 'Organizer']); + $vEvent->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + } + + public function testParseEventForOrganizerOnCreate(): void { + + // construct calendar and generate event info for newly created event with one attendee + $calendar = clone $this->vCalendar1a; + $previousEventInfo = [ + 'organizer' => null, + 'significantChangeHash' => '', + 'attendees' => [], + ]; + $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + // test iTip generation + $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]); + $this->assertCount(1, $messages); + $this->assertEquals('REQUEST', $messages[0]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient); + + } + + public function testParseEventForOrganizerOnModify(): void { + + // construct calendar and generate event info for modified event with one attendee + $calendar = clone $this->vCalendar1a; + $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z'); + $calendar->VEVENT->SEQUENCE->setValue(2); + $calendar->VEVENT->SUMMARY->setValue('Test Event Modified'); + $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + // test iTip generation + $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]); + $this->assertCount(1, $messages); + $this->assertEquals('REQUEST', $messages[0]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient); + + } + + public function testParseEventForOrganizerOnDelete(): void { + + // construct calendar and generate event info for modified event with one attendee + $calendar = clone $this->vCalendar1a; + $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + $currentEventInfo = $previousEventInfo; + $currentEventInfo['attendees'] = []; + ++$currentEventInfo['sequence']; + // test iTip generation + $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]); + $this->assertCount(1, $messages); + $this->assertEquals('CANCEL', $messages[0]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient); + + } + + public function testParseEventForOrganizerOnStatusCancelled(): void { + + // construct calendar and generate event info for modified event with one attendee + $calendar = clone $this->vCalendar1a; + $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z'); + $calendar->VEVENT->SEQUENCE->setValue(2); + $calendar->VEVENT->STATUS->setValue('CANCELLED'); + $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + // test iTip generation + $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]); + $this->assertCount(1, $messages); + $this->assertEquals('CANCEL', $messages[0]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient); + + } + + public function testParseEventForOrganizerOnAddAttendee(): void { + + // construct calendar and generate event info for modified event with two attendees + $calendar = clone $this->vCalendar1a; + $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z'); + $calendar->VEVENT->SEQUENCE->setValue(2); + $calendar->VEVENT->add('ATTENDEE', 'mailto:attendee2@testing.com', [ + 'CN' => 'Attendee Two', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + // test iTip generation + $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]); + $this->assertCount(2, $messages); + $this->assertEquals('REQUEST', $messages[0]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient); + $this->assertEquals('REQUEST', $messages[1]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[1]->getValue(), $messages[1]->recipient); + + } + + public function testParseEventForOrganizerOnRemoveAttendee(): void { + + // construct calendar and generate event info for modified event with two attendees + $calendar = clone $this->vCalendar1a; + $calendar->VEVENT->add('ATTENDEE', 'mailto:attendee2@testing.com', [ + 'CN' => 'Attendee Two', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $previousEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + $calendar->VEVENT->{'LAST-MODIFIED'}->setValue('20240701T020000Z'); + $calendar->VEVENT->SEQUENCE->setValue(2); + $calendar->VEVENT->remove('ATTENDEE'); + $calendar->VEVENT->add('ATTENDEE', 'mailto:attendee1@testing.com', [ + 'CN' => 'Attendee One', + 'CUTYPE' => 'INDIVIDUAL', + 'PARTSTAT' => 'NEEDS-ACTION', + 'ROLE' => 'REQ-PARTICIPANT', + 'RSVP' => 'TRUE' + ]); + $currentEventInfo = $this->invokePrivate($this->broker, 'parseEventInfo', [$calendar]); + // test iTip generation + $messages = $this->invokePrivate($this->broker, 'parseEventForOrganizer', [$calendar, $currentEventInfo, $previousEventInfo]); + $this->assertCount(2, $messages); + $this->assertEquals('REQUEST', $messages[0]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[0]->sender); + $this->assertEquals($calendar->VEVENT->ATTENDEE[0]->getValue(), $messages[0]->recipient); + $this->assertEquals('CANCEL', $messages[1]->method); + $this->assertEquals($calendar->VEVENT->ORGANIZER->getValue(), $messages[1]->sender); + $this->assertEquals('mailto:attendee2@testing.com', $messages[1]->recipient); + + } + +} diff --git a/apps/dav/tests/unit/CalDAV/Validation/CalDavValidatePluginTest.php b/apps/dav/tests/unit/CalDAV/Validation/CalDavValidatePluginTest.php new file mode 100644 index 00000000000..74fb4b5e94e --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/Validation/CalDavValidatePluginTest.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +/* + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\CalDAV\Validation; + +use OCA\DAV\CalDAV\Validation\CalDavValidatePlugin; +use OCP\IAppConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\Exception\Forbidden; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class CalDavValidatePluginTest extends TestCase { + private IAppConfig&MockObject $config; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; + + private CalDavValidatePlugin $plugin; + + protected function setUp(): void { + parent::setUp(); + // construct mock objects + $this->config = $this->createMock(IAppConfig::class); + $this->request = $this->createMock(RequestInterface::class); + $this->response = $this->createMock(ResponseInterface::class); + $this->plugin = new CalDavValidatePlugin( + $this->config, + ); + } + + public function testPutSizeLessThenLimit(): void { + + // construct method responses + $this->config + ->method('getValueInt') + ->with('dav', 'event_size_limit', 10485760) + ->willReturn(10485760); + $this->request + ->method('getRawServerValue') + ->with('CONTENT_LENGTH') + ->willReturn('1024'); + // test condition + $this->assertTrue( + $this->plugin->beforePut($this->request, $this->response) + ); + + } + + public function testPutSizeMoreThenLimit(): void { + + // construct method responses + $this->config + ->method('getValueInt') + ->with('dav', 'event_size_limit', 10485760) + ->willReturn(10485760); + $this->request + ->method('getRawServerValue') + ->with('CONTENT_LENGTH') + ->willReturn('16242880'); + $this->expectException(Forbidden::class); + // test condition + $this->plugin->beforePut($this->request, $this->response); + + } + +} diff --git a/apps/dav/tests/unit/CalDAV/WebcalCaching/ConnectionTest.php b/apps/dav/tests/unit/CalDAV/WebcalCaching/ConnectionTest.php new file mode 100644 index 00000000000..c29415ecef3 --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/WebcalCaching/ConnectionTest.php @@ -0,0 +1,176 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\CalDAV\WebcalCaching; + +use OCA\DAV\CalDAV\WebcalCaching\Connection; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\Http\Client\IResponse; +use OCP\Http\Client\LocalServerException; +use OCP\IAppConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; + +use Test\TestCase; + +class ConnectionTest extends TestCase { + + private IClientService&MockObject $clientService; + private IAppConfig&MockObject $config; + private LoggerInterface&MockObject $logger; + private Connection $connection; + + public function setUp(): void { + $this->clientService = $this->createMock(IClientService::class); + $this->config = $this->createMock(IAppConfig::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->connection = new Connection($this->clientService, $this->config, $this->logger); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('runLocalURLDataProvider')] + public function testLocalUrl($source): void { + $subscription = [ + 'id' => 42, + 'uri' => 'sub123', + 'refreshreate' => 'P1H', + 'striptodos' => 1, + 'stripalarms' => 1, + 'stripattachments' => 1, + 'source' => $source, + 'lastmodified' => 0, + ]; + + $client = $this->createMock(IClient::class); + $this->clientService->expects(self::once()) + ->method('newClient') + ->with() + ->willReturn($client); + + $this->config->expects(self::once()) + ->method('getValueString') + ->with('dav', 'webcalAllowLocalAccess', 'no') + ->willReturn('no'); + + $localServerException = new LocalServerException(); + $client->expects(self::once()) + ->method('get') + ->willThrowException($localServerException); + $this->logger->expects(self::once()) + ->method('warning') + ->with('Subscription 42 was not refreshed because it violates local access rules', ['exception' => $localServerException]); + + $this->connection->queryWebcalFeed($subscription); + } + + public function testInvalidUrl(): void { + $subscription = [ + 'id' => 42, + 'uri' => 'sub123', + 'refreshreate' => 'P1H', + 'striptodos' => 1, + 'stripalarms' => 1, + 'stripattachments' => 1, + 'source' => '!@#$', + 'lastmodified' => 0, + ]; + + $client = $this->createMock(IClient::class); + $this->config->expects(self::never()) + ->method('getValueString'); + $client->expects(self::never()) + ->method('get'); + + $this->connection->queryWebcalFeed($subscription); + + } + + /** + * @param string $result + * @param string $contentType + */ + #[\PHPUnit\Framework\Attributes\DataProvider('urlDataProvider')] + public function testConnection(string $url, string $result, string $contentType): void { + $client = $this->createMock(IClient::class); + $response = $this->createMock(IResponse::class); + $subscription = [ + 'id' => 42, + 'uri' => 'sub123', + 'refreshreate' => 'P1H', + 'striptodos' => 1, + 'stripalarms' => 1, + 'stripattachments' => 1, + 'source' => $url, + 'lastmodified' => 0, + ]; + + $this->clientService->expects($this->once()) + ->method('newClient') + ->with() + ->willReturn($client); + + $this->config->expects($this->once()) + ->method('getValueString') + ->with('dav', 'webcalAllowLocalAccess', 'no') + ->willReturn('no'); + + $client->expects($this->once()) + ->method('get') + ->with('https://foo.bar/bla2') + ->willReturn($response); + + $response->expects($this->once()) + ->method('getBody') + ->with() + ->willReturn($result); + $response->expects($this->once()) + ->method('getHeader') + ->with('Content-Type') + ->willReturn($contentType); + + $this->connection->queryWebcalFeed($subscription); + } + + public static function runLocalURLDataProvider(): array { + return [ + ['localhost/foo.bar'], + ['localHost/foo.bar'], + ['random-host/foo.bar'], + ['[::1]/bla.blub'], + ['[::]/bla.blub'], + ['192.168.0.1'], + ['172.16.42.1'], + ['[fdf8:f53b:82e4::53]/secret.ics'], + ['[fe80::200:5aee:feaa:20a2]/secret.ics'], + ['[0:0:0:0:0:0:10.0.0.1]/secret.ics'], + ['[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'], + ['10.0.0.1'], + ['another-host.local'], + ['service.localhost'], + ]; + } + + public static function urlDataProvider(): array { + return [ + [ + 'https://foo.bar/bla2', + "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + 'text/calendar;charset=utf8', + ], + [ + 'https://foo.bar/bla2', + '["vcalendar",[["prodid",{},"text","-//Example Corp.//Example Client//EN"],["version",{},"text","2.0"]],[["vtimezone",[["last-modified",{},"date-time","2004-01-10T03:28:45Z"],["tzid",{},"text","US/Eastern"]],[["daylight",[["dtstart",{},"date-time","2000-04-04T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":4}],["tzname",{},"text","EDT"],["tzoffsetfrom",{},"utc-offset","-05:00"],["tzoffsetto",{},"utc-offset","-04:00"]],[]],["standard",[["dtstart",{},"date-time","2000-10-26T02:00:00"],["rrule",{},"recur",{"freq":"YEARLY","byday":"1SU","bymonth":10}],["tzname",{},"text","EST"],["tzoffsetfrom",{},"utc-offset","-04:00"],["tzoffsetto",{},"utc-offset","-05:00"]],[]]]],["vevent",[["dtstamp",{},"date-time","2006-02-06T00:11:21Z"],["dtstart",{"tzid":"US/Eastern"},"date-time","2006-01-02T14:00:00"],["duration",{},"duration","PT1H"],["recurrence-id",{"tzid":"US/Eastern"},"date-time","2006-01-04T12:00:00"],["summary",{},"text","Event #2"],["uid",{},"text","12345"]],[]]]]', + 'application/calendar+json', + ], + [ + 'https://foo.bar/bla2', + '<?xml version="1.0" encoding="utf-8" ?><icalendar xmlns="urn:ietf:params:xml:ns:icalendar-2.0"><vcalendar><properties><prodid><text>-//Example Inc.//Example Client//EN</text></prodid><version><text>2.0</text></version></properties><components><vevent><properties><dtstamp><date-time>2006-02-06T00:11:21Z</date-time></dtstamp><dtstart><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T14:00:00</date-time></dtstart><duration><duration>PT1H</duration></duration><recurrence-id><parameters><tzid><text>US/Eastern</text></tzid></parameters><date-time>2006-01-04T12:00:00</date-time></recurrence-id><summary><text>Event #2 bis</text></summary><uid><text>12345</text></uid></properties></vevent></components></vcalendar></icalendar>', + 'application/calendar+xml', + ], + ]; + } +} diff --git a/apps/dav/tests/unit/CalDAV/WebcalCaching/PluginTest.php b/apps/dav/tests/unit/CalDAV/WebcalCaching/PluginTest.php index 82c03c5cf68..804af021d5a 100644 --- a/apps/dav/tests/unit/CalDAV/WebcalCaching/PluginTest.php +++ b/apps/dav/tests/unit/CalDAV/WebcalCaching/PluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php b/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php index c483fa3e234..d4f4b9e878f 100644 --- a/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php +++ b/apps/dav/tests/unit/CalDAV/WebcalCaching/RefreshWebcalServiceTest.php @@ -1,18 +1,16 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\DAV\Tests\unit\CalDAV\WebcalCaching; -use GuzzleHttp\HandlerStack; use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\WebcalCaching\Connection; use OCA\DAV\CalDAV\WebcalCaching\RefreshWebcalService; -use OCP\Http\Client\IClient; -use OCP\Http\Client\IClientService; -use OCP\Http\Client\IResponse; -use OCP\Http\Client\LocalServerException; -use OCP\IConfig; +use OCP\AppFramework\Utility\ITimeFactory; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\Exception\BadRequest; @@ -22,121 +20,187 @@ use Sabre\VObject\Recur\NoInstancesException; use Test\TestCase; class RefreshWebcalServiceTest extends TestCase { - - /** @var CalDavBackend | MockObject */ - private $caldavBackend; - - /** @var IClientService | MockObject */ - private $clientService; - - /** @var IConfig | MockObject */ - private $config; - - /** @var LoggerInterface | MockObject */ - private $logger; + private CalDavBackend&MockObject $caldavBackend; + private Connection&MockObject $connection; + private LoggerInterface&MockObject $logger; + private ITimeFactory&MockObject $time; protected function setUp(): void { parent::setUp(); $this->caldavBackend = $this->createMock(CalDavBackend::class); - $this->clientService = $this->createMock(IClientService::class); - $this->config = $this->createMock(IConfig::class); + $this->connection = $this->createMock(Connection::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->time = $this->createMock(ITimeFactory::class); } - /** - * @param string $body - * @param string $contentType - * @param string $result - * - * @dataProvider runDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')] public function testRun(string $body, string $contentType, string $result): void { $refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class) ->onlyMethods(['getRandomCalendarObjectUri']) - ->setConstructorArgs([$this->caldavBackend, $this->clientService, $this->config, $this->logger]) + ->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time]) ->getMock(); $refreshWebcalService ->method('getRandomCalendarObjectUri') ->willReturn('uri-1.ics'); - $this->caldavBackend->expects($this->once()) + $this->caldavBackend->expects(self::once()) ->method('getSubscriptionsForUser') ->with('principals/users/testuser') ->willReturn([ [ 'id' => '99', 'uri' => 'sub456', - '{http://apple.com/ns/ical/}refreshrate' => 'P1D', - '{http://calendarserver.org/ns/}subscribed-strip-todos' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-attachments' => '1', - 'source' => 'webcal://foo.bar/bla' + RefreshWebcalService::REFRESH_RATE => 'P1D', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla', + 'lastmodified' => 0, ], [ 'id' => '42', 'uri' => 'sub123', - '{http://apple.com/ns/ical/}refreshrate' => 'PT1H', - '{http://calendarserver.org/ns/}subscribed-strip-todos' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-attachments' => '1', - 'source' => 'webcal://foo.bar/bla2' + RefreshWebcalService::REFRESH_RATE => 'PT1H', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla2', + 'lastmodified' => 0, ], ]); - $client = $this->createMock(IClient::class); - $response = $this->createMock(IResponse::class); - $this->clientService->expects($this->once()) - ->method('newClient') - ->with() - ->willReturn($client); - - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('dav', 'webcalAllowLocalAccess', 'no') - ->willReturn('no'); - - $client->expects($this->once()) - ->method('get') - ->with('https://foo.bar/bla2', $this->callback(function ($obj) { - return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; - })) - ->willReturn($response); - - $response->expects($this->once()) - ->method('getBody') - ->with() - ->willReturn($body); - $response->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->willReturn($contentType); - - $this->caldavBackend->expects($this->once()) - ->method('purgeAllCachedEventsForSubscription') - ->with(42); - - $this->caldavBackend->expects($this->once()) + $this->connection->expects(self::once()) + ->method('queryWebcalFeed') + ->willReturn($result); + $this->caldavBackend->expects(self::once()) ->method('createCalendarObject') ->with(42, 'uri-1.ics', $result, 1); $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); } - /** - * @param string $body - * @param string $contentType - * @param string $result - * - * @dataProvider runDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('identicalDataProvider')] + public function testRunIdentical(string $uid, array $calendarObject, string $body, string $contentType, string $result): void { + $refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class) + ->onlyMethods(['getRandomCalendarObjectUri']) + ->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time]) + ->getMock(); + + $refreshWebcalService + ->method('getRandomCalendarObjectUri') + ->willReturn('uri-1.ics'); + + $this->caldavBackend->expects(self::once()) + ->method('getSubscriptionsForUser') + ->with('principals/users/testuser') + ->willReturn([ + [ + 'id' => '99', + 'uri' => 'sub456', + RefreshWebcalService::REFRESH_RATE => 'P1D', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla', + 'lastmodified' => 0, + ], + [ + 'id' => '42', + 'uri' => 'sub123', + RefreshWebcalService::REFRESH_RATE => 'PT1H', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla2', + 'lastmodified' => 0, + ], + ]); + + $this->connection->expects(self::once()) + ->method('queryWebcalFeed') + ->willReturn($result); + + $this->caldavBackend->expects(self::once()) + ->method('getLimitedCalendarObjects') + ->willReturn($calendarObject); + + $denormalised = [ + 'etag' => 100, + 'size' => strlen($calendarObject[$uid]['calendardata']), + 'uid' => 'sub456' + ]; + + $this->caldavBackend->expects(self::once()) + ->method('getDenormalizedData') + ->willReturn($denormalised); + + $this->caldavBackend->expects(self::never()) + ->method('createCalendarObject'); + + $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub456'); + } + + public function testRunJustUpdated(): void { + $refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class) + ->onlyMethods(['getRandomCalendarObjectUri']) + ->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time]) + ->getMock(); + + $refreshWebcalService + ->method('getRandomCalendarObjectUri') + ->willReturn('uri-1.ics'); + + $this->caldavBackend->expects(self::once()) + ->method('getSubscriptionsForUser') + ->with('principals/users/testuser') + ->willReturn([ + [ + 'id' => '99', + 'uri' => 'sub456', + RefreshWebcalService::REFRESH_RATE => 'P1D', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla', + 'lastmodified' => time(), + ], + [ + 'id' => '42', + 'uri' => 'sub123', + RefreshWebcalService::REFRESH_RATE => 'PT1H', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla2', + 'lastmodified' => time(), + ], + ]); + + $timeMock = $this->createMock(\DateTime::class); + $this->time->expects(self::once()) + ->method('getDateTime') + ->willReturn($timeMock); + $timeMock->expects(self::once()) + ->method('getTimestamp') + ->willReturn(2101724667); + $this->time->expects(self::once()) + ->method('getTime') + ->willReturn(time()); + $this->connection->expects(self::never()) + ->method('queryWebcalFeed'); + $this->caldavBackend->expects(self::never()) + ->method('createCalendarObject'); + + $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')] public function testRunCreateCalendarNoException(string $body, string $contentType, string $result): void { - $client = $this->createMock(IClient::class); - $response = $this->createMock(IResponse::class); $refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class) - ->onlyMethods(['getRandomCalendarObjectUri', 'getSubscription', 'queryWebcalFeed']) - ->setConstructorArgs([$this->caldavBackend, $this->clientService, $this->config, $this->logger]) + ->onlyMethods(['getRandomCalendarObjectUri', 'getSubscription',]) + ->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time]) ->getMock(); $refreshWebcalService @@ -148,72 +212,39 @@ class RefreshWebcalServiceTest extends TestCase { ->willReturn([ 'id' => '42', 'uri' => 'sub123', - '{http://apple.com/ns/ical/}refreshrate' => 'PT1H', - '{http://calendarserver.org/ns/}subscribed-strip-todos' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-attachments' => '1', - 'source' => 'webcal://foo.bar/bla2' + RefreshWebcalService::REFRESH_RATE => 'PT1H', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla2', + 'lastmodified' => 0, ]); - $this->clientService->expects($this->once()) - ->method('newClient') - ->with() - ->willReturn($client); - - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('dav', 'webcalAllowLocalAccess', 'no') - ->willReturn('no'); - - $client->expects($this->once()) - ->method('get') - ->with('https://foo.bar/bla2', $this->callback(function ($obj) { - return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; - })) - ->willReturn($response); - - $response->expects($this->once()) - ->method('getBody') - ->with() - ->willReturn($body); - $response->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->willReturn($contentType); - - $this->caldavBackend->expects($this->once()) - ->method('purgeAllCachedEventsForSubscription') - ->with(42); - - $this->caldavBackend->expects($this->once()) + $this->connection->expects(self::once()) + ->method('queryWebcalFeed') + ->willReturn($result); + + $this->caldavBackend->expects(self::once()) ->method('createCalendarObject') ->with(42, 'uri-1.ics', $result, 1); $noInstanceException = new NoInstancesException("can't add calendar object"); - $this->caldavBackend->expects($this->once()) - ->method("createCalendarObject") + $this->caldavBackend->expects(self::once()) + ->method('createCalendarObject') ->willThrowException($noInstanceException); - $this->logger->expects($this->once()) - ->method('error') + $this->logger->expects(self::once()) + ->method('warning') ->with('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $noInstanceException, 'subscriptionId' => '42', 'source' => 'webcal://foo.bar/bla2']); $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); } - /** - * @param string $body - * @param string $contentType - * @param string $result - * - * @dataProvider runDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('runDataProvider')] public function testRunCreateCalendarBadRequest(string $body, string $contentType, string $result): void { - $client = $this->createMock(IClient::class); - $response = $this->createMock(IResponse::class); $refreshWebcalService = $this->getMockBuilder(RefreshWebcalService::class) - ->onlyMethods(['getRandomCalendarObjectUri', 'getSubscription', 'queryWebcalFeed']) - ->setConstructorArgs([$this->caldavBackend, $this->clientService, $this->config, $this->logger]) + ->onlyMethods(['getRandomCalendarObjectUri', 'getSubscription']) + ->setConstructorArgs([$this->caldavBackend, $this->logger, $this->connection, $this->time]) ->getMock(); $refreshWebcalService @@ -225,63 +256,54 @@ class RefreshWebcalServiceTest extends TestCase { ->willReturn([ 'id' => '42', 'uri' => 'sub123', - '{http://apple.com/ns/ical/}refreshrate' => 'PT1H', - '{http://calendarserver.org/ns/}subscribed-strip-todos' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-alarms' => '1', - '{http://calendarserver.org/ns/}subscribed-strip-attachments' => '1', - 'source' => 'webcal://foo.bar/bla2' + RefreshWebcalService::REFRESH_RATE => 'PT1H', + RefreshWebcalService::STRIP_TODOS => '1', + RefreshWebcalService::STRIP_ALARMS => '1', + RefreshWebcalService::STRIP_ATTACHMENTS => '1', + 'source' => 'webcal://foo.bar/bla2', + 'lastmodified' => 0, ]); - $this->clientService->expects($this->once()) - ->method('newClient') - ->with() - ->willReturn($client); - - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('dav', 'webcalAllowLocalAccess', 'no') - ->willReturn('no'); - - $client->expects($this->once()) - ->method('get') - ->with('https://foo.bar/bla2', $this->callback(function ($obj) { - return $obj['allow_redirects']['redirects'] === 10 && $obj['handler'] instanceof HandlerStack; - })) - ->willReturn($response); - - $response->expects($this->once()) - ->method('getBody') - ->with() - ->willReturn($body); - $response->expects($this->once()) - ->method('getHeader') - ->with('Content-Type') - ->willReturn($contentType); - - $this->caldavBackend->expects($this->once()) - ->method('purgeAllCachedEventsForSubscription') - ->with(42); - - $this->caldavBackend->expects($this->once()) + $this->connection->expects(self::once()) + ->method('queryWebcalFeed') + ->willReturn($result); + + $this->caldavBackend->expects(self::once()) ->method('createCalendarObject') ->with(42, 'uri-1.ics', $result, 1); $badRequestException = new BadRequest("can't add reach calendar url"); - $this->caldavBackend->expects($this->once()) - ->method("createCalendarObject") + $this->caldavBackend->expects(self::once()) + ->method('createCalendarObject') ->willThrowException($badRequestException); - $this->logger->expects($this->once()) - ->method('error') + $this->logger->expects(self::once()) + ->method('warning') ->with('Unable to create calendar object from subscription {subscriptionId}', ['exception' => $badRequestException, 'subscriptionId' => '42', 'source' => 'webcal://foo.bar/bla2']); $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); } - /** - * @return array - */ - public function runDataProvider():array { + public static function identicalDataProvider(): array { + return [ + [ + '12345', + [ + '12345' => [ + 'id' => 42, + 'etag' => 100, + 'uri' => 'sub456', + 'calendardata' => "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ], + ], + "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + 'text/calendar;charset=utf8', + "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20180218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", + ], + ]; + } + + public static function runDataProvider(): array { return [ [ "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.1//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:12345\r\nDTSTAMP:20160218T133704Z\r\nDTSTART;VALUE=DATE:19000101\r\nDTEND;VALUE=DATE:19000102\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:12345's Birthday (1900)\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n", @@ -300,109 +322,4 @@ class RefreshWebcalServiceTest extends TestCase { ] ]; } - - /** - * @dataProvider runLocalURLDataProvider - */ - public function testRunLocalURL(string $source): void { - $refreshWebcalService = new RefreshWebcalService( - $this->caldavBackend, - $this->clientService, - $this->config, - $this->logger - ); - - $this->caldavBackend->expects($this->once()) - ->method('getSubscriptionsForUser') - ->with('principals/users/testuser') - ->willReturn([ - [ - 'id' => 42, - 'uri' => 'sub123', - 'refreshreate' => 'P1H', - 'striptodos' => 1, - 'stripalarms' => 1, - 'stripattachments' => 1, - 'source' => $source - ], - ]); - - $client = $this->createMock(IClient::class); - $this->clientService->expects($this->once()) - ->method('newClient') - ->with() - ->willReturn($client); - - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('dav', 'webcalAllowLocalAccess', 'no') - ->willReturn('no'); - - $localServerException = new LocalServerException(); - - $client->expects($this->once()) - ->method('get') - ->willThrowException($localServerException); - - $this->logger->expects($this->once()) - ->method('warning') - ->with("Subscription 42 was not refreshed because it violates local access rules", ['exception' => $localServerException]); - - $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); - } - - public function runLocalURLDataProvider():array { - return [ - ['localhost/foo.bar'], - ['localHost/foo.bar'], - ['random-host/foo.bar'], - ['[::1]/bla.blub'], - ['[::]/bla.blub'], - ['192.168.0.1'], - ['172.16.42.1'], - ['[fdf8:f53b:82e4::53]/secret.ics'], - ['[fe80::200:5aee:feaa:20a2]/secret.ics'], - ['[0:0:0:0:0:0:10.0.0.1]/secret.ics'], - ['[0:0:0:0:0:ffff:127.0.0.0]/secret.ics'], - ['10.0.0.1'], - ['another-host.local'], - ['service.localhost'], - ]; - } - - public function testInvalidUrl(): void { - $refreshWebcalService = new RefreshWebcalService($this->caldavBackend, - $this->clientService, $this->config, $this->logger); - - $this->caldavBackend->expects($this->once()) - ->method('getSubscriptionsForUser') - ->with('principals/users/testuser') - ->willReturn([ - [ - 'id' => 42, - 'uri' => 'sub123', - 'refreshreate' => 'P1H', - 'striptodos' => 1, - 'stripalarms' => 1, - 'stripattachments' => 1, - 'source' => '!@#$' - ], - ]); - - $client = $this->createMock(IClient::class); - $this->clientService->expects($this->once()) - ->method('newClient') - ->with() - ->willReturn($client); - - $this->config->expects($this->once()) - ->method('getAppValue') - ->with('dav', 'webcalAllowLocalAccess', 'no') - ->willReturn('no'); - - $client->expects($this->never()) - ->method('get'); - - $refreshWebcalService->refreshSubscription('principals/users/testuser', 'sub123'); - } } diff --git a/apps/dav/tests/unit/CapabilitiesTest.php b/apps/dav/tests/unit/CapabilitiesTest.php index acdfb4a2443..ad70d576d48 100644 --- a/apps/dav/tests/unit/CapabilitiesTest.php +++ b/apps/dav/tests/unit/CapabilitiesTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,6 +9,7 @@ namespace OCA\DAV\Tests\unit; use OCA\DAV\Capabilities; use OCP\IConfig; +use OCP\User\IAvailabilityCoordinator; use Test\TestCase; /** @@ -19,10 +22,15 @@ class CapabilitiesTest extends TestCase { ->method('getSystemValueBool') ->with('bulkupload.enabled', $this->isType('bool')) ->willReturn(false); - $capabilities = new Capabilities($config); + $coordinator = $this->createMock(IAvailabilityCoordinator::class); + $coordinator->expects($this->once()) + ->method('isEnabled') + ->willReturn(false); + $capabilities = new Capabilities($config, $coordinator); $expected = [ 'dav' => [ 'chunking' => '1.0', + 'public_shares_chunking' => true, ], ]; $this->assertSame($expected, $capabilities->getCapabilities()); @@ -34,13 +42,40 @@ class CapabilitiesTest extends TestCase { ->method('getSystemValueBool') ->with('bulkupload.enabled', $this->isType('bool')) ->willReturn(true); - $capabilities = new Capabilities($config); + $coordinator = $this->createMock(IAvailabilityCoordinator::class); + $coordinator->expects($this->once()) + ->method('isEnabled') + ->willReturn(false); + $capabilities = new Capabilities($config, $coordinator); $expected = [ 'dav' => [ 'chunking' => '1.0', + 'public_shares_chunking' => true, 'bulkupload' => '1.0', ], ]; $this->assertSame($expected, $capabilities->getCapabilities()); } + + public function testGetCapabilitiesWithAbsence(): void { + $config = $this->createMock(IConfig::class); + $config->expects($this->once()) + ->method('getSystemValueBool') + ->with('bulkupload.enabled', $this->isType('bool')) + ->willReturn(false); + $coordinator = $this->createMock(IAvailabilityCoordinator::class); + $coordinator->expects($this->once()) + ->method('isEnabled') + ->willReturn(true); + $capabilities = new Capabilities($config, $coordinator); + $expected = [ + 'dav' => [ + 'chunking' => '1.0', + 'public_shares_chunking' => true, + 'absence-supported' => true, + 'absence-replacement' => true, + ], + ]; + $this->assertSame($expected, $capabilities->getCapabilities()); + } } diff --git a/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php b/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php index 134a6ca5ca0..a070a3d7131 100644 --- a/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php +++ b/apps/dav/tests/unit/CardDAV/Activity/BackendTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -20,20 +22,11 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class BackendTest extends TestCase { - /** @var IManager|MockObject */ - protected $activityManager; - - /** @var IGroupManager|MockObject */ - protected $groupManager; - - /** @var IUserSession|MockObject */ - protected $userSession; - - /** @var IAppManager|MockObject */ - protected $appManager; - - /** @var IUserManager|MockObject */ - protected $userManager; + protected IManager&MockObject $activityManager; + protected IGroupManager&MockObject $groupManager; + protected IUserSession&MockObject $userSession; + protected IAppManager&MockObject $appManager; + protected IUserManager&MockObject $userManager; protected function setUp(): void { parent::setUp(); @@ -45,10 +38,9 @@ class BackendTest extends TestCase { } /** - * @param array $methods * @return Backend|MockObject */ - protected function getBackend(array $methods = []) { + protected function getBackend(array $methods = []): Backend { if (empty($methods)) { return new Backend( $this->activityManager, @@ -71,7 +63,7 @@ class BackendTest extends TestCase { } } - public function dataCallTriggerAddressBookActivity(): array { + public static function dataCallTriggerAddressBookActivity(): array { return [ ['onAddressbookCreate', [['data']], Addressbook::SUBJECT_ADD, [['data'], [], []]], ['onAddressbookUpdate', [['data'], ['shares'], ['changed-properties']], Addressbook::SUBJECT_UPDATE, [['data'], ['shares'], ['changed-properties']]], @@ -79,9 +71,7 @@ class BackendTest extends TestCase { ]; } - /** - * @dataProvider dataCallTriggerAddressBookActivity - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataCallTriggerAddressBookActivity')] public function testCallTriggerAddressBookActivity(string $method, array $payload, string $expectedSubject, array $expectedPayload): void { $backend = $this->getBackend(['triggerAddressbookActivity']); $backend->expects($this->once()) @@ -95,7 +85,7 @@ class BackendTest extends TestCase { call_user_func_array([$backend, $method], $payload); } - public function dataTriggerAddressBookActivity(): array { + public static function dataTriggerAddressBookActivity(): array { return [ // Add addressbook [Addressbook::SUBJECT_ADD, [], [], [], '', '', null, []], @@ -159,16 +149,10 @@ class BackendTest extends TestCase { } /** - * @dataProvider dataTriggerAddressBookActivity - * @param string $action - * @param array $data - * @param array $shares - * @param array $changedProperties - * @param string $currentUser - * @param string $author * @param string[]|null $shareUsers * @param string[] $users */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTriggerAddressBookActivity')] public function testTriggerAddressBookActivity(string $action, array $data, array $shares, array $changedProperties, string $currentUser, string $author, ?array $shareUsers, array $users): void { $backend = $this->getBackend(['getUsersForShares']); @@ -219,13 +203,13 @@ class BackendTest extends TestCase { ->method('userExists') ->willReturn(true); - $event->expects($this->exactly(sizeof($users))) + $event->expects($this->exactly(count($users))) ->method('setAffectedUser') ->willReturnSelf(); - $event->expects($this->exactly(sizeof($users))) + $event->expects($this->exactly(count($users))) ->method('setSubject') ->willReturnSelf(); - $this->activityManager->expects($this->exactly(sizeof($users))) + $this->activityManager->expects($this->exactly(count($users))) ->method('publish') ->with($event); } else { @@ -261,7 +245,7 @@ class BackendTest extends TestCase { ], [], []]); } - public function dataTriggerCardActivity(): array { + public static function dataTriggerCardActivity(): array { $cardData = "BEGIN:VCARD\r\nVERSION:3.0\r\nPRODID:-//Sabre//Sabre VObject 3.4.8//EN\r\nUID:test-user\r\nFN:test-user\r\nN:test-user;;;;\r\nEND:VCARD\r\n\r\n"; return [ @@ -329,16 +313,10 @@ class BackendTest extends TestCase { } /** - * @dataProvider dataTriggerCardActivity - * @param string $action - * @param array $addressBookData - * @param array $shares - * @param array $cardData - * @param string $currentUser - * @param string $author * @param string[]|null $shareUsers * @param string[] $users */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTriggerCardActivity')] public function testTriggerCardActivity(string $action, array $addressBookData, array $shares, array $cardData, string $currentUser, string $author, ?array $shareUsers, array $users): void { $backend = $this->getBackend(['getUsersForShares']); @@ -385,13 +363,13 @@ class BackendTest extends TestCase { ->with($author) ->willReturnSelf(); - $event->expects($this->exactly(sizeof($users))) + $event->expects($this->exactly(count($users))) ->method('setAffectedUser') ->willReturnSelf(); - $event->expects($this->exactly(sizeof($users))) + $event->expects($this->exactly(count($users))) ->method('setSubject') ->willReturnSelf(); - $this->activityManager->expects($this->exactly(sizeof($users))) + $this->activityManager->expects($this->exactly(count($users))) ->method('publish') ->with($event); } else { @@ -409,7 +387,7 @@ class BackendTest extends TestCase { $this->assertEmpty($this->invokePrivate($backend, 'triggerCardActivity', [Card::SUBJECT_UPDATE, ['principaluri' => 'principals/system/system'], [], []])); } - public function dataGetUsersForShares(): array { + public static function dataGetUsersForShares(): array { return [ [ [], @@ -452,12 +430,7 @@ class BackendTest extends TestCase { ]; } - /** - * @dataProvider dataGetUsersForShares - * @param array $shares - * @param array $groups - * @param array $expected - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataGetUsersForShares')] public function testGetUsersForShares(array $shares, array $groups, array $expected): void { $backend = $this->getBackend(); @@ -498,10 +471,9 @@ class BackendTest extends TestCase { } /** - * @param string $uid * @return IUser|MockObject */ - protected function getUserMock(string $uid) { + protected function getUserMock(string $uid): IUser { $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') diff --git a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php index fb12b5b0884..74699cf3925 100644 --- a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php +++ b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php @@ -10,30 +10,22 @@ namespace OCA\DAV\Tests\unit\CardDAV; use OCA\DAV\CardDAV\AddressBook; use OCA\DAV\CardDAV\AddressBookImpl; use OCA\DAV\CardDAV\CardDavBackend; +use OCA\DAV\Db\PropertyMapper; use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Component\VCard; use Sabre\VObject\Property\Text; //use Sabre\VObject\Property\; use Test\TestCase; class AddressBookImplTest extends TestCase { - /** @var AddressBookImpl */ - private $addressBookImpl; - - /** @var array */ - private $addressBookInfo; - - /** @var AddressBook | \PHPUnit\Framework\MockObject\MockObject */ - private $addressBook; - - /** @var IURLGenerator | \PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - - /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject */ - private $backend; - - /** @var VCard | \PHPUnit\Framework\MockObject\MockObject */ - private $vCard; + private array $addressBookInfo; + private AddressBook&MockObject $addressBook; + private IURLGenerator&MockObject $urlGenerator; + private CardDavBackend&MockObject $backend; + private PropertyMapper&MockObject $propertyMapper; + private VCard&MockObject $vCard; + private AddressBookImpl $addressBookImpl; protected function setUp(): void { parent::setUp(); @@ -44,18 +36,19 @@ class AddressBookImplTest extends TestCase { 'principaluri' => 'principals/system/system', '{DAV:}displayname' => 'display name', ]; - $this->addressBook = $this->getMockBuilder(AddressBook::class) - ->disableOriginalConstructor()->getMock(); - $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->disableOriginalConstructor()->getMock(); + $this->addressBook = $this->createMock(AddressBook::class); + $this->backend = $this->createMock(CardDavBackend::class); $this->vCard = $this->createMock(VCard::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); + $this->propertyMapper = $this->createMock(PropertyMapper::class); $this->addressBookImpl = new AddressBookImpl( $this->addressBook, $this->addressBookInfo, $this->backend, - $this->urlGenerator + $this->urlGenerator, + $this->propertyMapper, + null ); } @@ -70,7 +63,7 @@ class AddressBookImplTest extends TestCase { } public function testSearch(): void { - /** @var \PHPUnit\Framework\MockObject\MockObject | AddressBookImpl $addressBookImpl */ + /** @var MockObject&AddressBookImpl $addressBookImpl */ $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class) ->setConstructorArgs( [ @@ -78,9 +71,11 @@ class AddressBookImplTest extends TestCase { $this->addressBookInfo, $this->backend, $this->urlGenerator, + $this->propertyMapper, + null ] ) - ->setMethods(['vCard2Array', 'readCard']) + ->onlyMethods(['vCard2Array', 'readCard']) ->getMock(); $pattern = 'pattern'; @@ -98,25 +93,21 @@ class AddressBookImplTest extends TestCase { $addressBookImpl->expects($this->exactly(2))->method('readCard') ->willReturn($this->vCard); $addressBookImpl->expects($this->exactly(2))->method('vCard2Array') - ->withConsecutive( - ['foo.vcf', $this->vCard], - ['bar.vcf', $this->vCard] - )->willReturn('vCard'); + ->willReturnMap([ + ['foo.vcf', $this->vCard, 'vCard'], + ['bar.vcf', $this->vCard, 'vCard'], + ]); $result = $addressBookImpl->search($pattern, $searchProperties, []); $this->assertTrue((is_array($result))); $this->assertSame(2, count($result)); } - /** - * @dataProvider dataTestCreate - * - * @param array $properties - */ - public function testCreate($properties): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCreate')] + public function testCreate(array $properties): void { $uid = 'uid'; - /** @var \PHPUnit\Framework\MockObject\MockObject | AddressBookImpl $addressBookImpl */ + /** @var MockObject&AddressBookImpl $addressBookImpl */ $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class) ->setConstructorArgs( [ @@ -124,9 +115,11 @@ class AddressBookImplTest extends TestCase { $this->addressBookInfo, $this->backend, $this->urlGenerator, + $this->propertyMapper, + null ] ) - ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard']) + ->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard']) ->getMock(); $expectedProperties = 0; @@ -153,7 +146,7 @@ class AddressBookImplTest extends TestCase { $this->assertTrue($addressBookImpl->createOrUpdate($properties)); } - public function dataTestCreate() { + public static function dataTestCreate(): array { return [ [[]], [['FN' => 'John Doe']], @@ -166,7 +159,7 @@ class AddressBookImplTest extends TestCase { $uri = 'bla.vcf'; $properties = ['URI' => $uri, 'UID' => $uid, 'FN' => 'John Doe']; - /** @var \PHPUnit\Framework\MockObject\MockObject | AddressBookImpl $addressBookImpl */ + /** @var MockObject&AddressBookImpl $addressBookImpl */ $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class) ->setConstructorArgs( [ @@ -174,9 +167,11 @@ class AddressBookImplTest extends TestCase { $this->addressBookInfo, $this->backend, $this->urlGenerator, + $this->propertyMapper, + null ] ) - ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard']) + ->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard']) ->getMock(); $addressBookImpl->expects($this->never())->method('createUid'); @@ -203,7 +198,7 @@ class AddressBookImplTest extends TestCase { $vCard = new vCard; $textProperty = $vCard->createProperty('KEY', 'value'); - /** @var \PHPUnit\Framework\MockObject\MockObject | AddressBookImpl $addressBookImpl */ + /** @var MockObject&AddressBookImpl $addressBookImpl */ $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class) ->setConstructorArgs( [ @@ -211,9 +206,11 @@ class AddressBookImplTest extends TestCase { $this->addressBookInfo, $this->backend, $this->urlGenerator, + $this->propertyMapper, + null ] ) - ->setMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard']) + ->onlyMethods(['vCard2Array', 'createUid', 'createEmptyVCard', 'readCard']) ->getMock(); $this->backend->expects($this->once())->method('getCard') @@ -231,13 +228,8 @@ class AddressBookImplTest extends TestCase { $addressBookImpl->createOrUpdate($properties); } - /** - * @dataProvider dataTestGetPermissions - * - * @param array $permissions - * @param int $expected - */ - public function testGetPermissions($permissions, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestGetPermissions')] + public function testGetPermissions(array $permissions, int $expected): void { $this->addressBook->expects($this->once())->method('getACL') ->willReturn($permissions); @@ -246,17 +238,18 @@ class AddressBookImplTest extends TestCase { ); } - public function dataTestGetPermissions() { + public static function dataTestGetPermissions(): array { return [ [[], 0], - [[['privilege' => '{DAV:}read']], 1], - [[['privilege' => '{DAV:}write']], 6], - [[['privilege' => '{DAV:}all']], 31], - [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 7], - [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}all']], 31], - [[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}write']], 31], - [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write'],['privilege' => '{DAV:}all']], 31], - [[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 31], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system']], 1], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'], ['privilege' => '{DAV:}write', 'principal' => 'principals/someone/else']], 1], + [[['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 6], + [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 7], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31], ]; } @@ -284,7 +277,7 @@ class AddressBookImplTest extends TestCase { } public function testCreateUid(): void { - /** @var \PHPUnit\Framework\MockObject\MockObject | AddressBookImpl $addressBookImpl */ + /** @var MockObject&AddressBookImpl $addressBookImpl */ $addressBookImpl = $this->getMockBuilder(AddressBookImpl::class) ->setConstructorArgs( [ @@ -292,9 +285,11 @@ class AddressBookImplTest extends TestCase { $this->addressBookInfo, $this->backend, $this->urlGenerator, + $this->propertyMapper, + null ] ) - ->setMethods(['getUid']) + ->onlyMethods(['getUid']) ->getMock(); $addressBookImpl->expects($this->exactly(2)) @@ -488,7 +483,9 @@ class AddressBookImplTest extends TestCase { $this->addressBook, $addressBookInfo, $this->backend, - $this->urlGenerator + $this->urlGenerator, + $this->propertyMapper, + null ); $this->assertTrue($addressBookImpl->isSystemAddressBook()); @@ -507,7 +504,9 @@ class AddressBookImplTest extends TestCase { $this->addressBook, $addressBookInfo, $this->backend, - $this->urlGenerator + $this->urlGenerator, + $this->propertyMapper, + 'user2' ); $this->assertFalse($addressBookImpl->isSystemAddressBook()); @@ -527,7 +526,9 @@ class AddressBookImplTest extends TestCase { $this->addressBook, $addressBookInfo, $this->backend, - $this->urlGenerator + $this->urlGenerator, + $this->propertyMapper, + 'user2' ); $this->assertFalse($addressBookImpl->isSystemAddressBook()); diff --git a/apps/dav/tests/unit/CardDAV/AddressBookTest.php b/apps/dav/tests/unit/CardDAV/AddressBookTest.php index cbdb9a1402c..cf28b7b8a8e 100644 --- a/apps/dav/tests/unit/CardDAV/AddressBookTest.php +++ b/apps/dav/tests/unit/CardDAV/AddressBookTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -28,19 +29,20 @@ class AddressBookTest extends TestCase { 'uri' => 'default', ]; $l10n = $this->createMock(IL10N::class); - $logger = $this->createMock(LoggerInterface::class); - $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); + $addressBook = new AddressBook($backend, $addressBookInfo, $l10n); $card = new Card($backend, $addressBookInfo, ['id' => 5, 'carddata' => 'RANDOM VCF DATA', 'uri' => 'something', 'addressbookid' => 23]); - $backend->expects($this->once())->method('moveCard')->with(23, 666, 'something', 'user1')->willReturn(true); + $backend->expects($this->once())->method('moveCard') + ->with(23, 'something', 666, 'new') + ->willReturn(true); $addressBook->moveInto('new', 'old', $card); } public function testDelete(): void { /** @var MockObject | CardDavBackend $backend */ - $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $backend = $this->createMock(CardDavBackend::class); $backend->expects($this->once())->method('updateShares'); $backend->expects($this->any())->method('getShares')->willReturn([ ['href' => 'principal:user2'] @@ -54,7 +56,7 @@ class AddressBookTest extends TestCase { ]; $l10n = $this->createMock(IL10N::class); $logger = $this->createMock(LoggerInterface::class); - $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); + $addressBook = new AddressBook($backend, $addressBookInfo, $l10n); $addressBook->delete(); } @@ -63,7 +65,7 @@ class AddressBookTest extends TestCase { $this->expectException(Forbidden::class); /** @var MockObject | CardDavBackend $backend */ - $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $backend = $this->createMock(CardDavBackend::class); $backend->expects($this->never())->method('updateShares'); $backend->expects($this->any())->method('getShares')->willReturn([ ['href' => 'principal:group2'] @@ -77,14 +79,14 @@ class AddressBookTest extends TestCase { ]; $l10n = $this->createMock(IL10N::class); $logger = $this->createMock(LoggerInterface::class); - $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); + $addressBook = new AddressBook($backend, $addressBookInfo, $l10n); $addressBook->delete(); } public function testPropPatchShared(): void { /** @var MockObject | CardDavBackend $backend */ - $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $backend = $this->createMock(CardDavBackend::class); $backend->expects($this->never())->method('updateAddressBook'); $addressBookInfo = [ '{http://owncloud.org/ns}owner-principal' => 'user1', @@ -95,13 +97,13 @@ class AddressBookTest extends TestCase { ]; $l10n = $this->createMock(IL10N::class); $logger = $this->createMock(LoggerInterface::class); - $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); + $addressBook = new AddressBook($backend, $addressBookInfo, $l10n); $addressBook->propPatch(new PropPatch(['{DAV:}displayname' => 'Test address book'])); } public function testPropPatchNotShared(): void { /** @var MockObject | CardDavBackend $backend */ - $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $backend = $this->createMock(CardDavBackend::class); $backend->expects($this->atLeast(1))->method('updateAddressBook'); $addressBookInfo = [ '{DAV:}displayname' => 'Test address book', @@ -111,16 +113,14 @@ class AddressBookTest extends TestCase { ]; $l10n = $this->createMock(IL10N::class); $logger = $this->createMock(LoggerInterface::class); - $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); + $addressBook = new AddressBook($backend, $addressBookInfo, $l10n); $addressBook->propPatch(new PropPatch(['{DAV:}displayname' => 'Test address book'])); } - /** - * @dataProvider providesReadOnlyInfo - */ - public function testAcl($expectsWrite, $readOnlyValue, $hasOwnerSet): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesReadOnlyInfo')] + public function testAcl(bool $expectsWrite, ?bool $readOnlyValue, bool $hasOwnerSet): void { /** @var MockObject | CardDavBackend $backend */ - $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $backend = $this->createMock(CardDavBackend::class); $backend->expects($this->any())->method('applyShareAcl')->willReturnArgument(1); $addressBookInfo = [ '{DAV:}displayname' => 'Test address book', @@ -136,7 +136,7 @@ class AddressBookTest extends TestCase { } $l10n = $this->createMock(IL10N::class); $logger = $this->createMock(LoggerInterface::class); - $addressBook = new AddressBook($backend, $addressBookInfo, $l10n, $logger); + $addressBook = new AddressBook($backend, $addressBookInfo, $l10n); $acl = $addressBook->getACL(); $childAcl = $addressBook->getChildACL(); @@ -171,7 +171,7 @@ class AddressBookTest extends TestCase { $this->assertEquals($expectedAcl, $childAcl); } - public function providesReadOnlyInfo(): array { + public static function providesReadOnlyInfo(): array { return [ 'read-only property not set' => [true, null, true], 'read-only property is false' => [true, false, true], diff --git a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php index aeee04fd6ee..6908dfd17bc 100644 --- a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -14,25 +15,19 @@ use OCA\DAV\DAV\GroupPrincipalBackend; use OCP\IConfig; use OCP\IDBConnection; use OCP\IL10N; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Reader; use Test\TestCase; class BirthdayServiceTest extends TestCase { - /** @var BirthdayService */ - private $service; - /** @var CalDavBackend | \PHPUnit\Framework\MockObject\MockObject */ - private $calDav; - /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject */ - private $cardDav; - /** @var GroupPrincipalBackend | \PHPUnit\Framework\MockObject\MockObject */ - private $groupPrincipalBackend; - /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ - private $config; - /** @var IDBConnection | \PHPUnit\Framework\MockObject\MockObject */ - private $dbConnection; - /** @var IL10N | \PHPUnit\Framework\MockObject\MockObject */ - private $l10n; + private CalDavBackend&MockObject $calDav; + private CardDavBackend&MockObject $cardDav; + private GroupPrincipalBackend&MockObject $groupPrincipalBackend; + private IConfig&MockObject $config; + private IDBConnection&MockObject $dbConnection; + private IL10N&MockObject $l10n; + private BirthdayService $service; protected function setUp(): void { parent::setUp(); @@ -55,18 +50,8 @@ class BirthdayServiceTest extends TestCase { $this->dbConnection, $this->l10n); } - /** - * @dataProvider providesVCards - * @param string $expectedSummary - * @param string $expectedDTStart - * @param string $expectedRrule - * @param string $expectedFieldType - * @param string $expectedUnknownYear - * @param string $expectedOriginalYear - * @param string|null $expectedReminder - * @param string | null $data - */ - public function testBuildBirthdayFromContact($expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Bytes, $configuredReminder): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesVCards')] + public function testBuildBirthdayFromContact(?string $expectedSummary, ?string $expectedDTStart, ?string $expectedRrule, ?string $expectedFieldType, ?string $expectedUnknownYear, ?string $expectedOriginalYear, ?string $expectedReminder, ?string $data, string $fieldType, string $prefix, bool $supports4Bytes, ?string $configuredReminder): void { $this->dbConnection->method('supports4ByteText')->willReturn($supports4Bytes); $cal = $this->service->buildDateFromContact($data, $fieldType, $prefix, $configuredReminder); @@ -152,13 +137,17 @@ class BirthdayServiceTest extends TestCase { ->willReturn([ 'id' => 1234 ]); - $this->calDav->expects($this->exactly(3)) + $calls = [ + [1234, 'default-gump.vcf.ics'], + [1234, 'default-gump.vcf-death.ics'], + [1234, 'default-gump.vcf-anniversary.ics'], + ]; + $this->calDav->expects($this->exactly(count($calls))) ->method('deleteCalendarObject') - ->withConsecutive( - [1234, 'default-gump.vcf.ics'], - [1234, 'default-gump.vcf-death.ics'], - [1234, 'default-gump.vcf-anniversary.ics'], - ); + ->willReturnCallback(function ($calendarId, $objectUri) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, [$calendarId, $objectUri]); + }); $this->cardDav->expects($this->once())->method('getShares')->willReturn([]); $this->service->onCardDeleted(666, 'gump.vcf'); @@ -173,7 +162,7 @@ class BirthdayServiceTest extends TestCase { $this->cardDav->expects($this->never())->method('getAddressBookById'); $service = $this->getMockBuilder(BirthdayService::class) - ->setMethods(['buildDateFromContact', 'birthdayEvenChanged']) + ->onlyMethods(['buildDateFromContact', 'birthdayEvenChanged']) ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n]) ->getMock(); @@ -200,19 +189,17 @@ class BirthdayServiceTest extends TestCase { $this->cardDav->expects($this->once())->method('getShares')->willReturn([]); $this->calDav->expects($this->never())->method('getCalendarByUri'); - /** @var BirthdayService | \PHPUnit\Framework\MockObject\MockObject $service */ + /** @var BirthdayService&MockObject $service */ $service = $this->getMockBuilder(BirthdayService::class) - ->setMethods(['buildDateFromContact', 'birthdayEvenChanged']) + ->onlyMethods(['buildDateFromContact', 'birthdayEvenChanged']) ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n]) ->getMock(); $service->onCardChanged(666, 'gump.vcf', ''); } - /** - * @dataProvider providesCardChanges - */ - public function testOnCardChanged($expectedOp): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesCardChanges')] + public function testOnCardChanged(string $expectedOp): void { $this->config->expects($this->once()) ->method('getAppValue') ->with('dav', 'generateBirthdayCalendar', 'yes') @@ -220,11 +207,10 @@ class BirthdayServiceTest extends TestCase { $this->config->expects($this->exactly(2)) ->method('getUserValue') - ->withConsecutive( - ['user01', 'dav', 'generateBirthdayCalendar', 'yes'], - ['user01', 'dav', 'birthdayCalendarReminderOffset', 'PT9H'], - ) - ->willReturnOnConsecutiveCalls('yes', 'PT9H'); + ->willReturnMap([ + ['user01', 'dav', 'generateBirthdayCalendar', 'yes', 'yes'], + ['user01', 'dav', 'birthdayCalendarReminderOffset', 'PT9H', 'PT9H'], + ]); $this->cardDav->expects($this->once())->method('getAddressBookById') ->with(666) @@ -239,31 +225,45 @@ class BirthdayServiceTest extends TestCase { ]); $this->cardDav->expects($this->once())->method('getShares')->willReturn([]); - /** @var BirthdayService | \PHPUnit\Framework\MockObject\MockObject $service */ + /** @var BirthdayService&MockObject $service */ $service = $this->getMockBuilder(BirthdayService::class) - ->setMethods(['buildDateFromContact', 'birthdayEvenChanged']) + ->onlyMethods(['buildDateFromContact', 'birthdayEvenChanged']) ->setConstructorArgs([$this->calDav, $this->cardDav, $this->groupPrincipalBackend, $this->config, $this->dbConnection, $this->l10n]) ->getMock(); if ($expectedOp === 'delete') { $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn(''); $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn(null); - $this->calDav->expects($this->exactly(3))->method('deleteCalendarObject')->withConsecutive( + + $calls = [ [1234, 'default-gump.vcf.ics'], [1234, 'default-gump.vcf-death.ics'], [1234, 'default-gump.vcf-anniversary.ics'] - ); + ]; + $this->calDav->expects($this->exactly(count($calls))) + ->method('deleteCalendarObject') + ->willReturnCallback(function ($calendarId, $objectUri) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, [$calendarId, $objectUri]); + }); } if ($expectedOp === 'create') { $vCal = new VCalendar(); $vCal->PRODID = '-//Nextcloud testing//mocked object//'; $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn($vCal); - $this->calDav->expects($this->exactly(3))->method('createCalendarObject')->withConsecutive( + + $createCalendarObjectCalls = [ [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"], [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"], [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"] - ); + ]; + $this->calDav->expects($this->exactly(count($createCalendarObjectCalls))) + ->method('createCalendarObject') + ->willReturnCallback(function ($calendarId, $objectUri, $calendarData) use (&$createCalendarObjectCalls): void { + $expected = array_shift($createCalendarObjectCalls); + $this->assertEquals($expected, [$calendarId, $objectUri, $calendarData]); + }); } if ($expectedOp === 'update') { $vCal = new VCalendar(); @@ -272,23 +272,25 @@ class BirthdayServiceTest extends TestCase { $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn($vCal); $service->expects($this->exactly(3))->method('birthdayEvenChanged')->willReturn(true); $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn(['calendardata' => '']); - $this->calDav->expects($this->exactly(3))->method('updateCalendarObject')->withConsecutive( + + $updateCalendarObjectCalls = [ [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"], [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"], [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nCALSCALE:GREGORIAN\r\nPRODID:-//Nextcloud testing//mocked object//\r\nEND:VCALENDAR\r\n"] - ); + ]; + $this->calDav->expects($this->exactly(count($updateCalendarObjectCalls))) + ->method('updateCalendarObject') + ->willReturnCallback(function ($calendarId, $objectUri, $calendarData) use (&$updateCalendarObjectCalls): void { + $expected = array_shift($updateCalendarObjectCalls); + $this->assertEquals($expected, [$calendarId, $objectUri, $calendarData]); + }); } $service->onCardChanged(666, 'gump.vcf', ''); } - /** - * @dataProvider providesBirthday - * @param $expected - * @param $old - * @param $new - */ - public function testBirthdayEvenChanged($expected, $old, $new): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesBirthday')] + public function testBirthdayEvenChanged(bool $expected, string $old, string $new): void { $new = Reader::read($new); $this->assertEquals($expected, $this->service->birthdayEvenChanged($old, $new)); } @@ -354,18 +356,22 @@ class BirthdayServiceTest extends TestCase { ->with(42, 0) ->willReturn([['uri' => '1.ics'], ['uri' => '2.ics'], ['uri' => '3.ics']]); - $this->calDav->expects($this->exactly(3)) + $calls = [ + [42, '1.ics', 0], + [42, '2.ics', 0], + [42, '3.ics', 0], + ]; + $this->calDav->expects($this->exactly(count($calls))) ->method('deleteCalendarObject') - ->withConsecutive( - [42, '1.ics', 0], - [42, '2.ics', 0], - [42, '3.ics', 0], - ); + ->willReturnCallback(function ($calendarId, $objectUri, $calendarType) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, [$calendarId, $objectUri, $calendarType]); + }); $this->service->resetForUser('user123'); } - public function providesBirthday() { + public static function providesBirthday(): array { return [ [true, '', @@ -382,7 +388,7 @@ class BirthdayServiceTest extends TestCase { ]; } - public function providesCardChanges() { + public static function providesCardChanges(): array { return[ ['delete'], ['create'], @@ -390,7 +396,7 @@ class BirthdayServiceTest extends TestCase { ]; } - public function providesVCards() { + public static function providesVCards(): array { return [ // $expectedSummary, $expectedDTStart, $expectedRrule, $expectedFieldType, $expectedUnknownYear, $expectedOriginalYear, $expectedReminder, $data, $fieldType, $prefix, $supports4Byte, $configuredReminder [null, null, null, null, null, null, null, 'yasfewf', '', '', true, null], diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index b8887e6f930..c5eafa0764a 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -27,7 +27,9 @@ use OCP\IL10N; use OCP\IUserManager; use OCP\IUserSession; use OCP\L10N\IFactory; +use OCP\Server; use OCP\Share\IManager as ShareManager; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\PropPatch; @@ -44,70 +46,58 @@ use function time; * @package OCA\DAV\Tests\unit\CardDAV */ class CardDavBackendTest extends TestCase { - /** @var CardDavBackend */ - private $backend; - - /** @var Principal | \PHPUnit\Framework\MockObject\MockObject */ - private $principal; - - /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ - private $userManager; - - /** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */ - private $groupManager; - - /** @var IEventDispatcher|MockObject */ - private $dispatcher; + private Principal&MockObject $principal; + private IUserManager&MockObject $userManager; + private IGroupManager&MockObject $groupManager; + private IEventDispatcher&MockObject $dispatcher; + private IConfig&MockObject $config; private Backend $sharingBackend; - /** @var IDBConnection */ - private $db; - - /** @var string */ - private $dbCardsTable = 'cards'; - - /** @var string */ - private $dbCardsPropertiesTable = 'cards_properties'; + private IDBConnection $db; + private CardDavBackend $backend; + private string $dbCardsTable = 'cards'; + private string $dbCardsPropertiesTable = 'cards_properties'; public const UNIT_TEST_USER = 'principals/users/carddav-unit-test'; public const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1'; public const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group'; - private $vcardTest0 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test'.PHP_EOL. - 'FN:Test'.PHP_EOL. - 'N:Test;;;;'.PHP_EOL. - 'END:VCARD'; - - private $vcardTest1 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test2'.PHP_EOL. - 'FN:Test2'.PHP_EOL. - 'N:Test2;;;;'.PHP_EOL. - 'END:VCARD'; - - private $vcardTest2 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test3'.PHP_EOL. - 'FN:Test3'.PHP_EOL. - 'N:Test3;;;;'.PHP_EOL. - 'END:VCARD'; - - private $vcardTestNoUID = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'FN:TestNoUID'.PHP_EOL. - 'N:TestNoUID;;;;'.PHP_EOL. - 'END:VCARD'; + private $vcardTest0 = 'BEGIN:VCARD' . PHP_EOL + . 'VERSION:3.0' . PHP_EOL + . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL + . 'UID:Test' . PHP_EOL + . 'FN:Test' . PHP_EOL + . 'N:Test;;;;' . PHP_EOL + . 'END:VCARD'; + + private $vcardTest1 = 'BEGIN:VCARD' . PHP_EOL + . 'VERSION:3.0' . PHP_EOL + . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL + . 'UID:Test2' . PHP_EOL + . 'FN:Test2' . PHP_EOL + . 'N:Test2;;;;' . PHP_EOL + . 'END:VCARD'; + + private $vcardTest2 = 'BEGIN:VCARD' . PHP_EOL + . 'VERSION:3.0' . PHP_EOL + . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL + . 'UID:Test3' . PHP_EOL + . 'FN:Test3' . PHP_EOL + . 'N:Test3;;;;' . PHP_EOL + . 'END:VCARD'; + + private $vcardTestNoUID = 'BEGIN:VCARD' . PHP_EOL + . 'VERSION:3.0' . PHP_EOL + . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL + . 'FN:TestNoUID' . PHP_EOL + . 'N:TestNoUID;;;;' . PHP_EOL + . 'END:VCARD'; protected function setUp(): void { parent::setUp(); $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); + $this->config = $this->createMock(IConfig::class); $this->principal = $this->getMockBuilder(Principal::class) ->setConstructorArgs([ $this->userManager, @@ -118,10 +108,10 @@ class CardDavBackendTest extends TestCase { $this->createMock(IAppManager::class), $this->createMock(ProxyMapper::class), $this->createMock(KnownUserService::class), - $this->createMock(IConfig::class), + $this->config, $this->createMock(IFactory::class) ]) - ->setMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri']) + ->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri']) ->getMock(); $this->principal->method('getPrincipalByPath') ->willReturn([ @@ -133,7 +123,7 @@ class CardDavBackendTest extends TestCase { ->willReturn([self::UNIT_TEST_GROUP]); $this->dispatcher = $this->createMock(IEventDispatcher::class); - $this->db = \OC::$server->getDatabaseConnection(); + $this->db = Server::get(IDBConnection::class); $this->sharingBackend = new Backend($this->userManager, $this->groupManager, $this->principal, @@ -147,19 +137,24 @@ class CardDavBackendTest extends TestCase { $this->userManager, $this->dispatcher, $this->sharingBackend, + $this->config, ); // start every test with a empty cards_properties and cards table $query = $this->db->getQueryBuilder(); - $query->delete('cards_properties')->execute(); + $query->delete('cards_properties')->executeStatement(); $query = $this->db->getQueryBuilder(); - $query->delete('cards')->execute(); + $query->delete('cards')->executeStatement(); - $this->tearDown(); + $this->principal->method('getGroupMembership') + ->withAnyParameters() + ->willReturn([self::UNIT_TEST_GROUP]); + $books = $this->backend->getAddressBooksForUser(self::UNIT_TEST_USER); + foreach ($books as $book) { + $this->backend->deleteAddressBook($book['id']); + } } protected function tearDown(): void { - parent::tearDown(); - if (is_null($this->backend)) { return; } @@ -171,6 +166,8 @@ class CardDavBackendTest extends TestCase { foreach ($books as $book) { $this->backend->deleteAddressBook($book['id']); } + + parent::tearDown(); } public function testAddressBookOperations(): void { @@ -235,10 +232,11 @@ class CardDavBackendTest extends TestCase { } public function testCardOperations(): void { - /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject $backend */ + /** @var CardDavBackend&MockObject $backend */ $backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) - ->onlyMethods(['updateProperties', 'purgeProperties'])->getMock(); + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config]) + ->onlyMethods(['updateProperties', 'purgeProperties']) + ->getMock(); // create a new address book $backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); @@ -248,12 +246,16 @@ class CardDavBackendTest extends TestCase { $uri = $this->getUniqueID('card'); // updateProperties is expected twice, once for createCard and once for updateCard - $backend->expects($this->exactly(2)) + $calls = [ + [$bookId, $uri, $this->vcardTest0], + [$bookId, $uri, $this->vcardTest1], + ]; + $backend->expects($this->exactly(count($calls))) ->method('updateProperties') - ->withConsecutive( - [$bookId, $uri, $this->vcardTest0], - [$bookId, $uri, $this->vcardTest1], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); // Expect event $this->dispatcher @@ -292,8 +294,9 @@ class CardDavBackendTest extends TestCase { public function testMultiCard(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) - ->setMethods(['updateProperties'])->getMock(); + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config]) + ->onlyMethods(['updateProperties']) + ->getMock(); // create a new address book $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); @@ -332,7 +335,7 @@ class CardDavBackendTest extends TestCase { $this->assertArrayHasKey('lastmodified', $card); $this->assertArrayHasKey('etag', $card); $this->assertArrayHasKey('size', $card); - $this->assertEquals($this->{ 'vcardTest'.($index + 1) }, $card['carddata']); + $this->assertEquals($this->{ 'vcardTest' . ($index + 1) }, $card['carddata']); } // delete the card @@ -345,8 +348,9 @@ class CardDavBackendTest extends TestCase { public function testMultipleUIDOnDifferentAddressbooks(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) - ->onlyMethods(['updateProperties'])->getMock(); + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config]) + ->onlyMethods(['updateProperties']) + ->getMock(); // create 2 new address books $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); @@ -367,8 +371,9 @@ class CardDavBackendTest extends TestCase { public function testMultipleUIDDenied(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) - ->setMethods(['updateProperties'])->getMock(); + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) + ->onlyMethods(['updateProperties']) + ->getMock(); // create a new address book $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); @@ -388,8 +393,9 @@ class CardDavBackendTest extends TestCase { public function testNoValidUID(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) - ->setMethods(['updateProperties'])->getMock(); + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) + ->onlyMethods(['updateProperties']) + ->getMock(); // create a new address book $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); @@ -405,7 +411,7 @@ class CardDavBackendTest extends TestCase { public function testDeleteWithoutCard(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods([ 'getCardId', 'addChange', @@ -427,12 +433,17 @@ class CardDavBackendTest extends TestCase { ->method('getCardId') ->with($bookId, $uri) ->willThrowException(new \InvalidArgumentException()); - $this->backend->expects($this->exactly(2)) + + $calls = [ + [$bookId, $uri, 1], + [$bookId, $uri, 3], + ]; + $this->backend->expects($this->exactly(count($calls))) ->method('addChange') - ->withConsecutive( - [$bookId, $uri, 1], - [$bookId, $uri, 3] - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); $this->backend->expects($this->never()) ->method('purgeProperties'); @@ -445,8 +456,9 @@ class CardDavBackendTest extends TestCase { public function testSyncSupport(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) - ->setMethods(['updateProperties'])->getMock(); + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) + ->onlyMethods(['updateProperties']) + ->getMock(); // create a new address book $this->backend->createAddressBook(self::UNIT_TEST_USER, 'Example', []); @@ -513,7 +525,7 @@ class CardDavBackendTest extends TestCase { $cardId = 2; $backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods(['getCardId'])->getMock(); $backend->expects($this->any())->method('getCardId')->willReturn($cardId); @@ -526,7 +538,8 @@ class CardDavBackendTest extends TestCase { $query = $this->db->getQueryBuilder(); $query->select('*') - ->from('cards_properties'); + ->from('cards_properties') + ->orderBy('name'); $qResult = $query->execute(); $result = $qResult->fetchAll(); @@ -534,13 +547,13 @@ class CardDavBackendTest extends TestCase { $this->assertSame(2, count($result)); - $this->assertSame('UID', $result[0]['name']); - $this->assertSame($cardUri, $result[0]['value']); + $this->assertSame('FN', $result[0]['name']); + $this->assertSame('John Doe', $result[0]['value']); $this->assertSame($bookId, (int)$result[0]['addressbookid']); $this->assertSame($cardId, (int)$result[0]['cardid']); - $this->assertSame('FN', $result[1]['name']); - $this->assertSame('John Doe', $result[1]['value']); + $this->assertSame('UID', $result[1]['name']); + $this->assertSame($cardUri, $result[1]['value']); $this->assertSame($bookId, (int)$result[1]['addressbookid']); $this->assertSame($cardId, (int)$result[1]['cardid']); @@ -635,15 +648,8 @@ class CardDavBackendTest extends TestCase { $this->invokePrivate($this->backend, 'getCardId', [1, 'uri']); } - /** - * @dataProvider dataTestSearch - * - * @param string $pattern - * @param array $properties - * @param array $options - * @param array $expected - */ - public function testSearch($pattern, $properties, $options, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestSearch')] + public function testSearch(string $pattern, array $properties, array $options, array $expected): void { /** @var VCard $vCards */ $vCards = []; $vCards[0] = new VCard(); @@ -662,20 +668,21 @@ class CardDavBackendTest extends TestCase { $query = $this->db->getQueryBuilder(); for ($i = 0; $i < 3; $i++) { $query->insert($this->dbCardsTable) - ->values( - [ - 'addressbookid' => $query->createNamedParameter(0), - 'carddata' => $query->createNamedParameter($vCards[$i]->serialize(), IQueryBuilder::PARAM_LOB), - 'uri' => $query->createNamedParameter('uri' . $i), - 'lastmodified' => $query->createNamedParameter(time()), - 'etag' => $query->createNamedParameter('etag' . $i), - 'size' => $query->createNamedParameter(120), - ] - ); + ->values( + [ + 'addressbookid' => $query->createNamedParameter(0), + 'carddata' => $query->createNamedParameter($vCards[$i]->serialize(), IQueryBuilder::PARAM_LOB), + 'uri' => $query->createNamedParameter('uri' . $i), + 'lastmodified' => $query->createNamedParameter(time()), + 'etag' => $query->createNamedParameter('etag' . $i), + 'size' => $query->createNamedParameter(120), + ] + ); $query->execute(); $vCardIds[] = $query->getLastInsertId(); } + $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsPropertiesTable) ->values( [ @@ -687,6 +694,7 @@ class CardDavBackendTest extends TestCase { ] ); $query->execute(); + $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsPropertiesTable) ->values( [ @@ -698,6 +706,7 @@ class CardDavBackendTest extends TestCase { ] ); $query->execute(); + $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsPropertiesTable) ->values( [ @@ -709,6 +718,7 @@ class CardDavBackendTest extends TestCase { ] ); $query->execute(); + $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsPropertiesTable) ->values( [ @@ -720,6 +730,7 @@ class CardDavBackendTest extends TestCase { ] ); $query->execute(); + $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsPropertiesTable) ->values( [ @@ -749,7 +760,7 @@ class CardDavBackendTest extends TestCase { $this->assertSame(count($expected), count($found)); } - public function dataTestSearch() { + public static function dataTestSearch(): array { return [ ['John', ['FN'], [], [['uri0', 'John Doe'], ['uri1', 'John M. Doe']]], ['M. Doe', ['FN'], [], [['uri1', 'John M. Doe']]], @@ -766,16 +777,16 @@ class CardDavBackendTest extends TestCase { public function testGetCardUri(): void { $query = $this->db->getQueryBuilder(); $query->insert($this->dbCardsTable) - ->values( - [ - 'addressbookid' => $query->createNamedParameter(1), - 'carddata' => $query->createNamedParameter('carddata', IQueryBuilder::PARAM_LOB), - 'uri' => $query->createNamedParameter('uri'), - 'lastmodified' => $query->createNamedParameter(5489543), - 'etag' => $query->createNamedParameter('etag'), - 'size' => $query->createNamedParameter(120), - ] - ); + ->values( + [ + 'addressbookid' => $query->createNamedParameter(1), + 'carddata' => $query->createNamedParameter('carddata', IQueryBuilder::PARAM_LOB), + 'uri' => $query->createNamedParameter('uri'), + 'lastmodified' => $query->createNamedParameter(5489543), + 'etag' => $query->createNamedParameter('etag'), + 'size' => $query->createNamedParameter(120), + ] + ); $query->execute(); $id = $query->getLastInsertId(); @@ -794,16 +805,16 @@ class CardDavBackendTest extends TestCase { $query = $this->db->getQueryBuilder(); for ($i = 0; $i < 2; $i++) { $query->insert($this->dbCardsTable) - ->values( - [ - 'addressbookid' => $query->createNamedParameter($i), - 'carddata' => $query->createNamedParameter('carddata' . $i, IQueryBuilder::PARAM_LOB), - 'uri' => $query->createNamedParameter('uri' . $i), - 'lastmodified' => $query->createNamedParameter(5489543), - 'etag' => $query->createNamedParameter('etag' . $i), - 'size' => $query->createNamedParameter(120), - ] - ); + ->values( + [ + 'addressbookid' => $query->createNamedParameter($i), + 'carddata' => $query->createNamedParameter('carddata' . $i, IQueryBuilder::PARAM_LOB), + 'uri' => $query->createNamedParameter('uri' . $i), + 'lastmodified' => $query->createNamedParameter(5489543), + 'etag' => $query->createNamedParameter('etag' . $i), + 'size' => $query->createNamedParameter(120), + ] + ); $query->execute(); } @@ -837,7 +848,7 @@ class CardDavBackendTest extends TestCase { 'preferred' => $query->createNamedParameter(0) ] ) - ->execute(); + ->execute(); $result = $this->backend->collectCardProperties(666, 'FN'); $this->assertEquals(['John Doe'], $result); diff --git a/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php b/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php index 6b1abb2718d..bdd826f671b 100644 --- a/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php +++ b/apps/dav/tests/unit/CardDAV/ContactsManagerTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -9,25 +10,28 @@ namespace OCA\DAV\Tests\unit\CardDAV; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\ContactsManager; +use OCA\DAV\Db\PropertyMapper; use OCP\Contacts\IManager; use OCP\IL10N; use OCP\IURLGenerator; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class ContactsManagerTest extends TestCase { public function test(): void { - /** @var IManager | \PHPUnit\Framework\MockObject\MockObject $cm */ - $cm = $this->getMockBuilder(IManager::class)->disableOriginalConstructor()->getMock(); + /** @var IManager&MockObject $cm */ + $cm = $this->createMock(IManager::class); $cm->expects($this->exactly(2))->method('registerAddressBook'); - $urlGenerator = $this->getMockBuilder(IURLGenerator::class)->disableOriginalConstructor()->getMock(); - /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject $backEnd */ - $backEnd = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + $urlGenerator = $this->createMock(IURLGenerator::class); + /** @var CardDavBackend&MockObject $backEnd */ + $backEnd = $this->createMock(CardDavBackend::class); $backEnd->method('getAddressBooksForUser')->willReturn([ ['{DAV:}displayname' => 'Test address book', 'uri' => 'default'], ]); + $propertyMapper = $this->createMock(PropertyMapper::class); $l = $this->createMock(IL10N::class); - $app = new ContactsManager($backEnd, $l); + $app = new ContactsManager($backEnd, $l, $propertyMapper); $app->setupContactsProvider($cm, 'user01', $urlGenerator); } } diff --git a/apps/dav/tests/unit/CardDAV/ConverterTest.php b/apps/dav/tests/unit/CardDAV/ConverterTest.php index df6489d5053..00519b82766 100644 --- a/apps/dav/tests/unit/CardDAV/ConverterTest.php +++ b/apps/dav/tests/unit/CardDAV/ConverterTest.php @@ -22,17 +22,10 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class ConverterTest extends TestCase { - - /** @var IAccountManager|\PHPUnit\Framework\MockObject\MockObject */ - private $accountManager; - /** @var IUserManager|(IUserManager&MockObject)|MockObject */ - private IUserManager|MockObject $userManager; - - /** @var IURLGenerator */ - private $urlGenerator; - - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $logger; + private IAccountManager&MockObject $accountManager; + private IUserManager&MockObject $userManager; + private IURLGenerator&MockObject $urlGenerator; + private LoggerInterface&MockObject $logger; protected function setUp(): void { parent::setUp(); @@ -44,7 +37,7 @@ class ConverterTest extends TestCase { } /** - * @return IAccountProperty|MockObject + * @return IAccountProperty&MockObject */ protected function getAccountPropertyMock(string $name, ?string $value, string $scope) { $property = $this->createMock(IAccountProperty::class); @@ -77,17 +70,16 @@ class ConverterTest extends TestCase { yield $this->getAccountPropertyMock(IAccountManager::PROPERTY_TWITTER, '', IAccountManager::SCOPE_LOCAL); }); - $accountManager = $this->getMockBuilder(IAccountManager::class) - ->disableOriginalConstructor()->getMock(); + $accountManager = $this->createMock(IAccountManager::class); - $accountManager->expects($this->any())->method('getAccount')->willReturn($account); + $accountManager->expects($this->any()) + ->method('getAccount') + ->willReturn($account); return $accountManager; } - /** - * @dataProvider providesNewUsers - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesNewUsers')] public function testCreation($expectedVCard, $displayName = null, $eMailAddress = null, $cloudId = null): void { $user = $this->getUserMock((string)$displayName, $eMailAddress, $cloudId); $accountManager = $this->getAccountManager($user); @@ -104,7 +96,7 @@ class ConverterTest extends TestCase { } public function testManagerProp(): void { - $user = $this->getUserMock("user", "user@domain.tld", "user@cloud.domain.tld"); + $user = $this->getUserMock('user', 'user@domain.tld', 'user@cloud.domain.tld'); $user->method('getManagerUids') ->willReturn(['mgr']); $this->userManager->expects(self::once()) @@ -126,7 +118,7 @@ class ConverterTest extends TestCase { ); } - protected function compareData($expected, $data) { + protected function compareData(array $expected, array $data): void { foreach ($expected as $key => $value) { $found = false; foreach ($data[1] as $d) { @@ -141,7 +133,7 @@ class ConverterTest extends TestCase { } } - public function providesNewUsers() { + public static function providesNewUsers(): array { return [ [ null @@ -168,8 +160,8 @@ class ConverterTest extends TestCase { 'fn' => 'Dr. Foo Bar', 'photo' => 'MTIzNDU2Nzg5', ], - "Dr. Foo Bar", - "foo@bar.net", + 'Dr. Foo Bar', + 'foo@bar.net', 'foo@cloud.net' ], [ @@ -178,9 +170,9 @@ class ConverterTest extends TestCase { 'fn' => 'Dr. Foo Bar', 'photo' => 'MTIzNDU2Nzg5', ], - "Dr. Foo Bar", + 'Dr. Foo Bar', null, - "foo@cloud.net" + 'foo@cloud.net' ], [ [ @@ -195,19 +187,15 @@ class ConverterTest extends TestCase { ]; } - /** - * @dataProvider providesNames - * @param $expected - * @param $fullName - */ - public function testNameSplitter($expected, $fullName): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesNames')] + public function testNameSplitter(string $expected, string $fullName): void { $converter = new Converter($this->accountManager, $this->userManager, $this->urlGenerator, $this->logger); $r = $converter->splitFullName($fullName); $r = implode(';', $r); $this->assertEquals($expected, $r); } - public function providesNames() { + public static function providesNames(): array { return [ ['Sauron;;;;', 'Sauron'], ['Baggins;Bilbo;;;', 'Bilbo Baggins'], @@ -216,16 +204,13 @@ class ConverterTest extends TestCase { } /** - * @param $displayName - * @param $eMailAddress - * @param $cloudId - * @return IUser | \PHPUnit\Framework\MockObject\MockObject + * @return IUser&MockObject */ protected function getUserMock(string $displayName, ?string $eMailAddress, ?string $cloudId) { - $image0 = $this->getMockBuilder(IImage::class)->disableOriginalConstructor()->getMock(); + $image0 = $this->createMock(IImage::class); $image0->method('mimeType')->willReturn('image/jpeg'); $image0->method('data')->willReturn('123456789'); - $user = $this->getMockBuilder(IUser::class)->disableOriginalConstructor()->getMock(); + $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('12345'); $user->method('getDisplayName')->willReturn($displayName); $user->method('getEMailAddress')->willReturn($eMailAddress); diff --git a/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php index 6718153f910..d47f53bddcd 100644 --- a/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php +++ b/apps/dav/tests/unit/CardDAV/ImageExportPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -10,8 +11,10 @@ namespace OCA\DAV\Tests\unit\CardDAV; use OCA\DAV\CardDAV\AddressBook; use OCA\DAV\CardDAV\ImageExportPlugin; use OCA\DAV\CardDAV\PhotoCache; +use OCP\AppFramework\Http; use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\CardDAV\Card; use Sabre\DAV\Node; use Sabre\DAV\Server; @@ -21,18 +24,12 @@ use Sabre\HTTP\ResponseInterface; use Test\TestCase; class ImageExportPluginTest extends TestCase { - /** @var ResponseInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $response; - /** @var RequestInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var ImageExportPlugin|\PHPUnit\Framework\MockObject\MockObject */ - private $plugin; - /** @var Server */ - private $server; - /** @var Tree|\PHPUnit\Framework\MockObject\MockObject */ - private $tree; - /** @var PhotoCache|\PHPUnit\Framework\MockObject\MockObject */ - private $cache; + private ResponseInterface&MockObject $response; + private RequestInterface&MockObject $request; + private Server&MockObject $server; + private Tree&MockObject $tree; + private PhotoCache&MockObject $cache; + private ImageExportPlugin $plugin; protected function setUp(): void { parent::setUp(); @@ -44,24 +41,18 @@ class ImageExportPluginTest extends TestCase { $this->server->tree = $this->tree; $this->cache = $this->createMock(PhotoCache::class); - $this->plugin = $this->getMockBuilder(ImageExportPlugin::class) - ->setMethods(['getPhoto']) - ->setConstructorArgs([$this->cache]) - ->getMock(); + $this->plugin = new ImageExportPlugin($this->cache); $this->plugin->initialize($this->server); } - /** - * @dataProvider providesQueryParams - * @param $param - */ - public function testQueryParams($param): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesQueryParams')] + public function testQueryParams(array $param): void { $this->request->expects($this->once())->method('getQueryParameters')->willReturn($param); $result = $this->plugin->httpGet($this->request, $this->response); $this->assertTrue($result); } - public function providesQueryParams() { + public static function providesQueryParams(): array { return [ [[]], [['1']], @@ -86,7 +77,7 @@ class ImageExportPluginTest extends TestCase { $this->assertTrue($result); } - public function dataTestCard() { + public static function dataTestCard(): array { return [ [null, false], [null, true], @@ -95,13 +86,8 @@ class ImageExportPluginTest extends TestCase { ]; } - /** - * @dataProvider dataTestCard - * - * @param $size - * @param bool $photo - */ - public function testCard($size, $photo): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestCard')] + public function testCard(?int $size, bool $photo): void { $query = ['photo' => null]; if ($size !== null) { $query['size'] = $size; @@ -144,14 +130,18 @@ class ImageExportPluginTest extends TestCase { ->with(1, 'card', $size, $card) ->willReturn($file); - $this->response->expects($this->exactly(4)) + $setHeaderCalls = [ + ['Cache-Control', 'private, max-age=3600, must-revalidate'], + ['Etag', '"myEtag"'], + ['Content-Type', 'image/jpeg'], + ['Content-Disposition', 'attachment; filename=card.jpg'], + ]; + $this->response->expects($this->exactly(count($setHeaderCalls))) ->method('setHeader') - ->withConsecutive( - ['Cache-Control', 'private, max-age=3600, must-revalidate'], - ['Etag', '"myEtag"'], - ['Content-Type', 'image/jpeg'], - ['Content-Disposition', 'attachment; filename=card.jpg'], - ); + ->willReturnCallback(function () use (&$setHeaderCalls): void { + $expected = array_shift($setHeaderCalls); + $this->assertEquals($expected, func_get_args()); + }); $this->response->expects($this->once()) ->method('setStatus') @@ -160,18 +150,22 @@ class ImageExportPluginTest extends TestCase { ->method('setBody') ->with('imgdata'); } else { - $this->response->expects($this->exactly(2)) + $setHeaderCalls = [ + ['Cache-Control', 'private, max-age=3600, must-revalidate'], + ['Etag', '"myEtag"'], + ]; + $this->response->expects($this->exactly(count($setHeaderCalls))) ->method('setHeader') - ->withConsecutive( - ['Cache-Control', 'private, max-age=3600, must-revalidate'], - ['Etag', '"myEtag"'], - ); + ->willReturnCallback(function () use (&$setHeaderCalls): void { + $expected = array_shift($setHeaderCalls); + $this->assertEquals($expected, func_get_args()); + }); $this->cache->method('get') ->with(1, 'card', $size, $card) ->willThrowException(new NotFoundException()); $this->response->expects($this->once()) ->method('setStatus') - ->with(404); + ->with(Http::STATUS_NO_CONTENT); } $result = $this->plugin->httpGet($this->request, $this->response); diff --git a/apps/dav/tests/unit/CardDAV/Security/CardDavRateLimitingPluginTest.php b/apps/dav/tests/unit/CardDAV/Security/CardDavRateLimitingPluginTest.php index 33ab83a74ac..ee599d5a76c 100644 --- a/apps/dav/tests/unit/CardDAV/Security/CardDavRateLimitingPluginTest.php +++ b/apps/dav/tests/unit/CardDAV/Security/CardDavRateLimitingPluginTest.php @@ -24,11 +24,11 @@ use Test\TestCase; class CardDavRateLimitingPluginTest extends TestCase { - private Limiter|MockObject $limiter; - private CardDavBackend|MockObject $cardDavBackend; - private IUserManager|MockObject $userManager; - private LoggerInterface|MockObject $logger; - private IAppConfig|MockObject $config; + private Limiter&MockObject $limiter; + private CardDavBackend&MockObject $cardDavBackend; + private IUserManager&MockObject $userManager; + private LoggerInterface&MockObject $logger; + private IAppConfig&MockObject $config; private string $userId = 'user123'; private CardDavRateLimitingPlugin $plugin; diff --git a/apps/dav/tests/unit/CardDAV/Sharing/PluginTest.php b/apps/dav/tests/unit/CardDAV/Sharing/PluginTest.php index f35eb9f0040..1e934a69a53 100644 --- a/apps/dav/tests/unit/CardDAV/Sharing/PluginTest.php +++ b/apps/dav/tests/unit/CardDAV/Sharing/PluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -12,6 +13,7 @@ use OCA\DAV\DAV\Sharing\IShareable; use OCA\DAV\DAV\Sharing\Plugin; use OCP\IConfig; use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Server; use Sabre\DAV\SimpleCollection; use Sabre\HTTP\Request; @@ -19,31 +21,25 @@ use Sabre\HTTP\Response; use Test\TestCase; class PluginTest extends TestCase { - - /** @var Plugin */ - private $plugin; - /** @var Server */ - private $server; - /** @var IShareable | \PHPUnit\Framework\MockObject\MockObject */ - private $book; + private Plugin $plugin; + private Server $server; + private IShareable&MockObject $book; protected function setUp(): void { parent::setUp(); - /** @var Auth | \PHPUnit\Framework\MockObject\MockObject $authBackend */ - $authBackend = $this->getMockBuilder(Auth::class)->disableOriginalConstructor()->getMock(); - $authBackend->method('isDavAuthenticated')->willReturn(true); - - /** @var IRequest $request */ - $request = $this->getMockBuilder(IRequest::class)->disableOriginalConstructor()->getMock(); + $authBackend = $this->createMock(Auth::class); + $authBackend->method('isDavAuthenticated') + ->willReturn(true); + $request = $this->createMock(IRequest::class); $config = $this->createMock(IConfig::class); $this->plugin = new Plugin($authBackend, $request, $config); $root = new SimpleCollection('root'); $this->server = new \Sabre\DAV\Server($root); - /** @var SimpleCollection $node */ - $this->book = $this->getMockBuilder(IShareable::class)->disableOriginalConstructor()->getMock(); - $this->book->method('getName')->willReturn('addressbook1.vcf'); + $this->book = $this->createMock(IShareable::class); + $this->book->method('getName') + ->willReturn('addressbook1.vcf'); $root->addChild($this->book); $this->plugin->initialize($this->server); } diff --git a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php index a09d5de6a44..77caed336f4 100644 --- a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php @@ -7,54 +7,298 @@ */ namespace OCA\DAV\Tests\unit\CardDAV; +use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Psr7\Request as PsrRequest; +use GuzzleHttp\Psr7\Response as PsrResponse; +use OC\Http\Client\Response; use OCA\DAV\CardDAV\CardDavBackend; use OCA\DAV\CardDAV\Converter; use OCA\DAV\CardDAV\SyncService; +use OCP\Http\Client\IClient; +use OCP\Http\Client\IClientService; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IUser; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Http\Client\ClientExceptionInterface; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use Sabre\VObject\Component\VCard; use Test\TestCase; class SyncServiceTest extends TestCase { + + protected CardDavBackend&MockObject $backend; + protected IUserManager&MockObject $userManager; + protected IDBConnection&MockObject $dbConnection; + protected LoggerInterface $logger; + protected Converter&MockObject $converter; + protected IClient&MockObject $client; + protected IConfig&MockObject $config; + protected SyncService $service; + + public function setUp(): void { + parent::setUp(); + + $addressBook = [ + 'id' => 1, + 'uri' => 'system', + 'principaluri' => 'principals/system/system', + '{DAV:}displayname' => 'system', + // watch out, incomplete address book mock. + ]; + + $this->backend = $this->createMock(CardDavBackend::class); + $this->backend->method('getAddressBooksByUri') + ->with('principals/system/system', 1) + ->willReturn($addressBook); + + $this->userManager = $this->createMock(IUserManager::class); + $this->dbConnection = $this->createMock(IDBConnection::class); + $this->logger = new NullLogger(); + $this->converter = $this->createMock(Converter::class); + $this->client = $this->createMock(IClient::class); + $this->config = $this->createMock(IConfig::class); + + $clientService = $this->createMock(IClientService::class); + $clientService->method('newClient') + ->willReturn($this->client); + + $this->service = new SyncService( + $this->backend, + $this->userManager, + $this->dbConnection, + $this->logger, + $this->converter, + $clientService, + $this->config + ); + } + public function testEmptySync(): void { - $backend = $this->getBackendMock(0, 0, 0); + $this->backend->expects($this->exactly(0)) + ->method('createCard'); + $this->backend->expects($this->exactly(0)) + ->method('updateCard'); + $this->backend->expects($this->exactly(0)) + ->method('deleteCard'); + + $body = '<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns"> + <d:sync-token>http://sabre.io/ns/sync/1</d:sync-token> +</d:multistatus>'; + + $requestResponse = new Response(new PsrResponse( + 207, + ['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)], + $body + )); - $ss = $this->getSyncServiceMock($backend, []); - $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []); - $this->assertEquals('sync-token-1', $return); + $this->client + ->method('request') + ->willReturn($requestResponse); + + $token = $this->service->syncRemoteAddressBook( + '', + 'system', + 'system', + '1234567890', + null, + '1', + 'principals/system/system', + [] + )[0]; + + $this->assertEquals('http://sabre.io/ns/sync/1', $token); } public function testSyncWithNewElement(): void { - $backend = $this->getBackendMock(1, 0, 0); - $backend->method('getCard')->willReturn(false); + $this->backend->expects($this->exactly(1)) + ->method('createCard'); + $this->backend->expects($this->exactly(0)) + ->method('updateCard'); + $this->backend->expects($this->exactly(0)) + ->method('deleteCard'); + + $this->backend->method('getCard') + ->willReturn(false); - $ss = $this->getSyncServiceMock($backend, ['0' => [200 => '']]); - $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []); - $this->assertEquals('sync-token-1', $return); + + $body = '<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns"> + <d:response> + <d:href>/remote.php/dav/addressbooks/system/system/system/Database:alice.vcf</d:href> + <d:propstat> + <d:prop> + <d:getcontenttype>text/vcard; charset=utf-8</d:getcontenttype> + <d:getetag>"2df155fa5c2a24cd7f750353fc63f037"</d:getetag> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> + <d:sync-token>http://sabre.io/ns/sync/2</d:sync-token> +</d:multistatus>'; + + $reportResponse = new Response(new PsrResponse( + 207, + ['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)], + $body + )); + + $this->client + ->method('request') + ->willReturn($reportResponse); + + $vCard = 'BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 4.5.4//EN +UID:alice +FN;X-NC-SCOPE=v2-federated:alice +N;X-NC-SCOPE=v2-federated:alice;;;; +X-SOCIALPROFILE;TYPE=NEXTCLOUD;X-NC-SCOPE=v2-published:https://server2.internal/index.php/u/alice +CLOUD:alice@server2.internal +END:VCARD'; + + $getResponse = new Response(new PsrResponse( + 200, + ['Content-Type' => 'text/vcard; charset=utf-8', 'Content-Length' => strlen($vCard)], + $vCard, + )); + + $this->client + ->method('get') + ->willReturn($getResponse); + + $token = $this->service->syncRemoteAddressBook( + '', + 'system', + 'system', + '1234567890', + null, + '1', + 'principals/system/system', + [] + )[0]; + + $this->assertEquals('http://sabre.io/ns/sync/2', $token); } public function testSyncWithUpdatedElement(): void { - $backend = $this->getBackendMock(0, 1, 0); - $backend->method('getCard')->willReturn(true); + $this->backend->expects($this->exactly(0)) + ->method('createCard'); + $this->backend->expects($this->exactly(1)) + ->method('updateCard'); + $this->backend->expects($this->exactly(0)) + ->method('deleteCard'); + + $this->backend->method('getCard') + ->willReturn(true); + + + $body = '<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns"> + <d:response> + <d:href>/remote.php/dav/addressbooks/system/system/system/Database:alice.vcf</d:href> + <d:propstat> + <d:prop> + <d:getcontenttype>text/vcard; charset=utf-8</d:getcontenttype> + <d:getetag>"2df155fa5c2a24cd7f750353fc63f037"</d:getetag> + </d:prop> + <d:status>HTTP/1.1 200 OK</d:status> + </d:propstat> + </d:response> + <d:sync-token>http://sabre.io/ns/sync/3</d:sync-token> +</d:multistatus>'; + + $reportResponse = new Response(new PsrResponse( + 207, + ['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)], + $body + )); + + $this->client + ->method('request') + ->willReturn($reportResponse); + + $vCard = 'BEGIN:VCARD +VERSION:3.0 +PRODID:-//Sabre//Sabre VObject 4.5.4//EN +UID:alice +FN;X-NC-SCOPE=v2-federated:alice +N;X-NC-SCOPE=v2-federated:alice;;;; +X-SOCIALPROFILE;TYPE=NEXTCLOUD;X-NC-SCOPE=v2-published:https://server2.internal/index.php/u/alice +CLOUD:alice@server2.internal +END:VCARD'; + + $getResponse = new Response(new PsrResponse( + 200, + ['Content-Type' => 'text/vcard; charset=utf-8', 'Content-Length' => strlen($vCard)], + $vCard, + )); - $ss = $this->getSyncServiceMock($backend, ['0' => [200 => '']]); - $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []); - $this->assertEquals('sync-token-1', $return); + $this->client + ->method('get') + ->willReturn($getResponse); + + $token = $this->service->syncRemoteAddressBook( + '', + 'system', + 'system', + '1234567890', + null, + '1', + 'principals/system/system', + [] + )[0]; + + $this->assertEquals('http://sabre.io/ns/sync/3', $token); } public function testSyncWithDeletedElement(): void { - $backend = $this->getBackendMock(0, 0, 1); + $this->backend->expects($this->exactly(0)) + ->method('createCard'); + $this->backend->expects($this->exactly(0)) + ->method('updateCard'); + $this->backend->expects($this->exactly(1)) + ->method('deleteCard'); + + $body = '<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns"> +<d:response> + <d:href>/remote.php/dav/addressbooks/system/system/system/Database:alice.vcf</d:href> + <d:status>HTTP/1.1 404 Not Found</d:status> +</d:response> +<d:sync-token>http://sabre.io/ns/sync/4</d:sync-token> +</d:multistatus>'; + + $reportResponse = new Response(new PsrResponse( + 207, + ['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)], + $body + )); - $ss = $this->getSyncServiceMock($backend, ['0' => [404 => '']]); - $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []); - $this->assertEquals('sync-token-1', $return); + $this->client + ->method('request') + ->willReturn($reportResponse); + + $token = $this->service->syncRemoteAddressBook( + '', + 'system', + 'system', + '1234567890', + null, + '1', + 'principals/system/system', + [] + )[0]; + + $this->assertEquals('http://sabre.io/ns/sync/4', $token); } public function testEnsureSystemAddressBookExists(): void { - /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject $backend */ - $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); + /** @var CardDavBackend&MockObject $backend */ + $backend = $this->createMock(CardDavBackend::class); $backend->expects($this->exactly(1))->method('createAddressBook'); $backend->expects($this->exactly(2)) ->method('getAddressBooksByUri') @@ -63,34 +307,27 @@ class SyncServiceTest extends TestCase { [], ); - /** @var IUserManager $userManager */ - $userManager = $this->getMockBuilder(IUserManager::class)->disableOriginalConstructor()->getMock(); + $userManager = $this->createMock(IUserManager::class); $dbConnection = $this->createMock(IDBConnection::class); - $logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); + $logger = $this->createMock(LoggerInterface::class); $converter = $this->createMock(Converter::class); + $clientService = $this->createMock(IClientService::class); + $config = $this->createMock(IConfig::class); - $ss = new SyncService($backend, $userManager, $dbConnection, $logger, $converter); + $ss = new SyncService($backend, $userManager, $dbConnection, $logger, $converter, $clientService, $config); $ss->ensureSystemAddressBookExists('principals/users/adam', 'contacts', []); } - public function dataActivatedUsers() { + public static function dataActivatedUsers(): array { return [ [true, 1, 1, 1], [false, 0, 0, 3], ]; } - /** - * @dataProvider dataActivatedUsers - * - * @param boolean $activated - * @param integer $createCalls - * @param integer $updateCalls - * @param integer $deleteCalls - * @return void - */ - public function testUpdateAndDeleteUser($activated, $createCalls, $updateCalls, $deleteCalls): void { - /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject $backend */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataActivatedUsers')] + public function testUpdateAndDeleteUser(bool $activated, int $createCalls, int $updateCalls, int $deleteCalls): void { + /** @var CardDavBackend | MockObject $backend */ $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock(); $logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); @@ -106,12 +343,9 @@ class SyncServiceTest extends TestCase { ->with('principals/system/system', 'system') ->willReturn(['id' => -1]); - /** @var IUserManager | \PHPUnit\Framework\MockObject\MockObject $userManager */ - $userManager = $this->getMockBuilder(IUserManager::class)->disableOriginalConstructor()->getMock(); + $userManager = $this->createMock(IUserManager::class); $dbConnection = $this->createMock(IDBConnection::class); - - /** @var IUser | \PHPUnit\Framework\MockObject\MockObject $user */ - $user = $this->getMockBuilder(IUser::class)->disableOriginalConstructor()->getMock(); + $user = $this->createMock(IUser::class); $user->method('getBackendClassName')->willReturn('unittest'); $user->method('getUID')->willReturn('test-user'); $user->method('getCloudId')->willReturn('cloudId'); @@ -122,7 +356,10 @@ class SyncServiceTest extends TestCase { ->method('createCardFromUser') ->willReturn($this->createMock(VCard::class)); - $ss = new SyncService($backend, $userManager, $dbConnection, $logger, $converter); + $clientService = $this->createMock(IClientService::class); + $config = $this->createMock(IConfig::class); + + $ss = new SyncService($backend, $userManager, $dbConnection, $logger, $converter, $clientService, $config); $ss->updateUser($user); $ss->updateUser($user); @@ -130,45 +367,114 @@ class SyncServiceTest extends TestCase { $ss->deleteUser($user); } - /** - * @param int $createCount - * @param int $updateCount - * @param int $deleteCount - * @return \PHPUnit\Framework\MockObject\MockObject|CardDavBackend - */ - private function getBackendMock($createCount, $updateCount, $deleteCount) { - $backend = $this->getMockBuilder(CardDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $backend->expects($this->exactly($createCount))->method('createCard'); - $backend->expects($this->exactly($updateCount))->method('updateCard'); - $backend->expects($this->exactly($deleteCount))->method('deleteCard'); - return $backend; + public function testDeleteAddressbookWhenAccessRevoked(): void { + $this->expectException(ClientExceptionInterface::class); + + $this->backend->expects($this->exactly(0)) + ->method('createCard'); + $this->backend->expects($this->exactly(0)) + ->method('updateCard'); + $this->backend->expects($this->exactly(0)) + ->method('deleteCard'); + $this->backend->expects($this->exactly(1)) + ->method('deleteAddressBook'); + + $request = new PsrRequest( + 'REPORT', + 'https://server2.internal/remote.php/dav/addressbooks/system/system/system', + ['Content-Type' => 'application/xml'], + ); + + $body = '<?xml version="1.0" encoding="utf-8"?> +<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <s:exception>Sabre\DAV\Exception\NotAuthenticated</s:exception> + <s:message>No public access to this resource., Username or password was incorrect, No \'Authorization: Bearer\' header found. Either the client didn\'t send one, or the server is mis-configured, Username or password was incorrect</s:message> +</d:error>'; + + $response = new PsrResponse( + 401, + ['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)], + $body + ); + + $message = 'Client error: `REPORT https://server2.internal/cloud/remote.php/dav/addressbooks/system/system/system` resulted in a `401 Unauthorized` response: +<?xml version="1.0" encoding="utf-8"?> +<d:error xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns"> + <s:exception>Sabre\DA (truncated...) +'; + + $reportException = new ClientException( + $message, + $request, + $response + ); + + $this->client + ->method('request') + ->willThrowException($reportException); + + $this->service->syncRemoteAddressBook( + '', + 'system', + 'system', + '1234567890', + null, + '1', + 'principals/system/system', + [] + ); } - /** - * @param $backend - * @param $response - * @return SyncService|\PHPUnit\Framework\MockObject\MockObject - */ - private function getSyncServiceMock($backend, $response) { - $userManager = $this->getMockBuilder(IUserManager::class)->disableOriginalConstructor()->getMock(); - $dbConnection = $this->createMock(IDBConnection::class); - $logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock(); - $converter = $this->createMock(Converter::class); - /** @var SyncService | \PHPUnit\Framework\MockObject\MockObject $ss */ - $ss = $this->getMockBuilder(SyncService::class) - ->setMethods(['ensureSystemAddressBookExists', 'requestSyncReport', 'download', 'getCertPath']) - ->setConstructorArgs([$backend, $userManager, $dbConnection, $logger, $converter]) - ->getMock(); - $ss->method('requestSyncReport')->withAnyParameters()->willReturn(['response' => $response, 'token' => 'sync-token-1']); - $ss->method('ensureSystemAddressBookExists')->willReturn(['id' => 1]); - $ss->method('download')->willReturn([ - 'body' => '', - 'statusCode' => 200, - 'headers' => [] - ]); - $ss->method('getCertPath')->willReturn(''); - return $ss; + #[\PHPUnit\Framework\Attributes\DataProvider('providerUseAbsoluteUriReport')] + public function testUseAbsoluteUriReport(string $host, string $expected): void { + $body = '<?xml version="1.0"?> +<d:multistatus xmlns:d="DAV:" xmlns:s="http://sabredav.org/ns" xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:oc="http://owncloud.org/ns"> + <d:sync-token>http://sabre.io/ns/sync/1</d:sync-token> +</d:multistatus>'; + + $requestResponse = new Response(new PsrResponse( + 207, + ['Content-Type' => 'application/xml; charset=utf-8', 'Content-Length' => strlen($body)], + $body + )); + + $this->client + ->method('request') + ->with( + 'REPORT', + $this->callback(function ($uri) use ($expected) { + $this->assertEquals($expected, $uri); + return true; + }), + $this->callback(function ($options) { + $this->assertIsArray($options); + return true; + }), + ) + ->willReturn($requestResponse); + + $this->service->syncRemoteAddressBook( + $host, + 'system', + 'remote.php/dav/addressbooks/system/system/system', + '1234567890', + null, + '1', + 'principals/system/system', + [] + ); + } + + public static function providerUseAbsoluteUriReport(): array { + return [ + ['https://server.internal', 'https://server.internal/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal/', 'https://server.internal/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal/nextcloud', 'https://server.internal/nextcloud/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal/nextcloud/', 'https://server.internal/nextcloud/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal:8080', 'https://server.internal:8080/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal:8080/', 'https://server.internal:8080/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal:8080/nextcloud', 'https://server.internal:8080/nextcloud/remote.php/dav/addressbooks/system/system/system'], + ['https://server.internal:8080/nextcloud/', 'https://server.internal:8080/nextcloud/remote.php/dav/addressbooks/system/system/system'], + ]; } } diff --git a/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php b/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php index a1c614cb69d..4a218fa4616 100644 --- a/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php +++ b/apps/dav/tests/unit/CardDAV/SystemAddressBookTest.php @@ -30,15 +30,15 @@ use Sabre\VObject\Reader; use Test\TestCase; class SystemAddressBookTest extends TestCase { - private MockObject|BackendInterface $cardDavBackend; + private BackendInterface&MockObject $cardDavBackend; private array $addressBookInfo; - private IL10N|MockObject $l10n; - private IConfig|MockObject $config; + private IL10N&MockObject $l10n; + private IConfig&MockObject $config; private IUserSession $userSession; - private IRequest|MockObject $request; + private IRequest&MockObject $request; private array $server; - private TrustedServers|MockObject $trustedServers; - private IGroupManager|MockObject $groupManager; + private TrustedServers&MockObject $trustedServers; + private IGroupManager&MockObject $groupManager; private SystemAddressbook $addressBook; protected function setUp(): void { @@ -150,7 +150,7 @@ VCF; ->with(123, 'user.vcf') ->willReturn($originalCard); - $card = $this->addressBook->getChild("user.vcf"); + $card = $this->addressBook->getChild('user.vcf'); /** @var VCard $vCard */ $vCard = Reader::read($card->get()); @@ -180,7 +180,7 @@ VCF; ]); $this->expectException(NotFound::class); - $this->addressBook->getChild("LDAP:user.vcf"); + $this->addressBook->getChild('LDAP:user.vcf'); } public function testGetChildWithoutEnumeration(): void { @@ -193,7 +193,7 @@ VCF; ]); $this->expectException(Forbidden::class); - $this->addressBook->getChild("LDAP:user.vcf"); + $this->addressBook->getChild('LDAP:user.vcf'); } public function testGetChildAsGuest(): void { @@ -211,7 +211,7 @@ VCF; ->willReturn($user); $this->expectException(Forbidden::class); - $this->addressBook->getChild("LDAP:user.vcf"); + $this->addressBook->getChild('LDAP:user.vcf'); } public function testGetChildWithGroupEnumerationRestriction(): void { @@ -272,7 +272,7 @@ VCF; ->willReturn($user); $this->expectException(Forbidden::class); - $this->addressBook->getChild("LDAP:user.vcf"); + $this->addressBook->getChild('LDAP:user.vcf'); } public function testGetOwnChildWithPhoneNumberEnumerationRestriction(): void { @@ -305,7 +305,7 @@ VCF; 'carddata' => $cardData, ]); - $this->addressBook->getChild("LDAP:user.vcf"); + $this->addressBook->getChild('LDAP:user.vcf'); } public function testGetMultipleChildrenWithGroupEnumerationRestriction(): void { diff --git a/apps/dav/tests/unit/CardDAV/Validation/CardDavValidatePluginTest.php b/apps/dav/tests/unit/CardDAV/Validation/CardDavValidatePluginTest.php new file mode 100644 index 00000000000..058735ba32a --- /dev/null +++ b/apps/dav/tests/unit/CardDAV/Validation/CardDavValidatePluginTest.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +/* + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\CardDAV\Validation; + +use OCA\DAV\CardDAV\Validation\CardDavValidatePlugin; +use OCP\IAppConfig; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\Exception\Forbidden; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class CardDavValidatePluginTest extends TestCase { + + private CardDavValidatePlugin $plugin; + private IAppConfig&MockObject $config; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; + + protected function setUp(): void { + parent::setUp(); + // construct mock objects + $this->config = $this->createMock(IAppConfig::class); + $this->request = $this->createMock(RequestInterface::class); + $this->response = $this->createMock(ResponseInterface::class); + $this->plugin = new CardDavValidatePlugin( + $this->config, + ); + } + + public function testPutSizeLessThenLimit(): void { + + // construct method responses + $this->config + ->method('getValueInt') + ->with('dav', 'card_size_limit', 5242880) + ->willReturn(5242880); + $this->request + ->method('getRawServerValue') + ->with('CONTENT_LENGTH') + ->willReturn('1024'); + // test condition + $this->assertTrue( + $this->plugin->beforePut($this->request, $this->response) + ); + + } + + public function testPutSizeMoreThenLimit(): void { + + // construct method responses + $this->config + ->method('getValueInt') + ->with('dav', 'card_size_limit', 5242880) + ->willReturn(5242880); + $this->request + ->method('getRawServerValue') + ->with('CONTENT_LENGTH') + ->willReturn('6242880'); + $this->expectException(Forbidden::class); + // test condition + $this->plugin->beforePut($this->request, $this->response); + + } + +} diff --git a/apps/dav/tests/unit/Command/DeleteCalendarTest.php b/apps/dav/tests/unit/Command/DeleteCalendarTest.php index 3e3095a7bce..2bd269de6dc 100644 --- a/apps/dav/tests/unit/Command/DeleteCalendarTest.php +++ b/apps/dav/tests/unit/Command/DeleteCalendarTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Command; +namespace OCA\DAV\Tests\unit\Command; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CalDAV\CalDavBackend; @@ -28,23 +28,12 @@ class DeleteCalendarTest extends TestCase { public const USER = 'user'; public const NAME = 'calendar'; - /** @var CalDavBackend|MockObject */ - private $calDav; - - /** @var IConfig|MockObject */ - private $config; - - /** @var IL10N|MockObject */ - private $l10n; - - /** @var IUserManager|MockObject */ - private $userManager; - - /** @var DeleteCalendar */ - private $command; - - /** @var MockObject|LoggerInterface */ - private $logger; + private CalDavBackend&MockObject $calDav; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private IUserManager&MockObject $userManager; + private LoggerInterface&MockObject $logger; + private DeleteCalendar $command; protected function setUp(): void { parent::setUp(); @@ -100,7 +89,7 @@ class DeleteCalendarTest extends TestCase { public function testInvalidCalendar(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage( - 'User <' . self::USER . '> has no calendar named <' . self::NAME . '>.'); + 'User <' . self::USER . '> has no calendar named <' . self::NAME . '>.'); $this->userManager->expects($this->once()) ->method('userExists') @@ -126,7 +115,7 @@ class DeleteCalendarTest extends TestCase { $calendar = [ 'id' => $id, 'principaluri' => 'principals/users/' . self::USER, - 'uri' => self::NAME + 'uri' => self::NAME, ]; $this->userManager->expects($this->once()) @@ -187,7 +176,8 @@ class DeleteCalendarTest extends TestCase { $calendar = [ 'id' => $id, 'principaluri' => 'principals/users/' . self::USER, - 'uri' => BirthdayService::BIRTHDAY_CALENDAR_URI + 'uri' => BirthdayService::BIRTHDAY_CALENDAR_URI, + '{DAV:}displayname' => 'Test', ]; $this->userManager->expects($this->once()) @@ -216,7 +206,8 @@ class DeleteCalendarTest extends TestCase { $calendar = [ 'id' => 1234, 'principaluri' => 'principals/users/' . self::USER, - 'uri' => BirthdayService::BIRTHDAY_CALENDAR_URI + 'uri' => BirthdayService::BIRTHDAY_CALENDAR_URI, + '{DAV:}displayname' => 'Test', ]; $this->userManager->expects($this->once()) ->method('userExists') diff --git a/apps/dav/tests/unit/Command/ListAddressbooksTest.php b/apps/dav/tests/unit/Command/ListAddressbooksTest.php new file mode 100644 index 00000000000..2768ed576c3 --- /dev/null +++ b/apps/dav/tests/unit/Command/ListAddressbooksTest.php @@ -0,0 +1,107 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\Command; + +use OCA\DAV\CardDAV\CardDavBackend; +use OCA\DAV\Command\ListAddressbooks; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +/** + * Class ListCalendarsTest + * + * @package OCA\DAV\Tests\Command + */ +class ListAddressbooksTest extends TestCase { + private IUserManager&MockObject $userManager; + private CardDavBackend&MockObject $cardDavBackend; + private ListAddressbooks $command; + + public const USERNAME = 'username'; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->cardDavBackend = $this->createMock(CardDavBackend::class); + + $this->command = new ListAddressbooks( + $this->userManager, + $this->cardDavBackend + ); + } + + public function testWithBadUser(): void { + $this->expectException(\InvalidArgumentException::class); + + $this->userManager->expects($this->once()) + ->method('userExists') + ->with(self::USERNAME) + ->willReturn(false); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => self::USERNAME, + ]); + $this->assertStringContainsString('User <' . self::USERNAME . '> in unknown', $commandTester->getDisplay()); + } + + public function testWithCorrectUserWithNoCalendars(): void { + $this->userManager->expects($this->once()) + ->method('userExists') + ->with(self::USERNAME) + ->willReturn(true); + + $this->cardDavBackend->expects($this->once()) + ->method('getAddressBooksForUser') + ->with('principals/users/' . self::USERNAME) + ->willReturn([]); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => self::USERNAME, + ]); + $this->assertStringContainsString('User <' . self::USERNAME . "> has no addressbooks\n", $commandTester->getDisplay()); + } + + public static function dataExecute(): array { + return [ + [false, '✓'], + [true, 'x'] + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecute')] + public function testWithCorrectUser(bool $readOnly, string $output): void { + $this->userManager->expects($this->once()) + ->method('userExists') + ->with(self::USERNAME) + ->willReturn(true); + + $this->cardDavBackend->expects($this->once()) + ->method('getAddressBooksForUser') + ->with('principals/users/' . self::USERNAME) + ->willReturn([ + [ + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => $readOnly, + 'uri' => 'test', + '{DAV:}displayname' => 'dp', + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => 'owner-principal', + '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname' => 'owner-dp', + ] + ]); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => self::USERNAME, + ]); + $this->assertStringContainsString($output, $commandTester->getDisplay()); + } +} diff --git a/apps/dav/tests/unit/Command/ListCalendarSharesTest.php b/apps/dav/tests/unit/Command/ListCalendarSharesTest.php new file mode 100644 index 00000000000..e5d4251cbf9 --- /dev/null +++ b/apps/dav/tests/unit/Command/ListCalendarSharesTest.php @@ -0,0 +1,172 @@ +<?php + +declare(strict_types=1); +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\Command; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Command\ListCalendarShares; +use OCA\DAV\Connector\Sabre\Principal; +use OCA\DAV\DAV\Sharing\SharingMapper; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Symfony\Component\Console\Tester\CommandTester; +use Test\TestCase; + +class ListCalendarSharesTest extends TestCase { + + private IUserManager&MockObject $userManager; + private Principal&MockObject $principal; + private CalDavBackend&MockObject $caldav; + private SharingMapper $sharingMapper; + private ListCalendarShares $command; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->principal = $this->createMock(Principal::class); + $this->caldav = $this->createMock(CalDavBackend::class); + $this->sharingMapper = $this->createMock(SharingMapper::class); + + $this->command = new ListCalendarShares( + $this->userManager, + $this->principal, + $this->caldav, + $this->sharingMapper, + ); + } + + public function testUserUnknown(): void { + $user = 'bob'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("User $user is unknown"); + + $this->userManager->expects($this->once()) + ->method('userExists') + ->with($user) + ->willReturn(false); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => $user, + ]); + } + + public function testPrincipalNotFound(): void { + $user = 'bob'; + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Unable to fetch principal for user $user"); + + $this->userManager->expects($this->once()) + ->method('userExists') + ->with($user) + ->willReturn(true); + + $this->principal->expects($this->once()) + ->method('getPrincipalByPath') + ->with('principals/users/' . $user) + ->willReturn(null); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => $user, + ]); + } + + public function testNoCalendarShares(): void { + $user = 'bob'; + + $this->userManager->expects($this->once()) + ->method('userExists') + ->with($user) + ->willReturn(true); + + $this->principal->expects($this->once()) + ->method('getPrincipalByPath') + ->with('principals/users/' . $user) + ->willReturn([ + 'uri' => 'principals/users/' . $user, + ]); + + $this->principal->expects($this->once()) + ->method('getGroupMembership') + ->willReturn([]); + $this->principal->expects($this->once()) + ->method('getCircleMembership') + ->willReturn([]); + + $this->sharingMapper->expects($this->once()) + ->method('getSharesByPrincipals') + ->willReturn([]); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => $user, + ]); + + $this->assertStringContainsString( + "User $user has no calendar shares", + $commandTester->getDisplay() + ); + } + + public function testFilterByCalendarId(): void { + $user = 'bob'; + + $this->userManager->expects($this->once()) + ->method('userExists') + ->with($user) + ->willReturn(true); + + $this->principal->expects($this->once()) + ->method('getPrincipalByPath') + ->with('principals/users/' . $user) + ->willReturn([ + 'uri' => 'principals/users/' . $user, + ]); + + $this->principal->expects($this->once()) + ->method('getGroupMembership') + ->willReturn([]); + $this->principal->expects($this->once()) + ->method('getCircleMembership') + ->willReturn([]); + + $this->sharingMapper->expects($this->once()) + ->method('getSharesByPrincipals') + ->willReturn([ + [ + 'id' => 1000, + 'principaluri' => 'principals/users/bob', + 'type' => 'calendar', + 'access' => 2, + 'resourceid' => 10 + ], + [ + 'id' => 1001, + 'principaluri' => 'principals/users/bob', + 'type' => 'calendar', + 'access' => 3, + 'resourceid' => 11 + ], + ]); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'uid' => $user, + '--calendar-id' => 10, + ]); + + $this->assertStringNotContainsString( + '1001', + $commandTester->getDisplay() + ); + } +} diff --git a/apps/dav/tests/unit/Command/ListCalendarsTest.php b/apps/dav/tests/unit/Command/ListCalendarsTest.php index 30df9ee79dc..d398a7c772f 100644 --- a/apps/dav/tests/unit/Command/ListCalendarsTest.php +++ b/apps/dav/tests/unit/Command/ListCalendarsTest.php @@ -1,14 +1,17 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Command; +namespace OCA\DAV\Tests\unit\Command; use OCA\DAV\CalDAV\BirthdayService; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Command\ListCalendars; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\Console\Tester\CommandTester; use Test\TestCase; @@ -18,15 +21,9 @@ use Test\TestCase; * @package OCA\DAV\Tests\Command */ class ListCalendarsTest extends TestCase { - - /** @var \OCP\IUserManager|\PHPUnit\Framework\MockObject\MockObject $userManager */ - private $userManager; - - /** @var CalDavBackend|\PHPUnit\Framework\MockObject\MockObject $l10n */ - private $calDav; - - /** @var ListCalendars */ - private $command; + private IUserManager&MockObject $userManager; + private CalDavBackend&MockObject $calDav; + private ListCalendars $command; public const USERNAME = 'username'; @@ -54,7 +51,7 @@ class ListCalendarsTest extends TestCase { $commandTester->execute([ 'uid' => self::USERNAME, ]); - $this->assertStringContainsString("User <" . self::USERNAME . "> in unknown", $commandTester->getDisplay()); + $this->assertStringContainsString('User <' . self::USERNAME . '> in unknown', $commandTester->getDisplay()); } public function testWithCorrectUserWithNoCalendars(): void { @@ -72,19 +69,17 @@ class ListCalendarsTest extends TestCase { $commandTester->execute([ 'uid' => self::USERNAME, ]); - $this->assertStringContainsString("User <" . self::USERNAME . "> has no calendars\n", $commandTester->getDisplay()); + $this->assertStringContainsString('User <' . self::USERNAME . "> has no calendars\n", $commandTester->getDisplay()); } - public function dataExecute() { + public static function dataExecute(): array { return [ [false, '✓'], [true, 'x'] ]; } - /** - * @dataProvider dataExecute - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecute')] public function testWithCorrectUser(bool $readOnly, string $output): void { $this->userManager->expects($this->once()) ->method('userExists') diff --git a/apps/dav/tests/unit/Command/MoveCalendarTest.php b/apps/dav/tests/unit/Command/MoveCalendarTest.php index 09831fdb1b2..e9f016961f2 100644 --- a/apps/dav/tests/unit/Command/MoveCalendarTest.php +++ b/apps/dav/tests/unit/Command/MoveCalendarTest.php @@ -1,9 +1,11 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Command; +namespace OCA\DAV\Tests\unit\Command; use InvalidArgumentException; use OCA\DAV\CalDAV\CalDavBackend; @@ -24,29 +26,14 @@ use Test\TestCase; * @package OCA\DAV\Tests\Command */ class MoveCalendarTest extends TestCase { - /** @var \OCP\IUserManager|MockObject $userManager */ - private $userManager; - - /** @var \OCP\IGroupManager|MockObject $groupManager */ - private $groupManager; - - /** @var \OCP\Share\IManager|MockObject $shareManager */ - private $shareManager; - - /** @var IConfig|MockObject $l10n */ - private $config; - - /** @var IL10N|MockObject $l10n */ - private $l10n; - - /** @var CalDavBackend|MockObject $l10n */ - private $calDav; - - /** @var MoveCalendar */ - private $command; - - /** @var LoggerInterface|MockObject */ - private $logger; + private IUserManager&MockObject $userManager; + private IGroupManager&MockObject $groupManager; + private \OCP\Share\IManager&MockObject $shareManager; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private CalDavBackend&MockObject $calDav; + private LoggerInterface&MockObject $logger; + private MoveCalendar $command; protected function setUp(): void { parent::setUp(); @@ -70,32 +57,23 @@ class MoveCalendarTest extends TestCase { ); } - public function dataExecute() { + public static function dataExecute(): array { return [ [false, true], [true, false] ]; } - /** - * @dataProvider dataExecute - * - * @param $userOriginExists - * @param $userDestinationExists - */ - public function testWithBadUserOrigin($userOriginExists, $userDestinationExists): void { + #[\PHPUnit\Framework\Attributes\DataProvider('dataExecute')] + public function testWithBadUserOrigin(bool $userOriginExists, bool $userDestinationExists): void { $this->expectException(\InvalidArgumentException::class); $this->userManager->expects($this->exactly($userOriginExists ? 2 : 1)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturnOnConsecutiveCalls( - $userOriginExists, - $userDestinationExists, - ); + ->willReturnMap([ + ['user', $userOriginExists], + ['user2', $userDestinationExists], + ]); $commandTester = new CommandTester($this->command); $commandTester->execute([ @@ -112,11 +90,10 @@ class MoveCalendarTest extends TestCase { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->once())->method('getCalendarByUri') ->with('principals/users/user', 'personal') @@ -137,20 +114,20 @@ class MoveCalendarTest extends TestCase { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->exactly(2)) ->method('getCalendarByUri') - ->withConsecutive( - ['principals/users/user', 'personal'], - ['principals/users/user2', 'personal'], - ) - ->willReturn([ - 'id' => 1234, + ->willReturnMap([ + ['principals/users/user', 'personal', [ + 'id' => 1234, + ]], + ['principals/users/user2', 'personal', [ + 'id' => 1234, + ]], ]); $commandTester = new CommandTester($this->command); @@ -164,24 +141,19 @@ class MoveCalendarTest extends TestCase { public function testMove(): void { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->exactly(2)) ->method('getCalendarByUri') - ->withConsecutive( - ['principals/users/user', 'personal'], - ['principals/users/user2', 'personal'], - ) - ->willReturnOnConsecutiveCalls( - [ + ->willReturnMap([ + ['principals/users/user', 'personal', [ 'id' => 1234, - ], - null, - ); + ]], + ['principals/users/user2', 'personal', null], + ]); $this->calDav->expects($this->once())->method('getShares') ->with(1234) @@ -194,41 +166,34 @@ class MoveCalendarTest extends TestCase { 'destinationuid' => 'user2', ]); - $this->assertStringContainsString("[OK] Calendar <personal> was moved from user <user> to <user2>", $commandTester->getDisplay()); + $this->assertStringContainsString('[OK] Calendar <personal> was moved from user <user> to <user2>', $commandTester->getDisplay()); } - public function dataTestMoveWithDestinationNotPartOfGroup(): array { + public static function dataTestMoveWithDestinationNotPartOfGroup(): array { return [ [true], [false] ]; } - /** - * @dataProvider dataTestMoveWithDestinationNotPartOfGroup - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestMoveWithDestinationNotPartOfGroup')] public function testMoveWithDestinationNotPartOfGroup(bool $shareWithGroupMembersOnly): void { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->exactly(2)) ->method('getCalendarByUri') - ->withConsecutive( - ['principals/users/user', 'personal'], - ['principals/users/user2', 'personal'], - ) - ->willReturnOnConsecutiveCalls( - [ + ->willReturnMap([ + ['principals/users/user', 'personal', [ 'id' => 1234, 'uri' => 'personal', - ], - null, - ); + ]], + ['principals/users/user2', 'personal', null], + ]); $this->shareManager->expects($this->once())->method('shareWithGroupMembersOnly') ->willReturn($shareWithGroupMembersOnly); @@ -240,7 +205,7 @@ class MoveCalendarTest extends TestCase { ]); if ($shareWithGroupMembersOnly === true) { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("User <user2> is not part of the group <nextclouders> with whom the calendar <personal> was shared. You may use -f to move the calendar while deleting this share."); + $this->expectExceptionMessage('User <user2> is not part of the group <nextclouders> with whom the calendar <personal> was shared. You may use -f to move the calendar while deleting this share.'); } $commandTester = new CommandTester($this->command); @@ -254,25 +219,20 @@ class MoveCalendarTest extends TestCase { public function testMoveWithDestinationPartOfGroup(): void { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->exactly(2)) ->method('getCalendarByUri') - ->withConsecutive( - ['principals/users/user', 'personal'], - ['principals/users/user2', 'personal'], - ) - ->willReturnOnConsecutiveCalls( - [ + ->willReturnMap([ + ['principals/users/user', 'personal', [ 'id' => 1234, 'uri' => 'personal', - ], - null, - ); + ]], + ['principals/users/user2', 'personal', null], + ]); $this->shareManager->expects($this->once())->method('shareWithGroupMembersOnly') ->willReturn(true); @@ -294,32 +254,27 @@ class MoveCalendarTest extends TestCase { 'destinationuid' => 'user2', ]); - $this->assertStringContainsString("[OK] Calendar <personal> was moved from user <user> to <user2>", $commandTester->getDisplay()); + $this->assertStringContainsString('[OK] Calendar <personal> was moved from user <user> to <user2>', $commandTester->getDisplay()); } public function testMoveWithDestinationNotPartOfGroupAndForce(): void { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->exactly(2)) ->method('getCalendarByUri') - ->withConsecutive( - ['principals/users/user', 'personal'], - ['principals/users/user2', 'personal'], - ) - ->willReturnOnConsecutiveCalls( - [ + ->willReturnMap([ + ['principals/users/user', 'personal', [ 'id' => 1234, 'uri' => 'personal', '{DAV:}displayname' => 'Personal' - ], - null, - ); + ]], + ['principals/users/user2', 'personal', null], + ]); $this->shareManager->expects($this->once())->method('shareWithGroupMembersOnly') ->willReturn(true); @@ -342,55 +297,48 @@ class MoveCalendarTest extends TestCase { '--force' => true ]); - $this->assertStringContainsString("[OK] Calendar <personal> was moved from user <user> to <user2>", $commandTester->getDisplay()); + $this->assertStringContainsString('[OK] Calendar <personal> was moved from user <user> to <user2>', $commandTester->getDisplay()); } - public function dataTestMoveWithCalendarAlreadySharedToDestination(): array { + public static function dataTestMoveWithCalendarAlreadySharedToDestination(): array { return [ [true], [false] ]; } - /** - * @dataProvider dataTestMoveWithCalendarAlreadySharedToDestination - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestMoveWithCalendarAlreadySharedToDestination')] public function testMoveWithCalendarAlreadySharedToDestination(bool $force): void { $this->userManager->expects($this->exactly(2)) ->method('userExists') - ->withConsecutive( - ['user'], - ['user2'], - ) - ->willReturn(true); + ->willReturnMap([ + ['user', true], + ['user2', true], + ]); $this->calDav->expects($this->exactly(2)) ->method('getCalendarByUri') - ->withConsecutive( - ['principals/users/user', 'personal'], - ['principals/users/user2', 'personal'], - ) - ->willReturnOnConsecutiveCalls( - [ + ->willReturnMap([ + ['principals/users/user', 'personal', [ 'id' => 1234, 'uri' => 'personal', '{DAV:}displayname' => 'Personal' - ], - null, - ); + ]], + ['principals/users/user2', 'personal', null], + ]); $this->calDav->expects($this->once())->method('getShares') - ->with(1234) - ->willReturn([ - [ - 'href' => 'principal:principals/users/user2', - '{DAV:}displayname' => 'Personal' - ] - ]); + ->with(1234) + ->willReturn([ + [ + 'href' => 'principal:principals/users/user2', + '{DAV:}displayname' => 'Personal' + ] + ]); if ($force === false) { $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage("The calendar <personal> is already shared to user <user2>.You may use -f to move the calendar while deleting this share."); + $this->expectExceptionMessage('The calendar <personal> is already shared to user <user2>.You may use -f to move the calendar while deleting this share.'); } else { $this->calDav->expects($this->once())->method('updateShares'); } diff --git a/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php b/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php index 3c597e22a95..ec56aa64eb2 100644 --- a/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php +++ b/apps/dav/tests/unit/Command/RemoveInvalidSharesTest.php @@ -1,15 +1,17 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2018 ownCloud GmbH * SPDX-License-Identifier: AGPL-3.0-only */ -namespace OCA\DAV\Tests\Unit\Command; +namespace OCA\DAV\Tests\unit\Command; use OCA\DAV\Command\RemoveInvalidShares; use OCA\DAV\Connector\Sabre\Principal; -use OCP\Migration\IOutput; +use OCP\IDBConnection; +use OCP\Server; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Test\TestCase; @@ -23,7 +25,7 @@ use Test\TestCase; class RemoveInvalidSharesTest extends TestCase { protected function setUp(): void { parent::setUp(); - $db = \OC::$server->getDatabaseConnection(); + $db = Server::get(IDBConnection::class); $db->insertIfNotExist('*PREFIX*dav_shares', [ 'principaluri' => 'principal:unknown', @@ -34,19 +36,17 @@ class RemoveInvalidSharesTest extends TestCase { } public function test(): void { - $db = \OC::$server->getDatabaseConnection(); - /** @var Principal | \PHPUnit\Framework\MockObject\MockObject $principal */ + $db = Server::get(IDBConnection::class); $principal = $this->createMock(Principal::class); - /** @var IOutput | \PHPUnit\Framework\MockObject\MockObject $output */ - $output = $this->createMock(IOutput::class); - $repair = new RemoveInvalidShares($db, $principal); $this->invokePrivate($repair, 'run', [$this->createMock(InputInterface::class), $this->createMock(OutputInterface::class)]); $query = $db->getQueryBuilder(); - $result = $query->select('*')->from('dav_shares') - ->where($query->expr()->eq('principaluri', $query->createNamedParameter('principal:unknown')))->execute(); + $query->select('*') + ->from('dav_shares') + ->where($query->expr()->eq('principaluri', $query->createNamedParameter('principal:unknown'))); + $result = $query->executeQuery(); $data = $result->fetchAll(); $result->closeCursor(); $this->assertEquals(0, count($data)); diff --git a/apps/dav/tests/unit/Comments/CommentsNodeTest.php b/apps/dav/tests/unit/Comments/CommentsNodeTest.php index d2bb60af4b2..9e108b4cf63 100644 --- a/apps/dav/tests/unit/Comments/CommentsNodeTest.php +++ b/apps/dav/tests/unit/Comments/CommentsNodeTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -14,38 +15,26 @@ use OCP\Comments\MessageTooLongException; use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\PropPatch; class CommentsNodeTest extends \Test\TestCase { - - /** @var ICommentsManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $commentsManager; - - protected $comment; - protected $node; - protected $userManager; - protected $logger; - protected $userSession; + protected ICommentsManager&MockObject $commentsManager; + protected IComment&MockObject $comment; + protected IUserManager&MockObject $userManager; + protected LoggerInterface&MockObject $logger; + protected IUserSession&MockObject $userSession; + protected CommentNode $node; protected function setUp(): void { parent::setUp(); - $this->commentsManager = $this->getMockBuilder(ICommentsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->comment = $this->getMockBuilder(IComment::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); - $this->logger = $this->getMockBuilder(LoggerInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->commentsManager = $this->createMock(ICommentsManager::class); + $this->comment = $this->createMock(IComment::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->node = new CommentNode( $this->commentsManager, @@ -57,10 +46,7 @@ class CommentsNodeTest extends \Test\TestCase { } public function testDelete(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('alice'); @@ -92,10 +78,7 @@ class CommentsNodeTest extends \Test\TestCase { public function testDeleteForbidden(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('mallory'); @@ -144,10 +127,7 @@ class CommentsNodeTest extends \Test\TestCase { public function testUpdateComment(): void { $msg = 'Hello Earth'; - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('alice'); @@ -182,10 +162,7 @@ class CommentsNodeTest extends \Test\TestCase { $msg = null; - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('alice'); @@ -197,7 +174,7 @@ class CommentsNodeTest extends \Test\TestCase { $this->comment->expects($this->once()) ->method('setMessage') ->with($msg) - ->will($this->throwException(new \Exception('buh!'))); + ->willThrowException(new \Exception('buh!')); $this->comment->expects($this->any()) ->method('getActorType') @@ -221,10 +198,7 @@ class CommentsNodeTest extends \Test\TestCase { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('Message exceeds allowed character limit of'); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('alice'); @@ -235,7 +209,7 @@ class CommentsNodeTest extends \Test\TestCase { $this->comment->expects($this->once()) ->method('setMessage') - ->will($this->throwException(new MessageTooLongException())); + ->willThrowException(new MessageTooLongException()); $this->comment->expects($this->any()) ->method('getActorType') @@ -261,10 +235,7 @@ class CommentsNodeTest extends \Test\TestCase { $msg = 'HaXX0r'; - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('mallory'); @@ -296,10 +267,7 @@ class CommentsNodeTest extends \Test\TestCase { $msg = 'HaXX0r'; - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - + $user = $this->createMock(IUser::class); $user->expects($this->never()) ->method('getUID'); @@ -344,10 +312,7 @@ class CommentsNodeTest extends \Test\TestCase { } public function testPropPatch(): void { - $propPatch = $this->getMockBuilder(PropPatch::class) - ->disableOriginalConstructor() - ->getMock(); - + $propPatch = $this->createMock(PropPatch::class); $propPatch->expects($this->once()) ->method('handle') ->with('{http://owncloud.org/ns}message'); @@ -396,11 +361,10 @@ class CommentsNodeTest extends \Test\TestCase { $this->commentsManager->expects($this->exactly(2)) ->method('resolveDisplayName') - ->withConsecutive( - [$this->equalTo('user'), $this->equalTo('alice')], - [$this->equalTo('user'), $this->equalTo('bob')] - ) - ->willReturnOnConsecutiveCalls('Alice Al-Isson', 'Unknown user'); + ->willReturnMap([ + ['user', 'alice', 'Alice Al-Isson'], + ['user', 'bob', 'Unknown user'] + ]); $this->comment->expects($this->once()) ->method('getId') @@ -491,7 +455,7 @@ class CommentsNodeTest extends \Test\TestCase { $this->assertTrue(empty($expected)); } - public function readCommentProvider() { + public static function readCommentProvider(): array { $creationDT = new \DateTime('2016-01-19 18:48:00'); $diff = new \DateInterval('PT2H'); $readDT1 = clone $creationDT; @@ -505,11 +469,8 @@ class CommentsNodeTest extends \Test\TestCase { ]; } - /** - * @dataProvider readCommentProvider - * @param $expected - */ - public function testGetPropertiesUnreadProperty($creationDT, $readDT, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('readCommentProvider')] + public function testGetPropertiesUnreadProperty(\DateTime $creationDT, ?\DateTime $readDT, string $expected): void { $this->comment->expects($this->any()) ->method('getCreationDateTime') ->willReturn($creationDT); diff --git a/apps/dav/tests/unit/Comments/CommentsPluginTest.php b/apps/dav/tests/unit/Comments/CommentsPluginTest.php index 60c3e52ef54..18d32772f7b 100644 --- a/apps/dav/tests/unit/Comments/CommentsPluginTest.php +++ b/apps/dav/tests/unit/Comments/CommentsPluginTest.php @@ -14,44 +14,30 @@ use OCP\Comments\IComment; use OCP\Comments\ICommentsManager; use OCP\IUser; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\INode; use Sabre\DAV\Tree; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class CommentsPluginTest extends \Test\TestCase { - /** @var \Sabre\DAV\Server */ - private $server; - - /** @var Tree */ - private $tree; - - /** @var ICommentsManager */ - private $commentsManager; - - /** @var IUserSession */ - private $userSession; - - /** @var CommentsPluginImplementation */ - private $plugin; + private \Sabre\DAV\Server&MockObject $server; + private Tree&MockObject $tree; + private ICommentsManager&MockObject $commentsManager; + private IUserSession&MockObject $userSession; + private CommentsPluginImplementation $plugin; protected function setUp(): void { parent::setUp(); - $this->tree = $this->getMockBuilder(Tree::class) - ->disableOriginalConstructor() - ->getMock(); + $this->tree = $this->createMock(Tree::class); - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + $this->server = $this->getMockBuilder(\Sabre\DAV\Server::class) ->setConstructorArgs([$this->tree]) - ->setMethods(['getRequestUri']) + ->onlyMethods(['getRequestUri']) ->getMock(); - $this->commentsManager = $this->getMockBuilder(ICommentsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); + $this->commentsManager = $this->createMock(ICommentsManager::class); + $this->userSession = $this->createMock(IUserSession::class); $this->plugin = new CommentsPluginImplementation($this->commentsManager, $this->userSession); } @@ -151,7 +137,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - + public function testCreateCommentInvalidObject(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); @@ -198,7 +184,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('/' . $path) - ->will($this->throwException(new \Sabre\DAV\Exception\NotFound())); + ->willThrowException(new \Sabre\DAV\Exception\NotFound()); $request = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() @@ -233,7 +219,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - + public function testCreateCommentInvalidActor(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); @@ -321,7 +307,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - + public function testCreateCommentUnsupportedMediaType(): void { $this->expectException(\Sabre\DAV\Exception\UnsupportedMediaType::class); @@ -409,7 +395,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - + public function testCreateCommentInvalidPayload(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); @@ -503,7 +489,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - + public function testCreateCommentMessageTooLong(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('Message exceeds allowed character limit of'); @@ -597,7 +583,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - + public function testOnReportInvalidNode(): void { $this->expectException(\Sabre\DAV\Exception\ReportNotSupported::class); @@ -620,7 +606,7 @@ class CommentsPluginTest extends \Test\TestCase { $this->plugin->onReport(CommentsPluginImplementation::REPORT_NAME, [], '/' . $path); } - + public function testOnReportInvalidReportName(): void { $this->expectException(\Sabre\DAV\Exception\ReportNotSupported::class); diff --git a/apps/dav/tests/unit/Comments/EntityCollectionTest.php b/apps/dav/tests/unit/Comments/EntityCollectionTest.php index 0d4adae250e..29ebde7d602 100644 --- a/apps/dav/tests/unit/Comments/EntityCollectionTest.php +++ b/apps/dav/tests/unit/Comments/EntityCollectionTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,43 +8,32 @@ */ namespace OCA\DAV\Tests\unit\Comments; +use OCA\DAV\Comments\CommentNode; use OCA\DAV\Comments\EntityCollection; use OCP\Comments\IComment; use OCP\Comments\ICommentsManager; +use OCP\Comments\NotFoundException; use OCP\IUserManager; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; class EntityCollectionTest extends \Test\TestCase { - - /** @var \OCP\Comments\ICommentsManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $commentsManager; - /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $userManager; - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; - /** @var EntityCollection */ - protected $collection; - /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ - protected $userSession; + protected ICommentsManager&MockObject $commentsManager; + protected IUserManager&MockObject $userManager; + protected LoggerInterface&MockObject $logger; + protected IUserSession&MockObject $userSession; + protected EntityCollection $collection; protected function setUp(): void { parent::setUp(); - $this->commentsManager = $this->getMockBuilder(ICommentsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); - $this->logger = $this->getMockBuilder(LoggerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->collection = new \OCA\DAV\Comments\EntityCollection( + $this->commentsManager = $this->createMock(ICommentsManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->collection = new EntityCollection( '19', 'files', $this->commentsManager, @@ -68,7 +58,7 @@ class EntityCollectionTest extends \Test\TestCase { ); $node = $this->collection->getChild('55'); - $this->assertTrue($node instanceof \OCA\DAV\Comments\CommentNode); + $this->assertInstanceOf(CommentNode::class, $node); } @@ -78,7 +68,7 @@ class EntityCollectionTest extends \Test\TestCase { $this->commentsManager->expects($this->once()) ->method('get') ->with('55') - ->will($this->throwException(new \OCP\Comments\NotFoundException())); + ->willThrowException(new NotFoundException()); $this->collection->getChild('55'); } @@ -95,8 +85,8 @@ class EntityCollectionTest extends \Test\TestCase { $result = $this->collection->getChildren(); - $this->assertSame(count($result), 1); - $this->assertTrue($result[0] instanceof \OCA\DAV\Comments\CommentNode); + $this->assertCount(1, $result); + $this->assertInstanceOf(CommentNode::class, $result[0]); } public function testFindChildren(): void { @@ -112,8 +102,8 @@ class EntityCollectionTest extends \Test\TestCase { $result = $this->collection->findChildren(5, 15, $dt); - $this->assertSame(count($result), 1); - $this->assertTrue($result[0] instanceof \OCA\DAV\Comments\CommentNode); + $this->assertCount(1, $result); + $this->assertInstanceOf(CommentNode::class, $result[0]); } public function testChildExistsTrue(): void { @@ -124,7 +114,7 @@ class EntityCollectionTest extends \Test\TestCase { $this->commentsManager->expects($this->once()) ->method('get') ->with('44') - ->will($this->throwException(new \OCP\Comments\NotFoundException())); + ->willThrowException(new NotFoundException()); $this->assertFalse($this->collection->childExists('44')); } diff --git a/apps/dav/tests/unit/Comments/EntityTypeCollectionTest.php b/apps/dav/tests/unit/Comments/EntityTypeCollectionTest.php index 5ab9c38ecd0..e5178a3e786 100644 --- a/apps/dav/tests/unit/Comments/EntityTypeCollectionTest.php +++ b/apps/dav/tests/unit/Comments/EntityTypeCollectionTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,52 +9,38 @@ namespace OCA\DAV\Tests\unit\Comments; use OCA\DAV\Comments\EntityCollection as EntityCollectionImplemantation; +use OCA\DAV\Comments\EntityTypeCollection; use OCP\Comments\ICommentsManager; use OCP\IUserManager; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; class EntityTypeCollectionTest extends \Test\TestCase { - - /** @var ICommentsManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $commentsManager; - /** @var \OCP\IUserManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $userManager; - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; - /** @var \OCA\DAV\Comments\EntityTypeCollection */ - protected $collection; - /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ - protected $userSession; + protected ICommentsManager&MockObject $commentsManager; + protected IUserManager&MockObject $userManager; + protected LoggerInterface&MockObject $logger; + protected IUserSession&MockObject $userSession; + protected EntityTypeCollection $collection; protected $childMap = []; protected function setUp(): void { parent::setUp(); - $this->commentsManager = $this->getMockBuilder(ICommentsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); - $this->logger = $this->getMockBuilder(LoggerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $instance = $this; - - $this->collection = new \OCA\DAV\Comments\EntityTypeCollection( + $this->commentsManager = $this->createMock(ICommentsManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->logger = $this->createMock(LoggerInterface::class); + + $this->collection = new EntityTypeCollection( 'files', $this->commentsManager, $this->userManager, $this->userSession, $this->logger, - function ($child) use ($instance) { - return !empty($instance->childMap[$child]); + function ($child) { + return !empty($this->childMap[$child]); } ); } @@ -71,7 +58,7 @@ class EntityTypeCollectionTest extends \Test\TestCase { $this->childMap[17] = true; $ec = $this->collection->getChild('17'); - $this->assertTrue($ec instanceof EntityCollectionImplemantation); + $this->assertInstanceOf(EntityCollectionImplemantation::class, $ec); } diff --git a/apps/dav/tests/unit/Comments/RootCollectionTest.php b/apps/dav/tests/unit/Comments/RootCollectionTest.php index 6da96be5818..9a05d996c8c 100644 --- a/apps/dav/tests/unit/Comments/RootCollectionTest.php +++ b/apps/dav/tests/unit/Comments/RootCollectionTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -9,57 +10,41 @@ namespace OCA\DAV\Tests\unit\Comments; use OC\EventDispatcher\EventDispatcher; use OCA\DAV\Comments\EntityTypeCollection as EntityTypeCollectionImplementation; +use OCA\DAV\Comments\RootCollection; use OCP\Comments\CommentsEntityEvent; use OCP\Comments\ICommentsManager; use OCP\EventDispatcher\IEventDispatcher; use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; class RootCollectionTest extends \Test\TestCase { - - /** @var \OCP\Comments\ICommentsManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $commentsManager; - /** @var \OCP\IUserManager|\PHPUnit\Framework\MockObject\MockObject */ - protected $userManager; - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $logger; - /** @var \OCA\DAV\Comments\RootCollection */ - protected $collection; - /** @var \OCP\IUserSession|\PHPUnit\Framework\MockObject\MockObject */ - protected $userSession; - /** @var IEventDispatcher */ - protected $dispatcher; - /** @var \OCP\IUser|\PHPUnit\Framework\MockObject\MockObject */ - protected $user; + protected ICommentsManager&MockObject $commentsManager; + protected IUserManager&MockObject $userManager; + protected LoggerInterface&MockObject $logger; + protected IUserSession&MockObject $userSession; + protected IEventDispatcher $dispatcher; + protected IUser&MockObject $user; + protected RootCollection $collection; protected function setUp(): void { parent::setUp(); - $this->user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->commentsManager = $this->getMockBuilder(ICommentsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); - $this->logger = $this->getMockBuilder(LoggerInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->user = $this->createMock(IUser::class); + + $this->commentsManager = $this->createMock(ICommentsManager::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->logger = $this->createMock(LoggerInterface::class); $this->dispatcher = new EventDispatcher( new \Symfony\Component\EventDispatcher\EventDispatcher(), \OC::$server, $this->logger ); - $this->collection = new \OCA\DAV\Comments\RootCollection( + $this->collection = new RootCollection( $this->commentsManager, $this->userManager, $this->userSession, @@ -68,7 +53,7 @@ class RootCollectionTest extends \Test\TestCase { ); } - protected function prepareForInitCollections() { + protected function prepareForInitCollections(): void { $this->user->expects($this->any()) ->method('getUID') ->willReturn('alice'); @@ -101,7 +86,7 @@ class RootCollectionTest extends \Test\TestCase { public function testGetChild(): void { $this->prepareForInitCollections(); $etc = $this->collection->getChild('files'); - $this->assertTrue($etc instanceof EntityTypeCollectionImplementation); + $this->assertInstanceOf(EntityTypeCollectionImplementation::class, $etc); } @@ -124,7 +109,7 @@ class RootCollectionTest extends \Test\TestCase { $children = $this->collection->getChildren(); $this->assertFalse(empty($children)); foreach ($children as $child) { - $this->assertTrue($child instanceof EntityTypeCollectionImplementation); + $this->assertInstanceOf(EntityTypeCollectionImplementation::class, $child); } } diff --git a/apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php b/apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php index 4f3581d6071..8b8c775c8ec 100644 --- a/apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php +++ b/apps/dav/tests/unit/Connector/LegacyPublicAuthTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,12 +8,14 @@ */ namespace OCA\DAV\Tests\unit\Connector; +use OCA\DAV\Connector\LegacyPublicAuth; use OCP\IRequest; use OCP\ISession; use OCP\Security\Bruteforce\IThrottler; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; /** * Class LegacyPublicAuthTest @@ -22,38 +25,22 @@ use OCP\Share\IShare; * @package OCA\DAV\Tests\unit\Connector */ class LegacyPublicAuthTest extends \Test\TestCase { - - /** @var ISession|\PHPUnit\Framework\MockObject\MockObject */ - private $session; - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ - private $shareManager; - /** @var \OCA\DAV\Connector\LegacyPublicAuth */ - private $auth; - /** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */ - private $throttler; - - /** @var string */ - private $oldUser; + private ISession&MockObject $session; + private IRequest&MockObject $request; + private IManager&MockObject $shareManager; + private IThrottler&MockObject $throttler; + private LegacyPublicAuth $auth; + private string|false $oldUser; protected function setUp(): void { parent::setUp(); - $this->session = $this->getMockBuilder(ISession::class) - ->disableOriginalConstructor() - ->getMock(); - $this->request = $this->getMockBuilder(IRequest::class) - ->disableOriginalConstructor() - ->getMock(); - $this->shareManager = $this->getMockBuilder(IManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->throttler = $this->getMockBuilder(IThrottler::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->auth = new \OCA\DAV\Connector\LegacyPublicAuth( + $this->session = $this->createMock(ISession::class); + $this->request = $this->createMock(IRequest::class); + $this->shareManager = $this->createMock(IManager::class); + $this->throttler = $this->createMock(IThrottler::class); + + $this->auth = new LegacyPublicAuth( $this->request, $this->shareManager, $this->session, @@ -69,7 +56,9 @@ class LegacyPublicAuthTest extends \Test\TestCase { // Set old user \OC_User::setUserId($this->oldUser); - \OC_Util::setupFS($this->oldUser); + if ($this->oldUser !== false) { + \OC_Util::setupFS($this->oldUser); + } parent::tearDown(); } @@ -85,9 +74,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { } public function testShareNoPassword(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn(null); $this->shareManager->expects($this->once()) @@ -100,9 +87,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { } public function testSharePasswordFancyShareType(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(42); @@ -117,9 +102,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { public function testSharePasswordRemote(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_REMOTE); @@ -133,9 +116,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { } public function testSharePasswordLinkValidPassword(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_LINK); @@ -155,9 +136,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { } public function testSharePasswordMailValidPassword(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_EMAIL); @@ -177,9 +156,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { } public function testInvalidSharePasswordLinkValidSession(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_LINK); $share->method('getId')->willReturn('42'); @@ -203,9 +180,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { } public function testSharePasswordLinkInvalidSession(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_LINK); $share->method('getId')->willReturn('42'); @@ -230,9 +205,7 @@ class LegacyPublicAuthTest extends \Test\TestCase { public function testSharePasswordMailInvalidSession(): void { - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_EMAIL); $share->method('getId')->willReturn('42'); diff --git a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php index c9868debb43..4b42a815708 100644 --- a/apps/dav/tests/unit/Connector/Sabre/AuthTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/AuthTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,12 +8,16 @@ */ namespace OCA\DAV\Tests\unit\Connector\Sabre; +use OC\Authentication\Exceptions\PasswordLoginForbiddenException; use OC\Authentication\TwoFactorAuth\Manager; use OC\User\Session; +use OCA\DAV\Connector\Sabre\Auth; +use OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden; use OCP\IRequest; use OCP\ISession; use OCP\IUser; use OCP\Security\Bruteforce\IThrottler; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Server; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; @@ -25,34 +30,21 @@ use Test\TestCase; * @group DB */ class AuthTest extends TestCase { - /** @var ISession */ - private $session; - /** @var \OCA\DAV\Connector\Sabre\Auth */ - private $auth; - /** @var Session */ - private $userSession; - /** @var IRequest */ - private $request; - /** @var Manager */ - private $twoFactorManager; - /** @var IThrottler */ - private $throttler; + private ISession&MockObject $session; + private Session&MockObject $userSession; + private IRequest&MockObject $request; + private Manager&MockObject $twoFactorManager; + private IThrottler&MockObject $throttler; + private Auth $auth; protected function setUp(): void { parent::setUp(); - $this->session = $this->getMockBuilder(ISession::class) - ->disableOriginalConstructor()->getMock(); - $this->userSession = $this->getMockBuilder(Session::class) - ->disableOriginalConstructor()->getMock(); - $this->request = $this->getMockBuilder(IRequest::class) - ->disableOriginalConstructor()->getMock(); - $this->twoFactorManager = $this->getMockBuilder(Manager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->throttler = $this->getMockBuilder(IThrottler::class) - ->disableOriginalConstructor() - ->getMock(); - $this->auth = new \OCA\DAV\Connector\Sabre\Auth( + $this->session = $this->createMock(ISession::class); + $this->userSession = $this->createMock(Session::class); + $this->request = $this->createMock(IRequest::class); + $this->twoFactorManager = $this->createMock(Manager::class); + $this->throttler = $this->createMock(IThrottler::class); + $this->auth = new Auth( $this->session, $this->userSession, $this->request, @@ -68,7 +60,7 @@ class AuthTest extends TestCase { ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn(null); - $this->assertFalse($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + $this->assertFalse(self::invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); } public function testIsDavAuthenticatedWithWrongDavSession(): void { @@ -78,7 +70,7 @@ class AuthTest extends TestCase { ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn('AnotherUser'); - $this->assertFalse($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + $this->assertFalse(self::invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); } public function testIsDavAuthenticatedWithCorrectDavSession(): void { @@ -88,13 +80,11 @@ class AuthTest extends TestCase { ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn('MyTestUser'); - $this->assertTrue($this->invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); + $this->assertTrue(self::invokePrivate($this->auth, 'isDavAuthenticated', ['MyTestUser'])); } public function testValidateUserPassOfAlreadyDAVAuthenticatedUser(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->exactly(1)) ->method('getUID') ->willReturn('MyTestUser'); @@ -115,13 +105,11 @@ class AuthTest extends TestCase { ->expects($this->once()) ->method('close'); - $this->assertTrue($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + $this->assertTrue(self::invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); } public function testValidateUserPassOfInvalidDAVAuthenticatedUser(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->once()) ->method('getUID') ->willReturn('MyTestUser'); @@ -142,13 +130,11 @@ class AuthTest extends TestCase { ->expects($this->once()) ->method('close'); - $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + $this->assertFalse(self::invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); } public function testValidateUserPassOfInvalidDAVAuthenticatedUserWithValidPassword(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->exactly(2)) ->method('getUID') ->willReturn('MyTestUser'); @@ -178,7 +164,7 @@ class AuthTest extends TestCase { ->expects($this->once()) ->method('close'); - $this->assertTrue($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + $this->assertTrue(self::invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); } public function testValidateUserPassWithInvalidPassword(): void { @@ -195,12 +181,12 @@ class AuthTest extends TestCase { ->expects($this->once()) ->method('close'); - $this->assertFalse($this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); + $this->assertFalse(self::invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword'])); } public function testValidateUserPassWithPasswordLoginForbidden(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\PasswordLoginForbidden::class); + $this->expectException(PasswordLoginForbidden::class); $this->userSession ->expects($this->once()) @@ -210,21 +196,17 @@ class AuthTest extends TestCase { ->expects($this->once()) ->method('logClientIn') ->with('MyTestUser', 'MyTestPassword') - ->will($this->throwException(new \OC\Authentication\Exceptions\PasswordLoginForbiddenException())); + ->willThrowException(new PasswordLoginForbiddenException()); $this->session ->expects($this->once()) ->method('close'); - $this->invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword']); + self::invokePrivate($this->auth, 'validateUserPass', ['MyTestUser', 'MyTestPassword']); } public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGet(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -238,9 +220,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn(null); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('MyWrongDavUser'); @@ -262,12 +242,8 @@ class AuthTest extends TestCase { } public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenAndCorrectlyDavAuthenticated(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -285,9 +261,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn('LoggedInUser'); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('LoggedInUser'); @@ -307,12 +281,8 @@ class AuthTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class); $this->expectExceptionMessage('2FA challenge not passed.'); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -330,9 +300,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn('LoggedInUser'); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('LoggedInUser'); @@ -356,12 +324,8 @@ class AuthTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class); $this->expectExceptionMessage('CSRF check not passed.'); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -379,9 +343,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn('AnotherUser'); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('LoggedInUser'); @@ -397,12 +359,8 @@ class AuthTest extends TestCase { } public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForNonGetAndDesktopClient(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -420,9 +378,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn(null); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('MyWrongDavUser'); @@ -439,12 +395,8 @@ class AuthTest extends TestCase { } public function testAuthenticateAlreadyLoggedInWithoutCsrfTokenForGet(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -454,9 +406,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn(null); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('MyWrongDavUser'); @@ -474,12 +424,8 @@ class AuthTest extends TestCase { } public function testAuthenticateAlreadyLoggedInWithCsrfTokenForGet(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') @@ -489,9 +435,7 @@ class AuthTest extends TestCase { ->method('get') ->with('AUTHENTICATED_TO_DAV_BACKEND') ->willReturn(null); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('MyWrongDavUser'); @@ -509,15 +453,9 @@ class AuthTest extends TestCase { } public function testAuthenticateNoBasicAuthenticateHeadersProvided(): void { - $server = $this->getMockBuilder(Server::class) - ->disableOriginalConstructor() - ->getMock(); - $server->httpRequest = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $server->httpResponse = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $server = $this->createMock(Server::class); + $server->httpRequest = $this->createMock(RequestInterface::class); + $server->httpResponse = $this->createMock(ResponseInterface::class); $response = $this->auth->check($server->httpRequest, $server->httpResponse); $this->assertEquals([false, 'No \'Authorization: Basic\' header found. Either the client didn\'t send one, or the server is misconfigured'], $response); } @@ -527,39 +465,71 @@ class AuthTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class); $this->expectExceptionMessage('Cannot authenticate over ajax calls'); - /** @var \Sabre\HTTP\RequestInterface $httpRequest */ - $httpRequest = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - /** @var \Sabre\HTTP\ResponseInterface $httpResponse */ - $httpResponse = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + /** @var \Sabre\HTTP\RequestInterface&MockObject $httpRequest */ + $httpRequest = $this->createMock(RequestInterface::class); + /** @var \Sabre\HTTP\ResponseInterface&MockObject $httpResponse */ + $httpResponse = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->any()) ->method('isLoggedIn') ->willReturn(false); $httpRequest + ->expects($this->exactly(2)) + ->method('getHeader') + ->willReturnMap([ + ['X-Requested-With', 'XMLHttpRequest'], + ['Authorization', null], + ]); + + $this->auth->check($httpRequest, $httpResponse); + } + + public function testAuthenticateWithBasicAuthenticateHeadersProvidedWithAjax(): void { + // No CSRF + $this->request ->expects($this->once()) + ->method('passesCSRFCheck') + ->willReturn(false); + + /** @var \Sabre\HTTP\RequestInterface&MockObject $httpRequest */ + $httpRequest = $this->createMock(RequestInterface::class); + /** @var \Sabre\HTTP\ResponseInterface&MockObject $httpResponse */ + $httpResponse = $this->createMock(ResponseInterface::class); + $httpRequest + ->expects($this->any()) ->method('getHeader') - ->with('X-Requested-With') - ->willReturn('XMLHttpRequest'); + ->willReturnMap([ + ['X-Requested-With', 'XMLHttpRequest'], + ['Authorization', 'basic dXNlcm5hbWU6cGFzc3dvcmQ='], + ]); + + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('MyDavUser'); + $this->userSession + ->expects($this->any()) + ->method('isLoggedIn') + ->willReturn(false); + $this->userSession + ->expects($this->once()) + ->method('logClientIn') + ->with('username', 'password') + ->willReturn(true); + $this->userSession + ->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $this->auth->check($httpRequest, $httpResponse); } public function testAuthenticateNoBasicAuthenticateHeadersProvidedWithAjaxButUserIsStillLoggedIn(): void { /** @var \Sabre\HTTP\RequestInterface $httpRequest */ - $httpRequest = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $httpRequest = $this->createMock(RequestInterface::class); /** @var \Sabre\HTTP\ResponseInterface $httpResponse */ - $httpResponse = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); - /** @var IUser */ - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $httpResponse = $this->createMock(ResponseInterface::class); + $user = $this->createMock(IUser::class); $user->method('getUID')->willReturn('MyTestUser'); $this->userSession ->expects($this->any()) @@ -590,34 +560,21 @@ class AuthTest extends TestCase { } public function testAuthenticateValidCredentials(): void { - $server = $this->getMockBuilder(Server::class) - ->disableOriginalConstructor() - ->getMock(); - $server->httpRequest = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $server = $this->createMock(Server::class); + $server->httpRequest = $this->createMock(RequestInterface::class); $server->httpRequest - ->expects($this->exactly(2)) + ->expects($this->once()) ->method('getHeader') - ->withConsecutive( - ['X-Requested-With'], - ['Authorization'], - ) - ->willReturnOnConsecutiveCalls( - null, - 'basic dXNlcm5hbWU6cGFzc3dvcmQ=', - ); - $server->httpResponse = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + ->with('Authorization') + ->willReturn('basic dXNlcm5hbWU6cGFzc3dvcmQ='); + + $server->httpResponse = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->once()) ->method('logClientIn') ->with('username', 'password') ->willReturn(true); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->exactly(2)) ->method('getUID') ->willReturn('MyTestUser'); @@ -630,26 +587,16 @@ class AuthTest extends TestCase { } public function testAuthenticateInvalidCredentials(): void { - $server = $this->getMockBuilder(Server::class) - ->disableOriginalConstructor() - ->getMock(); - $server->httpRequest = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $server = $this->createMock(Server::class); + $server->httpRequest = $this->createMock(RequestInterface::class); $server->httpRequest ->expects($this->exactly(2)) ->method('getHeader') - ->withConsecutive( - ['X-Requested-With'], - ['Authorization'], - ) - ->willReturnOnConsecutiveCalls( - null, - 'basic dXNlcm5hbWU6cGFzc3dvcmQ=', - ); - $server->httpResponse = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + ->willReturnMap([ + ['Authorization', 'basic dXNlcm5hbWU6cGFzc3dvcmQ='], + ['X-Requested-With', null], + ]); + $server->httpResponse = $this->createMock(ResponseInterface::class); $this->userSession ->expects($this->once()) ->method('logClientIn') diff --git a/apps/dav/tests/unit/Connector/Sabre/BearerAuthTest.php b/apps/dav/tests/unit/Connector/Sabre/BearerAuthTest.php index 9922dee5e6e..1e6267d4cbb 100644 --- a/apps/dav/tests/unit/Connector/Sabre/BearerAuthTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/BearerAuthTest.php @@ -1,15 +1,20 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\DAV\Tests\unit\Connector\Sabre; +use OC\User\Session; use OCA\DAV\Connector\Sabre\BearerAuth; +use OCP\IConfig; use OCP\IRequest; use OCP\ISession; use OCP\IUser; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Test\TestCase; @@ -18,26 +23,26 @@ use Test\TestCase; * @group DB */ class BearerAuthTest extends TestCase { - /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ - private $userSession; - /** @var ISession|\PHPUnit\Framework\MockObject\MockObject */ - private $session; - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var BearerAuth */ - private $bearerAuth; + private IUserSession&MockObject $userSession; + private ISession&MockObject $session; + private IRequest&MockObject $request; + private BearerAuth $bearerAuth; + + private IConfig&MockObject $config; protected function setUp(): void { parent::setUp(); - $this->userSession = $this->createMock(\OC\User\Session::class); + $this->userSession = $this->createMock(Session::class); $this->session = $this->createMock(ISession::class); $this->request = $this->createMock(IRequest::class); + $this->config = $this->createMock(IConfig::class); $this->bearerAuth = new BearerAuth( $this->userSession, $this->session, - $this->request + $this->request, + $this->config, ); } @@ -67,9 +72,9 @@ class BearerAuthTest extends TestCase { } public function testChallenge(): void { - /** @var \PHPUnit\Framework\MockObject\MockObject|RequestInterface $request */ + /** @var RequestInterface&MockObject $request */ $request = $this->createMock(RequestInterface::class); - /** @var \PHPUnit\Framework\MockObject\MockObject|ResponseInterface $response */ + /** @var ResponseInterface&MockObject $response */ $response = $this->createMock(ResponseInterface::class); $result = $this->bearerAuth->challenge($request, $response); $this->assertEmpty($result); diff --git a/apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php index 607ad71b11d..366c9475b1b 100644 --- a/apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/BlockLegacyClientPluginTest.php @@ -10,42 +10,113 @@ declare(strict_types=1); namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin; +use OCA\Theming\ThemingDefaults; use OCP\IConfig; use PHPUnit\Framework\MockObject\MockObject; use Sabre\HTTP\RequestInterface; use Test\TestCase; +enum ERROR_TYPE { + case MIN_ERROR; + case MAX_ERROR; + case NONE; +} + /** * Class BlockLegacyClientPluginTest * * @package OCA\DAV\Tests\unit\Connector\Sabre */ class BlockLegacyClientPluginTest extends TestCase { - /** @var IConfig|MockObject */ - private $config; - /** @var BlockLegacyClientPlugin */ - private $blockLegacyClientVersionPlugin; + + private IConfig&MockObject $config; + private ThemingDefaults&MockObject $themingDefaults; + private BlockLegacyClientPlugin $blockLegacyClientVersionPlugin; protected function setUp(): void { parent::setUp(); $this->config = $this->createMock(IConfig::class); - $this->blockLegacyClientVersionPlugin = new BlockLegacyClientPlugin($this->config); + $this->themingDefaults = $this->createMock(ThemingDefaults::class); + $this->blockLegacyClientVersionPlugin = new BlockLegacyClientPlugin( + $this->config, + $this->themingDefaults, + ); } - public function oldDesktopClientProvider(): array { + public static function oldDesktopClientProvider(): array { return [ - ['Mozilla/5.0 (Windows) mirall/1.5.0'], - ['Mozilla/5.0 (Bogus Text) mirall/1.6.9'], + ['Mozilla/5.0 (Windows) mirall/1.5.0', ERROR_TYPE::MIN_ERROR], + ['Mozilla/5.0 (Bogus Text) mirall/1.6.9', ERROR_TYPE::MIN_ERROR], + ['Mozilla/5.0 (Windows) mirall/2.5.0', ERROR_TYPE::MAX_ERROR], + ['Mozilla/5.0 (Bogus Text) mirall/2.0.1', ERROR_TYPE::MAX_ERROR], + ['Mozilla/5.0 (Windows) mirall/2.0.0', ERROR_TYPE::NONE], + ['Mozilla/5.0 (Bogus Text) mirall/2.0.0', ERROR_TYPE::NONE], ]; } + #[\PHPUnit\Framework\Attributes\DataProvider('oldDesktopClientProvider')] + public function testBeforeHandlerException(string $userAgent, ERROR_TYPE $errorType): void { + $this->themingDefaults + ->expects($this->atMost(1)) + ->method('getSyncClientUrl') + ->willReturn('https://nextcloud.com/install/#install-clients'); + + $this->config + ->expects($this->exactly(2)) + ->method('getSystemValueString') + ->willReturnCallback(function (string $key) { + if ($key === 'minimum.supported.desktop.version') { + return '1.7.0'; + } + return '2.0.0'; + }); + + if ($errorType !== ERROR_TYPE::NONE) { + $errorString = $errorType === ERROR_TYPE::MIN_ERROR + ? 'This version of the client is unsupported. Upgrade to <a href="https://nextcloud.com/install/#install-clients">version 1.7.0 or later</a>.' + : 'This version of the client is unsupported. Downgrade to <a href="https://nextcloud.com/install/#install-clients">version 2.0.0 or earlier</a>.'; + $this->expectException(\Sabre\DAV\Exception\Forbidden::class); + $this->expectExceptionMessage($errorString); + } + + /** @var RequestInterface|MockObject $request */ + $request = $this->createMock(RequestInterface::class); + $request + ->expects($this->once()) + ->method('getHeader') + ->with('User-Agent') + ->willReturn($userAgent); + + $this->blockLegacyClientVersionPlugin->beforeHandler($request); + } + /** - * @dataProvider oldDesktopClientProvider + * Ensure that there is no room for XSS attack through configured URL / version */ - public function testBeforeHandlerException(string $userAgent): void { + #[\PHPUnit\Framework\Attributes\DataProvider('oldDesktopClientProvider')] + public function testBeforeHandlerExceptionPreventXSSAttack(string $userAgent, ERROR_TYPE $errorType): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); - $this->expectExceptionMessage('Unsupported client version.'); + + $this->themingDefaults + ->expects($this->atMost(1)) + ->method('getSyncClientUrl') + ->willReturn('https://example.com"><script>alter("hacked");</script>'); + + $this->config + ->expects($this->exactly(2)) + ->method('getSystemValueString') + ->willReturnCallback(function (string $key) { + if ($key === 'minimum.supported.desktop.version') { + return '1.7.0 <script>alert("unsafe")</script>'; + } + return '2.0.0 <script>alert("unsafe")</script>'; + }); + + $errorString = $errorType === ERROR_TYPE::MIN_ERROR + ? 'This version of the client is unsupported. Upgrade to <a href="https://example.com"><script>alter("hacked");</script>">version 1.7.0 <script>alert("unsafe")</script> or later</a>.' + : 'This version of the client is unsupported. Downgrade to <a href="https://example.com"><script>alter("hacked");</script>">version 2.0.0 <script>alert("unsafe")</script> or earlier</a>.'; + $this->expectExceptionMessage($errorString); /** @var RequestInterface|MockObject $request */ $request = $this->createMock('\Sabre\HTTP\RequestInterface'); @@ -55,26 +126,21 @@ class BlockLegacyClientPluginTest extends TestCase { ->with('User-Agent') ->willReturn($userAgent); - $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('minimum.supported.desktop.version', '2.3.0') - ->willReturn('1.7.0'); - $this->blockLegacyClientVersionPlugin->beforeHandler($request); } - public function newAndAlternateDesktopClientProvider(): array { + public static function newAndAlternateDesktopClientProvider(): array { return [ ['Mozilla/5.0 (Windows) mirall/1.7.0'], ['Mozilla/5.0 (Bogus Text) mirall/1.9.3'], ['Mozilla/5.0 (Not Our Client But Old Version) LegacySync/1.1.0'], + ['Mozilla/5.0 (Windows) mirall/4.7.0'], + ['Mozilla/5.0 (Bogus Text) mirall/3.9.3'], + ['Mozilla/5.0 (Not Our Client But Old Version) LegacySync/45.0.0'], ]; } - /** - * @dataProvider newAndAlternateDesktopClientProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('newAndAlternateDesktopClientProvider')] public function testBeforeHandlerSuccess(string $userAgent): void { /** @var RequestInterface|MockObject $request */ $request = $this->createMock(RequestInterface::class); @@ -85,10 +151,14 @@ class BlockLegacyClientPluginTest extends TestCase { ->willReturn($userAgent); $this->config - ->expects($this->once()) - ->method('getSystemValue') - ->with('minimum.supported.desktop.version', '2.3.0') - ->willReturn('1.7.0'); + ->expects($this->exactly(2)) + ->method('getSystemValueString') + ->willReturnCallback(function (string $key) { + if ($key === 'minimum.supported.desktop.version') { + return '1.7.0'; + } + return '10.0.0'; + }); $this->blockLegacyClientVersionPlugin->beforeHandler($request); } @@ -101,6 +171,7 @@ class BlockLegacyClientPluginTest extends TestCase { ->method('getHeader') ->with('User-Agent') ->willReturn(null); + $this->blockLegacyClientVersionPlugin->beforeHandler($request); } } diff --git a/apps/dav/tests/unit/Connector/Sabre/CommentsPropertiesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/CommentsPropertiesPluginTest.php index c2167214451..a934d6401c2 100644 --- a/apps/dav/tests/unit/Connector/Sabre/CommentsPropertiesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/CommentsPropertiesPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,62 +9,43 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\CommentPropertiesPlugin as CommentPropertiesPluginImplementation; +use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCP\Comments\ICommentsManager; use OCP\IUser; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\PropFind; +use Sabre\DAV\Server; class CommentsPropertiesPluginTest extends \Test\TestCase { - - /** @var CommentPropertiesPluginImplementation */ - protected $plugin; - protected $commentsManager; - protected $userSession; - protected $server; + protected CommentPropertiesPluginImplementation $plugin; + protected ICommentsManager&MockObject $commentsManager; + protected IUserSession&MockObject $userSession; + protected Server&MockObject $server; protected function setUp(): void { parent::setUp(); - $this->commentsManager = $this->getMockBuilder(ICommentsManager::class) - ->disableOriginalConstructor() - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') - ->disableOriginalConstructor() - ->getMock(); + $this->commentsManager = $this->createMock(ICommentsManager::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->server = $this->createMock(Server::class); $this->plugin = new CommentPropertiesPluginImplementation($this->commentsManager, $this->userSession); $this->plugin->initialize($this->server); } - public function nodeProvider() { - $mocks = []; - foreach (['\OCA\DAV\Connector\Sabre\File', '\OCA\DAV\Connector\Sabre\Directory', '\Sabre\DAV\INode'] as $class) { - $mocks[] = $this->getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); - } - + public static function nodeProvider(): array { return [ - [$mocks[0], true], - [$mocks[1], true], - [$mocks[2], false] + [File::class, true], + [Directory::class, true], + [\Sabre\DAV\INode::class, false] ]; } - /** - * @dataProvider nodeProvider - * @param $node - * @param $expectedSuccessful - */ - public function testHandleGetProperties($node, $expectedSuccessful): void { - $propFind = $this->getMockBuilder(PropFind::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('nodeProvider')] + public function testHandleGetProperties(string $class, bool $expectedSuccessful): void { + $propFind = $this->createMock(PropFind::class); if ($expectedSuccessful) { $propFind->expects($this->exactly(3)) @@ -73,10 +55,11 @@ class CommentsPropertiesPluginTest extends \Test\TestCase { ->method('handle'); } + $node = $this->createMock($class); $this->plugin->handleGetProperties($propFind, $node); } - public function baseUriProvider() { + public static function baseUriProvider(): array { return [ ['owncloud/remote.php/webdav/', '4567', 'owncloud/remote.php/dav/comments/files/4567'], ['owncloud/remote.php/files/', '4567', 'owncloud/remote.php/dav/comments/files/4567'], @@ -84,16 +67,9 @@ class CommentsPropertiesPluginTest extends \Test\TestCase { ]; } - /** - * @dataProvider baseUriProvider - * @param $baseUri - * @param $fid - * @param $expectedHref - */ - public function testGetCommentsLink($baseUri, $fid, $expectedHref): void { - $node = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('baseUriProvider')] + public function testGetCommentsLink(string $baseUri, string $fid, ?string $expectedHref): void { + $node = $this->createMock(File::class); $node->expects($this->any()) ->method('getId') ->willReturn($fid); @@ -106,29 +82,23 @@ class CommentsPropertiesPluginTest extends \Test\TestCase { $this->assertSame($expectedHref, $href); } - public function userProvider() { + public static function userProvider(): array { return [ - [ - $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock() - ], + [IUser::class], [null] ]; } - /** - * @dataProvider userProvider - * @param $user - */ - public function testGetUnreadCount($user): void { - $node = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('userProvider')] + public function testGetUnreadCount(?string $user): void { + $node = $this->createMock(File::class); $node->expects($this->any()) ->method('getId') ->willReturn('4567'); + if ($user !== null) { + $user = $this->createMock($user); + } $this->userSession->expects($this->once()) ->method('getUser') ->willReturn($user); diff --git a/apps/dav/tests/unit/Connector/Sabre/CopyEtagHeaderPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/CopyEtagHeaderPluginTest.php index bc19e071ee7..7067cf335ed 100644 --- a/apps/dav/tests/unit/Connector/Sabre/CopyEtagHeaderPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/CopyEtagHeaderPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -15,12 +16,8 @@ use Sabre\DAV\Tree; use Test\TestCase; class CopyEtagHeaderPluginTest extends TestCase { - - /** @var CopyEtagHeaderPlugin */ - private $plugin; - - /** @var Server */ - private $server; + private CopyEtagHeaderPlugin $plugin; + private Server $server; protected function setUp(): void { parent::setUp(); @@ -62,15 +59,11 @@ class CopyEtagHeaderPluginTest extends TestCase { } public function testAfterMove(): void { - $node = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(File::class); $node->expects($this->once()) ->method('getETag') ->willReturn('123456'); - $tree = $this->getMockBuilder(Tree::class) - ->disableOriginalConstructor() - ->getMock(); + $tree = $this->createMock(Tree::class); $tree->expects($this->once()) ->method('getNodeForPath') ->with('test.txt') diff --git a/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php index 4d6e1f13d5f..cafbdd3ca40 100644 --- a/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/CustomPropertiesBackendTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,9 +8,15 @@ */ namespace OCA\DAV\Tests\unit\Connector\Sabre; +use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; +use OCA\DAV\DAV\CustomPropertiesBackend; +use OCA\DAV\Db\PropertyMapper; +use OCP\IDBConnection; use OCP\IUser; +use OCP\Server; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Tree; /** @@ -20,56 +27,42 @@ use Sabre\DAV\Tree; * @package OCA\DAV\Tests\unit\Connector\Sabre */ class CustomPropertiesBackendTest extends \Test\TestCase { - - /** - * @var \Sabre\DAV\Server - */ - private $server; - - /** - * @var \Sabre\DAV\Tree - */ - private $tree; - - /** - * @var \OCA\DAV\DAV\CustomPropertiesBackend - */ - private $plugin; - - /** - * @var \OCP\IUser - */ - private $user; + private \Sabre\DAV\Server $server; + private \Sabre\DAV\Tree&MockObject $tree; + private IUser&MockObject $user; + private DefaultCalendarValidator&MockObject $defaultCalendarValidator; + private CustomPropertiesBackend $plugin; protected function setUp(): void { parent::setUp(); + $this->server = new \Sabre\DAV\Server(); - $this->tree = $this->getMockBuilder(Tree::class) - ->disableOriginalConstructor() - ->getMock(); + $this->tree = $this->createMock(Tree::class); - $userId = $this->getUniqueID('testcustompropertiesuser'); + $userId = self::getUniqueID('testcustompropertiesuser'); - $this->user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $this->user = $this->createMock(IUser::class); $this->user->expects($this->any()) ->method('getUID') ->willReturn($userId); - $this->plugin = new \OCA\DAV\DAV\CustomPropertiesBackend( + $this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class); + + $this->plugin = new CustomPropertiesBackend( $this->server, $this->tree, - \OC::$server->getDatabaseConnection(), - $this->user + Server::get(IDBConnection::class), + $this->user, + Server::get(PropertyMapper::class), + $this->defaultCalendarValidator, ); } protected function tearDown(): void { - $connection = \OC::$server->getDatabaseConnection(); + $connection = Server::get(IDBConnection::class); $deleteStatement = $connection->prepare( - 'DELETE FROM `*PREFIX*properties`' . - ' WHERE `userid` = ?' + 'DELETE FROM `*PREFIX*properties`' + . ' WHERE `userid` = ?' ); $deleteStatement->execute( [ @@ -77,12 +70,12 @@ class CustomPropertiesBackendTest extends \Test\TestCase { ] ); $deleteStatement->closeCursor(); + + parent::tearDown(); } - private function createTestNode($class) { - $node = $this->getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); + private function createTestNode(string $class) { + $node = $this->createMock($class); $node->expects($this->any()) ->method('getId') ->willReturn(123); diff --git a/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php b/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php index 3eca9b7ac1c..421ee1bdc12 100644 --- a/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/DirectoryTest.php @@ -1,11 +1,12 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ -namespace OCA\DAV\Tests\Unit\Connector\Sabre; +namespace OCA\DAV\Tests\unit\Connector\Sabre; use OC\Files\FileInfo; use OC\Files\Filesystem; @@ -13,20 +14,23 @@ use OC\Files\Node\Node; use OC\Files\Storage\Wrapper\Quota; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Connector\Sabre\Exception\Forbidden; +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\Files_Sharing\External\Storage; use OCP\Constants; use OCP\Files\ForbiddenException; +use OCP\Files\InvalidPathException; use OCP\Files\Mount\IMountPoint; +use OCP\Files\StorageNotAvailableException; +use PHPUnit\Framework\MockObject\MockObject; use Test\Traits\UserTrait; -class TestViewDirectory extends \OC\Files\View { - private $updatables; - private $deletables; - private $canRename; - - public function __construct($updatables, $deletables, $canRename = true) { - $this->updatables = $updatables; - $this->deletables = $deletables; - $this->canRename = $canRename; +class TestViewDirectory extends View { + public function __construct( + private $updatables, + private $deletables, + private $canRename = true, + ) { } public function isUpdatable($path) { @@ -41,7 +45,7 @@ class TestViewDirectory extends \OC\Files\View { return $this->deletables[$path]; } - public function rename($path1, $path2) { + public function rename($source, $target, array $options = []) { return $this->canRename; } @@ -57,29 +61,27 @@ class TestViewDirectory extends \OC\Files\View { class DirectoryTest extends \Test\TestCase { use UserTrait; - /** @var \OC\Files\View | \PHPUnit\Framework\MockObject\MockObject */ - private $view; - /** @var \OC\Files\FileInfo | \PHPUnit\Framework\MockObject\MockObject */ - private $info; + private View&MockObject $view; + private FileInfo&MockObject $info; protected function setUp(): void { parent::setUp(); - $this->view = $this->createMock('OC\Files\View'); - $this->info = $this->createMock('OC\Files\FileInfo'); + $this->view = $this->createMock(View::class); + $this->info = $this->createMock(FileInfo::class); $this->info->method('isReadable') ->willReturn(true); $this->info->method('getType') ->willReturn(Node::TYPE_FOLDER); $this->info->method('getName') - ->willReturn("folder"); + ->willReturn('folder'); $this->info->method('getPath') - ->willReturn("/admin/files/folder"); + ->willReturn('/admin/files/folder'); $this->info->method('getPermissions') ->willReturn(Constants::PERMISSION_READ); } - private function getDir($path = '/') { + private function getDir(string $path = '/'): Directory { $this->view->expects($this->once()) ->method('getRelativePath') ->willReturn($path); @@ -106,7 +108,7 @@ class DirectoryTest extends \Test\TestCase { public function testDeleteForbidden(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\Forbidden::class); + $this->expectException(Forbidden::class); // deletion allowed $this->info->expects($this->once()) @@ -172,12 +174,8 @@ class DirectoryTest extends \Test\TestCase { } public function testGetChildren(): void { - $info1 = $this->getMockBuilder(FileInfo::class) - ->disableOriginalConstructor() - ->getMock(); - $info2 = $this->getMockBuilder(FileInfo::class) - ->disableOriginalConstructor() - ->getMock(); + $info1 = $this->createMock(FileInfo::class); + $info2 = $this->createMock(FileInfo::class); $info1->method('getName') ->willReturn('first'); $info1->method('getPath') @@ -212,7 +210,7 @@ class DirectoryTest extends \Test\TestCase { $dir = new Directory($this->view, $this->info); $nodes = $dir->getChildren(); - $this->assertEquals(2, count($nodes)); + $this->assertCount(2, $nodes); // calling a second time just returns the cached values, // does not call getDirectoryContents again @@ -250,7 +248,7 @@ class DirectoryTest extends \Test\TestCase { $this->view->expects($this->once()) ->method('getFileInfo') - ->willThrowException(new \OCP\Files\StorageNotAvailableException()); + ->willThrowException(new StorageNotAvailableException()); $dir = new Directory($this->view, $this->info); $dir->getChild('.'); @@ -258,11 +256,11 @@ class DirectoryTest extends \Test\TestCase { public function testGetChildThrowInvalidPath(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); + $this->expectException(InvalidPath::class); $this->view->expects($this->once()) ->method('verifyPath') - ->willThrowException(new \OCP\Files\InvalidPathException()); + ->willThrowException(new InvalidPathException()); $this->view->expects($this->never()) ->method('getFileInfo'); @@ -271,20 +269,19 @@ class DirectoryTest extends \Test\TestCase { } public function testGetQuotaInfoUnlimited(): void { - self::createUser('user', 'password'); + $this->createUser('user', 'password'); self::loginAsUser('user'); $mountPoint = $this->createMock(IMountPoint::class); - $storage = $this->getMockBuilder(Quota::class) - ->disableOriginalConstructor() - ->getMock(); + $storage = $this->createMock(Quota::class); $mountPoint->method('getStorage') ->willReturn($storage); $storage->expects($this->any()) ->method('instanceOfStorage') ->willReturnMap([ - '\OCA\Files_Sharing\SharedStorage' => false, - '\OC\Files\Storage\Wrapper\Quota' => false, + ['\OCA\Files_Sharing\SharedStorage', false], + ['\OC\Files\Storage\Wrapper\Quota', false], + [Storage::class, false], ]); $storage->expects($this->once()) @@ -314,6 +311,10 @@ class DirectoryTest extends \Test\TestCase { ->method('getRelativePath') ->willReturn('/foo'); + $this->info->expects($this->once()) + ->method('getInternalPath') + ->willReturn('/foo'); + $mountPoint->method('getMountPoint') ->willReturn('/user/files/mymountpoint'); @@ -322,12 +323,10 @@ class DirectoryTest extends \Test\TestCase { } public function testGetQuotaInfoSpecific(): void { - self::createUser('user', 'password'); + $this->createUser('user', 'password'); self::loginAsUser('user'); $mountPoint = $this->createMock(IMountPoint::class); - $storage = $this->getMockBuilder(Quota::class) - ->disableOriginalConstructor() - ->getMock(); + $storage = $this->createMock(Quota::class); $mountPoint->method('getStorage') ->willReturn($storage); @@ -336,6 +335,7 @@ class DirectoryTest extends \Test\TestCase { ->willReturnMap([ ['\OCA\Files_Sharing\SharedStorage', false], ['\OC\Files\Storage\Wrapper\Quota', true], + [Storage::class, false], ]); $storage->expects($this->once()) @@ -358,6 +358,10 @@ class DirectoryTest extends \Test\TestCase { ->method('getMountPoint') ->willReturn($mountPoint); + $this->info->expects($this->once()) + ->method('getInternalPath') + ->willReturn('/foo'); + $mountPoint->method('getMountPoint') ->willReturn('/user/files/mymountpoint'); @@ -369,39 +373,33 @@ class DirectoryTest extends \Test\TestCase { $this->assertEquals([200, 800], $dir->getQuotaInfo()); //200 used, 800 free } - /** - * @dataProvider moveFailedProvider - */ - public function testMoveFailed($source, $destination, $updatables, $deletables): void { + #[\PHPUnit\Framework\Attributes\DataProvider('moveFailedProvider')] + public function testMoveFailed(string $source, string $destination, array $updatables, array $deletables): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->moveTest($source, $destination, $updatables, $deletables); } - /** - * @dataProvider moveSuccessProvider - */ - public function testMoveSuccess($source, $destination, $updatables, $deletables): void { + #[\PHPUnit\Framework\Attributes\DataProvider('moveSuccessProvider')] + public function testMoveSuccess(string $source, string $destination, array $updatables, array $deletables): void { $this->moveTest($source, $destination, $updatables, $deletables); $this->addToAssertionCount(1); } - /** - * @dataProvider moveFailedInvalidCharsProvider - */ - public function testMoveFailedInvalidChars($source, $destination, $updatables, $deletables): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); + #[\PHPUnit\Framework\Attributes\DataProvider('moveFailedInvalidCharsProvider')] + public function testMoveFailedInvalidChars(string $source, string $destination, array $updatables, array $deletables): void { + $this->expectException(InvalidPath::class); $this->moveTest($source, $destination, $updatables, $deletables); } - public function moveFailedInvalidCharsProvider() { + public static function moveFailedInvalidCharsProvider(): array { return [ - ['a/b', 'a/*', ['a' => true, 'a/b' => true, 'a/c*' => false], []], + ['a/valid', "a/i\nvalid", ['a' => true, 'a/valid' => true, 'a/c*' => false], []], ]; } - public function moveFailedProvider() { + public static function moveFailedProvider(): array { return [ ['a/b', 'a/c', ['a' => false, 'a/b' => false, 'a/c' => false], []], ['a/b', 'b/b', ['a' => false, 'a/b' => false, 'b' => false, 'b/b' => false], []], @@ -412,7 +410,7 @@ class DirectoryTest extends \Test\TestCase { ]; } - public function moveSuccessProvider() { + public static function moveSuccessProvider(): array { return [ ['a/b', 'b/b', ['a' => true, 'a/b' => true, 'b' => true, 'b/b' => false], ['a/b' => true]], // older files with special chars can still be renamed to valid names @@ -420,12 +418,7 @@ class DirectoryTest extends \Test\TestCase { ]; } - /** - * @param $source - * @param $destination - * @param $updatables - */ - private function moveTest($source, $destination, $updatables, $deletables): void { + private function moveTest(string $source, string $destination, array $updatables, array $deletables): void { $view = new TestViewDirectory($updatables, $deletables); $sourceInfo = new FileInfo($source, null, null, [ @@ -437,7 +430,7 @@ class DirectoryTest extends \Test\TestCase { $sourceNode = new Directory($view, $sourceInfo); $targetNode = $this->getMockBuilder(Directory::class) - ->setMethods(['childExists']) + ->onlyMethods(['childExists']) ->setConstructorArgs([$view, $targetInfo]) ->getMock(); $targetNode->expects($this->any())->method('childExists') @@ -463,7 +456,7 @@ class DirectoryTest extends \Test\TestCase { $sourceNode = new Directory($view, $sourceInfo); $targetNode = $this->getMockBuilder(Directory::class) - ->setMethods(['childExists']) + ->onlyMethods(['childExists']) ->setConstructorArgs([$view, $targetInfo]) ->getMock(); $targetNode->expects($this->once())->method('childExists') diff --git a/apps/dav/tests/unit/Connector/Sabre/DummyGetResponsePluginTest.php b/apps/dav/tests/unit/Connector/Sabre/DummyGetResponsePluginTest.php index 03c31dc47f8..2d688d64600 100644 --- a/apps/dav/tests/unit/Connector/Sabre/DummyGetResponsePluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/DummyGetResponsePluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -19,8 +20,7 @@ use Test\TestCase; * @package OCA\DAV\Tests\unit\Connector\Sabre */ class DummyGetResponsePluginTest extends TestCase { - /** @var DummyGetResponsePlugin */ - private $dummyGetResponsePlugin; + private DummyGetResponsePlugin $dummyGetResponsePlugin; protected function setUp(): void { parent::setUp(); @@ -29,10 +29,7 @@ class DummyGetResponsePluginTest extends TestCase { } public function testInitialize(): void { - /** @var Server $server */ - $server = $this->getMockBuilder(Server::class) - ->disableOriginalConstructor() - ->getMock(); + $server = $this->createMock(Server::class); $server ->expects($this->once()) ->method('on') @@ -44,13 +41,9 @@ class DummyGetResponsePluginTest extends TestCase { public function testHttpGet(): void { /** @var \Sabre\HTTP\RequestInterface $request */ - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); /** @var \Sabre\HTTP\ResponseInterface $response */ - $response = $server = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $response = $this->createMock(ResponseInterface::class); $response ->expects($this->once()) ->method('setBody'); diff --git a/apps/dav/tests/unit/Connector/Sabre/Exception/ForbiddenTest.php b/apps/dav/tests/unit/Connector/Sabre/Exception/ForbiddenTest.php index 74f90507bb3..2f9e0ae9196 100644 --- a/apps/dav/tests/unit/Connector/Sabre/Exception/ForbiddenTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/Exception/ForbiddenTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre\Exception; use OCA\DAV\Connector\Sabre\Exception\Forbidden; +use Sabre\DAV\Server; class ForbiddenTest extends \Test\TestCase { public function testSerialization(): void { @@ -20,7 +22,7 @@ class ForbiddenTest extends \Test\TestCase { $DOM->appendChild($error); // serialize the exception - $message = "1234567890"; + $message = '1234567890'; $retry = false; $expectedXml = <<<EOD <?xml version="1.0" encoding="utf-8"?> @@ -32,9 +34,7 @@ class ForbiddenTest extends \Test\TestCase { EOD; $ex = new Forbidden($message, $retry); - $server = $this->getMockBuilder('Sabre\DAV\Server') - ->disableOriginalConstructor() - ->getMock(); + $server = $this->createMock(Server::class); $ex->serialize($server, $error); // assert diff --git a/apps/dav/tests/unit/Connector/Sabre/Exception/InvalidPathTest.php b/apps/dav/tests/unit/Connector/Sabre/Exception/InvalidPathTest.php index 3da0c41b7b3..6f62bef86a3 100644 --- a/apps/dav/tests/unit/Connector/Sabre/Exception/InvalidPathTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/Exception/InvalidPathTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre\Exception; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use Sabre\DAV\Server; class InvalidPathTest extends \Test\TestCase { public function testSerialization(): void { @@ -20,7 +22,7 @@ class InvalidPathTest extends \Test\TestCase { $DOM->appendChild($error); // serialize the exception - $message = "1234567890"; + $message = '1234567890'; $retry = false; $expectedXml = <<<EOD <?xml version="1.0" encoding="utf-8"?> @@ -32,9 +34,7 @@ class InvalidPathTest extends \Test\TestCase { EOD; $ex = new InvalidPath($message, $retry); - $server = $this->getMockBuilder('Sabre\DAV\Server') - ->disableOriginalConstructor() - ->getMock(); + $server = $this->createMock(Server::class); $ex->serialize($server, $error); // assert diff --git a/apps/dav/tests/unit/Connector/Sabre/ExceptionLoggerPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/ExceptionLoggerPluginTest.php index 1d50fb2fb9a..416ac8a75c9 100644 --- a/apps/dav/tests/unit/Connector/Sabre/ExceptionLoggerPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/ExceptionLoggerPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -11,21 +12,16 @@ use OC\SystemConfig; use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; use OCA\DAV\Exception\ServerMaintenanceMode; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\Server; use Test\TestCase; class ExceptionLoggerPluginTest extends TestCase { - - /** @var Server */ - private $server; - - /** @var ExceptionLoggerPlugin */ - private $plugin; - - /** @var LoggerInterface | \PHPUnit\Framework\MockObject\MockObject */ - private $logger; + private Server $server; + private ExceptionLoggerPlugin $plugin; + private LoggerInterface&MockObject $logger; private function init(): void { $config = $this->createMock(SystemConfig::class); @@ -46,9 +42,7 @@ class ExceptionLoggerPluginTest extends TestCase { $this->plugin->initialize($this->server); } - /** - * @dataProvider providesExceptions - */ + #[\PHPUnit\Framework\Attributes\DataProvider('providesExceptions')] public function testLogging(string $expectedLogLevel, \Throwable $e): void { $this->init(); @@ -59,7 +53,7 @@ class ExceptionLoggerPluginTest extends TestCase { $this->plugin->logException($e); } - public function providesExceptions() { + public static function providesExceptions(): array { return [ ['debug', new NotFound()], ['debug', new ServerMaintenanceMode('System is in maintenance mode.')], diff --git a/apps/dav/tests/unit/Connector/Sabre/FakeLockerPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FakeLockerPluginTest.php index 40ebc69e42d..366932137f4 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FakeLockerPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FakeLockerPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -22,8 +23,7 @@ use Test\TestCase; * @package OCA\DAV\Tests\unit\Connector\Sabre */ class FakeLockerPluginTest extends TestCase { - /** @var FakeLockerPlugin */ - private $fakeLockerPlugin; + private FakeLockerPlugin $fakeLockerPlugin; protected function setUp(): void { parent::setUp(); @@ -32,18 +32,19 @@ class FakeLockerPluginTest extends TestCase { public function testInitialize(): void { /** @var Server $server */ - $server = $this->getMockBuilder(Server::class) - ->disableOriginalConstructor() - ->getMock(); - $server - ->expects($this->exactly(4)) + $server = $this->createMock(Server::class); + $calls = [ + ['method:LOCK', [$this->fakeLockerPlugin, 'fakeLockProvider'], 1], + ['method:UNLOCK', [$this->fakeLockerPlugin, 'fakeUnlockProvider'], 1], + ['propFind', [$this->fakeLockerPlugin, 'propFind'], 100], + ['validateTokens', [$this->fakeLockerPlugin, 'validateTokens'], 100], + ]; + $server->expects($this->exactly(count($calls))) ->method('on') - ->withConsecutive( - ['method:LOCK', [$this->fakeLockerPlugin, 'fakeLockProvider'], 1], - ['method:UNLOCK', [$this->fakeLockerPlugin, 'fakeUnlockProvider'], 1], - ['propFind', [$this->fakeLockerPlugin, 'propFind']], - ['validateTokens', [$this->fakeLockerPlugin, 'validateTokens']], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); $this->fakeLockerPlugin->initialize($server); } @@ -64,24 +65,24 @@ class FakeLockerPluginTest extends TestCase { } public function testPropFind(): void { - $propFind = $this->getMockBuilder(PropFind::class) - ->disableOriginalConstructor() - ->getMock(); - $node = $this->getMockBuilder(INode::class) - ->disableOriginalConstructor() - ->getMock(); + $propFind = $this->createMock(PropFind::class); + $node = $this->createMock(INode::class); - $propFind->expects($this->exactly(2)) + $calls = [ + '{DAV:}supportedlock', + '{DAV:}lockdiscovery', + ]; + $propFind->expects($this->exactly(count($calls))) ->method('handle') - ->withConsecutive( - ['{DAV:}supportedlock'], - ['{DAV:}lockdiscovery'], - ); + ->willReturnCallback(function ($propertyName) use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, $propertyName); + }); $this->fakeLockerPlugin->propFind($propFind, $node); } - public function tokenDataProvider() { + public static function tokenDataProvider(): array { return [ [ [ @@ -118,23 +119,15 @@ class FakeLockerPluginTest extends TestCase { ]; } - /** - * @dataProvider tokenDataProvider - * @param array $input - * @param array $expected - */ + #[\PHPUnit\Framework\Attributes\DataProvider('tokenDataProvider')] public function testValidateTokens(array $input, array $expected): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); $this->fakeLockerPlugin->validateTokens($request, $input); $this->assertSame($expected, $input); } public function testFakeLockProvider(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); $response = new Response(); $server = $this->getMockBuilder(Server::class) ->getMock(); @@ -152,19 +145,15 @@ class FakeLockerPluginTest extends TestCase { } public function testFakeUnlockProvider(): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $response->expects($this->once()) - ->method('setStatus') - ->with('204'); + ->method('setStatus') + ->with('204'); $response->expects($this->once()) - ->method('setHeader') - ->with('Content-Length', '0'); + ->method('setHeader') + ->with('Content-Length', '0'); $this->assertSame(false, $this->fakeLockerPlugin->fakeUnlockProvider($request, $response)); } diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index 2830ccc0f18..60c8382e131 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -13,16 +14,29 @@ use OC\Files\Storage\Local; use OC\Files\Storage\Temporary; use OC\Files\Storage\Wrapper\PermissionsMask; use OC\Files\View; +use OCA\DAV\Connector\Sabre\Exception\FileLocked; +use OCA\DAV\Connector\Sabre\Exception\Forbidden; +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\File; use OCP\Constants; +use OCP\Encryption\Exceptions\GenericEncryptionException; +use OCP\Files\EntityTooLargeException; use OCP\Files\FileInfo; use OCP\Files\ForbiddenException; -use OCP\Files\Storage; +use OCP\Files\InvalidContentException; +use OCP\Files\InvalidPathException; +use OCP\Files\LockNotAcquiredException; +use OCP\Files\NotPermittedException; +use OCP\Files\Storage\IStorage; +use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IRequestId; use OCP\ITempManager; use OCP\IUserManager; use OCP\Lock\ILockingProvider; +use OCP\Lock\LockedException; +use OCP\Server; +use OCP\Util; use PHPUnit\Framework\MockObject\MockObject; use Test\HookHelper; use Test\TestCase; @@ -40,16 +54,9 @@ class FileTest extends TestCase { use MountProviderTrait; use UserTrait; - /** - * @var string - */ - private $user; - - /** @var IConfig|MockObject */ - protected $config; - - /** @var IRequestId|MockObject */ - protected $requestId; + private string $user; + protected IConfig&MockObject $config; + protected IRequestId&MockObject $requestId; protected function setUp(): void { parent::setUp(); @@ -59,35 +66,27 @@ class FileTest extends TestCase { $this->user = 'test_user'; $this->createUser($this->user, 'pass'); - $this->loginAsUser($this->user); + self::loginAsUser($this->user); $this->config = $this->createMock(IConfig::class); $this->requestId = $this->createMock(IRequestId::class); } protected function tearDown(): void { - $userManager = \OCP\Server::get(IUserManager::class); + $userManager = Server::get(IUserManager::class); $userManager->get($this->user)->delete(); parent::tearDown(); } - /** - * @return MockObject|Storage - */ - private function getMockStorage() { - $storage = $this->getMockBuilder(Storage::class) - ->disableOriginalConstructor() - ->getMock(); + private function getMockStorage(): MockObject&IStorage { + $storage = $this->createMock(IStorage::class); $storage->method('getId') ->willReturn('home::someuser'); return $storage; } - /** - * @param string $string - */ - private function getStream($string) { + private function getStream(string $string) { $stream = fopen('php://temp', 'r+'); fwrite($stream, $string); fseek($stream, 0); @@ -95,7 +94,7 @@ class FileTest extends TestCase { } - public function fopenFailuresProvider() { + public static function fopenFailuresProvider(): array { return [ [ // return false @@ -104,39 +103,39 @@ class FileTest extends TestCase { false ], [ - new \OCP\Files\NotPermittedException(), + new NotPermittedException(), 'Sabre\DAV\Exception\Forbidden' ], [ - new \OCP\Files\EntityTooLargeException(), + new EntityTooLargeException(), 'OCA\DAV\Connector\Sabre\Exception\EntityTooLarge' ], [ - new \OCP\Files\InvalidContentException(), + new InvalidContentException(), 'OCA\DAV\Connector\Sabre\Exception\UnsupportedMediaType' ], [ - new \OCP\Files\InvalidPathException(), + new InvalidPathException(), 'Sabre\DAV\Exception\Forbidden' ], [ - new \OCP\Files\ForbiddenException('', true), + new ForbiddenException('', true), 'OCA\DAV\Connector\Sabre\Exception\Forbidden' ], [ - new \OCP\Files\LockNotAcquiredException('/test.txt', 1), + new LockNotAcquiredException('/test.txt', 1), 'OCA\DAV\Connector\Sabre\Exception\FileLocked' ], [ - new \OCP\Lock\LockedException('/test.txt'), + new LockedException('/test.txt'), 'OCA\DAV\Connector\Sabre\Exception\FileLocked' ], [ - new \OCP\Encryption\Exceptions\GenericEncryptionException(), + new GenericEncryptionException(), 'Sabre\DAV\Exception\ServiceUnavailable' ], [ - new \OCP\Files\StorageNotAvailableException(), + new StorageNotAvailableException(), 'Sabre\DAV\Exception\ServiceUnavailable' ], [ @@ -151,17 +150,15 @@ class FileTest extends TestCase { ]; } - /** - * @dataProvider fopenFailuresProvider - */ - public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true): void { + #[\PHPUnit\Framework\Attributes\DataProvider('fopenFailuresProvider')] + public function testSimplePutFails(?\Throwable $thrownException, string $expectedException, bool $checkPreviousClass = true): void { // setup $storage = $this->getMockBuilder(Local::class) ->onlyMethods(['writeStream']) - ->setConstructorArgs([['datadir' => \OCP\Server::get(ITempManager::class)->getTemporaryFolder()]]) + ->setConstructorArgs([['datadir' => Server::get(ITempManager::class)->getTemporaryFolder()]]) ->getMock(); - \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); - /** @var View | MockObject $view */ + Filesystem::mount($storage, [], $this->user . '/'); + /** @var View&MockObject $view */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['getRelativePath', 'resolvePath']) ->getMock(); @@ -176,7 +173,7 @@ class FileTest extends TestCase { if ($thrownException !== null) { $storage->expects($this->once()) ->method('writeStream') - ->will($this->throwException($thrownException)); + ->willThrowException($thrownException); } else { $storage->expects($this->once()) ->method('writeStream') @@ -188,11 +185,11 @@ class FileTest extends TestCase { ->willReturnArgument(0); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $caughtException = null; @@ -211,98 +208,18 @@ class FileTest extends TestCase { } /** - * Test putting a file using chunking - * - * @dataProvider fopenFailuresProvider - */ - public function testChunkedPutFails($thrownException, $expectedException, $checkPreviousClass = false): void { - // setup - $storage = $this->getMockBuilder(Local::class) - ->onlyMethods(['fopen']) - ->setConstructorArgs([['datadir' => \OCP\Server::get(ITempManager::class)->getTemporaryFolder()]]) - ->getMock(); - \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); - /** @var View|MockObject */ - $view = $this->getMockBuilder(View::class) - ->onlyMethods(['getRelativePath', 'resolvePath']) - ->getMock(); - $view->expects($this->atLeastOnce()) - ->method('resolvePath') - ->willReturnCallback( - function ($path) use ($storage) { - return [$storage, $path]; - } - ); - - if ($thrownException !== null) { - $storage->expects($this->once()) - ->method('fopen') - ->will($this->throwException($thrownException)); - } else { - $storage->expects($this->once()) - ->method('fopen') - ->willReturn(false); - } - - $view->expects($this->any()) - ->method('getRelativePath') - ->willReturnArgument(0); - - $request = new Request([ - 'server' => [ - 'HTTP_OC_CHUNKED' => 'true' - ] - ], $this->requestId, $this->config, null); - - $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-0', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request); - - // put first chunk - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $this->assertNull($file->put('test data one')); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - - $info = new \OC\Files\FileInfo('/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request); - - // action - $caughtException = null; - try { - // last chunk - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $file->put('test data two'); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - } catch (\Exception $e) { - $caughtException = $e; - } - - $this->assertInstanceOf($expectedException, $caughtException); - if ($checkPreviousClass) { - $this->assertInstanceOf(get_class($thrownException), $caughtException->getPrevious()); - } - - $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); - } - - /** * Simulate putting a file to the given path. * * @param string $path path to put the file into - * @param string $viewRoot root to use for the view + * @param ?string $viewRoot root to use for the view * @param null|Request $request the HTTP request * * @return null|string of the PUT operation which is usually the etag */ - private function doPut($path, $viewRoot = null, ?Request $request = null) { - $view = \OC\Files\Filesystem::getView(); + private function doPut(string $path, ?string $viewRoot = null, ?Request $request = null) { + $view = Filesystem::getView(); if (!is_null($viewRoot)) { - $view = new \OC\Files\View($viewRoot); + $view = new View($viewRoot); } else { $viewRoot = '/' . $this->user . '/files'; } @@ -312,14 +229,14 @@ class FileTest extends TestCase { $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null ); - /** @var \OCA\DAV\Connector\Sabre\File | MockObject $file */ - $file = $this->getMockBuilder(\OCA\DAV\Connector\Sabre\File::class) + /** @var File&MockObject $file */ + $file = $this->getMockBuilder(File::class) ->setConstructorArgs([$view, $info, null, $request]) ->onlyMethods(['header']) ->getMock(); @@ -342,64 +259,64 @@ class FileTest extends TestCase { $this->assertNotEmpty($this->doPut('/foo.txt')); } - public function legalMtimeProvider() { + public static function legalMtimeProvider(): array { return [ - "string" => [ - 'HTTP_X_OC_MTIME' => "string", - 'expected result' => null + 'string' => [ + 'requestMtime' => 'string', + 'resultMtime' => null ], - "castable string (int)" => [ - 'HTTP_X_OC_MTIME' => "987654321", - 'expected result' => 987654321 + 'castable string (int)' => [ + 'requestMtime' => '987654321', + 'resultMtime' => 987654321 ], - "castable string (float)" => [ - 'HTTP_X_OC_MTIME' => "123456789.56", - 'expected result' => 123456789 + 'castable string (float)' => [ + 'requestMtime' => '123456789.56', + 'resultMtime' => 123456789 ], - "float" => [ - 'HTTP_X_OC_MTIME' => 123456789.56, - 'expected result' => 123456789 + 'float' => [ + 'requestMtime' => 123456789.56, + 'resultMtime' => 123456789 ], - "zero" => [ - 'HTTP_X_OC_MTIME' => 0, - 'expected result' => null + 'zero' => [ + 'requestMtime' => 0, + 'resultMtime' => null ], - "zero string" => [ - 'HTTP_X_OC_MTIME' => "0", - 'expected result' => null + 'zero string' => [ + 'requestMtime' => '0', + 'resultMtime' => null ], - "negative zero string" => [ - 'HTTP_X_OC_MTIME' => "-0", - 'expected result' => null + 'negative zero string' => [ + 'requestMtime' => '-0', + 'resultMtime' => null ], - "string starting with number following by char" => [ - 'HTTP_X_OC_MTIME' => "2345asdf", - 'expected result' => null + 'string starting with number following by char' => [ + 'requestMtime' => '2345asdf', + 'resultMtime' => null ], - "string castable hex int" => [ - 'HTTP_X_OC_MTIME' => "0x45adf", - 'expected result' => null + 'string castable hex int' => [ + 'requestMtime' => '0x45adf', + 'resultMtime' => null ], - "string that looks like invalid hex int" => [ - 'HTTP_X_OC_MTIME' => "0x123g", - 'expected result' => null + 'string that looks like invalid hex int' => [ + 'requestMtime' => '0x123g', + 'resultMtime' => null ], - "negative int" => [ - 'HTTP_X_OC_MTIME' => -34, - 'expected result' => null + 'negative int' => [ + 'requestMtime' => -34, + 'resultMtime' => null ], - "negative float" => [ - 'HTTP_X_OC_MTIME' => -34.43, - 'expected result' => null + 'negative float' => [ + 'requestMtime' => -34.43, + 'resultMtime' => null ], ]; } /** * Test putting a file with string Mtime - * @dataProvider legalMtimeProvider */ - public function testPutSingleFileLegalMtime($requestMtime, $resultMtime): void { + #[\PHPUnit\Framework\Attributes\DataProvider('legalMtimeProvider')] + public function testPutSingleFileLegalMtime(mixed $requestMtime, ?int $resultMtime): void { $request = new Request([ 'server' => [ 'HTTP_X_OC_MTIME' => (string)$requestMtime, @@ -419,45 +336,6 @@ class FileTest extends TestCase { } /** - * Test putting a file with string Mtime using chunking - * @dataProvider legalMtimeProvider - */ - public function testChunkedPutLegalMtime($requestMtime, $resultMtime): void { - $request = new Request([ - 'server' => [ - 'HTTP_X_OC_MTIME' => (string)$requestMtime, - 'HTTP_OC_CHUNKED' => 'true' - ] - ], $this->requestId, $this->config, null); - - $file = 'foo.txt'; - - if ($resultMtime === null) { - $this->expectException(\Sabre\DAV\Exception::class); - } - - $this->doPut($file.'-chunking-12345-2-0', null, $request); - $this->doPut($file.'-chunking-12345-2-1', null, $request); - - if ($resultMtime !== null) { - $this->assertEquals($resultMtime, $this->getFileInfos($file)['mtime']); - } - } - - /** - * Test putting a file using chunking - */ - public function testChunkedPut(): void { - $request = new Request([ - 'server' => [ - 'HTTP_OC_CHUNKED' => 'true' - ] - ], $this->requestId, $this->config, null); - $this->assertNull($this->doPut('/test.txt-chunking-12345-2-0', null, $request)); - $this->assertNotEmpty($this->doPut('/test.txt-chunking-12345-2-1', null, $request)); - } - - /** * Test that putting a file triggers create hooks */ public function testPutSingleFileTriggersHooks(): void { @@ -492,7 +370,7 @@ class FileTest extends TestCase { * Test that putting a file triggers update hooks */ public function testPutOverwriteFileTriggersHooks(): void { - $view = \OC\Files\Filesystem::getView(); + $view = Filesystem::getView(); $view->file_put_contents('/foo.txt', 'some content that will be replaced'); HookHelper::setUpHooks(); @@ -528,7 +406,7 @@ class FileTest extends TestCase { * where the root is the share root) */ public function testPutSingleFileTriggersHooksDifferentRoot(): void { - $view = \OC\Files\Filesystem::getView(); + $view = Filesystem::getView(); $view->mkdir('noderoot'); HookHelper::setUpHooks(); @@ -559,83 +437,6 @@ class FileTest extends TestCase { ); } - /** - * Test that putting a file with chunks triggers create hooks - */ - public function testPutChunkedFileTriggersHooks(): void { - HookHelper::setUpHooks(); - - $request = new Request([ - 'server' => [ - 'HTTP_OC_CHUNKED' => 'true' - ] - ], $this->requestId, $this->config, null); - $this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0', null, $request)); - $this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1', null, $request)); - - $this->assertCount(4, HookHelper::$hookCalls); - $this->assertHookCall( - HookHelper::$hookCalls[0], - Filesystem::signal_create, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[1], - Filesystem::signal_write, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[2], - Filesystem::signal_post_create, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[3], - Filesystem::signal_post_write, - '/foo.txt' - ); - } - - /** - * Test that putting a chunked file triggers update hooks - */ - public function testPutOverwriteChunkedFileTriggersHooks(): void { - $view = \OC\Files\Filesystem::getView(); - $view->file_put_contents('/foo.txt', 'some content that will be replaced'); - - HookHelper::setUpHooks(); - - $request = new Request([ - 'server' => [ - 'HTTP_OC_CHUNKED' => 'true' - ] - ], $this->requestId, $this->config, null); - $this->assertNull($this->doPut('/foo.txt-chunking-12345-2-0', null, $request)); - $this->assertNotEmpty($this->doPut('/foo.txt-chunking-12345-2-1', null, $request)); - - $this->assertCount(4, HookHelper::$hookCalls); - $this->assertHookCall( - HookHelper::$hookCalls[0], - Filesystem::signal_update, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[1], - Filesystem::signal_write, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[2], - Filesystem::signal_post_update, - '/foo.txt' - ); - $this->assertHookCall( - HookHelper::$hookCalls[3], - Filesystem::signal_post_write, - '/foo.txt' - ); - } - public static function cancellingHook($params): void { self::$hookCalls[] = [ 'signal' => Filesystem::signal_post_create, @@ -647,7 +448,7 @@ class FileTest extends TestCase { * Test put file with cancelled hook */ public function testPutSingleFileCancelPreHook(): void { - \OCP\Util::connectHook( + Util::connectHook( Filesystem::CLASSNAME, Filesystem::signal_create, '\Test\HookHelper', @@ -671,7 +472,7 @@ class FileTest extends TestCase { */ public function testSimplePutFailsSizeCheck(): void { // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['rename', 'getRelativePath', 'filesize']) ->getMock(); @@ -695,11 +496,11 @@ class FileTest extends TestCase { ], $this->requestId, $this->config, null); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request); + $file = new File($view, $info, null, $request); // action $thrown = false; @@ -723,17 +524,17 @@ class FileTest extends TestCase { * Test exception during final rename in simple upload mode */ public function testSimplePutFailsMoveFromStorage(): void { - $view = new \OC\Files\View('/' . $this->user . '/files'); + $view = new View('/' . $this->user . '/files'); // simulate situation where the target file is locked $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $thrown = false; @@ -745,51 +546,7 @@ class FileTest extends TestCase { // afterMethod unlocks $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { - $thrown = true; - } - - $this->assertTrue($thrown); - $this->assertEmpty($this->listPartFiles($view, ''), 'No stray part files'); - } - - /** - * Test exception during final rename in chunk upload mode - */ - public function testChunkedPutFailsFinalRename(): void { - $view = new \OC\Files\View('/' . $this->user . '/files'); - - // simulate situation where the target file is locked - $view->lockFile('/test.txt', ILockingProvider::LOCK_EXCLUSIVE); - - $request = new Request([ - 'server' => [ - 'HTTP_OC_CHUNKED' => 'true' - ] - ], $this->requestId, $this->config, null); - - $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-0', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request); - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $this->assertNull($file->put('test data one')); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - - $info = new \OC\Files\FileInfo('/' . $this->user . '/files/test.txt-chunking-12345-2-1', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, - 'type' => FileInfo::TYPE_FOLDER, - ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request); - - // action - $thrown = false; - try { - $file->acquireLock(ILockingProvider::LOCK_SHARED); - $file->put($this->getStream('test data')); - $file->releaseLock(ILockingProvider::LOCK_SHARED); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { + } catch (FileLocked $e) { $thrown = true; } @@ -802,7 +559,7 @@ class FileTest extends TestCase { */ public function testSimplePutInvalidChars(): void { // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['getRelativePath']) ->getMock(); @@ -810,11 +567,11 @@ class FileTest extends TestCase { ->method('getRelativePath') ->willReturnArgument(0); - $info = new \OC\Files\FileInfo('/*', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + $info = new \OC\Files\FileInfo("/i\nvalid", $this->getMockStorage(), null, [ + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $thrown = false; @@ -826,7 +583,7 @@ class FileTest extends TestCase { // afterMethod unlocks $view->unlockFile($info->getPath(), ILockingProvider::LOCK_SHARED); - } catch (\OCA\DAV\Connector\Sabre\Exception\InvalidPath $e) { + } catch (InvalidPath $e) { $thrown = true; } @@ -839,10 +596,10 @@ class FileTest extends TestCase { * */ public function testSetNameInvalidChars(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); + $this->expectException(InvalidPath::class); // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['getRelativePath']) ->getMock(); @@ -851,18 +608,19 @@ class FileTest extends TestCase { ->method('getRelativePath') ->willReturnArgument(0); - $info = new \OC\Files\FileInfo('/*', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + $info = new \OC\Files\FileInfo('/valid', $this->getMockStorage(), null, [ + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); - $file->setName('/super*star.txt'); + $file = new File($view, $info); + + $file->setName("/i\nvalid"); } public function testUploadAbort(): void { // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['rename', 'getRelativePath', 'filesize']) ->getMock(); @@ -885,11 +643,11 @@ class FileTest extends TestCase { ], $this->requestId, $this->config, null); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info, null, $request); + $file = new File($view, $info, null, $request); // action $thrown = false; @@ -912,7 +670,7 @@ class FileTest extends TestCase { public function testDeleteWhenAllowed(): void { // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->getMock(); @@ -921,11 +679,11 @@ class FileTest extends TestCase { ->willReturn(true); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $file->delete(); @@ -936,7 +694,7 @@ class FileTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->getMock(); @@ -945,7 +703,7 @@ class FileTest extends TestCase { 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $file->delete(); @@ -956,7 +714,7 @@ class FileTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->getMock(); @@ -966,11 +724,11 @@ class FileTest extends TestCase { ->willReturn(false); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $file->delete(); @@ -978,10 +736,10 @@ class FileTest extends TestCase { public function testDeleteThrowsWhenDeletionThrows(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\Forbidden::class); + $this->expectException(Forbidden::class); // setup - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->getMock(); @@ -991,11 +749,11 @@ class FileTest extends TestCase { ->willThrowException(new ForbiddenException('', true)); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // action $file->delete(); @@ -1021,7 +779,7 @@ class FileTest extends TestCase { * Test whether locks are set before and after the operation */ public function testPutLocking(): void { - $view = new \OC\Files\View('/' . $this->user . '/files/'); + $view = new View('/' . $this->user . '/files/'); $path = 'test-locking.txt'; $info = new \OC\Files\FileInfo( @@ -1029,20 +787,20 @@ class FileTest extends TestCase { $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null ); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); $this->assertFalse( - $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED), + $this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED), 'File unlocked before put' ); $this->assertFalse( - $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE), + $this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE), 'File unlocked before put' ); @@ -1058,26 +816,26 @@ class FileTest extends TestCase { ->method('writeCallback') ->willReturnCallback( function () use ($view, $path, &$wasLockedPre): void { - $wasLockedPre = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED); - $wasLockedPre = $wasLockedPre && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); + $wasLockedPre = $this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED); + $wasLockedPre = $wasLockedPre && !$this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE); } ); $eventHandler->expects($this->once()) ->method('postWriteCallback') ->willReturnCallback( function () use ($view, $path, &$wasLockedPost): void { - $wasLockedPost = $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED); - $wasLockedPost = $wasLockedPost && !$this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE); + $wasLockedPost = $this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED); + $wasLockedPost = $wasLockedPost && !$this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE); } ); - \OCP\Util::connectHook( + Util::connectHook( Filesystem::CLASSNAME, Filesystem::signal_write, $eventHandler, 'writeCallback' ); - \OCP\Util::connectHook( + Util::connectHook( Filesystem::CLASSNAME, Filesystem::signal_post_write, $eventHandler, @@ -1096,11 +854,11 @@ class FileTest extends TestCase { $this->assertTrue($wasLockedPost, 'File was locked during post-hooks'); $this->assertFalse( - $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_SHARED), + $this->isFileLocked($view, $path, ILockingProvider::LOCK_SHARED), 'File unlocked after put' ); $this->assertFalse( - $this->isFileLocked($view, $path, \OCP\Lock\ILockingProvider::LOCK_EXCLUSIVE), + $this->isFileLocked($view, $path, ILockingProvider::LOCK_EXCLUSIVE), 'File unlocked after put' ); } @@ -1113,9 +871,9 @@ class FileTest extends TestCase { * * @return array list of part files */ - private function listPartFiles(?\OC\Files\View $userView = null, $path = '') { + private function listPartFiles(?View $userView = null, $path = '') { if ($userView === null) { - $userView = \OC\Files\Filesystem::getView(); + $userView = Filesystem::getView(); } $files = []; [$storage, $internalPath] = $userView->resolvePath($path); @@ -1144,10 +902,10 @@ class FileTest extends TestCase { $userView = Filesystem::getView(); } return [ - "filesize" => $userView->filesize($path), - "mtime" => $userView->filemtime($path), - "filetype" => $userView->filetype($path), - "mimetype" => $userView->getMimeType($path) + 'filesize' => $userView->filesize($path), + 'mtime' => $userView->filemtime($path), + 'filetype' => $userView->filetype($path), + 'mimetype' => $userView->getMimeType($path) ]; } @@ -1155,7 +913,7 @@ class FileTest extends TestCase { public function testGetFopenFails(): void { $this->expectException(\Sabre\DAV\Exception\ServiceUnavailable::class); - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['fopen']) ->getMock(); @@ -1164,20 +922,20 @@ class FileTest extends TestCase { ->willReturn(false); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FILE, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); $file->get(); } public function testGetFopenThrows(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\Forbidden::class); + $this->expectException(Forbidden::class); - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['fopen']) ->getMock(); @@ -1186,11 +944,11 @@ class FileTest extends TestCase { ->willThrowException(new ForbiddenException('', true)); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FILE, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); $file->get(); } @@ -1199,7 +957,7 @@ class FileTest extends TestCase { public function testGetThrowsIfNoPermission(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); - /** @var View|MockObject */ + /** @var View&MockObject */ $view = $this->getMockBuilder(View::class) ->onlyMethods(['fopen']) ->getMock(); @@ -1207,11 +965,11 @@ class FileTest extends TestCase { ->method('fopen'); $info = new \OC\Files\FileInfo('/test.txt', $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_CREATE, // no read perm + 'permissions' => Constants::PERMISSION_CREATE, // no read perm 'type' => FileInfo::TYPE_FOLDER, ], null); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); $file->get(); } @@ -1248,7 +1006,7 @@ class FileTest extends TestCase { } public function testPutLockExpired(): void { - $view = new \OC\Files\View('/' . $this->user . '/files/'); + $view = new View('/' . $this->user . '/files/'); $path = 'test-locking.txt'; $info = new \OC\Files\FileInfo( @@ -1256,13 +1014,13 @@ class FileTest extends TestCase { $this->getMockStorage(), null, [ - 'permissions' => \OCP\Constants::PERMISSION_ALL, + 'permissions' => Constants::PERMISSION_ALL, 'type' => FileInfo::TYPE_FOLDER, ], null ); - $file = new \OCA\DAV\Connector\Sabre\File($view, $info); + $file = new File($view, $info); // don't lock before the PUT to simulate an expired shared lock $this->assertNotEmpty($file->put($this->getStream('test data'))); diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php index f1460591a91..4df3accfda9 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,12 +8,18 @@ */ namespace OCA\DAV\Tests\unit\Connector\Sabre; +use OC\Accounts\Account; +use OC\Accounts\AccountProperty; use OC\User\User; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\Node; +use OCP\Accounts\IAccountManager; use OCP\Files\FileInfo; +use OCP\Files\IFilenameValidator; +use OCP\Files\InvalidPathException; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; use OCP\IPreview; @@ -32,51 +39,16 @@ use Test\TestCase; * @group DB */ class FilesPluginTest extends TestCase { - public const GETETAG_PROPERTYNAME = FilesPlugin::GETETAG_PROPERTYNAME; - public const FILEID_PROPERTYNAME = FilesPlugin::FILEID_PROPERTYNAME; - public const INTERNAL_FILEID_PROPERTYNAME = FilesPlugin::INTERNAL_FILEID_PROPERTYNAME; - public const SIZE_PROPERTYNAME = FilesPlugin::SIZE_PROPERTYNAME; - public const PERMISSIONS_PROPERTYNAME = FilesPlugin::PERMISSIONS_PROPERTYNAME; - public const LASTMODIFIED_PROPERTYNAME = FilesPlugin::LASTMODIFIED_PROPERTYNAME; - public const CREATIONDATE_PROPERTYNAME = FilesPlugin::CREATIONDATE_PROPERTYNAME; - public const DOWNLOADURL_PROPERTYNAME = FilesPlugin::DOWNLOADURL_PROPERTYNAME; - public const OWNER_ID_PROPERTYNAME = FilesPlugin::OWNER_ID_PROPERTYNAME; - public const OWNER_DISPLAY_NAME_PROPERTYNAME = FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME; - public const DATA_FINGERPRINT_PROPERTYNAME = FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME; - public const HAS_PREVIEW_PROPERTYNAME = FilesPlugin::HAS_PREVIEW_PROPERTYNAME; - /** - * @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject - */ - private $server; - - /** - * @var \Sabre\DAV\Tree | \PHPUnit\Framework\MockObject\MockObject - */ - private $tree; - - /** - * @var FilesPlugin - */ - private $plugin; - - /** - * @var \OCP\IConfig | \PHPUnit\Framework\MockObject\MockObject - */ - private $config; - - /** - * @var \OCP\IRequest | \PHPUnit\Framework\MockObject\MockObject - */ - private $request; - - /** - * @var \OCP\IPreview | \PHPUnit\Framework\MockObject\MockObject - */ - private $previewManager; - - /** @var IUserSession|MockObject */ - private $userSession; + private Tree&MockObject $tree; + private Server&MockObject $server; + private IConfig&MockObject $config; + private IRequest&MockObject $request; + private IPreview&MockObject $previewManager; + private IUserSession&MockObject $userSession; + private IFilenameValidator&MockObject $filenameValidator; + private IAccountManager&MockObject $accountManager; + private FilesPlugin $plugin; protected function setUp(): void { parent::setUp(); @@ -89,32 +61,28 @@ class FilesPluginTest extends TestCase { $this->request = $this->createMock(IRequest::class); $this->previewManager = $this->createMock(IPreview::class); $this->userSession = $this->createMock(IUserSession::class); + $this->filenameValidator = $this->createMock(IFilenameValidator::class); + $this->accountManager = $this->createMock(IAccountManager::class); $this->plugin = new FilesPlugin( $this->tree, $this->config, $this->request, $this->previewManager, - $this->userSession + $this->userSession, + $this->filenameValidator, + $this->accountManager, ); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $response = $this->createMock(ResponseInterface::class); $this->server->httpResponse = $response; $this->server->xml = new Service(); $this->plugin->initialize($this->server); } - /** - * @param string $class - * @return \PHPUnit\Framework\MockObject\MockObject - */ - private function createTestNode($class, $path = '/dummypath') { - $node = $this->getMockBuilder($class) - ->disableOriginalConstructor() - ->getMock(); + private function createTestNode(string $class, string $path = '/dummypath'): MockObject { + $node = $this->createMock($class); $node->expects($this->any()) ->method('getId') @@ -154,28 +122,27 @@ class FilesPluginTest extends TestCase { } public function testGetPropertiesForFile(): void { - /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + /** @var File&MockObject $node */ + $node = $this->createTestNode(File::class); $propFind = new PropFind( '/dummyPath', [ - self::GETETAG_PROPERTYNAME, - self::FILEID_PROPERTYNAME, - self::INTERNAL_FILEID_PROPERTYNAME, - self::SIZE_PROPERTYNAME, - self::PERMISSIONS_PROPERTYNAME, - self::DOWNLOADURL_PROPERTYNAME, - self::OWNER_ID_PROPERTYNAME, - self::OWNER_DISPLAY_NAME_PROPERTYNAME, - self::DATA_FINGERPRINT_PROPERTYNAME, - self::CREATIONDATE_PROPERTYNAME, + FilesPlugin::GETETAG_PROPERTYNAME, + FilesPlugin::FILEID_PROPERTYNAME, + FilesPlugin::INTERNAL_FILEID_PROPERTYNAME, + FilesPlugin::SIZE_PROPERTYNAME, + FilesPlugin::PERMISSIONS_PROPERTYNAME, + FilesPlugin::DOWNLOADURL_PROPERTYNAME, + FilesPlugin::OWNER_ID_PROPERTYNAME, + FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, + FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, + FilesPlugin::CREATIONDATE_PROPERTYNAME, ], 0 ); - $user = $this->getMockBuilder(User::class) - ->disableOriginalConstructor()->getMock(); + $user = $this->createMock(User::class); $user ->expects($this->once()) ->method('getUID') @@ -185,6 +152,12 @@ class FilesPluginTest extends TestCase { ->method('getDisplayName') ->willReturn('M. Foo'); + $owner = $this->createMock(Account::class); + $this->accountManager->expects($this->once()) + ->method('getAccount') + ->with($user) + ->willReturn($owner); + $node->expects($this->once()) ->method('getDirectDownload') ->willReturn(['url' => 'http://example.com/']); @@ -192,70 +165,170 @@ class FilesPluginTest extends TestCase { ->method('getOwner') ->willReturn($user); + $displayNameProp = $this->createMock(AccountProperty::class); + $owner + ->expects($this->once()) + ->method('getProperty') + ->with(IAccountManager::PROPERTY_DISPLAYNAME) + ->willReturn($displayNameProp); + $displayNameProp + ->expects($this->once()) + ->method('getScope') + ->willReturn(IAccountManager::SCOPE_PUBLISHED); + $this->plugin->handleGetProperties( $propFind, $node ); - $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); - $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME)); - $this->assertEquals('123', $propFind->get(self::INTERNAL_FILEID_PROPERTYNAME)); - $this->assertEquals('1973-11-29T21:33:09+00:00', $propFind->get(self::CREATIONDATE_PROPERTYNAME)); - $this->assertEquals(0, $propFind->get(self::SIZE_PROPERTYNAME)); - $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); - $this->assertEquals('http://example.com/', $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); - $this->assertEquals('foo', $propFind->get(self::OWNER_ID_PROPERTYNAME)); - $this->assertEquals('M. Foo', $propFind->get(self::OWNER_DISPLAY_NAME_PROPERTYNAME)); - $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); + $this->assertEquals('"abc"', $propFind->get(FilesPlugin::GETETAG_PROPERTYNAME)); + $this->assertEquals('00000123instanceid', $propFind->get(FilesPlugin::FILEID_PROPERTYNAME)); + $this->assertEquals('123', $propFind->get(FilesPlugin::INTERNAL_FILEID_PROPERTYNAME)); + $this->assertEquals('1973-11-29T21:33:09+00:00', $propFind->get(FilesPlugin::CREATIONDATE_PROPERTYNAME)); + $this->assertEquals(0, $propFind->get(FilesPlugin::SIZE_PROPERTYNAME)); + $this->assertEquals('DWCKMSR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals('http://example.com/', $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals('foo', $propFind->get(FilesPlugin::OWNER_ID_PROPERTYNAME)); + $this->assertEquals('M. Foo', $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME)); + $this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME)); $this->assertEquals([], $propFind->get404Properties()); } + public function testGetDisplayNamePropertyWhenNotPublished(): void { + $node = $this->createTestNode(File::class); + $propFind = new PropFind( + '/dummyPath', + [ + FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, + ], + 0 + ); + + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn(null); + + $user = $this->createMock(User::class); + + $user->expects($this->never()) + ->method('getDisplayName'); + + $owner = $this->createMock(Account::class); + $this->accountManager->expects($this->once()) + ->method('getAccount') + ->with($user) + ->willReturn($owner); + + $node->expects($this->once()) + ->method('getOwner') + ->willReturn($user); + + $displayNameProp = $this->createMock(AccountProperty::class); + $owner + ->expects($this->once()) + ->method('getProperty') + ->with(IAccountManager::PROPERTY_DISPLAYNAME) + ->willReturn($displayNameProp); + $displayNameProp + ->expects($this->once()) + ->method('getScope') + ->willReturn(IAccountManager::SCOPE_PRIVATE); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals(null, $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME)); + } + + public function testGetDisplayNamePropertyWhenNotPublishedButLoggedIn(): void { + $node = $this->createTestNode(File::class); + + $propFind = new PropFind( + '/dummyPath', + [ + FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME, + ], + 0 + ); + + $user = $this->createMock(User::class); + + $node->expects($this->once()) + ->method('getOwner') + ->willReturn($user); + + $loggedInUser = $this->createMock(User::class); + $this->userSession->expects($this->once()) + ->method('getUser') + ->willReturn($loggedInUser); + + $user + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('M. Foo'); + + $this->accountManager->expects($this->never()) + ->method('getAccount'); + + $this->plugin->handleGetProperties( + $propFind, + $node + ); + + $this->assertEquals('M. Foo', $propFind->get(FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME)); + } + public function testGetPropertiesStorageNotAvailable(): void { - /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + /** @var File&MockObject $node */ + $node = $this->createTestNode(File::class); $propFind = new PropFind( '/dummyPath', [ - self::DOWNLOADURL_PROPERTYNAME, + FilesPlugin::DOWNLOADURL_PROPERTYNAME, ], 0 ); $node->expects($this->once()) ->method('getDirectDownload') - ->will($this->throwException(new StorageNotAvailableException())); + ->willThrowException(new StorageNotAvailableException()); $this->plugin->handleGetProperties( $propFind, $node ); - $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME)); } public function testGetPublicPermissions(): void { + /** @var IRequest&MockObject */ + $request = $this->createMock(IRequest::class); $this->plugin = new FilesPlugin( $this->tree, $this->config, - $this->getMockBuilder(IRequest::class) - ->disableOriginalConstructor() - ->getMock(), + $request, $this->previewManager, $this->userSession, - true); + $this->filenameValidator, + $this->accountManager, + true, + ); $this->plugin->initialize($this->server); $propFind = new PropFind( '/dummyPath', [ - self::PERMISSIONS_PROPERTYNAME, + FilesPlugin::PERMISSIONS_PROPERTYNAME, ], 0 ); - /** @var \OCA\DAV\Connector\Sabre\File | \PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + /** @var File&MockObject $node */ + $node = $this->createTestNode(File::class); $node->expects($this->any()) ->method('getDavPermissions') ->willReturn('DWCKMSR'); @@ -265,22 +338,22 @@ class FilesPluginTest extends TestCase { $node ); - $this->assertEquals('DWCKR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals('DWCKR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME)); } public function testGetPropertiesForDirectory(): void { - /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + /** @var Directory&MockObject $node */ + $node = $this->createTestNode(Directory::class); $propFind = new PropFind( '/dummyPath', [ - self::GETETAG_PROPERTYNAME, - self::FILEID_PROPERTYNAME, - self::SIZE_PROPERTYNAME, - self::PERMISSIONS_PROPERTYNAME, - self::DOWNLOADURL_PROPERTYNAME, - self::DATA_FINGERPRINT_PROPERTYNAME, + FilesPlugin::GETETAG_PROPERTYNAME, + FilesPlugin::FILEID_PROPERTYNAME, + FilesPlugin::SIZE_PROPERTYNAME, + FilesPlugin::PERMISSIONS_PROPERTYNAME, + FilesPlugin::DOWNLOADURL_PROPERTYNAME, + FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, ], 0 ); @@ -294,20 +367,18 @@ class FilesPluginTest extends TestCase { $node ); - $this->assertEquals('"abc"', $propFind->get(self::GETETAG_PROPERTYNAME)); - $this->assertEquals('00000123instanceid', $propFind->get(self::FILEID_PROPERTYNAME)); - $this->assertEquals(1025, $propFind->get(self::SIZE_PROPERTYNAME)); - $this->assertEquals('DWCKMSR', $propFind->get(self::PERMISSIONS_PROPERTYNAME)); - $this->assertEquals(null, $propFind->get(self::DOWNLOADURL_PROPERTYNAME)); - $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); - $this->assertEquals([self::DOWNLOADURL_PROPERTYNAME], $propFind->get404Properties()); + $this->assertEquals('"abc"', $propFind->get(FilesPlugin::GETETAG_PROPERTYNAME)); + $this->assertEquals('00000123instanceid', $propFind->get(FilesPlugin::FILEID_PROPERTYNAME)); + $this->assertEquals(1025, $propFind->get(FilesPlugin::SIZE_PROPERTYNAME)); + $this->assertEquals('DWCKMSR', $propFind->get(FilesPlugin::PERMISSIONS_PROPERTYNAME)); + $this->assertEquals(null, $propFind->get(FilesPlugin::DOWNLOADURL_PROPERTYNAME)); + $this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME)); + $this->assertEquals([FilesPlugin::DOWNLOADURL_PROPERTYNAME], $propFind->get404Properties()); } public function testGetPropertiesForRootDirectory(): void { - /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + /** @var Directory&MockObject $node */ + $node = $this->createMock(Directory::class); $node->expects($this->any())->method('getPath')->willReturn('/'); $fileInfo = $this->createMock(FileInfo::class); @@ -322,7 +393,7 @@ class FilesPluginTest extends TestCase { $propFind = new PropFind( '/', [ - self::DATA_FINGERPRINT_PROPERTYNAME, + FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, ], 0 ); @@ -332,18 +403,15 @@ class FilesPluginTest extends TestCase { $node ); - $this->assertEquals('my_fingerprint', $propFind->get(self::DATA_FINGERPRINT_PROPERTYNAME)); + $this->assertEquals('my_fingerprint', $propFind->get(FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME)); } public function testGetPropertiesWhenNoPermission(): void { // No read permissions can be caused by files access control. // But we still want to load the directory list, so this is okay for us. // $this->expectException(\Sabre\DAV\Exception\NotFound::class); - - /** @var \OCA\DAV\Connector\Sabre\Directory|\PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + /** @var Directory&MockObject $node */ + $node = $this->createMock(Directory::class); $node->expects($this->any())->method('getPath')->willReturn('/'); $fileInfo = $this->createMock(FileInfo::class); @@ -358,7 +426,7 @@ class FilesPluginTest extends TestCase { $propFind = new PropFind( '/test', [ - self::DATA_FINGERPRINT_PROPERTYNAME, + FilesPlugin::DATA_FINGERPRINT_PROPERTYNAME, ], 0 ); @@ -372,7 +440,7 @@ class FilesPluginTest extends TestCase { } public function testUpdateProps(): void { - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\File'); + $node = $this->createTestNode(File::class); $testDate = 'Fri, 13 Feb 2015 00:01:02 GMT'; $testCreationDate = '2007-08-31T16:47+00:00'; @@ -392,9 +460,9 @@ class FilesPluginTest extends TestCase { // properties to set $propPatch = new PropPatch([ - self::GETETAG_PROPERTYNAME => 'newetag', - self::LASTMODIFIED_PROPERTYNAME => $testDate, - self::CREATIONDATE_PROPERTYNAME => $testCreationDate, + FilesPlugin::GETETAG_PROPERTYNAME => 'newetag', + FilesPlugin::LASTMODIFIED_PROPERTYNAME => $testDate, + FilesPlugin::CREATIONDATE_PROPERTYNAME => $testCreationDate, ]); @@ -408,19 +476,19 @@ class FilesPluginTest extends TestCase { $this->assertEmpty($propPatch->getRemainingMutations()); $result = $propPatch->getResult(); - $this->assertEquals(200, $result[self::LASTMODIFIED_PROPERTYNAME]); - $this->assertEquals(200, $result[self::GETETAG_PROPERTYNAME]); - $this->assertEquals(200, $result[self::CREATIONDATE_PROPERTYNAME]); + $this->assertEquals(200, $result[FilesPlugin::LASTMODIFIED_PROPERTYNAME]); + $this->assertEquals(200, $result[FilesPlugin::GETETAG_PROPERTYNAME]); + $this->assertEquals(200, $result[FilesPlugin::CREATIONDATE_PROPERTYNAME]); } public function testUpdatePropsForbidden(): void { $propPatch = new PropPatch([ - self::OWNER_ID_PROPERTYNAME => 'user2', - self::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two', - self::FILEID_PROPERTYNAME => 12345, - self::PERMISSIONS_PROPERTYNAME => 'C', - self::SIZE_PROPERTYNAME => 123, - self::DOWNLOADURL_PROPERTYNAME => 'http://example.com/', + FilesPlugin::OWNER_ID_PROPERTYNAME => 'user2', + FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME => 'User Two', + FilesPlugin::FILEID_PROPERTYNAME => 12345, + FilesPlugin::PERMISSIONS_PROPERTYNAME => 'C', + FilesPlugin::SIZE_PROPERTYNAME => 123, + FilesPlugin::DOWNLOADURL_PROPERTYNAME => 'http://example.com/', ]); $this->plugin->handleUpdateProperties( @@ -433,16 +501,16 @@ class FilesPluginTest extends TestCase { $this->assertEmpty($propPatch->getRemainingMutations()); $result = $propPatch->getResult(); - $this->assertEquals(403, $result[self::OWNER_ID_PROPERTYNAME]); - $this->assertEquals(403, $result[self::OWNER_DISPLAY_NAME_PROPERTYNAME]); - $this->assertEquals(403, $result[self::FILEID_PROPERTYNAME]); - $this->assertEquals(403, $result[self::PERMISSIONS_PROPERTYNAME]); - $this->assertEquals(403, $result[self::SIZE_PROPERTYNAME]); - $this->assertEquals(403, $result[self::DOWNLOADURL_PROPERTYNAME]); + $this->assertEquals(403, $result[FilesPlugin::OWNER_ID_PROPERTYNAME]); + $this->assertEquals(403, $result[FilesPlugin::OWNER_DISPLAY_NAME_PROPERTYNAME]); + $this->assertEquals(403, $result[FilesPlugin::FILEID_PROPERTYNAME]); + $this->assertEquals(403, $result[FilesPlugin::PERMISSIONS_PROPERTYNAME]); + $this->assertEquals(403, $result[FilesPlugin::SIZE_PROPERTYNAME]); + $this->assertEquals(403, $result[FilesPlugin::DOWNLOADURL_PROPERTYNAME]); } /** - * Testcase from https://github.com/owncloud/core/issues/5251 + * Test case from https://github.com/owncloud/core/issues/5251 * * |-FolderA * |-text.txt @@ -456,66 +524,122 @@ class FilesPluginTest extends TestCase { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->expectExceptionMessage('FolderA/test.txt cannot be deleted'); - $fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class) - ->disableOriginalConstructor() - ->getMock(); + $fileInfoFolderATestTXT = $this->createMock(FileInfo::class); $fileInfoFolderATestTXT->expects($this->once()) ->method('isDeletable') ->willReturn(false); - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); - $node->expects($this->once()) + $node = $this->createMock(Node::class); + $node->expects($this->atLeastOnce()) ->method('getFileInfo') ->willReturn($fileInfoFolderATestTXT); - $this->tree->expects($this->once())->method('getNodeForPath') + $this->tree->expects($this->atLeastOnce()) + ->method('getNodeForPath') ->willReturn($node); $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } public function testMoveSrcDeletable(): void { - $fileInfoFolderATestTXT = $this->getMockBuilder(FileInfo::class) - ->disableOriginalConstructor() - ->getMock(); + $fileInfoFolderATestTXT = $this->createMock(FileInfo::class); $fileInfoFolderATestTXT->expects($this->once()) ->method('isDeletable') ->willReturn(true); - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); - $node->expects($this->once()) + $node = $this->createMock(Node::class); + $node->expects($this->atLeastOnce()) ->method('getFileInfo') ->willReturn($fileInfoFolderATestTXT); - $this->tree->expects($this->once())->method('getNodeForPath') + $this->tree->expects($this->atLeastOnce()) + ->method('getNodeForPath') ->willReturn($node); $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } - public function testMoveSrcNotExist(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); $this->expectExceptionMessage('FolderA/test.txt does not exist'); - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); - $node->expects($this->once()) + $node = $this->createMock(Node::class); + $node->expects($this->atLeastOnce()) ->method('getFileInfo') ->willReturn(null); - $this->tree->expects($this->once())->method('getNodeForPath') + $this->tree->expects($this->atLeastOnce()) + ->method('getNodeForPath') ->willReturn($node); $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } - public function downloadHeadersProvider() { + public function testMoveDestinationInvalid(): void { + $this->expectException(InvalidPath::class); + $this->expectExceptionMessage('Mocked exception'); + + $fileInfoFolderATestTXT = $this->createMock(FileInfo::class); + $fileInfoFolderATestTXT->expects(self::any()) + ->method('isDeletable') + ->willReturn(true); + + $node = $this->createMock(Node::class); + $node->expects($this->atLeastOnce()) + ->method('getFileInfo') + ->willReturn($fileInfoFolderATestTXT); + + $this->tree->expects($this->atLeastOnce()) + ->method('getNodeForPath') + ->willReturn($node); + + $this->filenameValidator->expects(self::once()) + ->method('validateFilename') + ->with('invalid\\path.txt') + ->willThrowException(new InvalidPathException('Mocked exception')); + + $this->plugin->checkMove('FolderA/test.txt', 'invalid\\path.txt'); + } + + public function testCopySrcNotExist(): void { + $this->expectException(\Sabre\DAV\Exception\NotFound::class); + $this->expectExceptionMessage('FolderA/test.txt does not exist'); + + $node = $this->createMock(Node::class); + $node->expects($this->atLeastOnce()) + ->method('getFileInfo') + ->willReturn(null); + + $this->tree->expects($this->atLeastOnce()) + ->method('getNodeForPath') + ->willReturn($node); + + $this->plugin->checkCopy('FolderA/test.txt', 'test.txt'); + } + + public function testCopyDestinationInvalid(): void { + $this->expectException(InvalidPath::class); + $this->expectExceptionMessage('Mocked exception'); + + $fileInfoFolderATestTXT = $this->createMock(FileInfo::class); + $node = $this->createMock(Node::class); + $node->expects($this->atLeastOnce()) + ->method('getFileInfo') + ->willReturn($fileInfoFolderATestTXT); + + $this->tree->expects($this->atLeastOnce()) + ->method('getNodeForPath') + ->willReturn($node); + + $this->filenameValidator->expects(self::once()) + ->method('validateFilename') + ->with('invalid\\path.txt') + ->willThrowException(new InvalidPathException('Mocked exception')); + + $this->plugin->checkCopy('FolderA/test.txt', 'invalid\\path.txt'); + } + + public static function downloadHeadersProvider(): array { return [ [ false, @@ -528,25 +652,17 @@ class FilesPluginTest extends TestCase { ]; } - /** - * @dataProvider downloadHeadersProvider - */ - public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader): void { - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('downloadHeadersProvider')] + public function testDownloadHeaders(bool $isClumsyAgent, string $contentDispositionHeader): void { + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request ->expects($this->once()) ->method('getPath') ->willReturn('test/somefile.xml'); - $node = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(File::class); $node ->expects($this->once()) ->method('getName') @@ -563,25 +679,29 @@ class FilesPluginTest extends TestCase { ->method('isUserAgent') ->willReturn($isClumsyAgent); + $calls = [ + ['Content-Disposition', $contentDispositionHeader], + ['X-Accel-Buffering', 'no'], + ]; $response - ->expects($this->exactly(2)) + ->expects($this->exactly(count($calls))) ->method('addHeader') - ->withConsecutive( - ['Content-Disposition', $contentDispositionHeader], - ['X-Accel-Buffering', 'no'] - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertSame($expected, func_get_args()); + }); $this->plugin->httpGet($request, $response); } public function testHasPreview(): void { - /** @var \OCA\DAV\Connector\Sabre\Directory | \PHPUnit\Framework\MockObject\MockObject $node */ - $node = $this->createTestNode('\OCA\DAV\Connector\Sabre\Directory'); + /** @var Directory&MockObject $node */ + $node = $this->createTestNode(Directory::class); $propFind = new PropFind( '/dummyPath', [ - self::HAS_PREVIEW_PROPERTYNAME + FilesPlugin::HAS_PREVIEW_PROPERTYNAME ], 0 ); @@ -595,6 +715,6 @@ class FilesPluginTest extends TestCase { $node ); - $this->assertEquals("false", $propFind->get(self::HAS_PREVIEW_PROPERTYNAME)); + $this->assertEquals('false', $propFind->get(FilesPlugin::HAS_PREVIEW_PROPERTYNAME)); } } diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index 97f21572945..176949f999c 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -9,11 +10,14 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\FilesReportPlugin as FilesReportPluginImplementation; +use OCP\Accounts\IAccountManager; use OCP\App\IAppManager; use OCP\Files\File; use OCP\Files\FileInfo; use OCP\Files\Folder; +use OCP\Files\IFilenameValidator; use OCP\IConfig; use OCP\IGroupManager; use OCP\IPreview; @@ -28,83 +32,45 @@ use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagNotFoundException; use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\INode; +use Sabre\DAV\Server; use Sabre\DAV\Tree; use Sabre\HTTP\ResponseInterface; class FilesReportPluginTest extends \Test\TestCase { - /** @var \Sabre\DAV\Server|MockObject */ - private $server; - /** @var \Sabre\DAV\Tree|MockObject */ - private $tree; - - /** @var ISystemTagObjectMapper|MockObject */ - private $tagMapper; - - /** @var ISystemTagManager|MockObject */ - private $tagManager; - - /** @var ITags|MockObject */ - private $privateTags; - - private ITagManager|MockObject $privateTagManager; - - /** @var \OCP\IUserSession */ - private $userSession; - - /** @var FilesReportPluginImplementation */ - private $plugin; - - /** @var View|MockObject **/ - private $view; - - /** @var IGroupManager|MockObject **/ - private $groupManager; - - /** @var Folder|MockObject **/ - private $userFolder; - - /** @var IPreview|MockObject * */ - private $previewManager; - - /** @var IAppManager|MockObject * */ - private $appManager; + private \Sabre\DAV\Server&MockObject $server; + private Tree&MockObject $tree; + private ISystemTagObjectMapper&MockObject $tagMapper; + private ISystemTagManager&MockObject $tagManager; + private ITags&MockObject $privateTags; + private ITagManager&MockObject $privateTagManager; + private IUserSession&MockObject $userSession; + private FilesReportPluginImplementation $plugin; + private View&MockObject $view; + private IGroupManager&MockObject $groupManager; + private Folder&MockObject $userFolder; + private IPreview&MockObject $previewManager; + private IAppManager&MockObject $appManager; protected function setUp(): void { parent::setUp(); - $this->tree = $this->getMockBuilder(Tree::class) - ->disableOriginalConstructor() - ->getMock(); - $this->view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); + $this->tree = $this->createMock(Tree::class); + $this->view = $this->createMock(View::class); - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') + $this->server = $this->getMockBuilder(Server::class) ->setConstructorArgs([$this->tree]) - ->setMethods(['getRequestUri', 'getBaseUri']) + ->onlyMethods(['getRequestUri', 'getBaseUri']) ->getMock(); $this->server->expects($this->any()) ->method('getBaseUri') ->willReturn('http://example.com/owncloud/remote.php/dav'); - $this->groupManager = $this->getMockBuilder(IGroupManager::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->userFolder = $this->getMockBuilder(Folder::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->previewManager = $this->getMockBuilder(IPreview::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->appManager = $this->getMockBuilder(IAppManager::class) - ->disableOriginalConstructor() - ->getMock(); - + $this->groupManager = $this->createMock(IGroupManager::class); + $this->userFolder = $this->createMock(Folder::class); + $this->previewManager = $this->createMock(IPreview::class); + $this->appManager = $this->createMock(IAppManager::class); $this->tagManager = $this->createMock(ISystemTagManager::class); $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class); $this->userSession = $this->createMock(IUserSession::class); @@ -115,9 +81,7 @@ class FilesReportPluginTest extends \Test\TestCase { ->with('files') ->willReturn($this->privateTags); - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('testuser'); @@ -144,11 +108,7 @@ class FilesReportPluginTest extends \Test\TestCase { $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('/' . $path) - ->willReturn( - $this->getMockBuilder(INode::class) - ->disableOriginalConstructor() - ->getMock() - ); + ->willReturn($this->createMock(INode::class)); $this->server->expects($this->any()) ->method('getRequestUri') @@ -202,16 +162,12 @@ class FilesReportPluginTest extends \Test\TestCase { ->method('isAdmin') ->willReturn(true); - $reportTargetNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + $reportTargetNode = $this->createMock(Directory::class); $reportTargetNode->expects($this->any()) ->method('getPath') ->willReturn(''); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $response = $this->createMock(ResponseInterface::class); $response->expects($this->once()) ->method('setHeader') @@ -260,14 +216,10 @@ class FilesReportPluginTest extends \Test\TestCase { $this->userFolder->expects($this->exactly(2)) ->method('searchBySystemTag') - ->withConsecutive( - ['OneTwoThree'], - ['FourFiveSix'], - ) - ->willReturnOnConsecutiveCalls( - [$filesNode1], - [$filesNode2], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, [$filesNode1]], + ['FourFiveSix', 'testuser', 0, 0, [$filesNode2]], + ]); $this->server->expects($this->any()) ->method('getRequestUri') @@ -279,74 +231,56 @@ class FilesReportPluginTest extends \Test\TestCase { } public function testFindNodesByFileIdsRoot(): void { - $filesNode1 = $this->getMockBuilder(Folder::class) - ->disableOriginalConstructor() - ->getMock(); + $filesNode1 = $this->createMock(Folder::class); $filesNode1->expects($this->once()) ->method('getName') ->willReturn('first node'); - $filesNode2 = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + $filesNode2 = $this->createMock(File::class); $filesNode2->expects($this->once()) ->method('getName') ->willReturn('second node'); - $reportTargetNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + $reportTargetNode = $this->createMock(Directory::class); $reportTargetNode->expects($this->any()) ->method('getPath') ->willReturn('/'); $this->userFolder->expects($this->exactly(2)) ->method('getFirstNodeById') - ->withConsecutive( - ['111'], - ['222'], - ) - ->willReturnOnConsecutiveCalls( - $filesNode1, - $filesNode2, - ); + ->willReturnMap([ + [111, $filesNode1], + [222, $filesNode2], + ]); - /** @var \OCA\DAV\Connector\Sabre\Directory|MockObject $reportTargetNode */ + /** @var Directory&MockObject $reportTargetNode */ $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']); $this->assertCount(2, $result); - $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]); + $this->assertInstanceOf(Directory::class, $result[0]); $this->assertEquals('first node', $result[0]->getName()); - $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]); + $this->assertInstanceOf(\OCA\DAV\Connector\Sabre\File::class, $result[1]); $this->assertEquals('second node', $result[1]->getName()); } public function testFindNodesByFileIdsSubDir(): void { - $filesNode1 = $this->getMockBuilder(Folder::class) - ->disableOriginalConstructor() - ->getMock(); + $filesNode1 = $this->createMock(Folder::class); $filesNode1->expects($this->once()) ->method('getName') ->willReturn('first node'); - $filesNode2 = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + $filesNode2 = $this->createMock(File::class); $filesNode2->expects($this->once()) ->method('getName') ->willReturn('second node'); - $reportTargetNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + $reportTargetNode = $this->createMock(Directory::class); $reportTargetNode->expects($this->any()) ->method('getPath') ->willReturn('/sub1/sub2'); - $subNode = $this->getMockBuilder(Folder::class) - ->disableOriginalConstructor() - ->getMock(); + $subNode = $this->createMock(Folder::class); $this->userFolder->expects($this->once()) ->method('get') @@ -355,22 +289,18 @@ class FilesReportPluginTest extends \Test\TestCase { $subNode->expects($this->exactly(2)) ->method('getFirstNodeById') - ->withConsecutive( - ['111'], - ['222'], - ) - ->willReturnOnConsecutiveCalls( - $filesNode1, - $filesNode2, - ); + ->willReturnMap([ + [111, $filesNode1], + [222, $filesNode2], + ]); - /** @var \OCA\DAV\Connector\Sabre\Directory|MockObject $reportTargetNode */ + /** @var Directory&MockObject $reportTargetNode */ $result = $this->plugin->findNodesByFileIds($reportTargetNode, ['111', '222']); $this->assertCount(2, $result); - $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\Directory', $result[0]); + $this->assertInstanceOf(Directory::class, $result[0]); $this->assertEquals('first node', $result[0]->getName()); - $this->assertInstanceOf('\OCA\DAV\Connector\Sabre\File', $result[1]); + $this->assertInstanceOf(\OCA\DAV\Connector\Sabre\File::class, $result[1]); $this->assertEquals('second node', $result[1]->getName()); } @@ -380,12 +310,8 @@ class FilesReportPluginTest extends \Test\TestCase { $fileInfo = $this->createMock(FileInfo::class); $fileInfo->method('isReadable')->willReturn(true); - $node1 = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); - $node2 = $this->getMockBuilder(\OCA\DAV\Connector\Sabre\File::class) - ->disableOriginalConstructor() - ->getMock(); + $node1 = $this->createMock(Directory::class); + $node2 = $this->createMock(\OCA\DAV\Connector\Sabre\File::class); $node1->expects($this->once()) ->method('getInternalFileId') @@ -405,17 +331,19 @@ class FilesReportPluginTest extends \Test\TestCase { ->willReturn('/sub/node2'); $node2->method('getFileInfo')->willReturn($fileInfo); - $config = $this->getMockBuilder(IConfig::class) - ->disableOriginalConstructor() - ->getMock(); + $config = $this->createMock(IConfig::class); + $validator = $this->createMock(IFilenameValidator::class); + $accountManager = $this->createMock(IAccountManager::class); $this->server->addPlugin( - new \OCA\DAV\Connector\Sabre\FilesPlugin( + new FilesPlugin( $this->tree, $config, $this->createMock(IRequest::class), $this->previewManager, - $this->createMock(IUserSession::class) + $this->createMock(IUserSession::class), + $validator, + $accountManager, ) ); $this->plugin->initialize($this->server); @@ -476,7 +404,7 @@ class FilesReportPluginTest extends \Test\TestCase { ->with('OneTwoThree') ->willReturn([$filesNode1, $filesNode2]); - $this->assertEquals([$filesNode1, $filesNode2], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, 0, 0])); + $this->assertEquals([$filesNode1, $filesNode2], self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, 0, 0])); } public function testProcessFilterRulesAndCondition(): void { @@ -528,21 +456,17 @@ class FilesReportPluginTest extends \Test\TestCase { $this->userFolder->expects($this->exactly(2)) ->method('searchBySystemTag') - ->withConsecutive( - ['OneTwoThree'], - ['FourFiveSix'], - ) - ->willReturnOnConsecutiveCalls( - [$filesNode1, $filesNode2], - [$filesNode2, $filesNode3], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, [$filesNode1, $filesNode2]], + ['FourFiveSix', 'testuser', 0, 0, [$filesNode2, $filesNode3]], + ]); $rules = [ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ]; - $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); + $this->assertEquals([$filesNode2], array_values(self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); } public function testProcessFilterRulesAndConditionWithOneEmptyResult(): void { @@ -587,21 +511,17 @@ class FilesReportPluginTest extends \Test\TestCase { $this->userFolder->expects($this->exactly(2)) ->method('searchBySystemTag') - ->withConsecutive( - ['OneTwoThree'], - ['FourFiveSix'], - ) - ->willReturnOnConsecutiveCalls( - [$filesNode1, $filesNode2], - [], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, [$filesNode1, $filesNode2]], + ['FourFiveSix', 'testuser', 0, 0, []], + ]); $rules = [ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ]; - $this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])); + $this->assertEquals([], self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])); } public function testProcessFilterRulesAndConditionWithFirstEmptyResult(): void { @@ -646,18 +566,16 @@ class FilesReportPluginTest extends \Test\TestCase { $this->userFolder->expects($this->once()) ->method('searchBySystemTag') - ->with('OneTwoThree') - ->willReturnOnConsecutiveCalls( - [], - [$filesNode1, $filesNode2], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, []], + ]); $rules = [ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ]; - $this->assertEquals([], $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])); + $this->assertEquals([], self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null])); } public function testProcessFilterRulesAndConditionWithEmptyMidResult(): void { @@ -704,7 +622,7 @@ class FilesReportPluginTest extends \Test\TestCase { $tag789 = $this->createMock(ISystemTag::class); $tag789->expects($this->any()) ->method('getName') - ->willReturn('SevenEightNein'); + ->willReturn('SevenEightNine'); $tag789->expects($this->any()) ->method('isUserVisible') ->willReturn(true); @@ -716,12 +634,10 @@ class FilesReportPluginTest extends \Test\TestCase { $this->userFolder->expects($this->exactly(2)) ->method('searchBySystemTag') - ->withConsecutive(['OneTwoThree'], ['FourFiveSix'], ['SevenEightNein']) - ->willReturnOnConsecutiveCalls( - [$filesNode1, $filesNode2], - [$filesNode3], - [$filesNode1, $filesNode2], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, [$filesNode1, $filesNode2]], + ['FourFiveSix', 'testuser', 0, 0, [$filesNode3]], + ]); $rules = [ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], @@ -729,7 +645,7 @@ class FilesReportPluginTest extends \Test\TestCase { ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '789'], ]; - $this->assertEquals([], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); + $this->assertEquals([], array_values(self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); } public function testProcessFilterRulesInvisibleTagAsAdmin(): void { @@ -781,18 +697,17 @@ class FilesReportPluginTest extends \Test\TestCase { $this->userFolder->expects($this->exactly(2)) ->method('searchBySystemTag') - ->withConsecutive(['OneTwoThree'], ['FourFiveSix']) - ->willReturnOnConsecutiveCalls( - [$filesNode1, $filesNode2], - [$filesNode2, $filesNode3], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, [$filesNode1, $filesNode2]], + ['FourFiveSix', 'testuser', 0, 0, [$filesNode2, $filesNode3]], + ]); $rules = [ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ]; - $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); + $this->assertEquals([$filesNode2], array_values(self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); } @@ -831,7 +746,7 @@ class FilesReportPluginTest extends \Test\TestCase { ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ]; - $this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]); + self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]); } public function testProcessFilterRulesVisibleTagAsUser(): void { @@ -896,18 +811,17 @@ class FilesReportPluginTest extends \Test\TestCase { // main assertion: only user visible tags are being passed through. $this->userFolder->expects($this->exactly(2)) ->method('searchBySystemTag') - ->withConsecutive(['OneTwoThree'], ['FourFiveSix']) - ->willReturnOnConsecutiveCalls( - [$filesNode1, $filesNode2], - [$filesNode2, $filesNode3], - ); + ->willReturnMap([ + ['OneTwoThree', 'testuser', 0, 0, [$filesNode1, $filesNode2]], + ['FourFiveSix', 'testuser', 0, 0, [$filesNode2, $filesNode3]], + ]); $rules = [ ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '123'], ['name' => '{http://owncloud.org/ns}systemtag', 'value' => '456'], ]; - $this->assertEquals([$filesNode2], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); + $this->assertEquals([$filesNode2], array_values(self::invokePrivate($this->plugin, 'processFilterRulesForFileNodes', [$rules, null, null]))); } public function testProcessFavoriteFilter(): void { @@ -919,10 +833,10 @@ class FilesReportPluginTest extends \Test\TestCase { ->method('getFavorites') ->willReturn(['456', '789']); - $this->assertEquals(['456', '789'], array_values($this->invokePrivate($this->plugin, 'processFilterRulesForFileIDs', [$rules]))); + $this->assertEquals(['456', '789'], array_values(self::invokePrivate($this->plugin, 'processFilterRulesForFileIDs', [$rules]))); } - public function filesBaseUriProvider() { + public static function filesBaseUriProvider(): array { return [ ['', '', ''], ['files/username', '', '/files/username'], @@ -932,10 +846,8 @@ class FilesReportPluginTest extends \Test\TestCase { ]; } - /** - * @dataProvider filesBaseUriProvider - */ - public function testFilesBaseUri($uri, $reportPath, $expectedUri): void { - $this->assertEquals($expectedUri, $this->invokePrivate($this->plugin, 'getFilesBaseUri', [$uri, $reportPath])); + #[\PHPUnit\Framework\Attributes\DataProvider('filesBaseUriProvider')] + public function testFilesBaseUri(string $uri, string $reportPath, string $expectedUri): void { + $this->assertEquals($expectedUri, self::invokePrivate($this->plugin, 'getFilesBaseUri', [$uri, $reportPath])); } } diff --git a/apps/dav/tests/unit/Connector/Sabre/MaintenancePluginTest.php b/apps/dav/tests/unit/Connector/Sabre/MaintenancePluginTest.php index 28c22202c1e..bc1d50ac41f 100644 --- a/apps/dav/tests/unit/Connector/Sabre/MaintenancePluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/MaintenancePluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -10,6 +11,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\MaintenancePlugin; use OCP\IConfig; use OCP\IL10N; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; /** @@ -18,18 +20,15 @@ use Test\TestCase; * @package OCA\DAV\Tests\unit\Connector\Sabre */ class MaintenancePluginTest extends TestCase { - /** @var IConfig */ - private $config; - /** @var \PHPUnit\Framework\MockObject\Builder\InvocationMocker|\PHPUnit_Framework_MockObject_Builder_InvocationMocker|IL10N */ - private $l10n; - /** @var MaintenancePlugin */ - private $maintenancePlugin; + private IConfig&MockObject $config; + private IL10N&MockObject $l10n; + private MaintenancePlugin $maintenancePlugin; protected function setUp(): void { parent::setUp(); - $this->config = $this->getMockBuilder(IConfig::class)->getMock(); - $this->l10n = $this->getMockBuilder(IL10N::class)->getMock(); + $this->config = $this->createMock(IConfig::class); + $this->l10n = $this->createMock(IL10N::class); $this->maintenancePlugin = new MaintenancePlugin($this->config, $this->l10n); } diff --git a/apps/dav/tests/unit/Connector/Sabre/NodeTest.php b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php index 17550a2874f..11970769a1e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/NodeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/NodeTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -10,17 +11,20 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OC\Files\FileInfo; use OC\Files\Mount\MountPoint; +use OC\Files\Node\Folder; use OC\Files\View; use OC\Share20\ShareAttributes; +use OCA\DAV\Connector\Sabre\File; use OCA\Files_Sharing\SharedMount; use OCA\Files_Sharing\SharedStorage; use OCP\Constants; use OCP\Files\Cache\ICacheEntry; use OCP\Files\Mount\IMountPoint; -use OCP\Files\Storage; +use OCP\Files\Storage\IStorage; use OCP\ICache; use OCP\Share\IManager; use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; /** * Class NodeTest @@ -29,7 +33,7 @@ use OCP\Share\IShare; * @package OCA\DAV\Tests\unit\Connector\Sabre */ class NodeTest extends \Test\TestCase { - public function davPermissionsProvider() { + public static function davPermissionsProvider(): array { return [ [Constants::PERMISSION_ALL, 'file', false, Constants::PERMISSION_ALL, false, 'test', 'RGDNVW'], [Constants::PERMISSION_ALL, 'dir', false, Constants::PERMISSION_ALL, false, 'test', 'RGDNVCK'], @@ -47,10 +51,8 @@ class NodeTest extends \Test\TestCase { ]; } - /** - * @dataProvider davPermissionsProvider - */ - public function testDavPermissions($permissions, $type, $shared, $shareRootPermissions, $mounted, $internalPath, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('davPermissionsProvider')] + public function testDavPermissions(int $permissions, string $type, bool $shared, int $shareRootPermissions, bool $mounted, string $internalPath, string $expected): void { $info = $this->getMockBuilder(FileInfo::class) ->disableOriginalConstructor() ->onlyMethods(['getPermissions', 'isShared', 'isMounted', 'getType', 'getInternalPath', 'getStorage', 'getMountPoint']) @@ -73,7 +75,7 @@ class NodeTest extends \Test\TestCase { return $this->createMock(MountPoint::class); } }); - $storage = $this->createMock(Storage\IStorage::class); + $storage = $this->createMock(IStorage::class); if ($shared) { $storage->method('instanceOfStorage') ->willReturn(true); @@ -91,15 +93,13 @@ class NodeTest extends \Test\TestCase { } $info->method('getStorage') ->willReturn($storage); - $view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); + $view = $this->createMock(View::class); - $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $node = new File($view, $info); $this->assertEquals($expected, $node->getDavPermissions()); } - public function sharePermissionsProvider() { + public static function sharePermissionsProvider(): array { return [ [\OCP\Files\FileInfo::TYPE_FILE, null, 1, 1], [\OCP\Files\FileInfo::TYPE_FILE, null, 3, 3], @@ -139,21 +139,15 @@ class NodeTest extends \Test\TestCase { ]; } - /** - * @dataProvider sharePermissionsProvider - */ - public function testSharePermissions($type, $user, $permissions, $expected): void { - $storage = $this->getMockBuilder(Storage::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('sharePermissionsProvider')] + public function testSharePermissions(string $type, ?string $user, int $permissions, int $expected): void { + $storage = $this->createMock(IStorage::class); $storage->method('getPermissions')->willReturn($permissions); - $mountpoint = $this->getMockBuilder(IMountPoint::class) - ->disableOriginalConstructor() - ->getMock(); + $mountpoint = $this->createMock(IMountPoint::class); $mountpoint->method('getMountPoint')->willReturn('myPath'); - $shareManager = $this->getMockBuilder(IManager::class)->disableOriginalConstructor()->getMock(); - $share = $this->getMockBuilder(IShare::class)->disableOriginalConstructor()->getMock(); + $shareManager = $this->createMock(IManager::class); + $share = $this->createMock(IShare::class); if ($user === null) { $shareManager->expects($this->never())->method('getShareByToken'); @@ -166,7 +160,7 @@ class NodeTest extends \Test\TestCase { $info = $this->getMockBuilder(FileInfo::class) ->disableOriginalConstructor() - ->setMethods(['getStorage', 'getType', 'getMountPoint', 'getPermissions']) + ->onlyMethods(['getStorage', 'getType', 'getMountPoint', 'getPermissions']) ->getMock(); $info->method('getStorage')->willReturn($storage); @@ -174,11 +168,9 @@ class NodeTest extends \Test\TestCase { $info->method('getMountPoint')->willReturn($mountpoint); $info->method('getPermissions')->willReturn($permissions); - $view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); + $view = $this->createMock(View::class); - $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $node = new File($view, $info); $this->invokePrivate($node, 'shareManager', [$shareManager]); $this->assertEquals($expected, $node->getSharePermissions($user)); } @@ -186,11 +178,11 @@ class NodeTest extends \Test\TestCase { public function testShareAttributes(): void { $storage = $this->getMockBuilder(SharedStorage::class) ->disableOriginalConstructor() - ->setMethods(['getShare']) + ->onlyMethods(['getShare']) ->getMock(); - $shareManager = $this->getMockBuilder(IManager::class)->disableOriginalConstructor()->getMock(); - $share = $this->getMockBuilder(IShare::class)->disableOriginalConstructor()->getMock(); + $shareManager = $this->createMock(IManager::class); + $share = $this->createMock(IShare::class); $storage->expects($this->once()) ->method('getShare') @@ -201,58 +193,53 @@ class NodeTest extends \Test\TestCase { $share->expects($this->once())->method('getAttributes')->willReturn($attributes); - $info = $this->getMockBuilder(FileInfo::class) + /** @var Folder&MockObject $info */ + $info = $this->getMockBuilder(Folder::class) ->disableOriginalConstructor() - ->setMethods(['getStorage', 'getType']) + ->onlyMethods(['getStorage', 'getType']) ->getMock(); $info->method('getStorage')->willReturn($storage); $info->method('getType')->willReturn(FileInfo::TYPE_FOLDER); - $view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); + /** @var View&MockObject $view */ + $view = $this->createMock(View::class); - $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $node = new File($view, $info); $this->invokePrivate($node, 'shareManager', [$shareManager]); $this->assertEquals($attributes->toArray(), $node->getShareAttributes()); } public function testShareAttributesNonShare(): void { - $storage = $this->getMockBuilder(Storage::class) - ->disableOriginalConstructor() - ->getMock(); + $storage = $this->createMock(IStorage::class); + $shareManager = $this->createMock(IManager::class); - $shareManager = $this->getMockBuilder(IManager::class)->disableOriginalConstructor()->getMock(); - - $info = $this->getMockBuilder(FileInfo::class) + /** @var Folder&MockObject */ + $info = $this->getMockBuilder(Folder::class) ->disableOriginalConstructor() - ->setMethods(['getStorage', 'getType']) + ->onlyMethods(['getStorage', 'getType']) ->getMock(); $info->method('getStorage')->willReturn($storage); $info->method('getType')->willReturn(FileInfo::TYPE_FOLDER); - $view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); + /** @var View&MockObject */ + $view = $this->createMock(View::class); - $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $node = new File($view, $info); $this->invokePrivate($node, 'shareManager', [$shareManager]); $this->assertEquals([], $node->getShareAttributes()); } - public function sanitizeMtimeProvider() { + public static function sanitizeMtimeProvider(): array { return [ [123456789, 123456789], ['987654321', 987654321], ]; } - /** - * @dataProvider sanitizeMtimeProvider - */ - public function testSanitizeMtime($mtime, $expected): void { + #[\PHPUnit\Framework\Attributes\DataProvider('sanitizeMtimeProvider')] + public function testSanitizeMtime(string|int $mtime, int $expected): void { $view = $this->getMockBuilder(View::class) ->disableOriginalConstructor() ->getMock(); @@ -260,31 +247,25 @@ class NodeTest extends \Test\TestCase { ->disableOriginalConstructor() ->getMock(); - $node = new \OCA\DAV\Connector\Sabre\File($view, $info); + $node = new File($view, $info); $result = $this->invokePrivate($node, 'sanitizeMtime', [$mtime]); $this->assertEquals($expected, $result); } - public function invalidSanitizeMtimeProvider() { + public static function invalidSanitizeMtimeProvider(): array { return [ [-1337], [0], ['abcdef'], ['-1337'], ['0'], [12321], [24 * 60 * 60 - 1], ]; } - /** - * @dataProvider invalidSanitizeMtimeProvider - */ - public function testInvalidSanitizeMtime($mtime): void { + #[\PHPUnit\Framework\Attributes\DataProvider('invalidSanitizeMtimeProvider')] + public function testInvalidSanitizeMtime(int|string $mtime): void { $this->expectException(\InvalidArgumentException::class); - $view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); - $info = $this->getMockBuilder(FileInfo::class) - ->disableOriginalConstructor() - ->getMock(); + $view = $this->createMock(View::class); + $info = $this->createMock(FileInfo::class); - $node = new \OCA\DAV\Connector\Sabre\File($view, $info); - $result = $this->invokePrivate($node, 'sanitizeMtime', [$mtime]); + $node = new File($view, $info); + self::invokePrivate($node, 'sanitizeMtime', [$mtime]); } } diff --git a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php index ccdb501f7d3..b07778e4fbd 100644 --- a/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/ObjectTreeTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -10,9 +11,12 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OC\Files\FileInfo; use OC\Files\Filesystem; use OC\Files\Mount\Manager; +use OC\Files\Storage\Common; use OC\Files\Storage\Temporary; use OC\Files\View; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Connector\Sabre\Exception\InvalidPath; +use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\ObjectTree; use OCP\Files\Mount\IMountManager; @@ -24,7 +28,7 @@ use OCP\Files\Mount\IMountManager; * @package OCA\DAV\Tests\Unit\Connector\Sabre */ class ObjectTreeTest extends \Test\TestCase { - public function copyDataProvider() { + public static function copyDataProvider(): array { return [ // copy into same dir ['a', 'b', ''], @@ -35,10 +39,8 @@ class ObjectTreeTest extends \Test\TestCase { ]; } - /** - * @dataProvider copyDataProvider - */ - public function testCopy($sourcePath, $targetPath, $targetParent): void { + #[\PHPUnit\Framework\Attributes\DataProvider('copyDataProvider')] + public function testCopy(string $sourcePath, string $targetPath, string $targetParent): void { $view = $this->createMock(View::class); $view->expects($this->once()) ->method('verifyPath') @@ -64,7 +66,7 @@ class ObjectTreeTest extends \Test\TestCase { $rootDir = new Directory($view, $info); $objectTree = $this->getMockBuilder(ObjectTree::class) - ->setMethods(['nodeExists', 'getNodeForPath']) + ->onlyMethods(['nodeExists', 'getNodeForPath']) ->setConstructorArgs([$rootDir, $view]) ->getMock(); @@ -73,15 +75,13 @@ class ObjectTreeTest extends \Test\TestCase { ->with($this->identicalTo($sourcePath)) ->willReturn(false); - /** @var $objectTree \OCA\DAV\Connector\Sabre\ObjectTree */ + /** @var ObjectTree $objectTree */ $mountManager = Filesystem::getMountManager(); $objectTree->init($rootDir, $view, $mountManager); $objectTree->copy($sourcePath, $targetPath); } - /** - * @dataProvider copyDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('copyDataProvider')] public function testCopyFailNotCreatable($sourcePath, $targetPath, $targetParent): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); @@ -107,57 +107,42 @@ class ObjectTreeTest extends \Test\TestCase { $rootDir = new Directory($view, $info); $objectTree = $this->getMockBuilder(ObjectTree::class) - ->setMethods(['nodeExists', 'getNodeForPath']) + ->onlyMethods(['nodeExists', 'getNodeForPath']) ->setConstructorArgs([$rootDir, $view]) ->getMock(); $objectTree->expects($this->never()) ->method('getNodeForPath'); - /** @var $objectTree \OCA\DAV\Connector\Sabre\ObjectTree */ + /** @var ObjectTree $objectTree */ $mountManager = Filesystem::getMountManager(); $objectTree->init($rootDir, $view, $mountManager); $objectTree->copy($sourcePath, $targetPath); } - /** - * @dataProvider nodeForPathProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('nodeForPathProvider')] public function testGetNodeForPath( - $inputFileName, - $fileInfoQueryPath, - $outputFileName, - $type, - $enableChunkingHeader + string $inputFileName, + string $fileInfoQueryPath, + string $outputFileName, + string $type, ): void { - if ($enableChunkingHeader) { - $_SERVER['HTTP_OC_CHUNKED'] = true; - } - - $rootNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); - $mountManager = $this->getMockBuilder(Manager::class) - ->disableOriginalConstructor() - ->getMock(); - $view = $this->getMockBuilder(View::class) - ->disableOriginalConstructor() - ->getMock(); - $fileInfo = $this->getMockBuilder(FileInfo::class) - ->disableOriginalConstructor() - ->getMock(); + $rootNode = $this->createMock(Directory::class); + $mountManager = $this->createMock(Manager::class); + $view = $this->createMock(View::class); + $fileInfo = $this->createMock(FileInfo::class); $fileInfo->method('getType') ->willReturn($type); $fileInfo->method('getName') ->willReturn($outputFileName); $fileInfo->method('getStorage') - ->willReturn($this->createMock(\OC\Files\Storage\Common::class)); + ->willReturn($this->createMock(Common::class)); $view->method('getFileInfo') ->with($fileInfoQueryPath) ->willReturn($fileInfo); - $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree = new ObjectTree(); $tree->init($rootNode, $view, $mountManager); $node = $tree->getNodeForPath($inputFileName); @@ -166,15 +151,13 @@ class ObjectTreeTest extends \Test\TestCase { $this->assertEquals($outputFileName, $node->getName()); if ($type === 'file') { - $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\File); + $this->assertInstanceOf(File::class, $node); } else { - $this->assertTrue($node instanceof \OCA\DAV\Connector\Sabre\Directory); + $this->assertInstanceOf(Directory::class, $node); } - - unset($_SERVER['HTTP_OC_CHUNKED']); } - public function nodeForPathProvider() { + public static function nodeForPathProvider(): array { return [ // regular file [ @@ -182,7 +165,6 @@ class ObjectTreeTest extends \Test\TestCase { 'regularfile.txt', 'regularfile.txt', 'file', - false ], // regular directory [ @@ -190,31 +172,6 @@ class ObjectTreeTest extends \Test\TestCase { 'regulardir', 'regulardir', 'dir', - false - ], - // regular file with chunking - [ - 'regularfile.txt', - 'regularfile.txt', - 'regularfile.txt', - 'file', - true - ], - // regular directory with chunking - [ - 'regulardir', - 'regulardir', - 'regulardir', - 'dir', - true - ], - // file with chunky file name - [ - 'regularfile.txt-chunking-123566789-10-1', - 'regularfile.txt', - 'regularfile.txt', - 'file', - true ], // regular file in subdir [ @@ -222,7 +179,6 @@ class ObjectTreeTest extends \Test\TestCase { 'subdir/regularfile.txt', 'regularfile.txt', 'file', - false ], // regular directory in subdir [ @@ -230,22 +186,13 @@ class ObjectTreeTest extends \Test\TestCase { 'subdir/regulardir', 'regulardir', 'dir', - false - ], - // file with chunky file name in subdir - [ - 'subdir/regularfile.txt-chunking-123566789-10-1', - 'subdir/regularfile.txt', - 'regularfile.txt', - 'file', - true ], ]; } public function testGetNodeForPathInvalidPath(): void { - $this->expectException(\OCA\DAV\Connector\Sabre\Exception\InvalidPath::class); + $this->expectException(InvalidPath::class); $path = '/foo\bar'; @@ -253,7 +200,7 @@ class ObjectTreeTest extends \Test\TestCase { $storage = new Temporary([]); $view = $this->getMockBuilder(View::class) - ->setMethods(['resolvePath']) + ->onlyMethods(['resolvePath']) ->getMock(); $view->expects($this->once()) ->method('resolvePath') @@ -261,12 +208,10 @@ class ObjectTreeTest extends \Test\TestCase { return [$storage, ltrim($path, '/')]; }); - $rootNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + $rootNode = $this->createMock(Directory::class); $mountManager = $this->createMock(IMountManager::class); - $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree = new ObjectTree(); $tree->init($rootNode, $view, $mountManager); $tree->getNodeForPath($path); @@ -279,7 +224,7 @@ class ObjectTreeTest extends \Test\TestCase { $storage = new Temporary([]); $view = $this->getMockBuilder(View::class) - ->setMethods(['resolvePath']) + ->onlyMethods(['resolvePath']) ->getMock(); $view->expects($this->any()) ->method('resolvePath') @@ -287,12 +232,10 @@ class ObjectTreeTest extends \Test\TestCase { return [$storage, ltrim($path, '/')]; }); - $rootNode = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + $rootNode = $this->createMock(Directory::class); $mountManager = $this->createMock(IMountManager::class); - $tree = new \OCA\DAV\Connector\Sabre\ObjectTree(); + $tree = new ObjectTree(); $tree->init($rootNode, $view, $mountManager); $this->assertInstanceOf('\Sabre\DAV\INode', $tree->getNodeForPath($path)); diff --git a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php index 03910f32095..e32d2671063 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PrincipalTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -30,38 +32,21 @@ use Sabre\DAV\PropPatch; use Test\TestCase; class PrincipalTest extends TestCase { - /** @var IUserManager | MockObject */ - private $userManager; - - /** @var Principal */ - private $connector; - - /** @var IGroupManager | MockObject */ - private $groupManager; - - /** @var IAccountManager|MockObject */ - private $accountManager; - - /** @var IManager | MockObject */ - private $shareManager; - - /** @var IUserSession | MockObject */ - private $userSession; - - /** @var IAppManager | MockObject */ - private $appManager; - - /** @var ProxyMapper | MockObject */ - private $proxyMapper; - - /** @var KnownUserService|MockObject */ - private $knownUserService; - /** @var IConfig | MockObject */ - private $config; - /** @var IFactory|MockObject */ - private $languageFactory; + private IUserManager&MockObject $userManager; + private IGroupManager&MockObject $groupManager; + private IAccountManager&MockObject $accountManager; + private IManager&MockObject $shareManager; + private IUserSession&MockObject $userSession; + private IAppManager&MockObject $appManager; + private ProxyMapper&MockObject $proxyMapper; + private KnownUserService&MockObject $knownUserService; + private IConfig&MockObject $config; + private IFactory&MockObject $languageFactory; + private Principal $connector; protected function setUp(): void { + parent::setUp(); + $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); $this->accountManager = $this->createMock(IAccountManager::class); @@ -85,7 +70,6 @@ class PrincipalTest extends TestCase { $this->config, $this->languageFactory ); - parent::setUp(); } public function testGetPrincipalsByPrefixWithoutPrefix(): void { @@ -96,26 +80,26 @@ class PrincipalTest extends TestCase { public function testGetPrincipalsByPrefixWithUsers(): void { $fooUser = $this->createMock(User::class); $fooUser - ->expects($this->once()) - ->method('getUID') - ->willReturn('foo'); + ->expects($this->once()) + ->method('getUID') + ->willReturn('foo'); $fooUser - ->expects($this->once()) - ->method('getDisplayName') - ->willReturn('Dr. Foo-Bar'); + ->expects($this->once()) + ->method('getDisplayName') + ->willReturn('Dr. Foo-Bar'); $fooUser - ->expects($this->once()) - ->method('getSystemEMailAddress') - ->willReturn(''); + ->expects($this->once()) + ->method('getSystemEMailAddress') + ->willReturn(''); $barUser = $this->createMock(User::class); $barUser ->expects($this->once()) ->method('getUID') ->willReturn('bar'); $barUser - ->expects($this->once()) - ->method('getSystemEMailAddress') - ->willReturn('bar@nextcloud.com'); + ->expects($this->once()) + ->method('getSystemEMailAddress') + ->willReturn('bar@nextcloud.com'); $this->userManager ->expects($this->once()) ->method('search') @@ -125,13 +109,14 @@ class PrincipalTest extends TestCase { $this->languageFactory ->expects($this->exactly(2)) ->method('getUserLanguage') - ->withConsecutive([$fooUser], [$barUser]) - ->willReturnOnConsecutiveCalls('de', 'en'); + ->willReturnMap([ + [$fooUser, 'de'], + [$barUser, 'en'], + ]); $fooAccountPropertyCollection = $this->createMock(IAccountPropertyCollection::class); $fooAccountPropertyCollection->expects($this->once()) ->method('getProperties') - ->with() ->willReturn([]); $fooAccount = $this->createMock(IAccount::class); $fooAccount->expects($this->once()) @@ -142,18 +127,15 @@ class PrincipalTest extends TestCase { $emailPropertyOne = $this->createMock(IAccountProperty::class); $emailPropertyOne->expects($this->once()) ->method('getValue') - ->with() ->willReturn('alias@nextcloud.com'); $emailPropertyTwo = $this->createMock(IAccountProperty::class); $emailPropertyTwo->expects($this->once()) ->method('getValue') - ->with() ->willReturn('alias2@nextcloud.com'); $barAccountPropertyCollection = $this->createMock(IAccountPropertyCollection::class); $barAccountPropertyCollection->expects($this->once()) ->method('getProperties') - ->with() ->willReturn([$emailPropertyOne, $emailPropertyTwo]); $barAccount = $this->createMock(IAccount::class); $barAccount->expects($this->once()) @@ -164,8 +146,10 @@ class PrincipalTest extends TestCase { $this->accountManager ->expects($this->exactly(2)) ->method('getAccount') - ->withConsecutive([$fooUser], [$barUser]) - ->willReturnOnConsecutiveCalls($fooAccount, $barAccount); + ->willReturnMap([ + [$fooUser, $fooAccount], + [$barUser, $barAccount], + ]); $expectedResponse = [ 0 => [ @@ -229,13 +213,13 @@ class PrincipalTest extends TestCase { public function testGetPrincipalsByPathWithMail(): void { $fooUser = $this->createMock(User::class); $fooUser - ->expects($this->once()) - ->method('getSystemEMailAddress') - ->willReturn('foo@nextcloud.com'); + ->expects($this->once()) + ->method('getSystemEMailAddress') + ->willReturn('foo@nextcloud.com'); $fooUser - ->expects($this->once()) - ->method('getUID') - ->willReturn('foo'); + ->expects($this->once()) + ->method('getUID') + ->willReturn('foo'); $this->userManager ->expects($this->once()) ->method('get') @@ -479,10 +463,8 @@ class PrincipalTest extends TestCase { ['{http://sabredav.org/ns}email-address' => 'foo'])); } - /** - * @dataProvider searchPrincipalsDataProvider - */ - public function testSearchPrincipals($sharingEnabled, $groupsOnly, $test, $result): void { + #[\PHPUnit\Framework\Attributes\DataProvider('searchPrincipalsDataProvider')] + public function testSearchPrincipals(bool $sharingEnabled, bool $groupsOnly, string $test, array $result): void { $this->shareManager->expects($this->once()) ->method('shareAPIEnabled') ->willReturn($sharingEnabled); @@ -556,7 +538,7 @@ class PrincipalTest extends TestCase { '{DAV:}displayname' => 'User 12'], $test)); } - public function searchPrincipalsDataProvider(): array { + public static function searchPrincipalsDataProvider(): array { return [ [true, false, 'allof', ['principals/users/user3']], [true, false, 'anyof', ['principals/users/user2', 'principals/users/user3', 'principals/users/user4']], @@ -842,22 +824,20 @@ class PrincipalTest extends TestCase { $this->assertEquals(null, $this->connector->findByUri('mailto:user@foo.com', 'principals/users')); } - /** - * @dataProvider findByUriWithGroupRestrictionDataProvider - */ - public function testFindByUriWithGroupRestriction($uri, $email, $expects): void { + #[\PHPUnit\Framework\Attributes\DataProvider('findByUriWithGroupRestrictionDataProvider')] + public function testFindByUriWithGroupRestriction(string $uri, string $email, ?string $expects): void { $this->shareManager->expects($this->once()) ->method('shareApiEnabled') ->willReturn(true); $this->shareManager->expects($this->once()) - ->method('shareWithGroupMembersOnly') - ->willReturn(true); + ->method('shareWithGroupMembersOnly') + ->willReturn(true); $user = $this->createMock(IUser::class); $this->userSession->expects($this->once()) - ->method('getUser') - ->willReturn($user); + ->method('getUser') + ->willReturn($user); $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); @@ -865,55 +845,45 @@ class PrincipalTest extends TestCase { $user3->method('getUID')->willReturn('user3'); $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->willReturn([$email === 'user2@foo.bar' ? $user2 : $user3]); + ->method('getByEmail') + ->with($email) + ->willReturn([$email === 'user2@foo.bar' ? $user2 : $user3]); if ($email === 'user2@foo.bar') { $this->groupManager->expects($this->exactly(2)) ->method('getUserGroupIds') - ->withConsecutive( - [$user], - [$user2], - ) - ->willReturnOnConsecutiveCalls( - ['group1', 'group2'], - ['group1', 'group3'], - ); + ->willReturnMap([ + [$user, ['group1', 'group2']], + [$user2, ['group1', 'group3']], + ]); } else { $this->groupManager->expects($this->exactly(2)) ->method('getUserGroupIds') - ->withConsecutive( - [$user], - [$user3], - ) - ->willReturnOnConsecutiveCalls( - ['group1', 'group2'], - ['group3', 'group3'], - ); + ->willReturnMap([ + [$user, ['group1', 'group2']], + [$user3, ['group3', 'group3']], + ]); } $this->assertEquals($expects, $this->connector->findByUri($uri, 'principals/users')); } - public function findByUriWithGroupRestrictionDataProvider(): array { + public static function findByUriWithGroupRestrictionDataProvider(): array { return [ ['mailto:user2@foo.bar', 'user2@foo.bar', 'principals/users/user2'], ['mailto:user3@foo.bar', 'user3@foo.bar', null], ]; } - /** - * @dataProvider findByUriWithoutGroupRestrictionDataProvider - */ - public function testFindByUriWithoutGroupRestriction($uri, $email, $expects): void { + #[\PHPUnit\Framework\Attributes\DataProvider('findByUriWithoutGroupRestrictionDataProvider')] + public function testFindByUriWithoutGroupRestriction(string $uri, string $email, string $expects): void { $this->shareManager->expects($this->once()) ->method('shareApiEnabled') ->willReturn(true); $this->shareManager->expects($this->once()) - ->method('shareWithGroupMembersOnly') - ->willReturn(false); + ->method('shareWithGroupMembersOnly') + ->willReturn(false); $user2 = $this->createMock(IUser::class); $user2->method('getUID')->willReturn('user2'); @@ -921,14 +891,14 @@ class PrincipalTest extends TestCase { $user3->method('getUID')->willReturn('user3'); $this->userManager->expects($this->once()) - ->method('getByEmail') - ->with($email) - ->willReturn([$email === 'user2@foo.bar' ? $user2 : $user3]); + ->method('getByEmail') + ->with($email) + ->willReturn([$email === 'user2@foo.bar' ? $user2 : $user3]); $this->assertEquals($expects, $this->connector->findByUri($uri, 'principals/users')); } - public function findByUriWithoutGroupRestrictionDataProvider(): array { + public static function findByUriWithoutGroupRestrictionDataProvider(): array { return [ ['mailto:user2@foo.bar', 'user2@foo.bar', 'principals/users/user2'], ['mailto:user3@foo.bar', 'user3@foo.bar', 'principals/users/user3'], diff --git a/apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php new file mode 100644 index 00000000000..9d22befa201 --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/PropFindMonitorPluginTest.php @@ -0,0 +1,133 @@ +<?php + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\PropFindMonitorPlugin; +use OCA\DAV\Connector\Sabre\Server; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Sabre\HTTP\Request; +use Sabre\HTTP\Response; +use Test\TestCase; + +class PropFindMonitorPluginTest extends TestCase { + + private PropFindMonitorPlugin $plugin; + private Server&MockObject $server; + private LoggerInterface&MockObject $logger; + private Request&MockObject $request; + private Response&MockObject $response; + + public static function dataTest(): array { + $minQueriesTrigger = PropFindMonitorPlugin::THRESHOLD_QUERY_FACTOR + * PropFindMonitorPlugin::THRESHOLD_NODES; + return [ + 'No queries logged' => [[], 0], + 'Plugins with queries in less than threshold nodes should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => 100, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES - 1] + ], + [], + ] + ], + 0 + ], + 'Plugins with query-to-node ratio less than threshold should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger - 1, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES ], + ], + [], + ] + ], + 0 + ], + 'Plugins with more nodes scanned than queries executed should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES * 2], + ], + [],] + ], + 0 + ], + 'Plugins with queries only in highest depth level should not be logged' => [ + [ + 'propFind' => [ + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES - 1 + ] + ], + [ + 'PluginName' => [ + 'queries' => $minQueriesTrigger * 2, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES + ] + ], + ] + ], + 0 + ], + 'Plugins with too many queries should be logged' => [ + [ + 'propFind' => [ + [ + 'FirstPlugin' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES, + ], + 'SecondPlugin' => [ + 'queries' => $minQueriesTrigger, + 'nodes' => PropFindMonitorPlugin::THRESHOLD_NODES, + ] + ], + [], + ] + ], + 2 + ] + ]; + } + + /** + * @dataProvider dataTest + */ + public function test(array $queries, $expectedLogCalls): void { + $this->plugin->initialize($this->server); + $this->server->expects($this->once())->method('getPluginQueries') + ->willReturn($queries); + + $this->server->expects(empty($queries) ? $this->never() : $this->once()) + ->method('getLogger') + ->willReturn($this->logger); + + $this->logger->expects($this->exactly($expectedLogCalls))->method('error'); + $this->plugin->afterResponse($this->request, $this->response); + } + + protected function setUp(): void { + parent::setUp(); + + $this->plugin = new PropFindMonitorPlugin(); + $this->server = $this->createMock(Server::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->request = $this->createMock(Request::class); + $this->response = $this->createMock(Response::class); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php new file mode 100644 index 00000000000..52fe3eba5bf --- /dev/null +++ b/apps/dav/tests/unit/Connector/Sabre/PropFindPreloadNotifyPluginTest.php @@ -0,0 +1,92 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\Connector\Sabre; + +use OCA\DAV\Connector\Sabre\PropFindPreloadNotifyPlugin; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\ICollection; +use Sabre\DAV\IFile; +use Sabre\DAV\PropFind; +use Sabre\DAV\Server; +use Test\TestCase; + +class PropFindPreloadNotifyPluginTest extends TestCase { + + private Server&MockObject $server; + private PropFindPreloadNotifyPlugin $plugin; + + protected function setUp(): void { + parent::setUp(); + + $this->server = $this->createMock(Server::class); + $this->plugin = new PropFindPreloadNotifyPlugin(); + } + + public function testInitialize(): void { + $this->server + ->expects(self::once()) + ->method('on') + ->with('propFind', + $this->anything(), 1); + $this->plugin->initialize($this->server); + } + + public static function dataTestCollectionPreloadNotifier(): array { + return [ + 'When node is not a collection, should not emit' => [ + IFile::class, + 1, + false, + true + ], + 'When node is a collection but depth is zero, should not emit' => [ + ICollection::class, + 0, + false, + true + ], + 'When node is a collection, and depth > 0, should emit' => [ + ICollection::class, + 1, + true, + true + ], + 'When node is a collection, and depth is infinite, should emit' + => [ + ICollection::class, + Server::DEPTH_INFINITY, + true, + true + ], + 'When called called handler returns false, it should be returned' + => [ + ICollection::class, + 1, + true, + false + ] + ]; + } + + #[DataProvider(methodName: 'dataTestCollectionPreloadNotifier')] + public function testCollectionPreloadNotifier(string $nodeType, int $depth, bool $shouldEmit, bool $emitReturns): + void { + $this->plugin->initialize($this->server); + $propFind = $this->createMock(PropFind::class); + $propFind->expects(self::any())->method('getDepth')->willReturn($depth); + $node = $this->createMock($nodeType); + + $expectation = $shouldEmit ? self::once() : self::never(); + $this->server->expects($expectation)->method('emit')->with('preloadCollection', + [$propFind, $node])->willReturn($emitReturns); + $return = $this->plugin->collectionPreloadNotifier($propFind, $node); + $this->assertEquals($emitReturns, $return); + } +} diff --git a/apps/dav/tests/unit/Connector/Sabre/PropfindCompressionPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/PropfindCompressionPluginTest.php index 42414ad6efb..e6f696ed160 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PropfindCompressionPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PropfindCompressionPluginTest.php @@ -14,8 +14,7 @@ use Sabre\HTTP\Response; use Test\TestCase; class PropfindCompressionPluginTest extends TestCase { - /** @var PropfindCompressionPlugin */ - private $plugin; + private PropfindCompressionPlugin $plugin; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php b/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php index b6c91cd7ae5..fef62b51c67 100644 --- a/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/PublicAuthTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,12 +8,15 @@ */ namespace OCA\DAV\Tests\unit\Connector; +use OCA\DAV\Connector\Sabre\PublicAuth; use OCP\IRequest; use OCP\ISession; +use OCP\IURLGenerator; use OCP\Security\Bruteforce\IThrottler; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; /** @@ -24,21 +28,15 @@ use Psr\Log\LoggerInterface; */ class PublicAuthTest extends \Test\TestCase { - /** @var ISession|MockObject */ - private $session; - /** @var IRequest|MockObject */ - private $request; - /** @var IManager|MockObject */ - private $shareManager; - /** @var PublicAuth */ - private $auth; - /** @var IThrottler|MockObject */ - private $throttler; - /** @var LoggerInterface|MockObject */ - private $logger; - - /** @var string */ - private $oldUser; + private ISession&MockObject $session; + private IRequest&MockObject $request; + private IManager&MockObject $shareManager; + private IThrottler&MockObject $throttler; + private LoggerInterface&MockObject $logger; + private IURLGenerator&MockObject $urlGenerator; + private PublicAuth $auth; + + private bool|string $oldUser; protected function setUp(): void { parent::setUp(); @@ -48,13 +46,15 @@ class PublicAuthTest extends \Test\TestCase { $this->shareManager = $this->createMock(IManager::class); $this->throttler = $this->createMock(IThrottler::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); - $this->auth = new \OCA\DAV\Connector\Sabre\PublicAuth( + $this->auth = new PublicAuth( $this->request, $this->shareManager, $this->session, $this->throttler, $this->logger, + $this->urlGenerator, ); // Store current user @@ -66,7 +66,9 @@ class PublicAuthTest extends \Test\TestCase { // Set old user \OC_User::setUserId($this->oldUser); - \OC_Util::setupFS($this->oldUser); + if ($this->oldUser !== false) { + \OC_Util::setupFS($this->oldUser); + } parent::tearDown(); } @@ -75,7 +77,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $result = $this->invokePrivate($this->auth, 'getToken'); + $result = self::invokePrivate($this->auth, 'getToken'); $this->assertSame('GX9HSGQrGE', $result); } @@ -85,16 +87,14 @@ class PublicAuthTest extends \Test\TestCase { ->willReturn('/dav/files'); $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $this->invokePrivate($this->auth, 'getToken'); + self::invokePrivate($this->auth, 'getToken'); } public function testCheckTokenValidShare(): void { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn(null); $this->shareManager->expects($this->once()) @@ -102,7 +102,7 @@ class PublicAuthTest extends \Test\TestCase { ->with('GX9HSGQrGE') ->willReturn($share); - $result = $this->invokePrivate($this->auth, 'checkToken'); + $result = self::invokePrivate($this->auth, 'checkToken'); $this->assertSame([true, 'principals/GX9HSGQrGE'], $result); } @@ -114,19 +114,17 @@ class PublicAuthTest extends \Test\TestCase { ->expects($this->once()) ->method('getShareByToken') ->with('GX9HSGQrGE') - ->will($this->throwException(new ShareNotFound())); + ->willThrowException(new ShareNotFound()); $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $this->invokePrivate($this->auth, 'checkToken'); + self::invokePrivate($this->auth, 'checkToken'); } public function testCheckTokenAlreadyAuthenticated(): void { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getShareType')->willReturn(42); $this->shareManager->expects($this->once()) @@ -136,8 +134,8 @@ class PublicAuthTest extends \Test\TestCase { $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); $this->session->method('get')->with('public_link_authenticated')->willReturn('42'); - - $result = $this->invokePrivate($this->auth, 'checkToken'); + + $result = self::invokePrivate($this->auth, 'checkToken'); $this->assertSame([true, 'principals/GX9HSGQrGE'], $result); } @@ -145,9 +143,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(42); @@ -157,18 +153,16 @@ class PublicAuthTest extends \Test\TestCase { ->willReturn($share); $this->session->method('exists')->with('public_link_authenticated')->willReturn(false); - + $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class); - $this->invokePrivate($this->auth, 'checkToken'); + self::invokePrivate($this->auth, 'checkToken'); } public function testCheckTokenPasswordAuthenticatedWrongShare(): void { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(42); @@ -179,9 +173,9 @@ class PublicAuthTest extends \Test\TestCase { $this->session->method('exists')->with('public_link_authenticated')->willReturn(false); $this->session->method('get')->with('public_link_authenticated')->willReturn('43'); - + $this->expectException(\Sabre\DAV\Exception\NotAuthenticated::class); - $this->invokePrivate($this->auth, 'checkToken'); + self::invokePrivate($this->auth, 'checkToken'); } public function testNoShare(): void { @@ -193,7 +187,7 @@ class PublicAuthTest extends \Test\TestCase { ->with('GX9HSGQrGE') ->willThrowException(new ShareNotFound()); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertFalse($result); } @@ -202,9 +196,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn(null); $this->shareManager->expects($this->once()) @@ -212,7 +204,7 @@ class PublicAuthTest extends \Test\TestCase { ->with('GX9HSGQrGE') ->willReturn($share); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertTrue($result); } @@ -221,9 +213,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(42); @@ -232,7 +222,7 @@ class PublicAuthTest extends \Test\TestCase { ->with('GX9HSGQrGE') ->willReturn($share); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertFalse($result); } @@ -242,9 +232,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_REMOTE); @@ -253,7 +241,7 @@ class PublicAuthTest extends \Test\TestCase { ->with('GX9HSGQrGE') ->willReturn($share); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertTrue($result); } @@ -262,9 +250,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_LINK); @@ -279,7 +265,7 @@ class PublicAuthTest extends \Test\TestCase { $this->equalTo('password') )->willReturn(true); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertTrue($result); } @@ -288,9 +274,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_EMAIL); @@ -305,7 +289,7 @@ class PublicAuthTest extends \Test\TestCase { $this->equalTo('password') )->willReturn(true); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertTrue($result); } @@ -314,9 +298,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_LINK); $share->method('getId')->willReturn('42'); @@ -336,7 +318,7 @@ class PublicAuthTest extends \Test\TestCase { $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); $this->session->method('get')->with('public_link_authenticated')->willReturn('42'); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertTrue($result); } @@ -345,9 +327,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_LINK); $share->method('getId')->willReturn('42'); @@ -367,7 +347,7 @@ class PublicAuthTest extends \Test\TestCase { $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); $this->session->method('get')->with('public_link_authenticated')->willReturn('43'); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertFalse($result); } @@ -377,9 +357,7 @@ class PublicAuthTest extends \Test\TestCase { $this->request->method('getPathInfo') ->willReturn('/dav/files/GX9HSGQrGE'); - $share = $this->getMockBuilder(IShare::class) - ->disableOriginalConstructor() - ->getMock(); + $share = $this->createMock(IShare::class); $share->method('getPassword')->willReturn('password'); $share->method('getShareType')->willReturn(IShare::TYPE_EMAIL); $share->method('getId')->willReturn('42'); @@ -399,7 +377,7 @@ class PublicAuthTest extends \Test\TestCase { $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); $this->session->method('get')->with('public_link_authenticated')->willReturn('43'); - $result = $this->invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); + $result = self::invokePrivate($this->auth, 'validateUserPass', ['username', 'password']); $this->assertFalse($result); } diff --git a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php index ed4a8b0404a..6fe2d6ccabe 100644 --- a/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/QuotaPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2013-2016 ownCloud, Inc. @@ -13,75 +14,55 @@ use OCP\Files\FileInfo; use Test\TestCase; class QuotaPluginTest extends TestCase { - /** @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject */ - private $server; + private \Sabre\DAV\Server $server; - /** @var \OCA\DAV\Connector\Sabre\QuotaPlugin | \PHPUnit\Framework\MockObject\MockObject */ - private $plugin; + private QuotaPlugin $plugin; - private function init($quota, $checkedPath = ''): void { - $view = $this->buildFileViewMock($quota, $checkedPath); + private function init(int $quota, string $checkedPath = ''): void { + $view = $this->buildFileViewMock((string)$quota, $checkedPath); $this->server = new \Sabre\DAV\Server(); - $this->plugin = $this->getMockBuilder(QuotaPlugin::class) - ->setConstructorArgs([$view]) - ->setMethods(['getFileChunking']) - ->getMock(); + $this->plugin = new QuotaPlugin($view); $this->plugin->initialize($this->server); } - /** - * @dataProvider lengthProvider - */ - public function testLength($expected, $headers): void { + #[\PHPUnit\Framework\Attributes\DataProvider('lengthProvider')] + public function testLength(?int $expected, array $headers): void { $this->init(0); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); + $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $length = $this->plugin->getLength(); $this->assertEquals($expected, $length); } - /** - * @dataProvider quotaOkayProvider - */ - public function testCheckQuota($quota, $headers): void { + #[\PHPUnit\Framework\Attributes\DataProvider('quotaOkayProvider')] + public function testCheckQuota(int $quota, array $headers): void { $this->init($quota); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $result = $this->plugin->checkQuota(''); $this->assertTrue($result); } - /** - * @dataProvider quotaExceededProvider - */ - public function testCheckExceededQuota($quota, $headers): void { + #[\PHPUnit\Framework\Attributes\DataProvider('quotaExceededProvider')] + public function testCheckExceededQuota(int $quota, array $headers): void { $this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class); $this->init($quota); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $this->plugin->checkQuota(''); } - /** - * @dataProvider quotaOkayProvider - */ - public function testCheckQuotaOnPath($quota, $headers): void { + #[\PHPUnit\Framework\Attributes\DataProvider('quotaOkayProvider')] + public function testCheckQuotaOnPath(int $quota, array $headers): void { $this->init($quota, 'sub/test.txt'); - $this->plugin->expects($this->never()) - ->method('getFileChunking'); $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); $result = $this->plugin->checkQuota('/sub/test.txt'); $this->assertTrue($result); } - public function quotaOkayProvider() { + public static function quotaOkayProvider(): array { return [ [1024, []], [1024, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], @@ -100,7 +81,7 @@ class QuotaPluginTest extends TestCase { ]; } - public function quotaExceededProvider() { + public static function quotaExceededProvider(): array { return [ [1023, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], [511, ['CONTENT-LENGTH' => '512']], @@ -108,7 +89,7 @@ class QuotaPluginTest extends TestCase { ]; } - public function lengthProvider() { + public static function lengthProvider(): array { return [ [null, []], [1024, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], @@ -124,7 +105,7 @@ class QuotaPluginTest extends TestCase { ]; } - public function quotaChunkedOkProvider() { + public static function quotaChunkedOkProvider(): array { return [ [1024, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], [1024, 0, ['CONTENT-LENGTH' => '512']], @@ -143,30 +124,7 @@ class QuotaPluginTest extends TestCase { ]; } - /** - * @dataProvider quotaChunkedOkProvider - */ - public function testCheckQuotaChunkedOk($quota, $chunkTotalSize, $headers): void { - $this->init($quota, 'sub/test.txt'); - - $mockChunking = $this->getMockBuilder(\OC_FileChunking::class) - ->disableOriginalConstructor() - ->getMock(); - $mockChunking->expects($this->once()) - ->method('getCurrentSize') - ->willReturn($chunkTotalSize); - - $this->plugin->expects($this->once()) - ->method('getFileChunking') - ->willReturn($mockChunking); - - $headers['OC-CHUNKED'] = 1; - $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); - $result = $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); - $this->assertTrue($result); - } - - public function quotaChunkedFailProvider() { + public static function quotaChunkedFailProvider(): array { return [ [400, 0, ['X-EXPECTED-ENTITY-LENGTH' => '1024']], [400, 0, ['CONTENT-LENGTH' => '512']], @@ -178,39 +136,15 @@ class QuotaPluginTest extends TestCase { ]; } - /** - * @dataProvider quotaChunkedFailProvider - */ - public function testCheckQuotaChunkedFail($quota, $chunkTotalSize, $headers): void { - $this->expectException(\Sabre\DAV\Exception\InsufficientStorage::class); - - $this->init($quota, 'sub/test.txt'); - - $mockChunking = $this->getMockBuilder(\OC_FileChunking::class) - ->disableOriginalConstructor() - ->getMock(); - $mockChunking->expects($this->once()) - ->method('getCurrentSize') - ->willReturn($chunkTotalSize); - - $this->plugin->expects($this->once()) - ->method('getFileChunking') - ->willReturn($mockChunking); - - $headers['OC-CHUNKED'] = 1; - $this->server->httpRequest = new \Sabre\HTTP\Request('POST', 'dummy.file', $headers); - $this->plugin->checkQuota('/sub/test.txt-chunking-12345-3-1'); - } - - private function buildFileViewMock($quota, $checkedPath) { - // mock filesysten + private function buildFileViewMock(string $quota, string $checkedPath): View { + // mock filesystem $view = $this->getMockBuilder(View::class) - ->setMethods(['free_space']) + ->onlyMethods(['free_space']) ->disableOriginalConstructor() ->getMock(); $view->expects($this->any()) ->method('free_space') - ->with($this->identicalTo($checkedPath)) + ->with($checkedPath) ->willReturn($quota); return $view; diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php index bed5eb192d9..b01807d5bbb 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Auth.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,30 +8,23 @@ */ namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; +use OCP\IUserSession; +use OCP\Server; use Sabre\DAV\Auth\Backend\BackendInterface; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class Auth implements BackendInterface { /** - * @var string - */ - private $user; - - /** - * @var string - */ - private $password; - - /** * Auth constructor. * * @param string $user * @param string $password */ - public function __construct($user, $password) { - $this->user = $user; - $this->password = $password; + public function __construct( + private $user, + private $password, + ) { } /** @@ -62,7 +56,7 @@ class Auth implements BackendInterface { * @return array */ public function check(RequestInterface $request, ResponseInterface $response) { - $userSession = \OC::$server->getUserSession(); + $userSession = Server::get(IUserSession::class); $result = $userSession->login($this->user, $this->password); if ($result) { //we need to pass the user name, which may differ from login name @@ -72,7 +66,7 @@ class Auth implements BackendInterface { \OC::$server->getUserFolder($user); return [true, "principals/$user"]; } - return [false, "login failed"]; + return [false, 'login failed']; } /** diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/DeleteTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/DeleteTest.php index e7f20fbadfa..7d3488e6b5a 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/DeleteTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/DeleteTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,7 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; use OCP\AppFramework\Http; +use OCP\Files\FileInfo; /** * Class DeleteTest @@ -18,7 +20,7 @@ use OCP\AppFramework\Http; */ class DeleteTest extends RequestTestCase { public function testBasicUpload(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $view->file_put_contents('foo.txt', 'asd'); @@ -29,7 +31,7 @@ class DeleteTest extends RequestTestCase { $mount->getStorage()->unlink($mount->getInternalPath($internalPath)); // cache entry still exists - $this->assertInstanceOf('\OCP\Files\FileInfo', $view->getFileInfo('foo.txt')); + $this->assertInstanceOf(FileInfo::class, $view->getFileInfo('foo.txt')); $response = $this->request($view, $user, 'pass', 'DELETE', '/foo.txt'); diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php index bec4cd9967b..34171963ef0 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/DownloadTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -19,7 +20,7 @@ use OCP\Lock\ILockingProvider; */ class DownloadTest extends RequestTestCase { public function testDownload(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $view->file_put_contents('foo.txt', 'bar'); @@ -30,7 +31,7 @@ class DownloadTest extends RequestTestCase { } public function testDownloadWriteLocked(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $view->file_put_contents('foo.txt', 'bar'); @@ -42,7 +43,7 @@ class DownloadTest extends RequestTestCase { } public function testDownloadReadLocked(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $view->file_put_contents('foo.txt', 'bar'); diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php index cbaa0c3101b..615490ddc92 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionMasterKeyUploadTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,9 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; use OC\Files\View; +use OCP\IConfig; +use OCP\ITempManager; +use OCP\Server; use Test\Traits\EncryptionTrait; /** @@ -20,12 +24,12 @@ use Test\Traits\EncryptionTrait; class EncryptionMasterKeyUploadTest extends UploadTest { use EncryptionTrait; - protected function setupUser($name, $password) { + protected function setupUser($name, $password): View { $this->createUser($name, $password); - $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $tmpFolder = Server::get(ITempManager::class)->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); // we use the master key - \OC::$server->getConfig()->setAppValue('encryption', 'useMasterKey', '1'); + Server::get(IConfig::class)->setAppValue('encryption', 'useMasterKey', '1'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); return new View('/' . $name . '/files'); diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php index f830c54fd0d..efa7bb54cf8 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/EncryptionUploadTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,9 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; use OC\Files\View; +use OCP\IConfig; +use OCP\ITempManager; +use OCP\Server; use Test\Traits\EncryptionTrait; /** @@ -20,12 +24,12 @@ use Test\Traits\EncryptionTrait; class EncryptionUploadTest extends UploadTest { use EncryptionTrait; - protected function setupUser($name, $password) { + protected function setupUser($name, $password): View { $this->createUser($name, $password); - $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $tmpFolder = Server::get(ITempManager::class)->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); // we use per-user keys - \OC::$server->getConfig()->setAppValue('encryption', 'useMasterKey', '0'); + Server::get(IConfig::class)->setAppValue('encryption', 'useMasterKey', '0'); $this->setupForUser($name, $password); $this->loginWithEncryption($name); return new View('/' . $name . '/files'); diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/ExceptionPlugin.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/ExceptionPlugin.php index 9b20b2f8630..0c53e4b1009 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/ExceptionPlugin.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/ExceptionPlugin.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,7 +8,9 @@ */ namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; -class ExceptionPlugin extends \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin { +use OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin; + +class ExceptionPlugin extends ExceptionLoggerPlugin { /** * @var \Throwable[] */ diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/PartFileInRootUploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/PartFileInRootUploadTest.php index 200008bcfce..e6fa489fb24 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/PartFileInRootUploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/PartFileInRootUploadTest.php @@ -7,7 +7,9 @@ */ namespace OCA\DAV\Tests\unit\Connector\Sabre\RequestTest; +use OC\AllConfig; use OCP\IConfig; +use OCP\Server; /** * Class PartFileInRootUploadTest @@ -18,10 +20,8 @@ use OCP\IConfig; */ class PartFileInRootUploadTest extends UploadTest { protected function setUp(): void { - $config = \OC::$server->getConfig(); - $mockConfig = $this->getMockBuilder(IConfig::class) - ->disableOriginalConstructor() - ->getMock(); + $config = Server::get(IConfig::class); + $mockConfig = $this->createMock(IConfig::class); $mockConfig->expects($this->any()) ->method('getSystemValue') ->willReturnCallback(function ($key, $default) use ($config) { @@ -31,7 +31,7 @@ class PartFileInRootUploadTest extends UploadTest { return $config->getSystemValue($key, $default); } }); - $this->overwriteService('AllConfig', $mockConfig); + $this->overwriteService(AllConfig::class, $mockConfig); parent::setUp(); } diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php index 29574d53bca..404dc7fa5d7 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/RequestTestCase.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -11,9 +12,16 @@ use OC\Files\View; use OCA\DAV\Connector\Sabre\Server; use OCA\DAV\Connector\Sabre\ServerFactory; use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\Mount\IMountManager; use OCP\IConfig; +use OCP\IDBConnection; +use OCP\IPreview; use OCP\IRequest; use OCP\IRequestId; +use OCP\ITagManager; +use OCP\ITempManager; +use OCP\IUserSession; +use OCP\L10N\IFactory; use Psr\Log\LoggerInterface; use Sabre\HTTP\Request; use Test\TestCase; @@ -23,11 +31,7 @@ use Test\Traits\UserTrait; abstract class RequestTestCase extends TestCase { use UserTrait; use MountProviderTrait; - - /** - * @var \OCA\DAV\Connector\Sabre\ServerFactory - */ - protected $serverFactory; + protected ServerFactory $serverFactory; protected function getStream($string) { $stream = fopen('php://temp', 'r+'); @@ -40,31 +44,29 @@ abstract class RequestTestCase extends TestCase { parent::setUp(); $this->serverFactory = new ServerFactory( - \OC::$server->getConfig(), - \OC::$server->get(LoggerInterface::class), - \OC::$server->getDatabaseConnection(), - \OC::$server->getUserSession(), - \OC::$server->getMountManager(), - \OC::$server->getTagManager(), - $this->getMockBuilder(IRequest::class) - ->disableOriginalConstructor() - ->getMock(), - \OC::$server->getPreviewManager(), - \OC::$server->get(IEventDispatcher::class), - \OC::$server->getL10N('dav') + \OCP\Server::get(IConfig::class), + \OCP\Server::get(LoggerInterface::class), + \OCP\Server::get(IDBConnection::class), + \OCP\Server::get(IUserSession::class), + \OCP\Server::get(IMountManager::class), + \OCP\Server::get(ITagManager::class), + $this->createMock(IRequest::class), + \OCP\Server::get(IPreview::class), + \OCP\Server::get(IEventDispatcher::class), + \OCP\Server::get(IFactory::class)->get('dav'), ); } - protected function setupUser($name, $password) { + protected function setupUser($name, $password): View { $this->createUser($name, $password); - $tmpFolder = \OC::$server->getTempManager()->getTemporaryFolder(); + $tmpFolder = \OCP\Server::get(ITempManager::class)->getTemporaryFolder(); $this->registerMount($name, '\OC\Files\Storage\Local', '/' . $name, ['datadir' => $tmpFolder]); - $this->loginAsUser($name); + self::loginAsUser($name); return new View('/' . $name . '/files'); } /** - * @param \OC\Files\View $view the view to run the webdav server against + * @param View $view the view to run the webdav server against * @param string $user * @param string $password * @param string $method @@ -79,7 +81,7 @@ abstract class RequestTestCase extends TestCase { $body = $this->getStream($body); } $this->logout(); - $exceptionPlugin = new ExceptionPlugin('webdav', \OC::$server->get(LoggerInterface::class)); + $exceptionPlugin = new ExceptionPlugin('webdav', \OCP\Server::get(LoggerInterface::class)); $server = $this->getSabreServer($view, $user, $password, $exceptionPlugin); $request = new Request($method, $url, $headers, $body); @@ -131,7 +133,7 @@ abstract class RequestTestCase extends TestCase { $authBackend = new Auth($user, $password); $authPlugin = new \Sabre\DAV\Auth\Plugin($authBackend); - $server = $this->serverFactory->createServer('/', 'dummy', $authPlugin, function () use ($view) { + $server = $this->serverFactory->createServer(false, '/', 'dummy', $authPlugin, function () use ($view) { return $view; }); $server->addPlugin($exceptionPlugin); diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php index f7bcdd930ca..08d774e56b8 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/Sapi.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -12,11 +13,6 @@ use Sabre\HTTP\Response; class Sapi { /** - * @var \Sabre\HTTP\Request - */ - private $request; - - /** * @var \Sabre\HTTP\Response */ private $response; @@ -31,8 +27,9 @@ class Sapi { return $this->request; } - public function __construct(Request $request) { - $this->request = $request; + public function __construct( + private Request $request, + ) { } /** diff --git a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php index 286bba7d196..5c6d0f03334 100644 --- a/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/RequestTest/UploadTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -19,7 +20,7 @@ use OCP\Lock\ILockingProvider; */ class UploadTest extends RequestTestCase { public function testBasicUpload(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $this->assertFalse($view->file_exists('foo.txt')); @@ -35,7 +36,7 @@ class UploadTest extends RequestTestCase { } public function testUploadOverWrite(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $view->file_put_contents('foo.txt', 'foobar'); @@ -51,7 +52,7 @@ class UploadTest extends RequestTestCase { } public function testUploadOverWriteReadLocked(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $view->file_put_contents('foo.txt', 'bar'); @@ -63,7 +64,7 @@ class UploadTest extends RequestTestCase { } public function testUploadOverWriteWriteLocked(): void { - $user = $this->getUniqueID(); + $user = self::getUniqueID(); $view = $this->setupUser($user, 'pass'); $this->loginAsUser($user); @@ -74,115 +75,4 @@ class UploadTest extends RequestTestCase { $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt', 'asd'); $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); } - - public function testChunkedUpload(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - - $this->assertEquals(201, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertTrue($view->file_exists('foo.txt')); - - $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); - - $info = $view->getFileInfo('foo.txt'); - $this->assertInstanceOf('\OC\Files\FileInfo', $info); - $this->assertEquals(6, $info->getSize()); - } - - public function testChunkedUploadOverWrite(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $view->file_put_contents('foo.txt', 'bar'); - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertEquals('bar', $view->file_get_contents('foo.txt')); - - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - - $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); - - $info = $view->getFileInfo('foo.txt'); - $this->assertInstanceOf('\OC\Files\FileInfo', $info); - $this->assertEquals(6, $info->getSize()); - } - - public function testChunkedUploadOutOfOrder(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - - $this->assertEquals(201, $response->getStatus()); - $this->assertTrue($view->file_exists('foo.txt')); - - $this->assertEquals('asdbar', $view->file_get_contents('foo.txt')); - - $info = $view->getFileInfo('foo.txt'); - $this->assertInstanceOf('\OC\Files\FileInfo', $info); - $this->assertEquals(6, $info->getSize()); - } - - public function testChunkedUploadOutOfOrderReadLocked(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - - $view->lockFile('/foo.txt', ILockingProvider::LOCK_SHARED); - - try { - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { - $this->fail('Didn\'t expect locked error for the first chunk on read lock'); - return; - } - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - // last chunk should trigger the locked error since it tries to assemble - $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); - } - - public function testChunkedUploadOutOfOrderWriteLocked(): void { - $user = $this->getUniqueID(); - $view = $this->setupUser($user, 'pass'); - - $this->assertFalse($view->file_exists('foo.txt')); - - $view->lockFile('/foo.txt', ILockingProvider::LOCK_EXCLUSIVE); - - try { - $response = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-1', 'bar', ['OC-Chunked' => '1']); - } catch (\OCA\DAV\Connector\Sabre\Exception\FileLocked $e) { - $this->fail('Didn\'t expect locked error for the first chunk on write lock'); // maybe forbid this in the future for write locks only? - return; - } - - $this->assertEquals(Http::STATUS_CREATED, $response->getStatus()); - $this->assertFalse($view->file_exists('foo.txt')); - - // last chunk should trigger the locked error since it tries to assemble - $result = $this->request($view, $user, 'pass', 'PUT', '/foo.txt-chunking-123-2-0', 'asd', ['OC-Chunked' => '1']); - $this->assertEquals(Http::STATUS_LOCKED, $result->getStatus()); - } } diff --git a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php index 546be840cd8..33f579eb913 100644 --- a/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/SharesPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -10,41 +11,24 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\Node; +use OCA\DAV\Connector\Sabre\SharesPlugin; use OCA\DAV\Upload\UploadFile; use OCP\Files\Folder; use OCP\IUser; use OCP\IUserSession; use OCP\Share\IManager; use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Tree; class SharesPluginTest extends \Test\TestCase { - public const SHARETYPES_PROPERTYNAME = \OCA\DAV\Connector\Sabre\SharesPlugin::SHARETYPES_PROPERTYNAME; + public const SHARETYPES_PROPERTYNAME = SharesPlugin::SHARETYPES_PROPERTYNAME; - /** - * @var \Sabre\DAV\Server - */ - private $server; - - /** - * @var \Sabre\DAV\Tree - */ - private $tree; - - /** - * @var \OCP\Share\IManager - */ - private $shareManager; - - /** - * @var \OCP\Files\Folder - */ - private $userFolder; - - /** - * @var \OCA\DAV\Connector\Sabre\SharesPlugin - */ - private $plugin; + private \Sabre\DAV\Server $server; + private \Sabre\DAV\Tree&MockObject $tree; + private \OCP\Share\IManager&MockObject $shareManager; + private Folder&MockObject $userFolder; + private SharesPlugin $plugin; protected function setUp(): void { parent::setUp(); @@ -61,7 +45,7 @@ class SharesPluginTest extends \Test\TestCase { ->willReturn($user); $this->userFolder = $this->createMock(Folder::class); - $this->plugin = new \OCA\DAV\Connector\Sabre\SharesPlugin( + $this->plugin = new SharesPlugin( $this->tree, $userSession, $this->userFolder, @@ -70,13 +54,9 @@ class SharesPluginTest extends \Test\TestCase { $this->plugin->initialize($this->server); } - /** - * @dataProvider sharesGetPropertiesDataProvider - */ - public function testGetProperties($shareTypes): void { - $sabreNode = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('sharesGetPropertiesDataProvider')] + public function testGetProperties(array $shareTypes): void { + $sabreNode = $this->createMock(Node::class); $sabreNode->expects($this->any()) ->method('getId') ->willReturn(123); @@ -85,9 +65,7 @@ class SharesPluginTest extends \Test\TestCase { ->willReturn('/subdir'); // node API nodes - $node = $this->getMockBuilder(Folder::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(Folder::class); $sabreNode->method('getNode') ->willReturn($node); @@ -139,10 +117,8 @@ class SharesPluginTest extends \Test\TestCase { $this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes()); } - /** - * @dataProvider sharesGetPropertiesDataProvider - */ - public function testPreloadThenGetProperties($shareTypes): void { + #[\PHPUnit\Framework\Attributes\DataProvider('sharesGetPropertiesDataProvider')] + public function testPreloadThenGetProperties(array $shareTypes): void { $sabreNode1 = $this->createMock(File::class); $sabreNode1->method('getId') ->willReturn(111); @@ -181,7 +157,7 @@ class SharesPluginTest extends \Test\TestCase { ->willReturn($node2); $dummyShares = array_map(function ($type) { - $share = $this->getMockBuilder(IShare::class)->getMock(); + $share = $this->createMock(IShare::class); $share->expects($this->any()) ->method('getShareType') ->willReturn($type); @@ -247,6 +223,7 @@ class SharesPluginTest extends \Test\TestCase { 0 ); + $this->server->emit('preloadCollection', [$propFindRoot, $sabreNode]); $this->plugin->handleGetProperties( $propFindRoot, $sabreNode @@ -267,7 +244,7 @@ class SharesPluginTest extends \Test\TestCase { $this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes()); } - public function sharesGetPropertiesDataProvider() { + public static function sharesGetPropertiesDataProvider(): array { return [ [[]], [[IShare::TYPE_USER]], @@ -286,9 +263,7 @@ class SharesPluginTest extends \Test\TestCase { } public function testGetPropertiesSkipChunks(): void { - $sabreNode = $this->getMockBuilder(UploadFile::class) - ->disableOriginalConstructor() - ->getMock(); + $sabreNode = $this->createMock(UploadFile::class); $propFind = new \Sabre\DAV\PropFind( '/dummyPath', diff --git a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php index 635f62dac87..554a4a1424e 100644 --- a/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/TagsPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2014-2016 ownCloud, Inc. @@ -10,68 +11,56 @@ namespace OCA\DAV\Tests\unit\Connector\Sabre; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\Node; +use OCA\DAV\Connector\Sabre\TagList; +use OCA\DAV\Connector\Sabre\TagsPlugin; use OCA\DAV\Upload\UploadFile; +use OCP\EventDispatcher\IEventDispatcher; use OCP\ITagManager; use OCP\ITags; +use OCP\IUser; +use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Tree; class TagsPluginTest extends \Test\TestCase { - public const TAGS_PROPERTYNAME = \OCA\DAV\Connector\Sabre\TagsPlugin::TAGS_PROPERTYNAME; - public const FAVORITE_PROPERTYNAME = \OCA\DAV\Connector\Sabre\TagsPlugin::FAVORITE_PROPERTYNAME; - public const TAG_FAVORITE = \OCA\DAV\Connector\Sabre\TagsPlugin::TAG_FAVORITE; - - /** - * @var \Sabre\DAV\Server - */ - private $server; - - /** - * @var Tree - */ - private $tree; - - /** - * @var \OCP\ITagManager - */ - private $tagManager; - - /** - * @var \OCP\ITags - */ - private $tagger; - - /** - * @var \OCA\DAV\Connector\Sabre\TagsPlugin - */ - private $plugin; + public const TAGS_PROPERTYNAME = TagsPlugin::TAGS_PROPERTYNAME; + public const FAVORITE_PROPERTYNAME = TagsPlugin::FAVORITE_PROPERTYNAME; + public const TAG_FAVORITE = TagsPlugin::TAG_FAVORITE; + + private \Sabre\DAV\Server $server; + private Tree&MockObject $tree; + private ITagManager&MockObject $tagManager; + private ITags&MockObject $tagger; + private IEventDispatcher&MockObject $eventDispatcher; + private IUserSession&MockObject $userSession; + private TagsPlugin $plugin; protected function setUp(): void { parent::setUp(); + $this->server = new \Sabre\DAV\Server(); - $this->tree = $this->getMockBuilder(Tree::class) - ->disableOriginalConstructor() - ->getMock(); - $this->tagger = $this->getMockBuilder(ITags::class) - ->disableOriginalConstructor() - ->getMock(); - $this->tagManager = $this->getMockBuilder(ITagManager::class) - ->disableOriginalConstructor() - ->getMock(); + $this->tree = $this->createMock(Tree::class); + $this->tagger = $this->createMock(ITags::class); + $this->tagManager = $this->createMock(ITagManager::class); + $this->eventDispatcher = $this->createMock(IEventDispatcher::class); + $user = $this->createMock(IUser::class); + + $this->userSession = $this->createMock(IUserSession::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->withAnyParameters() + ->willReturn($user); $this->tagManager->expects($this->any()) ->method('load') ->with('files') ->willReturn($this->tagger); - $this->plugin = new \OCA\DAV\Connector\Sabre\TagsPlugin($this->tree, $this->tagManager); + $this->plugin = new TagsPlugin($this->tree, $this->tagManager, $this->eventDispatcher, $this->userSession); $this->plugin->initialize($this->server); } - /** - * @dataProvider tagsGetPropertiesDataProvider - */ - public function testGetProperties($tags, $requestedProperties, $expectedProperties): void { - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('tagsGetPropertiesDataProvider')] + public function testGetProperties(array $tags, array $requestedProperties, array $expectedProperties): void { + $node = $this->createMock(Node::class); $node->expects($this->any()) ->method('getId') ->willReturn(123); @@ -104,19 +93,13 @@ class TagsPluginTest extends \Test\TestCase { $this->assertEquals($expectedProperties, $result); } - /** - * @dataProvider tagsGetPropertiesDataProvider - */ - public function testPreloadThenGetProperties($tags, $requestedProperties, $expectedProperties): void { - $node1 = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('tagsGetPropertiesDataProvider')] + public function testPreloadThenGetProperties(array $tags, array $requestedProperties, array $expectedProperties): void { + $node1 = $this->createMock(File::class); $node1->expects($this->any()) ->method('getId') ->willReturn(111); - $node2 = $this->getMockBuilder(File::class) - ->disableOriginalConstructor() - ->getMock(); + $node2 = $this->createMock(File::class); $node2->expects($this->any()) ->method('getId') ->willReturn(222); @@ -129,9 +112,7 @@ class TagsPluginTest extends \Test\TestCase { $expectedCallCount = 1; } - $node = $this->getMockBuilder(Directory::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(Directory::class); $node->expects($this->any()) ->method('getId') ->willReturn(123); @@ -166,6 +147,8 @@ class TagsPluginTest extends \Test\TestCase { 0 ); + $this->server->emit('preloadCollection', [$propFindRoot, $node]); + $this->plugin->handleGetProperties( $propFindRoot, $node @@ -186,7 +169,7 @@ class TagsPluginTest extends \Test\TestCase { $this->assertEquals($expectedProperties, $result); } - public function tagsGetPropertiesDataProvider() { + public static function tagsGetPropertiesDataProvider(): array { return [ // request both, receive both [ @@ -194,7 +177,7 @@ class TagsPluginTest extends \Test\TestCase { [self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME], [ 200 => [ - self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(['tag1', 'tag2']), + self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']), self::FAVORITE_PROPERTYNAME => true, ] ] @@ -205,7 +188,7 @@ class TagsPluginTest extends \Test\TestCase { [self::TAGS_PROPERTYNAME], [ 200 => [ - self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(['tag1', 'tag2']), + self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']), ] ] ], @@ -233,7 +216,7 @@ class TagsPluginTest extends \Test\TestCase { [self::TAGS_PROPERTYNAME, self::FAVORITE_PROPERTYNAME], [ 200 => [ - self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList([]), + self::TAGS_PROPERTYNAME => new TagList([]), self::FAVORITE_PROPERTYNAME => false, ] ] @@ -242,9 +225,7 @@ class TagsPluginTest extends \Test\TestCase { } public function testGetPropertiesSkipChunks(): void { - $sabreNode = $this->getMockBuilder(UploadFile::class) - ->disableOriginalConstructor() - ->getMock(); + $sabreNode = $this->createMock(UploadFile::class); $propFind = new \Sabre\DAV\PropFind( '/dummyPath', @@ -264,9 +245,7 @@ class TagsPluginTest extends \Test\TestCase { public function testUpdateTags(): void { // this test will replace the existing tags "tagremove" with "tag1" and "tag2" // and keep "tagkeep" - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(Node::class); $node->expects($this->any()) ->method('getId') ->willReturn(123); @@ -282,12 +261,16 @@ class TagsPluginTest extends \Test\TestCase { ->willReturn([123 => ['tagkeep', 'tagremove', self::TAG_FAVORITE]]); // then tag as tag1 and tag2 - $this->tagger->expects($this->exactly(2)) + $calls = [ + [123, 'tag1'], + [123, 'tag2'], + ]; + $this->tagger->expects($this->exactly(count($calls))) ->method('tagAs') - ->withConsecutive( - [123, 'tag1'], - [123, 'tag2'], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); // it will untag tag3 $this->tagger->expects($this->once()) @@ -296,7 +279,7 @@ class TagsPluginTest extends \Test\TestCase { // properties to set $propPatch = new \Sabre\DAV\PropPatch([ - self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(['tag1', 'tag2', 'tagkeep']) + self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2', 'tagkeep']) ]); $this->plugin->handleUpdateProperties( @@ -311,13 +294,11 @@ class TagsPluginTest extends \Test\TestCase { $result = $propPatch->getResult(); $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); - $this->assertFalse(isset($result[self::FAVORITE_PROPERTYNAME])); + $this->assertArrayNotHasKey(self::FAVORITE_PROPERTYNAME, $result); } public function testUpdateTagsFromScratch(): void { - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(Node::class); $node->expects($this->any()) ->method('getId') ->willReturn(123); @@ -333,16 +314,20 @@ class TagsPluginTest extends \Test\TestCase { ->willReturn([]); // then tag as tag1 and tag2 - $this->tagger->expects($this->exactly(2)) + $calls = [ + [123, 'tag1'], + [123, 'tag2'], + ]; + $this->tagger->expects($this->exactly(count($calls))) ->method('tagAs') - ->withConsecutive( - [123, 'tag1'], - [123, 'tag2'], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); // properties to set $propPatch = new \Sabre\DAV\PropPatch([ - self::TAGS_PROPERTYNAME => new \OCA\DAV\Connector\Sabre\TagList(['tag1', 'tag2']) + self::TAGS_PROPERTYNAME => new TagList(['tag1', 'tag2']) ]); $this->plugin->handleUpdateProperties( @@ -357,15 +342,13 @@ class TagsPluginTest extends \Test\TestCase { $result = $propPatch->getResult(); $this->assertEquals(200, $result[self::TAGS_PROPERTYNAME]); - $this->assertFalse(false, isset($result[self::FAVORITE_PROPERTYNAME])); + $this->assertArrayNotHasKey(self::FAVORITE_PROPERTYNAME, $result); } public function testUpdateFav(): void { // this test will replace the existing tags "tagremove" with "tag1" and "tag2" // and keep "tagkeep" - $node = $this->getMockBuilder(Node::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(Node::class); $node->expects($this->any()) ->method('getId') ->willReturn(123); @@ -396,8 +379,8 @@ class TagsPluginTest extends \Test\TestCase { $this->assertEmpty($propPatch->getRemainingMutations()); $result = $propPatch->getResult(); - $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); - $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); + $this->assertArrayNotHasKey(self::TAGS_PROPERTYNAME, $result); + $this->assertEquals(200, $result[self::FAVORITE_PROPERTYNAME]); // unfavorite now // set favorite tag @@ -421,7 +404,7 @@ class TagsPluginTest extends \Test\TestCase { $this->assertEmpty($propPatch->getRemainingMutations()); $result = $propPatch->getResult(); - $this->assertFalse(false, isset($result[self::TAGS_PROPERTYNAME])); - $this->assertEquals(200, isset($result[self::FAVORITE_PROPERTYNAME])); + $this->assertArrayNotHasKey(self::TAGS_PROPERTYNAME, $result); + $this->assertEquals(200, $result[self::FAVORITE_PROPERTYNAME]); } } diff --git a/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php b/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php index c7c2bf0e431..9aa0ef3a2a7 100644 --- a/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php +++ b/apps/dav/tests/unit/Controller/BirthdayCalendarControllerTest.php @@ -1,43 +1,33 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\DAV\Controller; +namespace OCA\DAV\Tests\unit\DAV\Controller; use OCA\DAV\BackgroundJob\GenerateBirthdayCalendarBackgroundJob; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Controller\BirthdayCalendarController; +use OCP\AppFramework\Http\JSONResponse; use OCP\BackgroundJob\IJobList; use OCP\IConfig; use OCP\IDBConnection; use OCP\IRequest; use OCP\IUser; use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class BirthdayCalendarControllerTest extends TestCase { - - /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */ - private $config; - - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - - /** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */ - private $db; - - /** @var IJobList|\PHPUnit\Framework\MockObject\MockObject */ - private $jobList; - - /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */ - private $userManager; - - /** @var CalDavBackend|\PHPUnit\Framework\MockObject\MockObject */ - private $caldav; - - /** @var BirthdayCalendarController|\PHPUnit\Framework\MockObject\MockObject */ - private $controller; + private IConfig&MockObject $config; + private IRequest&MockObject $request; + private IDBConnection&MockObject $db; + private IJobList&MockObject $jobList; + private IUserManager&MockObject $userManager; + private CalDavBackend&MockObject $caldav; + private BirthdayCalendarController $controller; protected function setUp(): void { parent::setUp(); @@ -74,16 +64,20 @@ class BirthdayCalendarControllerTest extends TestCase { $closure($user3); }); + $calls = [ + [GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid1']], + [GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid2']], + [GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid3']], + ]; $this->jobList->expects($this->exactly(3)) ->method('add') - ->withConsecutive( - [GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid1']], - [GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid2']], - [GenerateBirthdayCalendarBackgroundJob::class, ['userId' => 'uid3']], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); $response = $this->controller->enable(); - $this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $response); + $this->assertInstanceOf(JSONResponse::class, $response); } public function testDisable(): void { @@ -97,6 +91,6 @@ class BirthdayCalendarControllerTest extends TestCase { ->method('deleteAllBirthdayCalendars'); $response = $this->controller->disable(); - $this->assertInstanceOf('OCP\AppFramework\Http\JSONResponse', $response); + $this->assertInstanceOf(JSONResponse::class, $response); } } diff --git a/apps/dav/tests/unit/Controller/DirectControllerTest.php b/apps/dav/tests/unit/Controller/DirectControllerTest.php index 2476681251c..837adde1da7 100644 --- a/apps/dav/tests/unit/Controller/DirectControllerTest.php +++ b/apps/dav/tests/unit/Controller/DirectControllerTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\DAV\Controller; +namespace OCA\DAV\Tests\unit\DAV\Controller; use OCA\DAV\Controller\DirectController; use OCA\DAV\Db\Direct; @@ -20,29 +20,18 @@ use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; use OCP\IRequest; -use OCP\IUrlGenerator; +use OCP\IURLGenerator; use OCP\Security\ISecureRandom; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class DirectControllerTest extends TestCase { - - /** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject */ - private $rootFolder; - - /** @var DirectMapper|\PHPUnit\Framework\MockObject\MockObject */ - private $directMapper; - - /** @var ISecureRandom|\PHPUnit\Framework\MockObject\MockObject */ - private $random; - - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; - - /** @var IUrlGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - - /** @var IEventDispatcher|\PHPUnit\Framework\MockObject\MockObject */ - private $eventDispatcher; + private IRootFolder&MockObject $rootFolder; + private DirectMapper&MockObject $directMapper; + private ISecureRandom&MockObject $random; + private ITimeFactory&MockObject $timeFactory; + private IURLGenerator&MockObject $urlGenerator; + private IEventDispatcher&MockObject $eventDispatcher; private DirectController $controller; @@ -53,7 +42,7 @@ class DirectControllerTest extends TestCase { $this->directMapper = $this->createMock(DirectMapper::class); $this->random = $this->createMock(ISecureRandom::class); $this->timeFactory = $this->createMock(ITimeFactory::class); - $this->urlGenerator = $this->createMock(IUrlGenerator::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->controller = new DirectController( @@ -136,7 +125,7 @@ class DirectControllerTest extends TestCase { $this->urlGenerator->method('getAbsoluteURL') ->willReturnCallback(function (string $url) { - return 'https://my.nextcloud/'.$url; + return 'https://my.nextcloud/' . $url; }); $result = $this->controller->getUrl(101); diff --git a/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php b/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php index 640c66b75e5..15b18d6c1b1 100644 --- a/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php +++ b/apps/dav/tests/unit/Controller/InvitationResponseControllerTest.php @@ -7,7 +7,7 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\DAV\Controller; +namespace OCA\DAV\Tests\unit\DAV\Controller; use OCA\DAV\CalDAV\InvitationResponse\InvitationResponseServer; use OCA\DAV\Controller\InvitationResponseController; @@ -18,24 +18,16 @@ use OCP\DB\QueryBuilder\IExpressionBuilder; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\ITip\Message; use Test\TestCase; class InvitationResponseControllerTest extends TestCase { - /** @var InvitationResponseController */ - private $controller; - - /** @var IDBConnection|\PHPUnit\Framework\MockObject\MockObject */ - private $dbConnection; - - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; - - /** @var InvitationResponseServer|\PHPUnit\Framework\MockObject\MockObject */ - private $responseServer; + private IDBConnection&MockObject $dbConnection; + private IRequest&MockObject $request; + private ITimeFactory&MockObject $timeFactory; + private InvitationResponseServer&MockObject $responseServer; + private InvitationResponseController $controller; protected function setUp(): void { parent::setUp(); @@ -43,9 +35,7 @@ class InvitationResponseControllerTest extends TestCase { $this->dbConnection = $this->createMock(IDBConnection::class); $this->request = $this->createMock(IRequest::class); $this->timeFactory = $this->createMock(ITimeFactory::class); - $this->responseServer = $this->getMockBuilder(InvitationResponseServer::class) - ->disableOriginalConstructor() - ->getMock(); + $this->responseServer = $this->createMock(InvitationResponseServer::class); $this->controller = new InvitationResponseController( 'appName', @@ -56,16 +46,14 @@ class InvitationResponseControllerTest extends TestCase { ); } - public function attendeeProvider(): array { + public static function attendeeProvider(): array { return [ 'local attendee' => [false], 'external attendee' => [true] ]; } - /** - * @dataProvider attendeeProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')] public function testAccept(bool $isExternalAttendee): void { $this->buildQueryExpects('TOKEN123', [ 'id' => 0, @@ -127,9 +115,7 @@ EOF; $this->assertTrue($called); } - /** - * @dataProvider attendeeProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')] public function testAcceptSequence(bool $isExternalAttendee): void { $this->buildQueryExpects('TOKEN123', [ 'id' => 0, @@ -191,9 +177,7 @@ EOF; $this->assertTrue($called); } - /** - * @dataProvider attendeeProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')] public function testAcceptRecurrenceId(bool $isExternalAttendee): void { $this->buildQueryExpects('TOKEN123', [ 'id' => 0, @@ -283,9 +267,7 @@ EOF; $this->assertEquals([], $response->getParams()); } - /** - * @dataProvider attendeeProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')] public function testDecline(bool $isExternalAttendee): void { $this->buildQueryExpects('TOKEN123', [ 'id' => 0, @@ -354,9 +336,7 @@ EOF; $this->assertEquals(['token' => 'TOKEN123'], $response->getParams()); } - /** - * @dataProvider attendeeProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('attendeeProvider')] public function testProcessMoreOptionsResult(bool $isExternalAttendee): void { $this->request->expects($this->once()) ->method('getParam') @@ -424,7 +404,7 @@ EOF; $this->assertTrue($called); } - private function buildQueryExpects($token, $return, $time): void { + private function buildQueryExpects(string $token, ?array $return, int $time): void { $queryBuilder = $this->createMock(IQueryBuilder::class); $stmt = $this->createMock(IResult::class); $expr = $this->createMock(IExpressionBuilder::class); diff --git a/apps/dav/tests/unit/Controller/UpcomingEventsControllerTest.php b/apps/dav/tests/unit/Controller/UpcomingEventsControllerTest.php new file mode 100644 index 00000000000..527943e5221 --- /dev/null +++ b/apps/dav/tests/unit/Controller/UpcomingEventsControllerTest.php @@ -0,0 +1,73 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\DAV\Service; + +use OCA\DAV\CalDAV\UpcomingEvent; +use OCA\DAV\CalDAV\UpcomingEventsService; +use OCA\DAV\Controller\UpcomingEventsController; +use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class UpcomingEventsControllerTest extends TestCase { + private IRequest&MockObject $request; + private UpcomingEventsService&MockObject $service; + + protected function setUp(): void { + parent::setUp(); + + $this->request = $this->createMock(IRequest::class); + $this->service = $this->createMock(UpcomingEventsService::class); + } + + public function testGetEventsAnonymously(): void { + $controller = new UpcomingEventsController( + $this->request, + null, + $this->service, + ); + + $response = $controller->getEvents('https://cloud.example.com/call/123'); + + self::assertNull($response->getData()); + self::assertSame(401, $response->getStatus()); + } + + public function testGetEventsByLocation(): void { + $controller = new UpcomingEventsController( + $this->request, + 'u1', + $this->service, + ); + $this->service->expects(self::once()) + ->method('getEvents') + ->with('u1', 'https://cloud.example.com/call/123') + ->willReturn([ + new UpcomingEvent( + 'abc-123', + null, + 'personal', + 123, + 'Test', + 'https://cloud.example.com/call/123', + null, + ), + ]); + + $response = $controller->getEvents('https://cloud.example.com/call/123'); + + self::assertNotNull($response->getData()); + self::assertIsArray($response->getData()); + self::assertCount(1, $response->getData()['events']); + self::assertSame(200, $response->getStatus()); + $event1 = $response->getData()['events'][0]; + self::assertEquals('abc-123', $event1['uri']); + } +} diff --git a/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php b/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php index ea5450391e8..c99ebf327c8 100644 --- a/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php +++ b/apps/dav/tests/unit/DAV/AnonymousOptionsTest.php @@ -1,9 +1,11 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\tests\unit\DAV; +namespace OCA\DAV\Tests\unit\DAV; use OCA\DAV\Connector\Sabre\AnonymousOptionsPlugin; use Sabre\DAV\Auth\Backend\BasicCallBack; @@ -14,7 +16,7 @@ use Sabre\HTTP\Sapi; use Test\TestCase; class AnonymousOptionsTest extends TestCase { - private function sendRequest($method, $path, $userAgent = '') { + private function sendRequest(string $method, string $path, string $userAgent = '') { $server = new Server(); $server->addPlugin(new AnonymousOptionsPlugin()); $server->addPlugin(new Plugin(new BasicCallBack(function () { diff --git a/apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php b/apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php index 088330cecff..0e82ef0a3ae 100644 --- a/apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php +++ b/apps/dav/tests/unit/DAV/BrowserErrorPagePluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,25 +9,22 @@ namespace OCA\DAV\Tests\unit\DAV; use OCA\DAV\Files\BrowserErrorPagePlugin; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\NotFound; use Sabre\HTTP\Response; class BrowserErrorPagePluginTest extends \Test\TestCase { - /** - * @dataProvider providesExceptions - * @param $expectedCode - * @param $exception - */ - public function test($expectedCode, $exception): void { - /** @var BrowserErrorPagePlugin | \PHPUnit\Framework\MockObject\MockObject $plugin */ - $plugin = $this->getMockBuilder(BrowserErrorPagePlugin::class)->setMethods(['sendResponse', 'generateBody'])->getMock(); + #[\PHPUnit\Framework\Attributes\DataProvider('providesExceptions')] + public function test(int $expectedCode, \Throwable $exception): void { + /** @var BrowserErrorPagePlugin&MockObject $plugin */ + $plugin = $this->getMockBuilder(BrowserErrorPagePlugin::class)->onlyMethods(['sendResponse', 'generateBody'])->getMock(); $plugin->expects($this->once())->method('generateBody')->willReturn(':boom:'); $plugin->expects($this->once())->method('sendResponse'); - /** @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject $server */ - $server = $this->getMockBuilder('Sabre\DAV\Server')->disableOriginalConstructor()->getMock(); + /** @var \Sabre\DAV\Server&MockObject $server */ + $server = $this->createMock('Sabre\DAV\Server'); $server->expects($this->once())->method('on'); - $httpResponse = $this->getMockBuilder(Response::class)->disableOriginalConstructor()->getMock(); + $httpResponse = $this->createMock(Response::class); $httpResponse->expects($this->once())->method('addHeaders'); $httpResponse->expects($this->once())->method('setStatus')->with($expectedCode); $httpResponse->expects($this->once())->method('setBody')->with(':boom:'); @@ -35,7 +33,7 @@ class BrowserErrorPagePluginTest extends \Test\TestCase { $plugin->logException($exception); } - public function providesExceptions() { + public static function providesExceptions(): array { return [ [ 404, new NotFound()], [ 500, new \RuntimeException()], diff --git a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php index cb6447511e4..517969fc9a3 100644 --- a/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php +++ b/apps/dav/tests/unit/DAV/CustomPropertiesBackendTest.php @@ -1,15 +1,20 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\DAV; +namespace OCA\DAV\Tests\unit\DAV; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\CalDAV\DefaultCalendarValidator; use OCA\DAV\DAV\CustomPropertiesBackend; +use OCA\DAV\Db\PropertyMapper; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\IUser; -use Sabre\CalDAV\ICalendar; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropFind; use Sabre\DAV\PropPatch; @@ -26,20 +31,13 @@ use Test\TestCase; class CustomPropertiesBackendTest extends TestCase { private const BASE_URI = '/remote.php/dav/'; - /** @var Server | \PHPUnit\Framework\MockObject\MockObject */ - private $server; - - /** @var Tree | \PHPUnit\Framework\MockObject\MockObject */ - private $tree; - - /** @var IDBConnection */ - private $dbConnection; - - /** @var IUser | \PHPUnit\Framework\MockObject\MockObject */ - private $user; - - /** @var CustomPropertiesBackend | \PHPUnit\Framework\MockObject\MockObject */ - private $backend; + private Server&MockObject $server; + private Tree&MockObject $tree; + private IDBConnection $dbConnection; + private IUser&MockObject $user; + private DefaultCalendarValidator&MockObject $defaultCalendarValidator; + private CustomPropertiesBackend $backend; + private PropertyMapper $propertyMapper; protected function setUp(): void { parent::setUp(); @@ -52,13 +50,17 @@ class CustomPropertiesBackendTest extends TestCase { $this->user->method('getUID') ->with() ->willReturn('dummy_user_42'); - $this->dbConnection = \OC::$server->getDatabaseConnection(); + $this->dbConnection = \OCP\Server::get(IDBConnection::class); + $this->propertyMapper = \OCP\Server::get(PropertyMapper::class); + $this->defaultCalendarValidator = $this->createMock(DefaultCalendarValidator::class); $this->backend = new CustomPropertiesBackend( $this->server, $this->tree, $this->dbConnection, $this->user, + $this->propertyMapper, + $this->defaultCalendarValidator, ); } @@ -78,13 +80,13 @@ class CustomPropertiesBackendTest extends TestCase { } } - protected function insertProps(string $user, string $path, array $props) { + protected function insertProps(string $user, string $path, array $props): void { foreach ($props as $name => $value) { $this->insertProp($user, $path, $name, $value); } } - protected function insertProp(string $user, string $path, string $name, mixed $value) { + protected function insertProp(string $user, string $path, string $name, mixed $value): void { $type = CustomPropertiesBackend::PROPERTY_TYPE_STRING; if ($value instanceof Href) { $value = $value->getHref(); @@ -103,7 +105,7 @@ class CustomPropertiesBackendTest extends TestCase { $query->execute(); } - protected function getProps(string $user, string $path) { + protected function getProps(string $user, string $path): array { $query = $this->dbConnection->getQueryBuilder(); $query->select('propertyname', 'propertyvalue', 'valuetype') ->from('properties') @@ -131,6 +133,8 @@ class CustomPropertiesBackendTest extends TestCase { $this->tree, $db, $this->user, + $this->propertyMapper, + $this->defaultCalendarValidator, ); $propFind = $this->createMock(PropFind::class); @@ -195,7 +199,7 @@ class CustomPropertiesBackendTest extends TestCase { public function testPropFindPrincipalCall(): void { $this->tree->method('getNodeForPath') ->willReturnCallback(function ($uri) { - $node = $this->createMock(ICalendar::class); + $node = $this->createMock(Calendar::class); $node->method('getOwner') ->willReturn('principals/users/dummy_user_42'); return $node; @@ -237,26 +241,26 @@ class CustomPropertiesBackendTest extends TestCase { $this->assertEquals($props, $setProps); } - public function propFindPrincipalScheduleDefaultCalendarProviderUrlProvider(): array { + public static function propFindPrincipalScheduleDefaultCalendarProviderUrlProvider(): array { // [ user, nodes, existingProps, requestedProps, returnedProps ] return [ [ // Exists 'dummy_user_42', - ['calendars/dummy_user_42/foo/' => ICalendar::class], + ['calendars/dummy_user_42/foo/' => Calendar::class], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/foo/')], ], [ // Doesn't exist 'dummy_user_42', - ['calendars/dummy_user_42/foo/' => ICalendar::class], + ['calendars/dummy_user_42/foo/' => Calendar::class], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/dummy_user_42/bar/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'], [], ], [ // No privilege 'dummy_user_42', - ['calendars/user2/baz/' => ICalendar::class], + ['calendars/user2/baz/' => Calendar::class], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('calendars/user2/baz/')], ['{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL'], [], @@ -272,9 +276,7 @@ class CustomPropertiesBackendTest extends TestCase { } - /** - * @dataProvider propFindPrincipalScheduleDefaultCalendarProviderUrlProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('propFindPrincipalScheduleDefaultCalendarProviderUrlProvider')] public function testPropFindPrincipalScheduleDefaultCalendarUrl( string $user, array $nodes, @@ -336,9 +338,7 @@ class CustomPropertiesBackendTest extends TestCase { $this->assertEquals($returnedProps, $setProps); } - /** - * @dataProvider propPatchProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('propPatchProvider')] public function testPropPatch(string $path, array $existing, array $props, array $result): void { $this->server->method('calculateUri') ->willReturnCallback(function ($uri) { @@ -349,7 +349,7 @@ class CustomPropertiesBackendTest extends TestCase { }); $this->tree->method('getNodeForPath') ->willReturnCallback(function ($uri) { - $node = $this->createMock(ICalendar::class); + $node = $this->createMock(Calendar::class); $node->method('getOwner') ->willReturn('principals/users/' . $this->user->getUID()); return $node; @@ -365,7 +365,7 @@ class CustomPropertiesBackendTest extends TestCase { $this->assertEquals($result, $storedProps); } - public function propPatchProvider() { + public static function propPatchProvider(): array { $longPath = str_repeat('long_path', 100); return [ ['foo_bar_path_1337', [], ['{DAV:}displayname' => 'anything'], ['{DAV:}displayname' => 'anything']], @@ -377,25 +377,63 @@ class CustomPropertiesBackendTest extends TestCase { ]; } - /** - * @dataProvider deleteProvider - */ + public function testPropPatchWithUnsuitableCalendar(): void { + $path = 'principals/users/' . $this->user->getUID(); + + $node = $this->createMock(Calendar::class); + $node->expects(self::once()) + ->method('getOwner') + ->willReturn($path); + + $this->defaultCalendarValidator->expects(self::once()) + ->method('validateScheduleDefaultCalendar') + ->with($node) + ->willThrowException(new \Sabre\DAV\Exception('Invalid calendar')); + + $this->server->method('calculateUri') + ->willReturnCallback(function ($uri) { + if (str_starts_with($uri, self::BASE_URI)) { + return trim(substr($uri, strlen(self::BASE_URI)), '/'); + } + return null; + }); + $this->tree->expects(self::once()) + ->method('getNodeForPath') + ->with('foo/bar/') + ->willReturn($node); + + $storedProps = $this->getProps($this->user->getUID(), $path); + $this->assertEquals([], $storedProps); + + $propPatch = new PropPatch([ + '{urn:ietf:params:xml:ns:caldav}schedule-default-calendar-URL' => new Href('foo/bar/'), + ]); + $this->backend->propPatch($path, $propPatch); + try { + $propPatch->commit(); + } catch (\Throwable $e) { + $this->assertInstanceOf(\Sabre\DAV\Exception::class, $e); + } + + $storedProps = $this->getProps($this->user->getUID(), $path); + $this->assertEquals([], $storedProps); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('deleteProvider')] public function testDelete(string $path): void { $this->insertProps('dummy_user_42', $path, ['foo' => 'bar']); $this->backend->delete($path); $this->assertEquals([], $this->getProps('dummy_user_42', $path)); } - public function deleteProvider() { + public static function deleteProvider(): array { return [ ['foo_bar_path_1337'], [str_repeat('long_path', 100)] ]; } - /** - * @dataProvider moveProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('moveProvider')] public function testMove(string $source, string $target): void { $this->insertProps('dummy_user_42', $source, ['foo' => 'bar']); $this->backend->move($source, $target); @@ -403,10 +441,26 @@ class CustomPropertiesBackendTest extends TestCase { $this->assertEquals(['foo' => 'bar'], $this->getProps('dummy_user_42', $target)); } - public function moveProvider() { + public static function moveProvider(): array { return [ ['foo_bar_path_1337', 'foo_bar_path_7333'], [str_repeat('long_path1', 100), str_repeat('long_path2', 100)] ]; } + + public function testDecodeValueFromDatabaseObjectCurrent(): void { + $propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"\x00*\x00value";s:6:"opaque";}'; + $propertyType = 3; + $decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]); + $this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue); + $this->assertEquals('opaque', $decodeValue->getValue()); + } + + public function testDecodeValueFromDatabaseObjectLegacy(): void { + $propertyValue = 'O:48:"Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp":1:{s:8:"' . chr(0) . '*' . chr(0) . 'value";s:6:"opaque";}'; + $propertyType = 3; + $decodeValue = $this->invokePrivate($this->backend, 'decodeValueFromDatabase', [$propertyValue, $propertyType]); + $this->assertInstanceOf(\Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp::class, $decodeValue); + $this->assertEquals('opaque', $decodeValue->getValue()); + } } diff --git a/apps/dav/tests/unit/DAV/GroupPrincipalTest.php b/apps/dav/tests/unit/DAV/GroupPrincipalTest.php index 92c89fc62f8..2756152a6e2 100644 --- a/apps/dav/tests/unit/DAV/GroupPrincipalTest.php +++ b/apps/dav/tests/unit/DAV/GroupPrincipalTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -18,20 +20,11 @@ use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\PropPatch; class GroupPrincipalTest extends \Test\TestCase { - /** @var IConfig|MockObject */ - private $config; - - /** @var IGroupManager | MockObject */ - private $groupManager; - - /** @var IUserSession | MockObject */ - private $userSession; - - /** @var IManager | MockObject */ - private $shareManager; - - /** @var GroupPrincipalBackend */ - private $connector; + private IConfig&MockObject $config; + private IGroupManager&MockObject $groupManager; + private IUserSession&MockObject $userSession; + private IManager&MockObject $shareManager; + private GroupPrincipalBackend $connector; protected function setUp(): void { $this->groupManager = $this->createMock(IGroupManager::class); @@ -199,14 +192,7 @@ class GroupPrincipalTest extends \Test\TestCase { ['{DAV:}displayname' => 'Foo'])); } - /** - * @dataProvider searchPrincipalsDataProvider - * @param bool $sharingEnabled - * @param bool $groupSharingEnabled - * @param bool $groupsOnly - * @param string $test - * @param array $result - */ + #[\PHPUnit\Framework\Attributes\DataProvider('searchPrincipalsDataProvider')] public function testSearchPrincipals(bool $sharingEnabled, bool $groupSharingEnabled, bool $groupsOnly, string $test, array $result): void { $this->shareManager->expects($this->once()) ->method('shareAPIEnabled') @@ -264,7 +250,7 @@ class GroupPrincipalTest extends \Test\TestCase { ['{DAV:}displayname' => 'Foo'], $test)); } - public function searchPrincipalsDataProvider() { + public static function searchPrincipalsDataProvider(): array { return [ [true, true, false, 'allof', ['principals/groups/group1', 'principals/groups/group2', 'principals/groups/group3', 'principals/groups/group4', 'principals/groups/group5']], [true, true, false, 'anyof', ['principals/groups/group1', 'principals/groups/group2', 'principals/groups/group3', 'principals/groups/group4', 'principals/groups/group5']], @@ -277,14 +263,7 @@ class GroupPrincipalTest extends \Test\TestCase { ]; } - /** - * @dataProvider findByUriDataProvider - * @param bool $sharingEnabled - * @param bool $groupSharingEnabled - * @param bool $groupsOnly - * @param string $findUri - * @param string|null $result - */ + #[\PHPUnit\Framework\Attributes\DataProvider('findByUriDataProvider')] public function testFindByUri(bool $sharingEnabled, bool $groupSharingEnabled, bool $groupsOnly, string $findUri, ?string $result): void { $this->shareManager->expects($this->once()) ->method('shareAPIEnabled') @@ -320,7 +299,7 @@ class GroupPrincipalTest extends \Test\TestCase { $this->assertEquals($result, $this->connector->findByUri($findUri, 'principals/groups')); } - public function findByUriDataProvider() { + public static function findByUriDataProvider(): array { return [ [false, false, false, 'principal:principals/groups/group1', null], [false, false, false, 'principal:principals/groups/group3', null], @@ -337,10 +316,7 @@ class GroupPrincipalTest extends \Test\TestCase { ]; } - /** - * @return Group|MockObject - */ - private function mockGroup($gid) { + private function mockGroup(string $gid): Group&MockObject { $fooGroup = $this->createMock(Group::class); $fooGroup ->expects($this->exactly(1)) @@ -349,7 +325,7 @@ class GroupPrincipalTest extends \Test\TestCase { $fooGroup ->expects($this->exactly(1)) ->method('getDisplayName') - ->willReturn('Group '.$gid); + ->willReturn('Group ' . $gid); return $fooGroup; } } diff --git a/apps/dav/tests/unit/DAV/HookManagerTest.php b/apps/dav/tests/unit/DAV/HookManagerTest.php deleted file mode 100644 index 746727cd2b3..00000000000 --- a/apps/dav/tests/unit/DAV/HookManagerTest.php +++ /dev/null @@ -1,222 +0,0 @@ -<?php - -/** - * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2016 ownCloud, Inc. - * SPDX-License-Identifier: AGPL-3.0-only - */ -namespace OCA\DAV\Tests\unit\DAV; - -use OCA\DAV\CalDAV\CalDavBackend; -use OCA\DAV\CardDAV\CardDavBackend; -use OCA\DAV\CardDAV\SyncService; -use OCA\DAV\HookManager; -use OCP\Defaults; -use OCP\IL10N; -use OCP\IUser; -use OCP\IUserManager; -use PHPUnit\Framework\MockObject\MockObject; -use Test\TestCase; - -class HookManagerTest extends TestCase { - /** @var IL10N */ - private $l10n; - - protected function setUp(): void { - parent::setUp(); - $this->l10n = $this->createMock(IL10N::class); - $this->l10n - ->expects($this->any()) - ->method('t') - ->willReturnCallback(function ($text, $parameters = []) { - return vsprintf($text, $parameters); - }); - } - - public function test(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - $user->expects($this->once())->method('getUID')->willReturn('newUser'); - - /** @var IUserManager | MockObject $userManager */ - $userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var SyncService | MockObject $syncService */ - $syncService = $this->getMockBuilder(SyncService::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var Defaults | MockObject $syncService */ - $defaults = $this->getMockBuilder(Defaults::class) - ->disableOriginalConstructor() - ->getMock(); - - $defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca'); - - /** @var CalDavBackend | MockObject $cal */ - $cal = $this->getMockBuilder(CalDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $cal->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0); - $cal->expects($this->once())->method('createCalendar')->with( - 'principals/users/newUser', - 'personal', [ - '{DAV:}displayname' => 'Personal', - '{http://apple.com/ns/ical/}calendar-color' => '#745bca', - 'components' => 'VEVENT' - ]); - - /** @var CardDavBackend | MockObject $card */ - $card = $this->getMockBuilder(CardDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $card->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0); - $card->expects($this->once())->method('createAddressBook')->with( - 'principals/users/newUser', - 'contacts', ['{DAV:}displayname' => 'Contacts']); - - $hm = new HookManager($userManager, $syncService, $cal, $card, $defaults); - $hm->firstLogin($user); - } - - public function testWithExisting(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - $user->expects($this->once())->method('getUID')->willReturn('newUser'); - - /** @var IUserManager | MockObject $userManager */ - $userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var SyncService | MockObject $syncService */ - $syncService = $this->getMockBuilder(SyncService::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var Defaults | MockObject $syncService */ - $defaults = $this->getMockBuilder(Defaults::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var CalDavBackend | MockObject $cal */ - $cal = $this->getMockBuilder(CalDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $cal->expects($this->once())->method('getCalendarsForUserCount')->willReturn(1); - $cal->expects($this->never())->method('createCalendar'); - - /** @var CardDavBackend | MockObject $card */ - $card = $this->getMockBuilder(CardDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $card->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(1); - $card->expects($this->never())->method('createAddressBook'); - - $hm = new HookManager($userManager, $syncService, $cal, $card, $defaults); - $hm->firstLogin($user); - } - - public function testWithBirthdayCalendar(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - $user->expects($this->once())->method('getUID')->willReturn('newUser'); - - /** @var IUserManager | MockObject $userManager */ - $userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var SyncService | MockObject $syncService */ - $syncService = $this->getMockBuilder(SyncService::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var Defaults | MockObject $syncService */ - $defaults = $this->getMockBuilder(Defaults::class) - ->disableOriginalConstructor() - ->getMock(); - $defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca'); - - /** @var CalDavBackend | MockObject $cal */ - $cal = $this->getMockBuilder(CalDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $cal->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0); - $cal->expects($this->once())->method('createCalendar')->with( - 'principals/users/newUser', - 'personal', [ - '{DAV:}displayname' => 'Personal', - '{http://apple.com/ns/ical/}calendar-color' => '#745bca', - 'components' => 'VEVENT' - ]); - - /** @var CardDavBackend | MockObject $card */ - $card = $this->getMockBuilder(CardDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $card->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0); - $card->expects($this->once())->method('createAddressBook')->with( - 'principals/users/newUser', - 'contacts', ['{DAV:}displayname' => 'Contacts']); - - $hm = new HookManager($userManager, $syncService, $cal, $card, $defaults); - $hm->firstLogin($user); - } - - public function testDeleteCalendar(): void { - $user = $this->getMockBuilder(IUser::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var IUserManager | MockObject $userManager */ - $userManager = $this->getMockBuilder(IUserManager::class) - ->disableOriginalConstructor() - ->getMock(); - $userManager->expects($this->once())->method('get')->willReturn($user); - - /** @var SyncService | MockObject $syncService */ - $syncService = $this->getMockBuilder(SyncService::class) - ->disableOriginalConstructor() - ->getMock(); - $syncService->expects($this->once()) - ->method('deleteUser'); - - /** @var Defaults | MockObject $syncService */ - $defaults = $this->getMockBuilder(Defaults::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var CalDavBackend | MockObject $cal */ - $cal = $this->getMockBuilder(CalDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $cal->expects($this->once())->method('getUsersOwnCalendars')->willReturn([ - ['id' => 'personal'] - ]); - $cal->expects($this->once())->method('getSubscriptionsForUser')->willReturn([ - ['id' => 'some-subscription'] - ]); - $cal->expects($this->once())->method('deleteCalendar')->with('personal'); - $cal->expects($this->once())->method('deleteSubscription')->with('some-subscription'); - $cal->expects($this->once())->method('deleteAllSharesByUser'); - - /** @var CardDavBackend | MockObject $card */ - $card = $this->getMockBuilder(CardDavBackend::class) - ->disableOriginalConstructor() - ->getMock(); - $card->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([ - ['id' => 'personal'] - ]); - $card->expects($this->once())->method('deleteAddressBook'); - - $hm = new HookManager($userManager, $syncService, $cal, $card, $defaults); - $hm->preDeleteUser(['uid' => 'newUser']); - $hm->postDeleteUser(['uid' => 'newUser']); - } -} diff --git a/apps/dav/tests/unit/DAV/Listener/UserEventsListenerTest.php b/apps/dav/tests/unit/DAV/Listener/UserEventsListenerTest.php new file mode 100644 index 00000000000..8e410eb0a78 --- /dev/null +++ b/apps/dav/tests/unit/DAV/Listener/UserEventsListenerTest.php @@ -0,0 +1,183 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-FileCopyrightText: 2016 ownCloud, Inc. + * SPDX-License-Identifier: AGPL-3.0-only + */ + +namespace OCA\DAV\Tests\unit\DAV\Listener; + +use OCA\DAV\BackgroundJob\UserStatusAutomation; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CardDAV\CardDavBackend; +use OCA\DAV\CardDAV\SyncService; +use OCA\DAV\Listener\UserEventsListener; +use OCA\DAV\Service\ExampleContactService; +use OCA\DAV\Service\ExampleEventService; +use OCP\BackgroundJob\IJobList; +use OCP\Defaults; +use OCP\IUser; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Test\TestCase; + +class UserEventsListenerTest extends TestCase { + private IUserManager&MockObject $userManager; + private SyncService&MockObject $syncService; + private CalDavBackend&MockObject $calDavBackend; + private CardDavBackend&MockObject $cardDavBackend; + private Defaults&MockObject $defaults; + private ExampleContactService&MockObject $exampleContactService; + private ExampleEventService&MockObject $exampleEventService; + private LoggerInterface&MockObject $logger; + + private UserEventsListener $userEventsListener; + + protected function setUp(): void { + parent::setUp(); + + $this->userManager = $this->createMock(IUserManager::class); + $this->syncService = $this->createMock(SyncService::class); + $this->calDavBackend = $this->createMock(CalDavBackend::class); + $this->cardDavBackend = $this->createMock(CardDavBackend::class); + $this->defaults = $this->createMock(Defaults::class); + $this->exampleContactService = $this->createMock(ExampleContactService::class); + $this->exampleEventService = $this->createMock(ExampleEventService::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->jobList = $this->createMock(IJobList::class); + + $this->userEventsListener = new UserEventsListener( + $this->userManager, + $this->syncService, + $this->calDavBackend, + $this->cardDavBackend, + $this->defaults, + $this->exampleContactService, + $this->exampleEventService, + $this->logger, + $this->jobList, + ); + } + + public function test(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('newUser'); + + $this->defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca'); + + $this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0); + $this->calDavBackend->expects($this->once())->method('createCalendar')->with( + 'principals/users/newUser', + 'personal', [ + '{DAV:}displayname' => 'Personal', + '{http://apple.com/ns/ical/}calendar-color' => '#745bca', + 'components' => 'VEVENT' + ]) + ->willReturn(1000); + $this->calDavBackend->expects(self::never()) + ->method('getCalendarsForUser'); + $this->exampleEventService->expects(self::once()) + ->method('createExampleEvent') + ->with(1000); + + $this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0); + $this->cardDavBackend->expects($this->once())->method('createAddressBook')->with( + 'principals/users/newUser', + 'contacts', ['{DAV:}displayname' => 'Contacts']); + + $this->userEventsListener->firstLogin($user); + } + + public function testWithExisting(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('newUser'); + + $this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(1); + $this->calDavBackend->expects($this->never())->method('createCalendar'); + $this->calDavBackend->expects(self::never()) + ->method('createCalendar'); + $this->exampleEventService->expects(self::never()) + ->method('createExampleEvent'); + + $this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(1); + $this->cardDavBackend->expects($this->never())->method('createAddressBook'); + + $this->userEventsListener->firstLogin($user); + } + + public function testWithBirthdayCalendar(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('newUser'); + + $this->defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca'); + + $this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0); + $this->calDavBackend->expects($this->once())->method('createCalendar')->with( + 'principals/users/newUser', + 'personal', [ + '{DAV:}displayname' => 'Personal', + '{http://apple.com/ns/ical/}calendar-color' => '#745bca', + 'components' => 'VEVENT' + ]); + + $this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0); + $this->cardDavBackend->expects($this->once())->method('createAddressBook')->with( + 'principals/users/newUser', + 'contacts', ['{DAV:}displayname' => 'Contacts']); + + $this->userEventsListener->firstLogin($user); + } + + public function testDeleteCalendar(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('newUser'); + + $this->syncService->expects($this->once()) + ->method('deleteUser'); + + $this->calDavBackend->expects($this->once())->method('getUsersOwnCalendars')->willReturn([ + ['id' => 'personal'] + ]); + $this->calDavBackend->expects($this->once())->method('getSubscriptionsForUser')->willReturn([ + ['id' => 'some-subscription'] + ]); + $this->calDavBackend->expects($this->once())->method('deleteCalendar')->with('personal'); + $this->calDavBackend->expects($this->once())->method('deleteSubscription')->with('some-subscription'); + $this->calDavBackend->expects($this->once())->method('deleteAllSharesByUser'); + + $this->cardDavBackend->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([ + ['id' => 'personal'] + ]); + $this->cardDavBackend->expects($this->once())->method('deleteAddressBook'); + + $this->userEventsListener->preDeleteUser($user); + $this->userEventsListener->postDeleteUser('newUser'); + } + + public function testDeleteUserAutomationEvent(): void { + $user = $this->createMock(IUser::class); + $user->expects($this->once())->method('getUID')->willReturn('newUser'); + + $this->syncService->expects($this->once()) + ->method('deleteUser'); + + $this->calDavBackend->expects($this->once())->method('getUsersOwnCalendars')->willReturn([ + ['id' => []] + ]); + $this->calDavBackend->expects($this->once())->method('getSubscriptionsForUser')->willReturn([ + ['id' => []] + ]); + $this->cardDavBackend->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([ + ['id' => []] + ]); + + $this->jobList->expects(self::once())->method('remove')->with(UserStatusAutomation::class, ['userId' => 'newUser']); + + $this->userEventsListener->preDeleteUser($user); + $this->userEventsListener->postDeleteUser('newUser'); + } +} diff --git a/apps/dav/tests/unit/DAV/Sharing/BackendTest.php b/apps/dav/tests/unit/DAV/Sharing/BackendTest.php index 344d57d1808..556a623a73f 100644 --- a/apps/dav/tests/unit/DAV/Sharing/BackendTest.php +++ b/apps/dav/tests/unit/DAV/Sharing/BackendTest.php @@ -24,14 +24,14 @@ use Test\TestCase; class BackendTest extends TestCase { - private IDBConnection|MockObject $db; - private IUserManager|MockObject $userManager; - private IGroupManager|MockObject $groupManager; - private MockObject|Principal $principalBackend; - private MockObject|ICache $shareCache; - private LoggerInterface|MockObject $logger; - private MockObject|ICacheFactory $cacheFactory; - private Service|MockObject $calendarService; + private IDBConnection&MockObject $db; + private IUserManager&MockObject $userManager; + private IGroupManager&MockObject $groupManager; + private Principal&MockObject $principalBackend; + private ICache&MockObject $shareCache; + private LoggerInterface&MockObject $logger; + private ICacheFactory&MockObject $cacheFactory; + private Service&MockObject $calendarService; private CalendarSharingBackend $backend; protected function setUp(): void { @@ -214,10 +214,7 @@ class BackendTest extends TestCase { 'getResourceId' => 42, ]); $remove = [ - [ - 'href' => 'principal:principals/users/bob', - 'readOnly' => true, - ] + 'principal:principals/users/bob', ]; $principal = 'principals/users/bob'; @@ -229,9 +226,6 @@ class BackendTest extends TestCase { $this->calendarService->expects(self::once()) ->method('deleteShare') ->with($shareable->getResourceId(), $principal); - $this->calendarService->expects(self::once()) - ->method('hasGroupShare') - ->willReturn(false); $this->calendarService->expects(self::never()) ->method('unshare'); @@ -244,10 +238,7 @@ class BackendTest extends TestCase { 'getResourceId' => 42, ]); $remove = [ - [ - 'href' => 'principal:principals/users/bob', - 'readOnly' => true, - ] + 'principal:principals/users/bob', ]; $oldShares = [ [ @@ -269,13 +260,8 @@ class BackendTest extends TestCase { $this->calendarService->expects(self::once()) ->method('deleteShare') ->with($shareable->getResourceId(), 'principals/users/bob'); - $this->calendarService->expects(self::once()) - ->method('hasGroupShare') - ->with($oldShares) - ->willReturn(true); - $this->calendarService->expects(self::once()) - ->method('unshare') - ->with($shareable->getResourceId(), 'principals/users/bob'); + $this->calendarService->expects(self::never()) + ->method('unshare'); $this->backend->updateShares($shareable, [], $remove, $oldShares); } diff --git a/apps/dav/tests/unit/DAV/Sharing/PluginTest.php b/apps/dav/tests/unit/DAV/Sharing/PluginTest.php index 9c6950f19e8..7a88f7cc5dd 100644 --- a/apps/dav/tests/unit/DAV/Sharing/PluginTest.php +++ b/apps/dav/tests/unit/DAV/Sharing/PluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -12,6 +13,7 @@ use OCA\DAV\DAV\Sharing\IShareable; use OCA\DAV\DAV\Sharing\Plugin; use OCP\IConfig; use OCP\IRequest; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Server; use Sabre\DAV\SimpleCollection; use Sabre\HTTP\Request; @@ -19,32 +21,24 @@ use Sabre\HTTP\Response; use Test\TestCase; class PluginTest extends TestCase { - - /** @var Plugin */ - private $plugin; - /** @var Server */ - private $server; - /** @var IShareable | \PHPUnit\Framework\MockObject\MockObject */ - private $book; + private Plugin $plugin; + private Server $server; + private IShareable&MockObject $book; protected function setUp(): void { parent::setUp(); - /** @var Auth | \PHPUnit\Framework\MockObject\MockObject $authBackend */ - $authBackend = $this->getMockBuilder(Auth::class)->disableOriginalConstructor()->getMock(); + $authBackend = $this->createMock(Auth::class); $authBackend->method('isDavAuthenticated')->willReturn(true); - /** @var IRequest $request */ - $request = $this->getMockBuilder(IRequest::class)->disableOriginalConstructor()->getMock(); + $request = $this->createMock(IRequest::class); $config = $this->createMock(IConfig::class); $this->plugin = new Plugin($authBackend, $request, $config); $root = new SimpleCollection('root'); $this->server = new \Sabre\DAV\Server($root); /** @var SimpleCollection $node */ - $this->book = $this->getMockBuilder(IShareable::class)-> - disableOriginalConstructor()-> - getMock(); + $this->book = $this->createMock(IShareable::class); $this->book->method('getName')->willReturn('addressbook1.vcf'); $root->addChild($this->book); $this->plugin->initialize($this->server); diff --git a/apps/dav/tests/unit/DAV/Sharing/SharingServiceTest.php b/apps/dav/tests/unit/DAV/Sharing/SharingServiceTest.php deleted file mode 100644 index fad077eeda3..00000000000 --- a/apps/dav/tests/unit/DAV/Sharing/SharingServiceTest.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -declare(strict_types=1); -/** - * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -namespace OCA\DAV\Tests\unit\DAV\Sharing; - -use OCA\DAV\CalDAV\Sharing\Service; -use OCA\DAV\DAV\Sharing\SharingMapper; -use OCA\DAV\DAV\Sharing\SharingService; -use Test\TestCase; - -class SharingServiceTest extends TestCase { - - private SharingService $service; - - protected function setUp(): void { - parent::setUp(); - $this->service = new Service($this->createMock(SharingMapper::class)); - } - - public function testHasGroupShare(): void { - $oldShares = [ - [ - 'href' => 'principal:principals/groups/bob', - 'commonName' => 'bob', - 'status' => 1, - 'readOnly' => true, - '{http://owncloud.org/ns}principal' => 'principals/groups/bob', - '{http://owncloud.org/ns}group-share' => true, - ], - [ - 'href' => 'principal:principals/users/bob', - 'commonName' => 'bob', - 'status' => 1, - 'readOnly' => true, - '{http://owncloud.org/ns}principal' => 'principals/users/bob', - '{http://owncloud.org/ns}group-share' => false, - ] - ]; - - $this->assertTrue($this->service->hasGroupShare($oldShares)); - - $oldShares = [ - [ - 'href' => 'principal:principals/users/bob', - 'commonName' => 'bob', - 'status' => 1, - 'readOnly' => true, - '{http://owncloud.org/ns}principal' => 'principals/users/bob', - '{http://owncloud.org/ns}group-share' => false, - ] - ]; - $this->assertFalse($this->service->hasGroupShare($oldShares)); - } -} diff --git a/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php b/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php index 32916804080..3df861accf2 100644 --- a/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php +++ b/apps/dav/tests/unit/DAV/SystemPrincipalBackendTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,22 +9,19 @@ namespace OCA\DAV\Tests\unit\DAV; use OCA\DAV\DAV\SystemPrincipalBackend; +use Sabre\DAV\Exception; use Test\TestCase; class SystemPrincipalBackendTest extends TestCase { - /** - * @dataProvider providesPrefix - * @param $expected - * @param $prefix - */ - public function testGetPrincipalsByPrefix($expected, $prefix): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesPrefix')] + public function testGetPrincipalsByPrefix(array $expected, string $prefix): void { $backend = new SystemPrincipalBackend(); $result = $backend->getPrincipalsByPrefix($prefix); $this->assertEquals($expected, $result); } - public function providesPrefix() { + public static function providesPrefix(): array { return [ [[], ''], [[[ @@ -38,18 +36,14 @@ class SystemPrincipalBackendTest extends TestCase { ]; } - /** - * @dataProvider providesPath - * @param $expected - * @param $path - */ - public function testGetPrincipalByPath($expected, $path): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesPath')] + public function testGetPrincipalByPath(?array $expected, string $path): void { $backend = new SystemPrincipalBackend(); $result = $backend->getPrincipalByPath($path); $this->assertEquals($expected, $result); } - public function providesPath() { + public static function providesPath(): array { return [ [null, ''], [null, 'principals'], @@ -61,59 +55,43 @@ class SystemPrincipalBackendTest extends TestCase { ]; } - /** - * @dataProvider providesPrincipalForGetGroupMemberSet - * - * @param string $principal - * @throws \Sabre\DAV\Exception - */ - public function testGetGroupMemberSetExceptional($principal): void { - $this->expectException(\Sabre\DAV\Exception::class); + #[\PHPUnit\Framework\Attributes\DataProvider('providesPrincipalForGetGroupMemberSet')] + public function testGetGroupMemberSetExceptional(?string $principal): void { + $this->expectException(Exception::class); $this->expectExceptionMessage('Principal not found'); $backend = new SystemPrincipalBackend(); $backend->getGroupMemberSet($principal); } - public function providesPrincipalForGetGroupMemberSet() { + public static function providesPrincipalForGetGroupMemberSet(): array { return [ [null], ['principals/system'], ]; } - /** - * @throws \Sabre\DAV\Exception - */ public function testGetGroupMemberSet(): void { $backend = new SystemPrincipalBackend(); $result = $backend->getGroupMemberSet('principals/system/system'); $this->assertEquals(['principals/system/system'], $result); } - /** - * @dataProvider providesPrincipalForGetGroupMembership - * - * @param string $principal - * @throws \Sabre\DAV\Exception - */ - public function testGetGroupMembershipExceptional($principal): void { - $this->expectException(\Sabre\DAV\Exception::class); + #[\PHPUnit\Framework\Attributes\DataProvider('providesPrincipalForGetGroupMembership')] + public function testGetGroupMembershipExceptional(string $principal): void { + $this->expectException(Exception::class); $this->expectExceptionMessage('Principal not found'); $backend = new SystemPrincipalBackend(); $backend->getGroupMembership($principal); } - public function providesPrincipalForGetGroupMembership() { + public static function providesPrincipalForGetGroupMembership(): array { return [ ['principals/system/a'], ]; } - /** - * @throws \Sabre\DAV\Exception - */ public function testGetGroupMembership(): void { $backend = new SystemPrincipalBackend(); $result = $backend->getGroupMembership('principals/system/system'); diff --git a/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php b/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php index 63dd5b9145c..eefbc53fd22 100644 --- a/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php +++ b/apps/dav/tests/unit/DAV/ViewOnlyPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2019 ownCloud GmbH @@ -15,34 +16,34 @@ use OCA\Files_Versions\Sabre\VersionFile; use OCA\Files_Versions\Versions\IVersion; use OCP\Files\File; use OCP\Files\Folder; +use OCP\Files\Storage\ISharedStorage; use OCP\Files\Storage\IStorage; use OCP\IUser; use OCP\Share\IAttributes; use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Server; use Sabre\DAV\Tree; use Sabre\HTTP\RequestInterface; use Test\TestCase; class ViewOnlyPluginTest extends TestCase { - + private Tree&MockObject $tree; + private RequestInterface&MockObject $request; + private Folder&MockObject $userFolder; private ViewOnlyPlugin $plugin; - /** @var Tree | \PHPUnit\Framework\MockObject\MockObject */ - private $tree; - /** @var RequestInterface | \PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var Folder | \PHPUnit\Framework\MockObject\MockObject */ - private $userFolder; public function setUp(): void { + parent::setUp(); + $this->userFolder = $this->createMock(Folder::class); - $this->plugin = new ViewOnlyPlugin( - $this->userFolder, - ); $this->request = $this->createMock(RequestInterface::class); $this->tree = $this->createMock(Tree::class); - $server = $this->createMock(Server::class); + + $this->plugin = new ViewOnlyPlugin( + $this->userFolder, + ); $server->tree = $this->tree; $this->plugin->initialize($server); @@ -65,32 +66,36 @@ class ViewOnlyPluginTest extends TestCase { $storage = $this->createMock(IStorage::class); $file->method('getStorage')->willReturn($storage); - $storage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(false); + $storage->method('instanceOfStorage')->with(ISharedStorage::class)->willReturn(false); $this->assertTrue($this->plugin->checkViewOnly($this->request)); } - public function providesDataForCanGet(): array { + public static function providesDataForCanGet(): array { return [ // has attribute permissions-download enabled - can get file - [false, true, true], + [false, true, true, true], // has no attribute permissions-download - can get file - [false, null, true], - // has attribute permissions-download disabled- cannot get the file - [false, false, false], + [false, null, true, true], // has attribute permissions-download enabled - can get file version - [true, true, true], + [true, true, true, true], // has no attribute permissions-download - can get file version - [true, null, true], - // has attribute permissions-download disabled- cannot get the file version - [true, false, false], + [true, null, true, true], + // has attribute permissions-download disabled - cannot get the file + [false, false, false, false], + // has attribute permissions-download disabled - cannot get the file version + [true, false, false, false], + + // Has global allowViewWithoutDownload option enabled + // has attribute permissions-download disabled - can get file + [false, false, false, true], + // has attribute permissions-download disabled - can get file version + [true, false, false, true], ]; } - /** - * @dataProvider providesDataForCanGet - */ - public function testCanGet(bool $isVersion, ?bool $attrEnabled, bool $expectCanDownloadFile): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesDataForCanGet')] + public function testCanGet(bool $isVersion, ?bool $attrEnabled, bool $expectCanDownloadFile, bool $allowViewWithoutDownload): void { $nodeInfo = $this->createMock(File::class); if ($isVersion) { $davPath = 'versions/alice/versions/117/123456'; @@ -131,24 +136,28 @@ class ViewOnlyPluginTest extends TestCase { $this->request->expects($this->once())->method('getPath')->willReturn($davPath); $this->tree->expects($this->once()) - ->method('getNodeForPath') - ->with($davPath) - ->willReturn($davNode); + ->method('getNodeForPath') + ->with($davPath) + ->willReturn($davNode); $storage = $this->createMock(SharedStorage::class); $share = $this->createMock(IShare::class); $nodeInfo->expects($this->once()) ->method('getStorage') ->willReturn($storage); - $storage->method('instanceOfStorage')->with(SharedStorage::class)->willReturn(true); + $storage->method('instanceOfStorage')->with(ISharedStorage::class)->willReturn(true); $storage->method('getShare')->willReturn($share); $extAttr = $this->createMock(IAttributes::class); $share->method('getAttributes')->willReturn($extAttr); $extAttr->expects($this->once()) - ->method('getAttribute') - ->with('permissions', 'download') - ->willReturn($attrEnabled); + ->method('getAttribute') + ->with('permissions', 'download') + ->willReturn($attrEnabled); + + $share->expects($this->once()) + ->method('canSeeContent') + ->willReturn($allowViewWithoutDownload); if (!$expectCanDownloadFile) { $this->expectException(Forbidden::class); diff --git a/apps/dav/tests/unit/Direct/DirectFileTest.php b/apps/dav/tests/unit/Direct/DirectFileTest.php index 07d19807ef0..f6f0f49fa8c 100644 --- a/apps/dav/tests/unit/Direct/DirectFileTest.php +++ b/apps/dav/tests/unit/Direct/DirectFileTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\Direct; +namespace OCA\DAV\Tests\unit\Direct; use OCA\DAV\Db\Direct; use OCA\DAV\Direct\DirectFile; @@ -14,28 +14,17 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\File; use OCP\Files\Folder; use OCP\Files\IRootFolder; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\Forbidden; use Test\TestCase; class DirectFileTest extends TestCase { - - /** @var Direct */ - private $direct; - - /** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject */ - private $rootFolder; - - /** @var Folder|\PHPUnit\Framework\MockObject\MockObject */ - private $userFolder; - - /** @var File|\PHPUnit\Framework\MockObject\MockObject */ - private $file; - - /** @var DirectFile */ - private $directFile; - - /** @var IEventDispatcher */ - private $eventDispatcher; + private Direct $direct; + private IRootFolder&MockObject $rootFolder; + private Folder&MockObject $userFolder; + private File&MockObject $file; + private IEventDispatcher&MockObject $eventDispatcher; + private DirectFile $directFile; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/Direct/DirectHomeTest.php b/apps/dav/tests/unit/Direct/DirectHomeTest.php index 1134f0cd3af..94c82c2b7c5 100644 --- a/apps/dav/tests/unit/Direct/DirectHomeTest.php +++ b/apps/dav/tests/unit/Direct/DirectHomeTest.php @@ -6,7 +6,7 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\Direct; +namespace OCA\DAV\Tests\unit\Direct; use OCA\DAV\Db\Direct; use OCA\DAV\Db\DirectMapper; @@ -18,33 +18,20 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\Files\IRootFolder; use OCP\IRequest; use OCP\Security\Bruteforce\IThrottler; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\MethodNotAllowed; use Sabre\DAV\Exception\NotFound; use Test\TestCase; class DirectHomeTest extends TestCase { - - /** @var DirectMapper|\PHPUnit\Framework\MockObject\MockObject */ - private $directMapper; - - /** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject */ - private $rootFolder; - - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; - - /** @var IThrottler|\PHPUnit\Framework\MockObject\MockObject */ - private $throttler; - - /** @var IRequest */ - private $request; - - /** @var DirectHome */ - private $directHome; - - /** @var IEventDispatcher */ - private $eventDispatcher; + private DirectMapper&MockObject $directMapper; + private IRootFolder&MockObject $rootFolder; + private ITimeFactory&MockObject $timeFactory; + private IThrottler&MockObject $throttler; + private IRequest&MockObject $request; + private IEventDispatcher&MockObject $eventDispatcher; + private DirectHome $directHome; protected function setUp(): void { parent::setUp(); @@ -160,7 +147,7 @@ class DirectHomeTest extends TestCase { '1.2.3.4' ); $this->throttler->expects($this->once()) - ->method('sleepDelay') + ->method('sleepDelayOrThrowOnMax') ->with( '1.2.3.4', 'directlink' diff --git a/apps/dav/tests/unit/Files/FileSearchBackendTest.php b/apps/dav/tests/unit/Files/FileSearchBackendTest.php index f6fe8b1c116..c6d6f85347b 100644 --- a/apps/dav/tests/unit/Files/FileSearchBackendTest.php +++ b/apps/dav/tests/unit/Files/FileSearchBackendTest.php @@ -1,10 +1,12 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Files; +namespace OCA\DAV\Tests\unit\Files; use OC\Files\Search\SearchComparison; use OC\Files\Search\SearchQuery; @@ -13,6 +15,7 @@ use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Connector\Sabre\File; use OCA\DAV\Connector\Sabre\FilesPlugin; use OCA\DAV\Connector\Sabre\ObjectTree; +use OCA\DAV\Connector\Sabre\Server; use OCA\DAV\Files\FileSearchBackend; use OCP\Files\FileInfo; use OCP\Files\Folder; @@ -23,36 +26,25 @@ use OCP\Files\Search\ISearchQuery; use OCP\FilesMetadata\IFilesMetadataManager; use OCP\IUser; use OCP\Share\IManager; +use PHPUnit\Framework\MockObject\MockObject; use SearchDAV\Backend\SearchPropertyDefinition; use SearchDAV\Query\Limit; +use SearchDAV\Query\Literal; use SearchDAV\Query\Operator; use SearchDAV\Query\Query; +use SearchDAV\Query\Scope; use Test\TestCase; class FileSearchBackendTest extends TestCase { - /** @var ObjectTree|\PHPUnit\Framework\MockObject\MockObject */ - private $tree; - - /** @var IUser */ - private $user; - - /** @var IRootFolder|\PHPUnit\Framework\MockObject\MockObject */ - private $rootFolder; - - /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ - private $shareManager; - - /** @var View|\PHPUnit\Framework\MockObject\MockObject */ - private $view; - - /** @var Folder|\PHPUnit\Framework\MockObject\MockObject */ - private $searchFolder; - - /** @var FileSearchBackend */ - private $search; - - /** @var Directory|\PHPUnit\Framework\MockObject\MockObject */ - private $davFolder; + private ObjectTree&MockObject $tree; + private Server&MockObject $server; + private IUser&MockObject $user; + private IRootFolder&MockObject $rootFolder; + private IManager&MockObject $shareManager; + private View&MockObject $view; + private Folder&MockObject $searchFolder; + private Directory&MockObject $davFolder; + private FileSearchBackend $search; protected function setUp(): void { parent::setUp(); @@ -62,11 +54,14 @@ class FileSearchBackendTest extends TestCase { ->method('getUID') ->willReturn('test'); - $this->tree = $this->getMockBuilder(ObjectTree::class) - ->disableOriginalConstructor() - ->getMock(); - + $this->tree = $this->createMock(ObjectTree::class); + $this->server = $this->createMock(Server::class); $this->view = $this->createMock(View::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + $this->shareManager = $this->createMock(IManager::class); + $this->searchFolder = $this->createMock(Folder::class); + $fileInfo = $this->createMock(FileInfo::class); + $this->davFolder = $this->createMock(Directory::class); $this->view->expects($this->any()) ->method('getRoot') @@ -76,16 +71,6 @@ class FileSearchBackendTest extends TestCase { ->method('getRelativePath') ->willReturnArgument(0); - $this->rootFolder = $this->createMock(IRootFolder::class); - - $this->shareManager = $this->createMock(IManager::class); - - $this->searchFolder = $this->createMock(Folder::class); - - $fileInfo = $this->createMock(FileInfo::class); - - $this->davFolder = $this->createMock(Directory::class); - $this->davFolder->expects($this->any()) ->method('getFileInfo') ->willReturn($fileInfo); @@ -96,7 +81,7 @@ class FileSearchBackendTest extends TestCase { $filesMetadataManager = $this->createMock(IFilesMetadataManager::class); - $this->search = new FileSearchBackend($this->tree, $this->user, $this->rootFolder, $this->shareManager, $this->view, $filesMetadataManager); + $this->search = new FileSearchBackend($this->server, $this->tree, $this->user, $this->rootFolder, $this->shareManager, $this->view, $filesMetadataManager); } public function testSearchFilename(): void { @@ -259,8 +244,8 @@ class FileSearchBackendTest extends TestCase { $this->search->search($query); } - private function getBasicQuery($type, $property, $value = null) { - $scope = new \SearchDAV\Query\Scope('/', 'infinite'); + private function getBasicQuery(string $type, string $property, int|string|null $value = null) { + $scope = new Scope('/', 'infinite'); $scope->path = '/'; $from = [$scope]; $orderBy = []; @@ -268,12 +253,12 @@ class FileSearchBackendTest extends TestCase { if (is_null($value)) { $where = new Operator( $type, - [new \SearchDAV\Query\Literal($property)] + [new Literal($property)] ); } else { $where = new Operator( $type, - [new SearchPropertyDefinition($property, true, true, true), new \SearchDAV\Query\Literal($value)] + [new SearchPropertyDefinition($property, true, true, true), new Literal($value)] ); } $limit = new Limit(); @@ -346,11 +331,11 @@ class FileSearchBackendTest extends TestCase { [ new Operator( Operator::OPERATION_EQUAL, - [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new \SearchDAV\Query\Literal('image/png')] + [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new Literal('image/png')] ), new Operator( Operator::OPERATION_EQUAL, - [new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, true), new \SearchDAV\Query\Literal($this->user->getUID())] + [new SearchPropertyDefinition(FilesPlugin::OWNER_ID_PROPERTYNAME, true, true, true), new Literal($this->user->getUID())] ), ] ); @@ -379,7 +364,7 @@ class FileSearchBackendTest extends TestCase { $innerOperator = new Operator( Operator::OPERATION_EQUAL, - [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new \SearchDAV\Query\Literal('image/png')] + [new SearchPropertyDefinition('{DAV:}getcontenttype', true, true, true), new Literal('image/png')] ); // 5 child operators $level1Operator = new Operator( @@ -420,4 +405,17 @@ class FileSearchBackendTest extends TestCase { $this->expectException(\InvalidArgumentException::class); $this->search->search($query); } + + public function testPreloadPropertyFor(): void { + $node1 = $this->createMock(File::class); + $node2 = $this->createMock(Directory::class); + $nodes = [$node1, $node2]; + $requestProperties = ['{DAV:}getcontenttype', '{DAV:}getlastmodified']; + + $this->server->expects($this->once()) + ->method('emit') + ->with('preloadProperties', [$nodes, $requestProperties]); + + $this->search->preloadPropertyFor($nodes, $requestProperties); + } } diff --git a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php index 3458708b23b..dc0e884f07c 100644 --- a/apps/dav/tests/unit/Files/MultipartRequestParserTest.php +++ b/apps/dav/tests/unit/Files/MultipartRequestParserTest.php @@ -1,61 +1,68 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-only */ -namespace OCA\DAV\Tests\unit\DAV; +namespace OCA\DAV\Tests\unit\Files; use OCA\DAV\BulkUpload\MultipartRequestParser; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; +use Sabre\HTTP\RequestInterface; use Test\TestCase; class MultipartRequestParserTest extends TestCase { - protected LoggerInterface $logger; + protected LoggerInterface&MockObject $logger; protected function setUp(): void { + parent::setUp(); $this->logger = $this->createMock(LoggerInterface::class); } - private function getValidBodyObject() { + private static function getValidBodyObject(): array { return [ [ - "headers" => [ - "Content-Length" => 7, - "X-File-MD5" => "4f2377b4d911f7ec46325fe603c3af03", - "X-File-Path" => "/coucou.txt" + 'headers' => [ + 'Content-Length' => 7, + 'X-File-MD5' => '4f2377b4d911f7ec46325fe603c3af03', + 'OC-Checksum' => 'md5:4f2377b4d911f7ec46325fe603c3af03', + 'X-File-Path' => '/coucou.txt' ], - "content" => "Coucou\n" + 'content' => "Coucou\n" ] ]; } - private function getMultipartParser(array $parts, array $headers = [], string $boundary = "boundary_azertyuiop"): MultipartRequestParser { - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + private function getMultipartParser(array $parts, array $headers = [], string $boundary = 'boundary_azertyuiop'): MultipartRequestParser { + /** @var RequestInterface&MockObject $request */ + $request = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() ->getMock(); - $headers = array_merge(['Content-Type' => 'multipart/related; boundary='.$boundary], $headers); + $headers = array_merge(['Content-Type' => 'multipart/related; boundary=' . $boundary], $headers); $request->expects($this->any()) ->method('getHeader') ->willReturnCallback(function (string $key) use (&$headers) { return $headers[$key]; }); - $body = ""; + $body = ''; foreach ($parts as $part) { - $body .= '--'.$boundary."\r\n"; + $body .= '--' . $boundary . "\r\n"; foreach ($part['headers'] as $headerKey => $headerPart) { - $body .= $headerKey.": ".$headerPart."\r\n"; + $body .= $headerKey . ': ' . $headerPart . "\r\n"; } $body .= "\r\n"; - $body .= $part['content']."\r\n"; + $body .= $part['content'] . "\r\n"; } - $body .= '--'.$boundary."--"; + $body .= '--' . $boundary . '--'; $stream = fopen('php://temp', 'r+'); fwrite($stream, $body); @@ -73,8 +80,9 @@ class MultipartRequestParserTest extends TestCase { * Test validation of the request's body type */ public function testBodyTypeValidation(): void { - $bodyStream = "I am not a stream, but pretend to be"; - $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + $bodyStream = 'I am not a stream, but pretend to be'; + /** @var RequestInterface&MockObject $request */ + $request = $this->getMockBuilder(RequestInterface::class) ->disableOriginalConstructor() ->getMock(); $request->expects($this->any()) @@ -88,50 +96,91 @@ class MultipartRequestParserTest extends TestCase { /** * Test with valid request. * - valid boundary - * - valid md5 hash + * - valid hash * - valid content-length * - valid file content * - valid file path */ public function testValidRequest(): void { - $multipartParser = $this->getMultipartParser( - $this->getValidBodyObject() - ); + $bodyObject = self::getValidBodyObject(); + unset($bodyObject['0']['headers']['X-File-MD5']); + + $multipartParser = $this->getMultipartParser($bodyObject); [$headers, $content] = $multipartParser->parseNextPart(); - $this->assertSame((int)$headers["content-length"], 7, "Content-Length header should be the same as provided."); - $this->assertSame($headers["x-file-md5"], "4f2377b4d911f7ec46325fe603c3af03", "X-File-MD5 header should be the same as provided."); - $this->assertSame($headers["x-file-path"], "/coucou.txt", "X-File-Path header should be the same as provided."); + $this->assertSame((int)$headers['content-length'], 7, 'Content-Length header should be the same as provided.'); + $this->assertSame($headers['oc-checksum'], 'md5:4f2377b4d911f7ec46325fe603c3af03', 'OC-Checksum header should be the same as provided.'); + $this->assertSame($headers['x-file-path'], '/coucou.txt', 'X-File-Path header should be the same as provided.'); + + $this->assertSame($content, "Coucou\n", 'Content should be the same'); + } - $this->assertSame($content, "Coucou\n", "Content should be the same"); + /** + * Test with valid request. + * - valid boundary + * - valid md5 hash + * - valid content-length + * - valid file content + * - valid file path + */ + public function testValidRequestWithMd5(): void { + $bodyObject = self::getValidBodyObject(); + unset($bodyObject['0']['headers']['OC-Checksum']); + + $multipartParser = $this->getMultipartParser($bodyObject); + + [$headers, $content] = $multipartParser->parseNextPart(); + + $this->assertSame((int)$headers['content-length'], 7, 'Content-Length header should be the same as provided.'); + $this->assertSame($headers['x-file-md5'], '4f2377b4d911f7ec46325fe603c3af03', 'X-File-MD5 header should be the same as provided.'); + $this->assertSame($headers['x-file-path'], '/coucou.txt', 'X-File-Path header should be the same as provided.'); + + $this->assertSame($content, "Coucou\n", 'Content should be the same'); + } + + /** + * Test with invalid hash. + */ + public function testInvalidHash(): void { + $bodyObject = self::getValidBodyObject(); + $bodyObject['0']['headers']['OC-Checksum'] = 'md5:f2377b4d911f7ec46325fe603c3af03'; + unset($bodyObject['0']['headers']['X-File-MD5']); + $multipartParser = $this->getMultipartParser( + $bodyObject + ); + + $this->expectExceptionMessage('Computed md5 hash is incorrect (4f2377b4d911f7ec46325fe603c3af03).'); + $multipartParser->parseNextPart(); } /** * Test with invalid md5 hash. */ public function testInvalidMd5Hash(): void { - $bodyObject = $this->getValidBodyObject(); - $bodyObject["0"]["headers"]["X-File-MD5"] = "f2377b4d911f7ec46325fe603c3af03"; + $bodyObject = self::getValidBodyObject(); + unset($bodyObject['0']['headers']['OC-Checksum']); + $bodyObject['0']['headers']['X-File-MD5'] = 'f2377b4d911f7ec46325fe603c3af03'; $multipartParser = $this->getMultipartParser( $bodyObject ); - $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $this->expectExceptionMessage('Computed md5 hash is incorrect (4f2377b4d911f7ec46325fe603c3af03).'); $multipartParser->parseNextPart(); } /** - * Test with a null md5 hash. + * Test with a null hash headers. */ - public function testNullMd5Hash(): void { - $bodyObject = $this->getValidBodyObject(); - unset($bodyObject["0"]["headers"]["X-File-MD5"]); + public function testNullHash(): void { + $bodyObject = self::getValidBodyObject(); + unset($bodyObject['0']['headers']['OC-Checksum']); + unset($bodyObject['0']['headers']['X-File-MD5']); $multipartParser = $this->getMultipartParser( $bodyObject ); - $this->expectExceptionMessage('The X-File-MD5 header must not be null.'); + $this->expectExceptionMessage('The hash headers must not be null.'); $multipartParser->parseNextPart(); } @@ -139,8 +188,8 @@ class MultipartRequestParserTest extends TestCase { * Test with a null Content-Length. */ public function testNullContentLength(): void { - $bodyObject = $this->getValidBodyObject(); - unset($bodyObject["0"]["headers"]["Content-Length"]); + $bodyObject = self::getValidBodyObject(); + unset($bodyObject['0']['headers']['Content-Length']); $multipartParser = $this->getMultipartParser( $bodyObject ); @@ -153,13 +202,13 @@ class MultipartRequestParserTest extends TestCase { * Test with a lower Content-Length. */ public function testLowerContentLength(): void { - $bodyObject = $this->getValidBodyObject(); - $bodyObject["0"]["headers"]["Content-Length"] = 6; + $bodyObject = self::getValidBodyObject(); + $bodyObject['0']['headers']['Content-Length'] = 6; $multipartParser = $this->getMultipartParser( $bodyObject ); - $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $this->expectExceptionMessage('Computed md5 hash is incorrect (41060d3ddfdf63e68fc2bf196f652ee9).'); $multipartParser->parseNextPart(); } @@ -167,13 +216,13 @@ class MultipartRequestParserTest extends TestCase { * Test with a higher Content-Length. */ public function testHigherContentLength(): void { - $bodyObject = $this->getValidBodyObject(); - $bodyObject["0"]["headers"]["Content-Length"] = 8; + $bodyObject = self::getValidBodyObject(); + $bodyObject['0']['headers']['Content-Length'] = 8; $multipartParser = $this->getMultipartParser( $bodyObject ); - $this->expectExceptionMessage('Computed md5 hash is incorrect.'); + $this->expectExceptionMessage('Computed md5 hash is incorrect (0161002bbee6a744f18741b8a914e413).'); $multipartParser->parseNextPart(); } @@ -181,7 +230,7 @@ class MultipartRequestParserTest extends TestCase { * Test with wrong boundary in body. */ public function testWrongBoundary(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $multipartParser = $this->getMultipartParser( $bodyObject, ['Content-Type' => 'multipart/related; boundary=boundary_poiuytreza'] @@ -195,7 +244,7 @@ class MultipartRequestParserTest extends TestCase { * Test with no boundary in request headers. */ public function testNoBoundaryInHeader(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $this->expectExceptionMessage('Error while parsing boundary in Content-Type header.'); $this->getMultipartParser( $bodyObject, @@ -207,7 +256,7 @@ class MultipartRequestParserTest extends TestCase { * Test with no boundary in the request's headers. */ public function testNoBoundaryInBody(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $multipartParser = $this->getMultipartParser( $bodyObject, ['Content-Type' => 'multipart/related; boundary=boundary_azertyuiop'], @@ -222,7 +271,7 @@ class MultipartRequestParserTest extends TestCase { * Test with a boundary with quotes in the request's headers. */ public function testBoundaryWithQuotes(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $multipartParser = $this->getMultipartParser( $bodyObject, ['Content-Type' => 'multipart/related; boundary="boundary_azertyuiop"'], @@ -238,7 +287,7 @@ class MultipartRequestParserTest extends TestCase { * Test with a wrong Content-Type in the request's headers. */ public function testWrongContentType(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $this->expectExceptionMessage('Content-Type must be multipart/related'); $this->getMultipartParser( $bodyObject, @@ -250,7 +299,7 @@ class MultipartRequestParserTest extends TestCase { * Test with a wrong key after the content type in the request's headers. */ public function testWrongKeyInContentType(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $this->expectExceptionMessage('Boundary is invalid'); $this->getMultipartParser( $bodyObject, @@ -262,7 +311,7 @@ class MultipartRequestParserTest extends TestCase { * Test with a null Content-Type in the request's headers. */ public function testNullContentType(): void { - $bodyObject = $this->getValidBodyObject(); + $bodyObject = self::getValidBodyObject(); $this->expectExceptionMessage('Content-Type can not be null'); $this->getMultipartParser( $bodyObject, diff --git a/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php b/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php index 9a077e35076..1a7ab7179e1 100644 --- a/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php +++ b/apps/dav/tests/unit/Files/Sharing/FilesDropPluginTest.php @@ -1,13 +1,19 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Files\Sharing; +namespace OCA\DAV\Tests\unit\Files\Sharing; -use OC\Files\View; use OCA\DAV\Files\Sharing\FilesDropPlugin; -use Sabre\DAV\Exception\MethodNotAllowed; +use OCP\Files\Folder; +use OCP\Files\NotFoundException; +use OCP\Share\IAttributes; +use OCP\Share\IShare; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\Exception\BadRequest; use Sabre\DAV\Server; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; @@ -15,51 +21,42 @@ use Test\TestCase; class FilesDropPluginTest extends TestCase { - /** @var View|\PHPUnit\Framework\MockObject\MockObject */ - private $view; - - /** @var Server|\PHPUnit\Framework\MockObject\MockObject */ - private $server; - - /** @var FilesDropPlugin */ - private $plugin; + private FilesDropPlugin $plugin; - /** @var RequestInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $request; - - /** @var ResponseInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $response; + private Folder&MockObject $node; + private IShare&MockObject $share; + private Server&MockObject $server; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; protected function setUp(): void { parent::setUp(); - $this->view = $this->createMock(View::class); + $this->node = $this->createMock(Folder::class); + $this->node->method('getPath') + ->willReturn('/files/token'); + + $this->share = $this->createMock(IShare::class); + $this->share->expects(self::any()) + ->method('getNode') + ->willReturn($this->node); $this->server = $this->createMock(Server::class); $this->plugin = new FilesDropPlugin(); $this->request = $this->createMock(RequestInterface::class); $this->response = $this->createMock(ResponseInterface::class); - $this->response->expects($this->never()) - ->method($this->anything()); - } - - public function testInitialize(): void { - $this->server->expects($this->once()) - ->method('on') - ->with( - $this->equalTo('beforeMethod:*'), - $this->equalTo([$this->plugin, 'beforeMethod']), - $this->equalTo(999) - ); + $attributes = $this->createMock(IAttributes::class); + $this->share->expects($this->any()) + ->method('getAttributes') + ->willReturn($attributes); - $this->plugin->initialize($this->server); + $this->share + ->method('getToken') + ->willReturn('token'); } public function testNotEnabled(): void { - $this->view->expects($this->never()) - ->method($this->anything()); - $this->request->expects($this->never()) ->method($this->anything()); @@ -68,95 +65,194 @@ class FilesDropPluginTest extends TestCase { public function testValid(): void { $this->plugin->enable(); - $this->plugin->setView($this->view); + $this->plugin->setShare($this->share); $this->request->method('getMethod') ->willReturn('PUT'); $this->request->method('getPath') - ->willReturn('file.txt'); + ->willReturn('/files/token/file.txt'); $this->request->method('getBaseUrl') ->willReturn('https://example.com'); - $this->view->method('file_exists') - ->with('/file.txt') - ->willReturn(false); + $this->node->expects(self::once()) + ->method('getNonExistingName') + ->with('file.txt') + ->willReturn('file.txt'); $this->request->expects($this->once()) ->method('setUrl') - ->with('https://example.com/file.txt'); + ->with('https://example.com/files/token/file.txt'); $this->plugin->beforeMethod($this->request, $this->response); } public function testFileAlreadyExistsValid(): void { $this->plugin->enable(); - $this->plugin->setView($this->view); + $this->plugin->setShare($this->share); $this->request->method('getMethod') ->willReturn('PUT'); $this->request->method('getPath') - ->willReturn('file.txt'); + ->willReturn('/files/token/file.txt'); $this->request->method('getBaseUrl') ->willReturn('https://example.com'); - $this->view->method('file_exists') - ->willReturnCallback(function ($path) { - if ($path === 'file.txt' || $path === '/file.txt') { - return true; - } else { - return false; - } - }); + $this->node->method('getNonExistingName') + ->with('file.txt') + ->willReturn('file (2).txt'); $this->request->expects($this->once()) ->method('setUrl') - ->with($this->equalTo('https://example.com/file (2).txt')); + ->with($this->equalTo('https://example.com/files/token/file (2).txt')); $this->plugin->beforeMethod($this->request, $this->response); } - public function testNoMKCOL(): void { + public function testNoMKCOLWithoutNickname(): void { $this->plugin->enable(); - $this->plugin->setView($this->view); + $this->plugin->setShare($this->share); $this->request->method('getMethod') ->willReturn('MKCOL'); - $this->expectException(MethodNotAllowed::class); + $this->expectException(BadRequest::class); + + $this->plugin->beforeMethod($this->request, $this->response); + } + + public function testMKCOLWithNickname(): void { + $this->plugin->enable(); + $this->plugin->setShare($this->share); + + $this->request->method('getMethod') + ->willReturn('MKCOL'); + + $this->request->method('hasHeader') + ->with('X-NC-Nickname') + ->willReturn(true); + $this->request->method('getHeader') + ->with('X-NC-Nickname') + ->willReturn('nickname'); + + $this->expectNotToPerformAssertions(); $this->plugin->beforeMethod($this->request, $this->response); } - public function testNoSubdirPut(): void { + public function testSubdirPut(): void { $this->plugin->enable(); - $this->plugin->setView($this->view); + $this->plugin->setShare($this->share); $this->request->method('getMethod') ->willReturn('PUT'); + $this->request->method('hasHeader') + ->with('X-NC-Nickname') + ->willReturn(true); + $this->request->method('getHeader') + ->with('X-NC-Nickname') + ->willReturn('nickname'); + $this->request->method('getPath') - ->willReturn('folder/file.txt'); + ->willReturn('/files/token/folder/file.txt'); $this->request->method('getBaseUrl') ->willReturn('https://example.com'); - $this->view->method('file_exists') - ->willReturnCallback(function ($path) { - if ($path === 'file.txt' || $path === '/file.txt') { - return true; - } else { - return false; - } - }); + $nodeName = $this->createMock(Folder::class); + $nodeFolder = $this->createMock(Folder::class); + $nodeFolder->expects(self::once()) + ->method('getPath') + ->willReturn('/files/token/nickname/folder'); + $nodeFolder->method('getNonExistingName') + ->with('file.txt') + ->willReturn('file.txt'); + $nodeName->expects(self::once()) + ->method('get') + ->with('folder') + ->willThrowException(new NotFoundException()); + $nodeName->expects(self::once()) + ->method('newFolder') + ->with('folder') + ->willReturn($nodeFolder); + + $this->node->expects(self::once()) + ->method('get') + ->willThrowException(new NotFoundException()); + $this->node->expects(self::once()) + ->method('newFolder') + ->with('nickname') + ->willReturn($nodeName); $this->request->expects($this->once()) ->method('setUrl') - ->with($this->equalTo('https://example.com/file (2).txt')); + ->with($this->equalTo('https://example.com/files/token/nickname/folder/file.txt')); $this->plugin->beforeMethod($this->request, $this->response); } + + public function testRecursiveFolderCreation(): void { + $this->plugin->enable(); + $this->plugin->setShare($this->share); + + $this->request->method('getMethod') + ->willReturn('PUT'); + $this->request->method('hasHeader') + ->with('X-NC-Nickname') + ->willReturn(true); + $this->request->method('getHeader') + ->with('X-NC-Nickname') + ->willReturn('nickname'); + + $this->request->method('getPath') + ->willReturn('/files/token/folder/subfolder/file.txt'); + $this->request->method('getBaseUrl') + ->willReturn('https://example.com'); + + $this->request->expects($this->once()) + ->method('setUrl') + ->with($this->equalTo('https://example.com/files/token/nickname/folder/subfolder/file.txt')); + + $subfolder = $this->createMock(Folder::class); + $subfolder->expects(self::once()) + ->method('getNonExistingName') + ->with('file.txt') + ->willReturn('file.txt'); + $subfolder->expects(self::once()) + ->method('getPath') + ->willReturn('/files/token/nickname/folder/subfolder'); + + $folder = $this->createMock(Folder::class); + $folder->expects(self::once()) + ->method('get') + ->with('subfolder') + ->willReturn($subfolder); + + $nickname = $this->createMock(Folder::class); + $nickname->expects(self::once()) + ->method('get') + ->with('folder') + ->willReturn($folder); + + $this->node->method('get') + ->with('nickname') + ->willReturn($nickname); + $this->plugin->beforeMethod($this->request, $this->response); + } + + public function testOnMkcol(): void { + $this->plugin->enable(); + $this->plugin->setShare($this->share); + + $this->response->expects($this->once()) + ->method('setStatus') + ->with(201); + + $response = $this->plugin->onMkcol($this->request, $this->response); + $this->assertFalse($response); + } } diff --git a/apps/dav/tests/unit/Listener/ActivityUpdaterListenerTest.php b/apps/dav/tests/unit/Listener/ActivityUpdaterListenerTest.php index 7b517c93d5d..8519dca7126 100644 --- a/apps/dav/tests/unit/Listener/ActivityUpdaterListenerTest.php +++ b/apps/dav/tests/unit/Listener/ActivityUpdaterListenerTest.php @@ -6,24 +6,22 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\Listener; +namespace OCA\DAV\Tests\unit\Listener; use OCA\DAV\CalDAV\Activity\Backend as ActivityBackend; use OCA\DAV\CalDAV\Activity\Provider\Event; use OCA\DAV\DAV\Sharing\Plugin as SharingPlugin; use OCA\DAV\Events\CalendarDeletedEvent; -use OCA\DAV\Events\CalendarObjectDeletedEvent; use OCA\DAV\Listener\ActivityUpdaterListener; +use OCP\Calendar\Events\CalendarObjectDeletedEvent; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Test\TestCase; class ActivityUpdaterListenerTest extends TestCase { - /** @var ActivityBackend|MockObject */ - private $activityBackend; - /** @var LoggerInterface|MockObject */ - private $logger; + private ActivityBackend&MockObject $activityBackend; + private LoggerInterface&MockObject $logger; private ActivityUpdaterListener $listener; protected function setUp(): void { @@ -38,9 +36,7 @@ class ActivityUpdaterListenerTest extends TestCase { ); } - /** - * @dataProvider dataForTestHandleCalendarObjectDeletedEvent - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataForTestHandleCalendarObjectDeletedEvent')] public function testHandleCalendarObjectDeletedEvent(int $calendarId, array $calendarData, array $shares, array $objectData, bool $createsActivity): void { $event = new CalendarObjectDeletedEvent($calendarId, $calendarData, $shares, $objectData); $this->logger->expects($this->once())->method('debug')->with( @@ -55,16 +51,14 @@ class ActivityUpdaterListenerTest extends TestCase { $this->listener->handle($event); } - public function dataForTestHandleCalendarObjectDeletedEvent(): array { + public static function dataForTestHandleCalendarObjectDeletedEvent(): array { return [ [1, [], [], [], true], [1, [], [], ['{' . SharingPlugin::NS_NEXTCLOUD . '}deleted-at' => 120], false], ]; } - /** - * @dataProvider dataForTestHandleCalendarDeletedEvent - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataForTestHandleCalendarDeletedEvent')] public function testHandleCalendarDeletedEvent(int $calendarId, array $calendarData, array $shares, bool $createsActivity): void { $event = new CalendarDeletedEvent($calendarId, $calendarData, $shares); $this->logger->expects($this->once())->method('debug')->with( @@ -77,7 +71,7 @@ class ActivityUpdaterListenerTest extends TestCase { $this->listener->handle($event); } - public function dataForTestHandleCalendarDeletedEvent(): array { + public static function dataForTestHandleCalendarDeletedEvent(): array { return [ [1, [], [], true], [1, ['{' . SharingPlugin::NS_NEXTCLOUD . '}deleted-at' => 120], [], false], diff --git a/apps/dav/tests/unit/Listener/CalendarContactInteractionListenerTest.php b/apps/dav/tests/unit/Listener/CalendarContactInteractionListenerTest.php index f11438858d7..dc3dce8a62f 100644 --- a/apps/dav/tests/unit/Listener/CalendarContactInteractionListenerTest.php +++ b/apps/dav/tests/unit/Listener/CalendarContactInteractionListenerTest.php @@ -6,12 +6,12 @@ declare(strict_types=1); * SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\Listener; +namespace OCA\DAV\Tests\unit\Listener; use OCA\DAV\Connector\Sabre\Principal; -use OCA\DAV\Events\CalendarObjectCreatedEvent; use OCA\DAV\Events\CalendarShareUpdatedEvent; use OCA\DAV\Listener\CalendarContactInteractionListener; +use OCP\Calendar\Events\CalendarObjectCreatedEvent; use OCP\Contacts\Events\ContactInteractedWithEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; @@ -23,24 +23,12 @@ use Psr\Log\LoggerInterface; use Test\TestCase; class CalendarContactInteractionListenerTest extends TestCase { - - /** @var IEventDispatcher|MockObject */ - private $eventDispatcher; - - /** @var IUserSession|MockObject */ - private $userSession; - - /** @var Principal|MockObject */ - private $principalConnector; - - /** @var LoggerInterface|MockObject */ - private $logger; - - /** @var IMailer|MockObject */ - private $mailer; - - /** @var CalendarContactInteractionListener */ - private $listener; + private IEventDispatcher&MockObject $eventDispatcher; + private IUserSession&MockObject $userSession; + private Principal&MockObject $principalConnector; + private LoggerInterface&MockObject $logger; + private IMailer&MockObject $mailer; + private CalendarContactInteractionListener $listener; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php b/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php index e1f474678b0..971d113b742 100644 --- a/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php +++ b/apps/dav/tests/unit/Listener/OutOfOfficeListenerTest.php @@ -7,7 +7,7 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\Listener; +namespace OCA\DAV\Tests\unit\Listener; use DateTimeImmutable; use InvalidArgumentException; @@ -27,7 +27,6 @@ use OCP\User\Events\OutOfOfficeChangedEvent; use OCP\User\Events\OutOfOfficeClearedEvent; use OCP\User\Events\OutOfOfficeScheduledEvent; use OCP\User\IOutOfOfficeData; -use OCP\UserStatus\IManager; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\DAV\Exception\NotFound; @@ -42,11 +41,10 @@ use Test\TestCase; */ class OutOfOfficeListenerTest extends TestCase { - private ServerFactory|MockObject $serverFactory; - private IConfig|MockObject $appConfig; - private LoggerInterface|MockObject $loggerInterface; - private MockObject|TimezoneService $timezoneService; - private IManager|MockObject $manager; + private ServerFactory&MockObject $serverFactory; + private IConfig&MockObject $appConfig; + private LoggerInterface&MockObject $loggerInterface; + private TimezoneService&MockObject $timezoneService; private OutOfOfficeListener $listener; protected function setUp(): void { @@ -56,14 +54,12 @@ class OutOfOfficeListenerTest extends TestCase { $this->appConfig = $this->createMock(IConfig::class); $this->timezoneService = $this->createMock(TimezoneService::class); $this->loggerInterface = $this->createMock(LoggerInterface::class); - $this->manager = $this->createMock(IManager::class); $this->listener = new OutOfOfficeListener( $this->serverFactory, $this->appConfig, $this->timezoneService, $this->loggerInterface, - $this->manager ); } @@ -202,7 +198,7 @@ class OutOfOfficeListenerTest extends TestCase { ->willReturn($calendar); $calendar->expects(self::once()) ->method('createFile') - ->willReturnCallback(function ($name, $data) { + ->willReturnCallback(function ($name, $data): void { $vcalendar = Reader::read($data); if (!($vcalendar instanceof VCalendar)) { throw new InvalidArgumentException('Calendar data should be a VCALENDAR'); @@ -352,7 +348,7 @@ class OutOfOfficeListenerTest extends TestCase { ->willThrowException(new NotFound()); $calendar->expects(self::once()) ->method('createFile') - ->willReturnCallback(function ($name, $data) { + ->willReturnCallback(function ($name, $data): void { $vcalendar = Reader::read($data); if (!($vcalendar instanceof VCalendar)) { throw new InvalidArgumentException('Calendar data should be a VCALENDAR'); @@ -419,7 +415,7 @@ class OutOfOfficeListenerTest extends TestCase { ->willReturn($eventNode); $eventNode->expects(self::once()) ->method('put') - ->willReturnCallback(function ($data) { + ->willReturnCallback(function ($data): void { $vcalendar = Reader::read($data); if (!($vcalendar instanceof VCalendar)) { throw new InvalidArgumentException('Calendar data should be a VCALENDAR'); @@ -453,8 +449,6 @@ class OutOfOfficeListenerTest extends TestCase { ->method('getPlugin') ->with('caldav') ->willReturn($caldavPlugin); - $this->manager->expects(self::never()) - ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); @@ -483,8 +477,6 @@ class OutOfOfficeListenerTest extends TestCase { ->method('getNodeForPath') ->with('/home/calendar') ->willThrowException(new NotFound('nope')); - $this->manager->expects(self::never()) - ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); @@ -522,8 +514,6 @@ class OutOfOfficeListenerTest extends TestCase { ->method('getChild') ->with('personal-1') ->willThrowException(new NotFound('nope')); - $this->manager->expects(self::never()) - ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); @@ -565,8 +555,6 @@ class OutOfOfficeListenerTest extends TestCase { $calendar->expects(self::once()) ->method('getChild') ->willThrowException(new NotFound()); - $this->manager->expects(self::never()) - ->method('revertUserStatus'); $event = new OutOfOfficeClearedEvent($data); $this->listener->handle($event); diff --git a/apps/dav/tests/unit/Migration/CalDAVRemoveEmptyValueTest.php b/apps/dav/tests/unit/Migration/CalDAVRemoveEmptyValueTest.php index 1cebf199d29..1852d2709c1 100644 --- a/apps/dav/tests/unit/Migration/CalDAVRemoveEmptyValueTest.php +++ b/apps/dav/tests/unit/Migration/CalDAVRemoveEmptyValueTest.php @@ -1,13 +1,18 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\DAV\Migration; +namespace OCA\DAV\Tests\unit\DAV\Migration; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\Migration\CalDAVRemoveEmptyValue; +use OCP\IDBConnection; use OCP\Migration\IOutput; +use OCP\Server; +use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; use Sabre\VObject\InvalidDataException; use Test\TestCase; @@ -19,18 +24,10 @@ use Test\TestCase; * @group DB */ class CalDAVRemoveEmptyValueTest extends TestCase { - - /** @var LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */ - private $logger; - - /** @var CalDavBackend|\PHPUnit\Framework\MockObject\MockObject */ - private $backend; - - /** @var IOutput|\PHPUnit\Framework\MockObject\MockObject */ - private $output; - - /** @var string */ - private $invalid = 'BEGIN:VCALENDAR + private LoggerInterface&MockObject $logger; + private CalDavBackend&MockObject $backend; + private IOutput&MockObject $output; + private string $invalid = 'BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.11.2//EN CALSCALE:GREGORIAN @@ -50,8 +47,7 @@ CREATED;VALUE=:20151214T091032Z END:VEVENT END:VCALENDAR'; - /** @var string */ - private $valid = 'BEGIN:VCALENDAR + private string $valid = 'BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Apple Inc.//Mac OS X 10.11.2//EN CALSCALE:GREGORIAN @@ -80,14 +76,14 @@ END:VCALENDAR'; } public function testRunAllValid(): void { - /** @var CalDAVRemoveEmptyValue|\PHPUnit\Framework\MockObject\MockObject $step */ + /** @var CalDAVRemoveEmptyValue&MockObject $step */ $step = $this->getMockBuilder(CalDAVRemoveEmptyValue::class) ->setConstructorArgs([ - \OC::$server->getDatabaseConnection(), + Server::get(IDBConnection::class), $this->backend, $this->logger ]) - ->setMethods(['getInvalidObjects']) + ->onlyMethods(['getInvalidObjects']) ->getMock(); $step->expects($this->once()) @@ -104,14 +100,14 @@ END:VCALENDAR'; } public function testRunInvalid(): void { - /** @var CalDAVRemoveEmptyValue|\PHPUnit\Framework\MockObject\MockObject $step */ + /** @var CalDAVRemoveEmptyValue&MockObject $step */ $step = $this->getMockBuilder(CalDAVRemoveEmptyValue::class) ->setConstructorArgs([ - \OC::$server->getDatabaseConnection(), + Server::get(IDBConnection::class), $this->backend, $this->logger ]) - ->setMethods(['getInvalidObjects']) + ->onlyMethods(['getInvalidObjects']) ->getMock(); $step->expects($this->once()) @@ -147,14 +143,14 @@ END:VCALENDAR'; } public function testRunValid(): void { - /** @var CalDAVRemoveEmptyValue|\PHPUnit\Framework\MockObject\MockObject $step */ + /** @var CalDAVRemoveEmptyValue&MockObject $step */ $step = $this->getMockBuilder(CalDAVRemoveEmptyValue::class) ->setConstructorArgs([ - \OC::$server->getDatabaseConnection(), + Server::get(IDBConnection::class), $this->backend, $this->logger ]) - ->setMethods(['getInvalidObjects']) + ->onlyMethods(['getInvalidObjects']) ->getMock(); $step->expects($this->once()) @@ -189,14 +185,14 @@ END:VCALENDAR'; } public function testRunStillInvalid(): void { - /** @var CalDAVRemoveEmptyValue|\PHPUnit\Framework\MockObject\MockObject $step */ + /** @var CalDAVRemoveEmptyValue&MockObject $step */ $step = $this->getMockBuilder(CalDAVRemoveEmptyValue::class) ->setConstructorArgs([ - \OC::$server->getDatabaseConnection(), + Server::get(IDBConnection::class), $this->backend, $this->logger ]) - ->setMethods(['getInvalidObjects']) + ->onlyMethods(['getInvalidObjects']) ->getMock(); $step->expects($this->once()) diff --git a/apps/dav/tests/unit/Migration/CreateSystemAddressBookStepTest.php b/apps/dav/tests/unit/Migration/CreateSystemAddressBookStepTest.php new file mode 100644 index 00000000000..667d2e39d3a --- /dev/null +++ b/apps/dav/tests/unit/Migration/CreateSystemAddressBookStepTest.php @@ -0,0 +1,47 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\Migration; + +use OCA\DAV\CardDAV\SyncService; +use OCA\DAV\Migration\CreateSystemAddressBookStep; +use OCP\Migration\IOutput; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CreateSystemAddressBookStepTest extends TestCase { + + private SyncService&MockObject $syncService; + private CreateSystemAddressBookStep $step; + + protected function setUp(): void { + parent::setUp(); + + $this->syncService = $this->createMock(SyncService::class); + + $this->step = new CreateSystemAddressBookStep( + $this->syncService, + ); + } + + public function testGetName(): void { + $name = $this->step->getName(); + + self::assertEquals('Create system address book', $name); + } + + public function testRun(): void { + $output = $this->createMock(IOutput::class); + + $this->step->run($output); + + $this->addToAssertionCount(1); + } + +} diff --git a/apps/dav/tests/unit/Migration/RefreshWebcalJobRegistrarTest.php b/apps/dav/tests/unit/Migration/RefreshWebcalJobRegistrarTest.php index bf4c60b3cf1..8e7bf366cbf 100644 --- a/apps/dav/tests/unit/Migration/RefreshWebcalJobRegistrarTest.php +++ b/apps/dav/tests/unit/Migration/RefreshWebcalJobRegistrarTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -12,17 +14,13 @@ use OCP\DB\IResult; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\IDBConnection; use OCP\Migration\IOutput; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class RefreshWebcalJobRegistrarTest extends TestCase { - /** @var IDBConnection | \PHPUnit\Framework\MockObject\MockObject */ - private $db; - - /** @var IJobList | \PHPUnit\Framework\MockObject\MockObject */ - private $jobList; - - /** @var RefreshWebcalJobRegistrar */ - private $migration; + private IDBConnection&MockObject $db; + private IJobList&MockObject $jobList; + private RefreshWebcalJobRegistrar $migration; protected function setUp(): void { parent::setUp(); @@ -80,35 +78,37 @@ class RefreshWebcalJobRegistrarTest extends TestCase { $this->jobList->expects($this->exactly(3)) ->method('has') - ->withConsecutive( + ->willReturnMap([ [RefreshWebcalJob::class, [ 'principaluri' => 'foo1', 'uri' => 'bar1', - ]], + ], false], [RefreshWebcalJob::class, [ 'principaluri' => 'foo2', 'uri' => 'bar2', - ]], + ], true ], [RefreshWebcalJob::class, [ 'principaluri' => 'foo3', 'uri' => 'bar3', - ]]) - ->willReturnOnConsecutiveCalls( - false, - true, - false, - ); + ], false], + ]); + + $calls = [ + [RefreshWebcalJob::class, [ + 'principaluri' => 'foo1', + 'uri' => 'bar1', + ]], + [RefreshWebcalJob::class, [ + 'principaluri' => 'foo3', + 'uri' => 'bar3', + ]] + ]; $this->jobList->expects($this->exactly(2)) ->method('add') - ->withConsecutive( - [RefreshWebcalJob::class, [ - 'principaluri' => 'foo1', - 'uri' => 'bar1', - ]], - [RefreshWebcalJob::class, [ - 'principaluri' => 'foo3', - 'uri' => 'bar3', - ]]); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); $output->expects($this->once()) ->method('info') diff --git a/apps/dav/tests/unit/Migration/RegenerateBirthdayCalendarsTest.php b/apps/dav/tests/unit/Migration/RegenerateBirthdayCalendarsTest.php index e2ac45526d2..6f681badb8b 100644 --- a/apps/dav/tests/unit/Migration/RegenerateBirthdayCalendarsTest.php +++ b/apps/dav/tests/unit/Migration/RegenerateBirthdayCalendarsTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -10,18 +12,13 @@ use OCA\DAV\Migration\RegenerateBirthdayCalendars; use OCP\BackgroundJob\IJobList; use OCP\IConfig; use OCP\Migration\IOutput; +use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class RegenerateBirthdayCalendarsTest extends TestCase { - - /** @var IJobList | \PHPUnit\Framework\MockObject\MockObject */ - private $jobList; - - /** @var IConfig | \PHPUnit\Framework\MockObject\MockObject */ - private $config; - - /** @var RegenerateBirthdayCalendars */ - private $migration; + private IJobList&MockObject $jobList; + private IConfig&MockObject $config; + private RegenerateBirthdayCalendars $migration; protected function setUp(): void { parent::setUp(); diff --git a/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php b/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php index a3daf1c918a..a9758470573 100644 --- a/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php +++ b/apps/dav/tests/unit/Migration/RemoveDeletedUsersCalendarSubscriptionsTest.php @@ -22,23 +22,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class RemoveDeletedUsersCalendarSubscriptionsTest extends TestCase { - /** - * @var IDBConnection|MockObject - */ - private $dbConnection; - /** - * @var IUserManager|MockObject - */ - private $userManager; - - /** - * @var IOutput|MockObject - */ - private $output; - /** - * @var RemoveDeletedUsersCalendarSubscriptions - */ - private $migration; + private IDBConnection&MockObject $dbConnection; + private IUserManager&MockObject $userManager; + private IOutput&MockObject $output; + private RemoveDeletedUsersCalendarSubscriptions $migration; protected function setUp(): void { @@ -58,13 +45,7 @@ class RemoveDeletedUsersCalendarSubscriptionsTest extends TestCase { ); } - /** - * @dataProvider dataTestRun - * @param array $subscriptions - * @param array $userExists - * @param int $deletions - * @throws \Exception - */ + #[\PHPUnit\Framework\Attributes\DataProvider('dataTestRun')] public function testRun(array $subscriptions, array $userExists, int $deletions): void { $qb = $this->createMock(IQueryBuilder::class); @@ -132,21 +113,28 @@ class RemoveDeletedUsersCalendarSubscriptionsTest extends TestCase { $this->migration->run($this->output); } - public function dataTestRun(): array { + public static function dataTestRun(): array { return [ [[], [], 0], - [[[ - 'id' => 1, - 'principaluri' => 'users/principals/foo1', - ], + [ [ - 'id' => 2, - 'principaluri' => 'users/principals/bar1', + [ + 'id' => 1, + 'principaluri' => 'users/principals/foo1', + ], + [ + 'id' => 2, + 'principaluri' => 'users/principals/bar1', + ], + [ + 'id' => 3, + 'principaluri' => 'users/principals/bar1', + ], + [], ], - [ - 'id' => 3, - 'principaluri' => 'users/principals/bar1', - ]], ['foo1' => true, 'bar1' => false], 2] + ['foo1' => true, 'bar1' => false], + 2 + ], ]; } } diff --git a/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningNodeTest.php b/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningNodeTest.php index 0979aff8a81..4f04aebb3e8 100644 --- a/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningNodeTest.php +++ b/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningNodeTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -7,15 +9,13 @@ namespace OCA\DAV\Tests\unit\Provisioning\Apple; use OCA\DAV\Provisioning\Apple\AppleProvisioningNode; use OCP\AppFramework\Utility\ITimeFactory; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\PropPatch; use Test\TestCase; class AppleProvisioningNodeTest extends TestCase { - /** @var ITimeFactory|\PHPUnit\Framework\MockObject\MockObject */ - private $timeFactory; - - /** @var AppleProvisioningNode */ - private $node; + private ITimeFactory&MockObject $timeFactory; + private AppleProvisioningNode $node; protected function setUp(): void { parent::setUp(); @@ -28,7 +28,6 @@ class AppleProvisioningNodeTest extends TestCase { $this->assertEquals('apple-provisioning.mobileconfig', $this->node->getName()); } - public function testSetName(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->expectExceptionMessage('Renaming apple-provisioning.mobileconfig is forbidden'); @@ -55,7 +54,7 @@ class AppleProvisioningNodeTest extends TestCase { $this->assertEquals([ '{DAV:}getcontentlength' => 42, - '{DAV:}getlastmodified' => 'Sat, 01 Jan 2000 00:00:00 +0000', + '{DAV:}getlastmodified' => 'Sat, 01 Jan 2000 00:00:00 GMT', ], $this->node->getProperties([])); } diff --git a/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningPluginTest.php b/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningPluginTest.php index dbb399ea7a6..58e588aa68d 100644 --- a/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningPluginTest.php +++ b/apps/dav/tests/unit/Provisioning/Apple/AppleProvisioningPluginTest.php @@ -1,4 +1,6 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later @@ -12,40 +14,27 @@ use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserSession; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\Server; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; use Test\TestCase; class AppleProvisioningPluginTest extends TestCase { - /** @var \Sabre\DAV\Server|\PHPUnit\Framework\MockObject\MockObject */ - protected $server; - - /** @var IUserSession|\PHPUnit\Framework\MockObject\MockObject */ - protected $userSession; - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - protected $urlGenerator; - - /** @var ThemingDefaults|\PHPUnit\Framework\MockObject\MockObject */ - protected $themingDefaults; - - /** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */ - protected $request; - - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - protected $l10n; - - /** @var \Sabre\HTTP\RequestInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $sabreRequest; - - /** @var \Sabre\HTTP\ResponseInterface|\PHPUnit\Framework\MockObject\MockObject */ - protected $sabreResponse; - - /** @var AppleProvisioningPlugin */ - protected $plugin; + protected Server&MockObject $server; + protected IUserSession&MockObject $userSession; + protected IURLGenerator&MockObject $urlGenerator; + protected ThemingDefaults&MockObject $themingDefaults; + protected IRequest&MockObject $request; + protected IL10N&MockObject $l10n; + protected RequestInterface&MockObject $sabreRequest; + protected ResponseInterface&MockObject $sabreResponse; + protected AppleProvisioningPlugin $plugin; protected function setUp(): void { parent::setUp(); - $this->server = $this->createMock(\Sabre\DAV\Server::class); + $this->server = $this->createMock(Server::class); $this->userSession = $this->createMock(IUserSession::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->themingDefaults = $this->createMock(ThemingDefaults::class); @@ -62,12 +51,12 @@ class AppleProvisioningPluginTest extends TestCase { } ); - $this->sabreRequest = $this->createMock(\Sabre\HTTP\RequestInterface::class); - $this->sabreResponse = $this->createMock(\Sabre\HTTP\ResponseInterface::class); + $this->sabreRequest = $this->createMock(RequestInterface::class); + $this->sabreResponse = $this->createMock(ResponseInterface::class); } public function testInitialize(): void { - $server = $this->createMock(\Sabre\DAV\Server::class); + $server = $this->createMock(Server::class); $plugin = new AppleProvisioningPlugin($this->userSession, $this->urlGenerator, $this->themingDefaults, $this->request, $this->l10n, @@ -149,24 +138,25 @@ class AppleProvisioningPluginTest extends TestCase { $this->l10n->expects($this->exactly(2)) ->method('t') - ->withConsecutive( - ['Configures a CalDAV account'], - ['Configures a CardDAV account'], - ) - ->willReturnOnConsecutiveCalls( - 'LocalizedConfiguresCalDAV', - 'LocalizedConfiguresCardDAV', - ); + ->willReturnMap([ + ['Configures a CalDAV account', [], 'LocalizedConfiguresCalDAV'], + ['Configures a CardDAV account', [], 'LocalizedConfiguresCardDAV'], + ]); $this->sabreResponse->expects($this->once()) ->method('setStatus') ->with(200); + + $calls = [ + ['Content-Disposition', 'attachment; filename="userName-apple-provisioning.mobileconfig"'], + ['Content-Type', 'application/xml; charset=utf-8'], + ]; $this->sabreResponse->expects($this->exactly(2)) ->method('setHeader') - ->withConsecutive( - ['Content-Disposition', 'attachment; filename="userName-apple-provisioning.mobileconfig"'], - ['Content-Type', 'application/xml; charset=utf-8'], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); $this->sabreResponse->expects($this->once()) ->method('setBody') ->with(<<<EOF diff --git a/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php b/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php index bfc57dc61d7..f4dc13a5c06 100644 --- a/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php +++ b/apps/dav/tests/unit/Search/ContactsSearchProviderTest.php @@ -17,43 +17,34 @@ use OCP\IUser; use OCP\Search\ISearchQuery; use OCP\Search\SearchResult; use OCP\Search\SearchResultEntry; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Reader; use Test\TestCase; class ContactsSearchProviderTest extends TestCase { - - /** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */ - private $appManager; - - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - private $l10n; - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - - /** @var CardDavBackend|\PHPUnit\Framework\MockObject\MockObject */ - private $backend; - - /** @var ContactsSearchProvider */ - private $provider; - - private $vcardTest0 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'UID:Test'.PHP_EOL. - 'FN:FN of Test'.PHP_EOL. - 'N:Test;;;;'.PHP_EOL. - 'EMAIL:forrestgump@example.com'.PHP_EOL. - 'END:VCARD'; - - private $vcardTest1 = 'BEGIN:VCARD'.PHP_EOL. - 'VERSION:3.0'.PHP_EOL. - 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. - 'PHOTO;ENCODING=b;TYPE=image/jpeg:'.PHP_EOL. - 'UID:Test2'.PHP_EOL. - 'FN:FN of Test2'.PHP_EOL. - 'N:Test2;;;;'.PHP_EOL. - 'END:VCARD'; + private IAppManager&MockObject $appManager; + private IL10N&MockObject $l10n; + private IURLGenerator&MockObject $urlGenerator; + private CardDavBackend&MockObject $backend; + private ContactsSearchProvider $provider; + + private string $vcardTest0 = 'BEGIN:VCARD' . PHP_EOL + . 'VERSION:3.0' . PHP_EOL + . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL + . 'UID:Test' . PHP_EOL + . 'FN:FN of Test' . PHP_EOL + . 'N:Test;;;;' . PHP_EOL + . 'EMAIL:forrestgump@example.com' . PHP_EOL + . 'END:VCARD'; + + private string $vcardTest1 = 'BEGIN:VCARD' . PHP_EOL + . 'VERSION:3.0' . PHP_EOL + . 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN' . PHP_EOL + . 'PHOTO;ENCODING=b;TYPE=image/jpeg:' . PHP_EOL + . 'UID:Test2' . PHP_EOL + . 'FN:FN of Test2' . PHP_EOL + . 'N:Test2;;;;' . PHP_EOL + . 'END:VCARD'; protected function setUp(): void { parent::setUp(); @@ -174,7 +165,7 @@ class ContactsSearchProviderTest extends TestCase { $this->urlGenerator, $this->backend, ]) - ->setMethods([ + ->onlyMethods([ 'getDavUrlForContact', 'getDeepLinkToContactsApp', 'generateSubline', @@ -191,11 +182,10 @@ class ContactsSearchProviderTest extends TestCase { ->willReturn('subline'); $provider->expects($this->exactly(2)) ->method('getDeepLinkToContactsApp') - ->withConsecutive( - ['addressbook-uri-99', 'Test'], - ['addressbook-uri-123', 'Test2'] - ) - ->willReturn('deep-link-to-contacts'); + ->willReturnMap([ + ['addressbook-uri-99', 'Test', 'deep-link-to-contacts'], + ['addressbook-uri-123', 'Test2', 'deep-link-to-contacts'], + ]); $actual = $provider->search($user, $query); $data = $actual->jsonSerialize(); diff --git a/apps/dav/tests/unit/Search/EventsSearchProviderTest.php b/apps/dav/tests/unit/Search/EventsSearchProviderTest.php index d194b7fa7c6..d5d536fd201 100644 --- a/apps/dav/tests/unit/Search/EventsSearchProviderTest.php +++ b/apps/dav/tests/unit/Search/EventsSearchProviderTest.php @@ -18,204 +18,196 @@ use OCP\Search\IFilter; use OCP\Search\ISearchQuery; use OCP\Search\SearchResult; use OCP\Search\SearchResultEntry; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Reader; use Test\TestCase; class EventsSearchProviderTest extends TestCase { - /** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */ - private $appManager; - - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - private $l10n; - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - - /** @var CalDavBackend|\PHPUnit\Framework\MockObject\MockObject */ - private $backend; - - /** @var EventsSearchProvider */ - private $provider; + private IAppManager&MockObject $appManager; + private IL10N&MockObject $l10n; + private IURLGenerator&MockObject $urlGenerator; + private CalDavBackend&MockObject $backend; + private EventsSearchProvider $provider; // NO SUMMARY - private $vEvent0 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DTEND;VALUE=DATE:20161008'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent0 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20161004T144433Z' . PHP_EOL + . 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL + . 'DTEND;VALUE=DATE:20161008' . PHP_EOL + . 'TRANSP:TRANSPARENT' . PHP_EOL + . 'DTSTART;VALUE=DATE:20161005' . PHP_EOL + . 'DTSTAMP:20161004T144437Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // TIMED SAME DAY - private $vEvent1 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Tests//'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VTIMEZONE'.PHP_EOL. - 'TZID:Europe/Berlin'.PHP_EOL. - 'BEGIN:DAYLIGHT'.PHP_EOL. - 'TZOFFSETFROM:+0100'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19810329T020000'.PHP_EOL. - 'TZNAME:GMT+2'.PHP_EOL. - 'TZOFFSETTO:+0200'.PHP_EOL. - 'END:DAYLIGHT'.PHP_EOL. - 'BEGIN:STANDARD'.PHP_EOL. - 'TZOFFSETFROM:+0200'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19961027T030000'.PHP_EOL. - 'TZNAME:GMT+1'.PHP_EOL. - 'TZOFFSETTO:+0100'.PHP_EOL. - 'END:STANDARD'.PHP_EOL. - 'END:VTIMEZONE'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20160809T163629Z'.PHP_EOL. - 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02'.PHP_EOL. - 'DTEND;TZID=Europe/Berlin:20160816T100000'.PHP_EOL. - 'TRANSP:OPAQUE'.PHP_EOL. - 'SUMMARY:Test Europe Berlin'.PHP_EOL. - 'DTSTART;TZID=Europe/Berlin:20160816T090000'.PHP_EOL. - 'DTSTAMP:20160809T163632Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent1 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Tests//' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VTIMEZONE' . PHP_EOL + . 'TZID:Europe/Berlin' . PHP_EOL + . 'BEGIN:DAYLIGHT' . PHP_EOL + . 'TZOFFSETFROM:+0100' . PHP_EOL + . 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL + . 'DTSTART:19810329T020000' . PHP_EOL + . 'TZNAME:GMT+2' . PHP_EOL + . 'TZOFFSETTO:+0200' . PHP_EOL + . 'END:DAYLIGHT' . PHP_EOL + . 'BEGIN:STANDARD' . PHP_EOL + . 'TZOFFSETFROM:+0200' . PHP_EOL + . 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL + . 'DTSTART:19961027T030000' . PHP_EOL + . 'TZNAME:GMT+1' . PHP_EOL + . 'TZOFFSETTO:+0100' . PHP_EOL + . 'END:STANDARD' . PHP_EOL + . 'END:VTIMEZONE' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20160809T163629Z' . PHP_EOL + . 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL + . 'DTEND;TZID=Europe/Berlin:20160816T100000' . PHP_EOL + . 'TRANSP:OPAQUE' . PHP_EOL + . 'SUMMARY:Test Europe Berlin' . PHP_EOL + . 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL + . 'DTSTAMP:20160809T163632Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // TIMED DIFFERENT DAY - private $vEvent2 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Tests//'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VTIMEZONE'.PHP_EOL. - 'TZID:Europe/Berlin'.PHP_EOL. - 'BEGIN:DAYLIGHT'.PHP_EOL. - 'TZOFFSETFROM:+0100'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19810329T020000'.PHP_EOL. - 'TZNAME:GMT+2'.PHP_EOL. - 'TZOFFSETTO:+0200'.PHP_EOL. - 'END:DAYLIGHT'.PHP_EOL. - 'BEGIN:STANDARD'.PHP_EOL. - 'TZOFFSETFROM:+0200'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19961027T030000'.PHP_EOL. - 'TZNAME:GMT+1'.PHP_EOL. - 'TZOFFSETTO:+0100'.PHP_EOL. - 'END:STANDARD'.PHP_EOL. - 'END:VTIMEZONE'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20160809T163629Z'.PHP_EOL. - 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02'.PHP_EOL. - 'DTEND;TZID=Europe/Berlin:20160817T100000'.PHP_EOL. - 'TRANSP:OPAQUE'.PHP_EOL. - 'SUMMARY:Test Europe Berlin'.PHP_EOL. - 'DTSTART;TZID=Europe/Berlin:20160816T090000'.PHP_EOL. - 'DTSTAMP:20160809T163632Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent2 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Tests//' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VTIMEZONE' . PHP_EOL + . 'TZID:Europe/Berlin' . PHP_EOL + . 'BEGIN:DAYLIGHT' . PHP_EOL + . 'TZOFFSETFROM:+0100' . PHP_EOL + . 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL + . 'DTSTART:19810329T020000' . PHP_EOL + . 'TZNAME:GMT+2' . PHP_EOL + . 'TZOFFSETTO:+0200' . PHP_EOL + . 'END:DAYLIGHT' . PHP_EOL + . 'BEGIN:STANDARD' . PHP_EOL + . 'TZOFFSETFROM:+0200' . PHP_EOL + . 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL + . 'DTSTART:19961027T030000' . PHP_EOL + . 'TZNAME:GMT+1' . PHP_EOL + . 'TZOFFSETTO:+0100' . PHP_EOL + . 'END:STANDARD' . PHP_EOL + . 'END:VTIMEZONE' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20160809T163629Z' . PHP_EOL + . 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL + . 'DTEND;TZID=Europe/Berlin:20160817T100000' . PHP_EOL + . 'TRANSP:OPAQUE' . PHP_EOL + . 'SUMMARY:Test Europe Berlin' . PHP_EOL + . 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL + . 'DTSTAMP:20160809T163632Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // ALL-DAY ONE-DAY - private $vEvent3 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DTEND;VALUE=DATE:20161006'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent3 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20161004T144433Z' . PHP_EOL + . 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL + . 'DTEND;VALUE=DATE:20161006' . PHP_EOL + . 'TRANSP:TRANSPARENT' . PHP_EOL + . 'DTSTART;VALUE=DATE:20161005' . PHP_EOL + . 'DTSTAMP:20161004T144437Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // ALL-DAY MULTIPLE DAYS - private $vEvent4 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DTEND;VALUE=DATE:20161008'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent4 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20161004T144433Z' . PHP_EOL + . 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL + . 'DTEND;VALUE=DATE:20161008' . PHP_EOL + . 'TRANSP:TRANSPARENT' . PHP_EOL + . 'DTSTART;VALUE=DATE:20161005' . PHP_EOL + . 'DTSTAMP:20161004T144437Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // DURATION - private $vEvent5 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'DURATION:P5D'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent5 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20161004T144433Z' . PHP_EOL + . 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL + . 'DURATION:P5D' . PHP_EOL + . 'TRANSP:TRANSPARENT' . PHP_EOL + . 'DTSTART;VALUE=DATE:20161005' . PHP_EOL + . 'DTSTAMP:20161004T144437Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // NO DTEND - DATE - private $vEvent6 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20161004T144433Z'.PHP_EOL. - 'UID:85560E76-1B0D-47E1-A735-21625767FCA4'.PHP_EOL. - 'TRANSP:TRANSPARENT'.PHP_EOL. - 'DTSTART;VALUE=DATE:20161005'.PHP_EOL. - 'DTSTAMP:20161004T144437Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent6 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Apple Inc.//Mac OS X 10.11.6//EN' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20161004T144433Z' . PHP_EOL + . 'UID:85560E76-1B0D-47E1-A735-21625767FCA4' . PHP_EOL + . 'TRANSP:TRANSPARENT' . PHP_EOL + . 'DTSTART;VALUE=DATE:20161005' . PHP_EOL + . 'DTSTAMP:20161004T144437Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; // NO DTEND - DATE-TIME - private $vEvent7 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'PRODID:-//Tests//'.PHP_EOL. - 'CALSCALE:GREGORIAN'.PHP_EOL. - 'BEGIN:VTIMEZONE'.PHP_EOL. - 'TZID:Europe/Berlin'.PHP_EOL. - 'BEGIN:DAYLIGHT'.PHP_EOL. - 'TZOFFSETFROM:+0100'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19810329T020000'.PHP_EOL. - 'TZNAME:GMT+2'.PHP_EOL. - 'TZOFFSETTO:+0200'.PHP_EOL. - 'END:DAYLIGHT'.PHP_EOL. - 'BEGIN:STANDARD'.PHP_EOL. - 'TZOFFSETFROM:+0200'.PHP_EOL. - 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU'.PHP_EOL. - 'DTSTART:19961027T030000'.PHP_EOL. - 'TZNAME:GMT+1'.PHP_EOL. - 'TZOFFSETTO:+0100'.PHP_EOL. - 'END:STANDARD'.PHP_EOL. - 'END:VTIMEZONE'.PHP_EOL. - 'BEGIN:VEVENT'.PHP_EOL. - 'CREATED:20160809T163629Z'.PHP_EOL. - 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02'.PHP_EOL. - 'TRANSP:OPAQUE'.PHP_EOL. - 'SUMMARY:Test Europe Berlin'.PHP_EOL. - 'DTSTART;TZID=Europe/Berlin:20160816T090000'.PHP_EOL. - 'DTSTAMP:20160809T163632Z'.PHP_EOL. - 'SEQUENCE:0'.PHP_EOL. - 'END:VEVENT'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vEvent7 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'PRODID:-//Tests//' . PHP_EOL + . 'CALSCALE:GREGORIAN' . PHP_EOL + . 'BEGIN:VTIMEZONE' . PHP_EOL + . 'TZID:Europe/Berlin' . PHP_EOL + . 'BEGIN:DAYLIGHT' . PHP_EOL + . 'TZOFFSETFROM:+0100' . PHP_EOL + . 'RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU' . PHP_EOL + . 'DTSTART:19810329T020000' . PHP_EOL + . 'TZNAME:GMT+2' . PHP_EOL + . 'TZOFFSETTO:+0200' . PHP_EOL + . 'END:DAYLIGHT' . PHP_EOL + . 'BEGIN:STANDARD' . PHP_EOL + . 'TZOFFSETFROM:+0200' . PHP_EOL + . 'RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU' . PHP_EOL + . 'DTSTART:19961027T030000' . PHP_EOL + . 'TZNAME:GMT+1' . PHP_EOL + . 'TZOFFSETTO:+0100' . PHP_EOL + . 'END:STANDARD' . PHP_EOL + . 'END:VTIMEZONE' . PHP_EOL + . 'BEGIN:VEVENT' . PHP_EOL + . 'CREATED:20160809T163629Z' . PHP_EOL + . 'UID:0AD16F58-01B3-463B-A215-FD09FC729A02' . PHP_EOL + . 'TRANSP:OPAQUE' . PHP_EOL + . 'SUMMARY:Test Europe Berlin' . PHP_EOL + . 'DTSTART;TZID=Europe/Berlin:20160816T090000' . PHP_EOL + . 'DTSTAMP:20160809T163632Z' . PHP_EOL + . 'SEQUENCE:0' . PHP_EOL + . 'END:VEVENT' . PHP_EOL + . 'END:VCALENDAR'; protected function setUp(): void { parent::setUp(); @@ -327,19 +319,19 @@ class EventsSearchProviderTest extends TestCase { 'calendarid' => 99, 'calendartype' => CalDavBackend::CALENDAR_TYPE_CALENDAR, 'uri' => 'event0.ics', - 'calendardata' => $this->vEvent0, + 'calendardata' => self::$vEvent0, ], [ 'calendarid' => 123, 'calendartype' => CalDavBackend::CALENDAR_TYPE_CALENDAR, 'uri' => 'event1.ics', - 'calendardata' => $this->vEvent1, + 'calendardata' => self::$vEvent1, ], [ 'calendarid' => 1337, 'calendartype' => CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION, 'uri' => 'event2.ics', - 'calendardata' => $this->vEvent2, + 'calendardata' => self::$vEvent2, ] ]); @@ -350,7 +342,7 @@ class EventsSearchProviderTest extends TestCase { $this->urlGenerator, $this->backend, ]) - ->setMethods([ + ->onlyMethods([ 'getDeepLinkToCalendarApp', 'generateSubline', ]) @@ -361,12 +353,11 @@ class EventsSearchProviderTest extends TestCase { ->willReturn('subline'); $provider->expects($this->exactly(3)) ->method('getDeepLinkToCalendarApp') - ->withConsecutive( - ['principals/users/john.doe', 'calendar-uri-99', 'event0.ics'], - ['principals/users/john.doe', 'calendar-uri-123', 'event1.ics'], - ['principals/users/john.doe', 'subscription-uri-1337', 'event2.ics'] - ) - ->willReturn('deep-link-to-calendar'); + ->willReturnMap([ + ['principals/users/john.doe', 'calendar-uri-99', 'event0.ics', 'deep-link-to-calendar'], + ['principals/users/john.doe', 'calendar-uri-123', 'event1.ics', 'deep-link-to-calendar'], + ['principals/users/john.doe', 'subscription-uri-1337', 'event2.ics', 'deep-link-to-calendar'] + ]); $actual = $provider->search($user, $query); $data = $actual->jsonSerialize(); @@ -427,12 +418,7 @@ class EventsSearchProviderTest extends TestCase { $this->assertEquals('absolute-url-to-route', $actual); } - /** - * @param string $ics - * @param string $expectedSubline - * - * @dataProvider generateSublineDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('generateSublineDataProvider')] public function testGenerateSubline(string $ics, string $expectedSubline): void { $vCalendar = Reader::read($ics, Reader::OPTION_FORGIVING); $eventComponent = $vCalendar->VEVENT; @@ -450,15 +436,15 @@ class EventsSearchProviderTest extends TestCase { $this->assertEquals($expectedSubline, $actual); } - public function generateSublineDataProvider(): array { + public static function generateSublineDataProvider(): array { return [ - [$this->vEvent1, '08-16 09:00 - 10:00'], - [$this->vEvent2, '08-16 09:00 - 08-17 10:00'], - [$this->vEvent3, '10-05'], - [$this->vEvent4, '10-05 - 10-07'], - [$this->vEvent5, '10-05 - 10-09'], - [$this->vEvent6, '10-05'], - [$this->vEvent7, '08-16 09:00 - 09:00'], + [self::$vEvent1, '08-16 09:00 - 10:00'], + [self::$vEvent2, '08-16 09:00 - 08-17 10:00'], + [self::$vEvent3, '10-05'], + [self::$vEvent4, '10-05 - 10-07'], + [self::$vEvent5, '10-05 - 10-09'], + [self::$vEvent6, '10-05'], + [self::$vEvent7, '08-16 09:00 - 09:00'], ]; } } diff --git a/apps/dav/tests/unit/Search/TasksSearchProviderTest.php b/apps/dav/tests/unit/Search/TasksSearchProviderTest.php index 18b6f0a5930..7f9a2842de9 100644 --- a/apps/dav/tests/unit/Search/TasksSearchProviderTest.php +++ b/apps/dav/tests/unit/Search/TasksSearchProviderTest.php @@ -17,89 +17,80 @@ use OCP\IUser; use OCP\Search\ISearchQuery; use OCP\Search\SearchResult; use OCP\Search\SearchResultEntry; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\VObject\Reader; use Test\TestCase; class TasksSearchProviderTest extends TestCase { - - /** @var IAppManager|\PHPUnit\Framework\MockObject\MockObject */ - private $appManager; - - /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject */ - private $l10n; - - /** @var IURLGenerator|\PHPUnit\Framework\MockObject\MockObject */ - private $urlGenerator; - - /** @var CalDavBackend|\PHPUnit\Framework\MockObject\MockObject */ - private $backend; - - /** @var TasksSearchProvider */ - private $provider; + private IAppManager&MockObject $appManager; + private IL10N&MockObject $l10n; + private IURLGenerator&MockObject $urlGenerator; + private CalDavBackend&MockObject $backend; + private TasksSearchProvider $provider; // NO DUE NOR COMPLETED NOR SUMMARY - private $vTodo0 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vTodo0 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'PRODID:TEST' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'BEGIN:VTODO' . PHP_EOL + . 'UID:20070313T123432Z-456553@example.com' . PHP_EOL + . 'DTSTAMP:20070313T123432Z' . PHP_EOL + . 'STATUS:NEEDS-ACTION' . PHP_EOL + . 'END:VTODO' . PHP_EOL + . 'END:VCALENDAR'; // DUE AND COMPLETED - private $vTodo1 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'COMPLETED:20070707T100000Z'.PHP_EOL. - 'DUE;VALUE=DATE:20070501'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vTodo1 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'PRODID:TEST' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'BEGIN:VTODO' . PHP_EOL + . 'UID:20070313T123432Z-456553@example.com' . PHP_EOL + . 'DTSTAMP:20070313T123432Z' . PHP_EOL + . 'COMPLETED:20070707T100000Z' . PHP_EOL + . 'DUE;VALUE=DATE:20070501' . PHP_EOL + . 'SUMMARY:Task title' . PHP_EOL + . 'STATUS:NEEDS-ACTION' . PHP_EOL + . 'END:VTODO' . PHP_EOL + . 'END:VCALENDAR'; // COMPLETED ONLY - private $vTodo2 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'COMPLETED:20070707T100000Z'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vTodo2 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'PRODID:TEST' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'BEGIN:VTODO' . PHP_EOL + . 'UID:20070313T123432Z-456553@example.com' . PHP_EOL + . 'DTSTAMP:20070313T123432Z' . PHP_EOL + . 'COMPLETED:20070707T100000Z' . PHP_EOL + . 'SUMMARY:Task title' . PHP_EOL + . 'STATUS:NEEDS-ACTION' . PHP_EOL + . 'END:VTODO' . PHP_EOL + . 'END:VCALENDAR'; // DUE DATE - private $vTodo3 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'DUE;VALUE=DATE:20070501'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vTodo3 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'PRODID:TEST' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'BEGIN:VTODO' . PHP_EOL + . 'UID:20070313T123432Z-456553@example.com' . PHP_EOL + . 'DTSTAMP:20070313T123432Z' . PHP_EOL + . 'DUE;VALUE=DATE:20070501' . PHP_EOL + . 'SUMMARY:Task title' . PHP_EOL + . 'STATUS:NEEDS-ACTION' . PHP_EOL + . 'END:VTODO' . PHP_EOL + . 'END:VCALENDAR'; // DUE DATETIME - private $vTodo4 = 'BEGIN:VCALENDAR'.PHP_EOL. - 'PRODID:TEST'.PHP_EOL. - 'VERSION:2.0'.PHP_EOL. - 'BEGIN:VTODO'.PHP_EOL. - 'UID:20070313T123432Z-456553@example.com'.PHP_EOL. - 'DTSTAMP:20070313T123432Z'.PHP_EOL. - 'DUE:20070709T130000Z'.PHP_EOL. - 'SUMMARY:Task title'.PHP_EOL. - 'STATUS:NEEDS-ACTION'.PHP_EOL. - 'END:VTODO'.PHP_EOL. - 'END:VCALENDAR'; + private static string $vTodo4 = 'BEGIN:VCALENDAR' . PHP_EOL + . 'PRODID:TEST' . PHP_EOL + . 'VERSION:2.0' . PHP_EOL + . 'BEGIN:VTODO' . PHP_EOL + . 'UID:20070313T123432Z-456553@example.com' . PHP_EOL + . 'DTSTAMP:20070313T123432Z' . PHP_EOL + . 'DUE:20070709T130000Z' . PHP_EOL + . 'SUMMARY:Task title' . PHP_EOL + . 'STATUS:NEEDS-ACTION' . PHP_EOL + . 'END:VTODO' . PHP_EOL + . 'END:VCALENDAR'; protected function setUp(): void { parent::setUp(); @@ -204,19 +195,19 @@ class TasksSearchProviderTest extends TestCase { 'calendarid' => 99, 'calendartype' => CalDavBackend::CALENDAR_TYPE_CALENDAR, 'uri' => 'todo0.ics', - 'calendardata' => $this->vTodo0, + 'calendardata' => self::$vTodo0, ], [ 'calendarid' => 123, 'calendartype' => CalDavBackend::CALENDAR_TYPE_CALENDAR, 'uri' => 'todo1.ics', - 'calendardata' => $this->vTodo1, + 'calendardata' => self::$vTodo1, ], [ 'calendarid' => 1337, 'calendartype' => CalDavBackend::CALENDAR_TYPE_SUBSCRIPTION, 'uri' => 'todo2.ics', - 'calendardata' => $this->vTodo2, + 'calendardata' => self::$vTodo2, ] ]); @@ -227,7 +218,7 @@ class TasksSearchProviderTest extends TestCase { $this->urlGenerator, $this->backend, ]) - ->setMethods([ + ->onlyMethods([ 'getDeepLinkToTasksApp', 'generateSubline', ]) @@ -238,12 +229,11 @@ class TasksSearchProviderTest extends TestCase { ->willReturn('subline'); $provider->expects($this->exactly(3)) ->method('getDeepLinkToTasksApp') - ->withConsecutive( - ['calendar-uri-99', 'todo0.ics'], - ['calendar-uri-123', 'todo1.ics'], - ['subscription-uri-1337', 'todo2.ics'] - ) - ->willReturn('deep-link-to-tasks'); + ->willReturnMap([ + ['calendar-uri-99', 'todo0.ics', 'deep-link-to-tasks'], + ['calendar-uri-123', 'todo1.ics', 'deep-link-to-tasks'], + ['subscription-uri-1337', 'todo2.ics', 'deep-link-to-tasks'] + ]); $actual = $provider->search($user, $query); $data = $actual->jsonSerialize(); @@ -292,37 +282,32 @@ class TasksSearchProviderTest extends TestCase { ->willReturn('link-to-route-tasks.index'); $this->urlGenerator->expects($this->once()) ->method('getAbsoluteURL') - ->with('link-to-route-tasks.index#/calendars/uri-john.doe/tasks/task-uri.ics') - ->willReturn('absolute-url-link-to-route-tasks.index#/calendars/uri-john.doe/tasks/task-uri.ics'); + ->with('link-to-route-tasks.indexcalendars/uri-john.doe/tasks/task-uri.ics') + ->willReturn('absolute-url-link-to-route-tasks.indexcalendars/uri-john.doe/tasks/task-uri.ics'); $actual = self::invokePrivate($this->provider, 'getDeepLinkToTasksApp', ['uri-john.doe', 'task-uri.ics']); - $this->assertEquals('absolute-url-link-to-route-tasks.index#/calendars/uri-john.doe/tasks/task-uri.ics', $actual); + $this->assertEquals('absolute-url-link-to-route-tasks.indexcalendars/uri-john.doe/tasks/task-uri.ics', $actual); } - /** - * @param string $ics - * @param string $expectedSubline - * - * @dataProvider generateSublineDataProvider - */ + #[\PHPUnit\Framework\Attributes\DataProvider('generateSublineDataProvider')] public function testGenerateSubline(string $ics, string $expectedSubline): void { $vCalendar = Reader::read($ics, Reader::OPTION_FORGIVING); $taskComponent = $vCalendar->VTODO; $this->l10n->method('t')->willReturnArgument(0); - $this->l10n->method('l')->willReturnArgument(''); + $this->l10n->method('l')->willReturnArgument(0); $actual = self::invokePrivate($this->provider, 'generateSubline', [$taskComponent]); $this->assertEquals($expectedSubline, $actual); } - public function generateSublineDataProvider(): array { + public static function generateSublineDataProvider(): array { return [ - [$this->vTodo0, ''], - [$this->vTodo1, 'Completed on %s'], - [$this->vTodo2, 'Completed on %s'], - [$this->vTodo3, 'Due on %s'], - [$this->vTodo4, 'Due on %s by %s'], + [self::$vTodo0, ''], + [self::$vTodo1, 'Completed on %s'], + [self::$vTodo2, 'Completed on %s'], + [self::$vTodo3, 'Due on %s'], + [self::$vTodo4, 'Due on %s by %s'], ]; } } diff --git a/apps/dav/tests/unit/ServerTest.php b/apps/dav/tests/unit/ServerTest.php index 16e0a5b80aa..9ffe86d3053 100644 --- a/apps/dav/tests/unit/ServerTest.php +++ b/apps/dav/tests/unit/ServerTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -19,10 +20,8 @@ use OCP\IRequest; */ class ServerTest extends \Test\TestCase { - /** - * @dataProvider providesUris - */ - public function test($uri, array $plugins): void { + #[\PHPUnit\Framework\Attributes\DataProvider('providesUris')] + public function test(string $uri, array $plugins): void { /** @var IRequest | \PHPUnit\Framework\MockObject\MockObject $r */ $r = $this->createMock(IRequest::class); $r->expects($this->any())->method('getRequestUri')->willReturn($uri); @@ -33,7 +32,7 @@ class ServerTest extends \Test\TestCase { $this->assertNotNull($s->server->getPlugin($plugin)); } } - public function providesUris() { + public static function providesUris(): array { return [ 'principals' => ['principals/users/admin', ['caldav', 'oc-resource-sharing', 'carddav']], 'calendars' => ['calendars/admin', ['caldav', 'oc-resource-sharing']], diff --git a/apps/dav/tests/unit/Service/AbsenceServiceTest.php b/apps/dav/tests/unit/Service/AbsenceServiceTest.php index 1bc5f53f18c..c16c715d5c2 100644 --- a/apps/dav/tests/unit/Service/AbsenceServiceTest.php +++ b/apps/dav/tests/unit/Service/AbsenceServiceTest.php @@ -7,7 +7,7 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\dav\tests\unit\Service; +namespace OCA\DAV\Tests\unit\Service; use DateTimeImmutable; use DateTimeZone; @@ -24,25 +24,16 @@ use OCP\EventDispatcher\IEventDispatcher; use OCP\IUser; use OCP\User\Events\OutOfOfficeChangedEvent; use OCP\User\Events\OutOfOfficeScheduledEvent; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class AbsenceServiceTest extends TestCase { private AbsenceService $absenceService; - - /** @var MockObject|AbsenceMapper */ - private $absenceMapper; - - /** @var MockObject|IEventDispatcher */ - private $eventDispatcher; - - /** @var MockObject|IJobList */ - private $jobList; - - /** @var MockObject|TimezoneService */ - private $timezoneService; - - /** @var MockObject|ITimeFactory */ - private $timeFactory; + private AbsenceMapper&MockObject $absenceMapper; + private IEventDispatcher&MockObject $eventDispatcher; + private IJobList&MockObject $jobList; + private TimezoneService&MockObject $timezoneService; + private ITimeFactory&MockObject $timeFactory; protected function setUp(): void { parent::setUp(); @@ -62,7 +53,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testCreateAbsenceEmitsScheduledEvent() { + public function testCreateAbsenceEmitsScheduledEvent(): void { $tz = new DateTimeZone('Europe/Berlin'); $user = $this->createMock(IUser::class); $user->method('getUID') @@ -117,7 +108,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testUpdateAbsenceEmitsChangedEvent() { + public function testUpdateAbsenceEmitsChangedEvent(): void { $tz = new DateTimeZone('Europe/Berlin'); $user = $this->createMock(IUser::class); $user->method('getUID') @@ -181,7 +172,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testCreateAbsenceSchedulesBothJobs() { + public function testCreateAbsenceSchedulesBothJobs(): void { $tz = new DateTimeZone('Europe/Berlin'); $startDateString = '2023-01-05'; $startDate = new DateTimeImmutable($startDateString, $tz); @@ -230,7 +221,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testCreateAbsenceSchedulesOnlyEndJob() { + public function testCreateAbsenceSchedulesOnlyEndJob(): void { $tz = new DateTimeZone('Europe/Berlin'); $endDateString = '2023-01-10'; $endDate = new DateTimeImmutable($endDateString, $tz); @@ -271,7 +262,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testCreateAbsenceSchedulesNoJob() { + public function testCreateAbsenceSchedulesNoJob(): void { $tz = new DateTimeZone('Europe/Berlin'); $user = $this->createMock(IUser::class); $user->method('getUID') @@ -306,7 +297,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testUpdateAbsenceSchedulesBothJobs() { + public function testUpdateAbsenceSchedulesBothJobs(): void { $tz = new DateTimeZone('Europe/Berlin'); $startDateString = '2023-01-05'; $startDate = new DateTimeImmutable($startDateString, $tz); @@ -362,7 +353,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testUpdateSchedulesOnlyEndJob() { + public function testUpdateSchedulesOnlyEndJob(): void { $tz = new DateTimeZone('Europe/Berlin'); $endDateString = '2023-01-10'; $endDate = new DateTimeImmutable($endDateString, $tz); @@ -410,7 +401,7 @@ class AbsenceServiceTest extends TestCase { ); } - public function testUpdateAbsenceSchedulesNoJob() { + public function testUpdateAbsenceSchedulesNoJob(): void { $tz = new DateTimeZone('Europe/Berlin'); $user = $this->createMock(IUser::class); $user->method('getUID') diff --git a/apps/dav/tests/unit/Service/ExampleContactServiceTest.php b/apps/dav/tests/unit/Service/ExampleContactServiceTest.php new file mode 100644 index 00000000000..027b66a6fb2 --- /dev/null +++ b/apps/dav/tests/unit/Service/ExampleContactServiceTest.php @@ -0,0 +1,194 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\Service; + +use OCA\DAV\CardDAV\CardDavBackend; +use OCA\DAV\Service\ExampleContactService; +use OCP\App\IAppManager; +use OCP\AppFramework\Services\IAppConfig; +use OCP\Files\AppData\IAppDataFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Log\LoggerInterface; +use Symfony\Component\Uid\Uuid; +use Test\TestCase; + +class ExampleContactServiceTest extends TestCase { + protected ExampleContactService $service; + protected CardDavBackend&MockObject $cardDav; + protected IAppManager&MockObject $appManager; + protected IAppDataFactory&MockObject $appDataFactory; + protected LoggerInterface&MockObject $logger; + protected IAppConfig&MockObject $appConfig; + protected IAppData&MockObject $appData; + + protected function setUp(): void { + parent::setUp(); + + $this->cardDav = $this->createMock(CardDavBackend::class); + $this->appDataFactory = $this->createMock(IAppDataFactory::class); + $this->logger = $this->createMock(LoggerInterface::class); + $this->appConfig = $this->createMock(IAppConfig::class); + + $this->appData = $this->createMock(IAppData::class); + $this->appDataFactory->method('get') + ->with('dav') + ->willReturn($this->appData); + + $this->service = new ExampleContactService( + $this->appDataFactory, + $this->appConfig, + $this->logger, + $this->cardDav, + ); + } + + public function testCreateDefaultContactWithInvalidCard(): void { + // Invalid vCard missing required FN property + $vcardContent = "BEGIN:VCARD\nVERSION:3.0\nEND:VCARD"; + $this->appConfig->method('getAppValueBool') + ->with('enableDefaultContact', true) + ->willReturn(true); + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $file->method('getContent')->willReturn($vcardContent); + $folder->method('getFile')->willReturn($file); + $this->appData->method('getFolder')->willReturn($folder); + + $this->logger->expects($this->once()) + ->method('error') + ->with('Default contact is invalid', $this->anything()); + + $this->cardDav->expects($this->never()) + ->method('createCard'); + + $this->service->createDefaultContact(123); + } + + public function testUidAndRevAreUpdated(): void { + $originalUid = 'original-uid'; + $originalRev = '20200101T000000Z'; + $vcardContent = "BEGIN:VCARD\nVERSION:3.0\nFN:Test User\nUID:$originalUid\nREV:$originalRev\nEND:VCARD"; + + $this->appConfig->method('getAppValueBool') + ->with('enableDefaultContact', true) + ->willReturn(true); + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $file->method('getContent')->willReturn($vcardContent); + $folder->method('getFile')->willReturn($file); + $this->appData->method('getFolder')->willReturn($folder); + + $capturedCardData = null; + $this->cardDav->expects($this->once()) + ->method('createCard') + ->with( + $this->anything(), + $this->anything(), + $this->callback(function ($cardData) use (&$capturedCardData) { + $capturedCardData = $cardData; + return true; + }), + $this->anything() + )->willReturn(null); + + $this->service->createDefaultContact(123); + + $vcard = \Sabre\VObject\Reader::read($capturedCardData); + $this->assertNotEquals($originalUid, $vcard->UID->getValue()); + $this->assertTrue(Uuid::isValid($vcard->UID->getValue())); + $this->assertNotEquals($originalRev, $vcard->REV->getValue()); + } + + public function testDefaultContactFileDoesNotExist(): void { + $this->appConfig->method('getAppValueBool') + ->with('enableDefaultContact', true) + ->willReturn(true); + $this->appData->method('getFolder')->willThrowException(new NotFoundException()); + + $this->cardDav->expects($this->never()) + ->method('createCard'); + + $this->service->createDefaultContact(123); + } + + public function testUidAndRevAreAddedIfMissing(): void { + $vcardContent = "BEGIN:VCARD\nVERSION:3.0\nFN:Test User\nEND:VCARD"; + + $this->appConfig->method('getAppValueBool') + ->with('enableDefaultContact', true) + ->willReturn(true); + $folder = $this->createMock(ISimpleFolder::class); + $file = $this->createMock(ISimpleFile::class); + $file->method('getContent')->willReturn($vcardContent); + $folder->method('getFile')->willReturn($file); + $this->appData->method('getFolder')->willReturn($folder); + + $capturedCardData = 'new-card-data'; + + $this->cardDav + ->expects($this->once()) + ->method('createCard') + ->with( + $this->anything(), + $this->anything(), + $this->callback(function ($cardData) use (&$capturedCardData) { + $capturedCardData = $cardData; + return true; + }), + $this->anything() + ); + + $this->service->createDefaultContact(123); + $vcard = \Sabre\VObject\Reader::read($capturedCardData); + + $this->assertNotNull($vcard->REV); + $this->assertNotNull($vcard->UID); + $this->assertTrue(Uuid::isValid($vcard->UID->getValue())); + } + + public function testDefaultContactIsNotCreatedIfEnabled(): void { + $this->appConfig->method('getAppValueBool') + ->with('enableDefaultContact', true) + ->willReturn(false); + $this->logger->expects($this->never()) + ->method('error'); + $this->cardDav->expects($this->never()) + ->method('createCard'); + + $this->service->createDefaultContact(123); + } + + public static function provideDefaultContactEnableData(): array { + return [[true], [false]]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('provideDefaultContactEnableData')] + public function testIsDefaultContactEnabled(bool $enabled): void { + $this->appConfig->expects(self::once()) + ->method('getAppValueBool') + ->with('enableDefaultContact', true) + ->willReturn($enabled); + + $this->assertEquals($enabled, $this->service->isDefaultContactEnabled()); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('provideDefaultContactEnableData')] + public function testSetDefaultContactEnabled(bool $enabled): void { + $this->appConfig->expects(self::once()) + ->method('setAppValueBool') + ->with('enableDefaultContact', $enabled); + + $this->service->setDefaultContactEnabled($enabled); + } +} diff --git a/apps/dav/tests/unit/Service/ExampleEventServiceTest.php b/apps/dav/tests/unit/Service/ExampleEventServiceTest.php new file mode 100644 index 00000000000..0f423624fb8 --- /dev/null +++ b/apps/dav/tests/unit/Service/ExampleEventServiceTest.php @@ -0,0 +1,196 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\Service; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Service\ExampleEventService; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Files\IAppData; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\Files\SimpleFS\ISimpleFolder; +use OCP\IAppConfig; +use OCP\IL10N; +use OCP\Security\ISecureRandom; +use PHPUnit\Framework\MockObject\MockObject; +use Test\TestCase; + +class ExampleEventServiceTest extends TestCase { + private ExampleEventService $service; + + private CalDavBackend&MockObject $calDavBackend; + private ISecureRandom&MockObject $random; + private ITimeFactory&MockObject $time; + private IAppData&MockObject $appData; + private IAppConfig&MockObject $appConfig; + private IL10N&MockObject $l10n; + + protected function setUp(): void { + parent::setUp(); + + $this->calDavBackend = $this->createMock(CalDavBackend::class); + $this->random = $this->createMock(ISecureRandom::class); + $this->time = $this->createMock(ITimeFactory::class); + $this->appData = $this->createMock(IAppData::class); + $this->appConfig = $this->createMock(IAppConfig::class); + $this->l10n = $this->createMock(IL10N::class); + + $this->l10n->method('t') + ->willReturnArgument(0); + + $this->service = new ExampleEventService( + $this->calDavBackend, + $this->random, + $this->time, + $this->appData, + $this->appConfig, + $this->l10n, + ); + } + + public static function provideCustomEventData(): array { + return [ + [file_get_contents(__DIR__ . '/../test_fixtures/example-event.ics')], + [file_get_contents(__DIR__ . '/../test_fixtures/example-event-with-attendees.ics')], + ]; + } + + #[\PHPUnit\Framework\Attributes\DataProvider('provideCustomEventData')] + public function testCreateExampleEventWithCustomEvent($customEventIcs): void { + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('dav', 'create_example_event', true) + ->willReturn(true); + + $exampleEventFolder = $this->createMock(ISimpleFolder::class); + $this->appData->expects(self::once()) + ->method('getFolder') + ->with('example_event') + ->willReturn($exampleEventFolder); + $exampleEventFile = $this->createMock(ISimpleFile::class); + $exampleEventFolder->expects(self::once()) + ->method('getFile') + ->with('example_event.ics') + ->willReturn($exampleEventFile); + $exampleEventFile->expects(self::once()) + ->method('getContent') + ->willReturn($customEventIcs); + + $this->random->expects(self::once()) + ->method('generate') + ->with(32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->willReturn('RANDOM-UID'); + + $now = new \DateTimeImmutable('2025-01-21T00:00:00Z'); + $this->time->expects(self::exactly(2)) + ->method('now') + ->willReturn($now); + + $expectedIcs = file_get_contents(__DIR__ . '/../test_fixtures/example-event-expected.ics'); + $this->calDavBackend->expects(self::once()) + ->method('createCalendarObject') + ->with(1000, 'RANDOM-UID.ics', $expectedIcs); + + $this->service->createExampleEvent(1000); + } + + public function testCreateExampleEventWithDefaultEvent(): void { + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('dav', 'create_example_event', true) + ->willReturn(true); + + $this->appData->expects(self::once()) + ->method('getFolder') + ->with('example_event') + ->willThrowException(new NotFoundException()); + + $this->random->expects(self::once()) + ->method('generate') + ->with(32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->willReturn('RANDOM-UID'); + + $now = new \DateTimeImmutable('2025-01-21T00:00:00Z'); + $this->time->expects(self::exactly(3)) + ->method('now') + ->willReturn($now); + + $expectedIcs = file_get_contents(__DIR__ . '/../test_fixtures/example-event-default-expected.ics'); + $this->calDavBackend->expects(self::once()) + ->method('createCalendarObject') + ->with(1000, 'RANDOM-UID.ics', $expectedIcs); + + $this->service->createExampleEvent(1000); + } + + public function testCreateExampleWhenDisabled(): void { + $this->appConfig->expects(self::once()) + ->method('getValueBool') + ->with('dav', 'create_example_event', true) + ->willReturn(false); + + $this->calDavBackend->expects(self::never()) + ->method('createCalendarObject'); + + $this->service->createExampleEvent(1000); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('provideCustomEventData')] + public function testGetExampleEventWithCustomEvent($customEventIcs): void { + $exampleEventFolder = $this->createMock(ISimpleFolder::class); + $this->appData->expects(self::once()) + ->method('getFolder') + ->with('example_event') + ->willReturn($exampleEventFolder); + $exampleEventFile = $this->createMock(ISimpleFile::class); + $exampleEventFolder->expects(self::once()) + ->method('getFile') + ->with('example_event.ics') + ->willReturn($exampleEventFile); + $exampleEventFile->expects(self::once()) + ->method('getContent') + ->willReturn($customEventIcs); + + $this->random->expects(self::once()) + ->method('generate') + ->with(32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->willReturn('RANDOM-UID'); + + $now = new \DateTimeImmutable('2025-01-21T00:00:00Z'); + $this->time->expects(self::exactly(2)) + ->method('now') + ->willReturn($now); + + $expectedIcs = file_get_contents(__DIR__ . '/../test_fixtures/example-event-expected.ics'); + $actualIcs = $this->service->getExampleEvent()->getIcs(); + $this->assertEquals($expectedIcs, $actualIcs); + } + + public function testGetExampleEventWithDefault(): void { + $this->appData->expects(self::once()) + ->method('getFolder') + ->with('example_event') + ->willThrowException(new NotFoundException()); + + $this->random->expects(self::once()) + ->method('generate') + ->with(32, 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789') + ->willReturn('RANDOM-UID'); + + $now = new \DateTimeImmutable('2025-01-21T00:00:00Z'); + $this->time->expects(self::exactly(3)) + ->method('now') + ->willReturn($now); + + $expectedIcs = file_get_contents(__DIR__ . '/../test_fixtures/example-event-default-expected.ics'); + $actualIcs = $this->service->getExampleEvent()->getIcs(); + $this->assertEquals($expectedIcs, $actualIcs); + } +} diff --git a/apps/dav/tests/unit/Service/UpcomingEventsServiceTest.php b/apps/dav/tests/unit/Service/UpcomingEventsServiceTest.php new file mode 100644 index 00000000000..fdfe37d8918 --- /dev/null +++ b/apps/dav/tests/unit/Service/UpcomingEventsServiceTest.php @@ -0,0 +1,89 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +namespace OCA\DAV\Tests\unit\DAV\Service; + +use DateTimeImmutable; +use OCA\DAV\CalDAV\UpcomingEventsService; +use OCP\App\IAppManager; +use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Calendar\ICalendarQuery; +use OCP\Calendar\IManager; +use OCP\IURLGenerator; +use OCP\IUserManager; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class UpcomingEventsServiceTest extends TestCase { + + private IManager&MockObject $calendarManager; + private ITimeFactory&MockObject $timeFactory; + private IUserManager&MockObject $userManager; + private IAppManager&MockObject $appManager; + private IURLGenerator&MockObject $urlGenerator; + private UpcomingEventsService $service; + + protected function setUp(): void { + parent::setUp(); + + $this->calendarManager = $this->createMock(IManager::class); + $this->timeFactory = $this->createMock(ITimeFactory::class); + $this->userManager = $this->createMock(IUserManager::class); + $this->appManager = $this->createMock(IAppManager::class); + $this->urlGenerator = $this->createMock(IURLGenerator::class); + + $this->service = new UpcomingEventsService( + $this->calendarManager, + $this->timeFactory, + $this->userManager, + $this->appManager, + $this->urlGenerator, + ); + } + + public function testGetEventsByLocation(): void { + $now = new DateTimeImmutable('2024-07-08T18:20:20Z'); + $this->timeFactory->method('now') + ->willReturn($now); + $query = $this->createMock(ICalendarQuery::class); + $this->appManager->method('isEnabledForUser')->willReturn(false); + $this->calendarManager->method('newQuery') + ->with('principals/users/user1') + ->willReturn($query); + $query->expects(self::once()) + ->method('addSearchProperty') + ->with('LOCATION'); + $query->expects(self::once()) + ->method('setSearchPattern') + ->with('https://cloud.example.com/call/123'); + $this->calendarManager->expects(self::once()) + ->method('searchForPrincipal') + ->with($query) + ->willReturn([ + [ + 'uri' => 'ev1', + 'calendar-key' => '1', + 'calendar-uri' => 'personal', + 'objects' => [ + 0 => [ + 'DTSTART' => [ + new DateTimeImmutable('now'), + ], + ], + ], + ], + ]); + + $events = $this->service->getEvents('user1', 'https://cloud.example.com/call/123'); + + self::assertCount(1, $events); + $event1 = $events[0]; + self::assertEquals('ev1', $event1->getUri()); + } +} diff --git a/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php b/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php index 46a8db6f3eb..032759d64b7 100644 --- a/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php +++ b/apps/dav/tests/unit/Settings/CalDAVSettingsTest.php @@ -1,11 +1,14 @@ <?php + +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -namespace OCA\DAV\Tests\Unit\DAV\Settings; +namespace OCA\DAV\Tests\unit\DAV\Settings; use OCA\DAV\Settings\CalDAVSettings; +use OCP\App\IAppManager; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; use OCP\IConfig; @@ -14,16 +17,10 @@ use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; class CalDAVSettingsTest extends TestCase { - - /** @var IConfig|MockObject */ - private $config; - - /** @var IInitialState|MockObject */ - private $initialState; - - /** @var IURLGenerator|MockObject */ - private $urlGenerator; - + private IConfig&MockObject $config; + private IInitialState&MockObject $initialState; + private IURLGenerator&MockObject $urlGenerator; + private IAppManager&MockObject $appManager; private CalDAVSettings $settings; protected function setUp(): void { @@ -32,42 +29,59 @@ class CalDAVSettingsTest extends TestCase { $this->config = $this->createMock(IConfig::class); $this->initialState = $this->createMock(IInitialState::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); - $this->settings = new CalDAVSettings($this->config, $this->initialState, $this->urlGenerator); + $this->appManager = $this->createMock(IAppManager::class); + $this->settings = new CalDAVSettings($this->config, $this->initialState, $this->urlGenerator, $this->appManager); } public function testGetForm(): void { $this->config->method('getAppValue') - ->withConsecutive( - ['dav', 'sendInvitations', 'yes'], - ['dav', 'generateBirthdayCalendar', 'yes'], - ['dav', 'sendEventReminders', 'yes'], - ['dav', 'sendEventRemindersToSharedUsers', 'yes'], - ['dav', 'sendEventRemindersPush', 'yes'], - ) - ->will($this->onConsecutiveCalls('yes', 'no', 'yes', 'yes', 'yes')); + ->willReturnMap([ + ['dav', 'sendInvitations', 'yes', 'yes'], + ['dav', 'generateBirthdayCalendar', 'yes', 'no'], + ['dav', 'sendEventReminders', 'yes', 'yes'], + ['dav', 'sendEventRemindersToSharedUsers', 'yes', 'yes'], + ['dav', 'sendEventRemindersPush', 'yes', 'yes'], + ]); $this->urlGenerator ->expects($this->once()) ->method('linkToDocs') ->with('user-sync-calendars') ->willReturn('Some docs URL'); + + $calls = [ + ['userSyncCalendarsDocUrl', 'Some docs URL'], + ['sendInvitations', true], + ['generateBirthdayCalendar', false], + ['sendEventReminders', true], + ['sendEventRemindersToSharedUsers', true], + ['sendEventRemindersPush', true], + ]; $this->initialState->method('provideInitialState') - ->withConsecutive( - ['userSyncCalendarsDocUrl', 'Some docs URL'], - ['sendInvitations', true], - ['generateBirthdayCalendar', false], - ['sendEventReminders', true], - ['sendEventRemindersToSharedUsers', true], - ['sendEventRemindersPush', true], - ); + ->willReturnCallback(function () use (&$calls): void { + $expected = array_shift($calls); + $this->assertEquals($expected, func_get_args()); + }); $result = $this->settings->getForm(); $this->assertInstanceOf(TemplateResponse::class, $result); } public function testGetSection(): void { + $this->appManager->expects(self::once()) + ->method('isBackendRequired') + ->with(IAppManager::BACKEND_CALDAV) + ->willReturn(true); $this->assertEquals('groupware', $this->settings->getSection()); } + public function testGetSectionWithoutCaldavBackend(): void { + $this->appManager->expects(self::once()) + ->method('isBackendRequired') + ->with(IAppManager::BACKEND_CALDAV) + ->willReturn(false); + $this->assertEquals(null, $this->settings->getSection()); + } + public function testGetPriority(): void { $this->assertEquals(10, $this->settings->getPriority()); } diff --git a/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php b/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php index 308950cef34..39342811377 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagMappingNodeTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,35 +9,34 @@ namespace OCA\DAV\Tests\unit\SystemTag; use OC\SystemTag\SystemTag; +use OCA\DAV\SystemTag\SystemTagMappingNode; use OCP\IUser; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagNotFoundException; +use PHPUnit\Framework\MockObject\MockObject; class SystemTagMappingNodeTest extends \Test\TestCase { - private ISystemTagManager $tagManager; - private ISystemTagObjectMapper $tagMapper; - private IUser $user; + private ISystemTagManager&MockObject $tagManager; + private ISystemTagObjectMapper&MockObject $tagMapper; + private IUser&MockObject $user; protected function setUp(): void { parent::setUp(); - $this->tagManager = $this->getMockBuilder(ISystemTagManager::class) - ->getMock(); - $this->tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class) - ->getMock(); - $this->user = $this->getMockBuilder(IUser::class) - ->getMock(); + $this->tagManager = $this->createMock(ISystemTagManager::class); + $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class); + $this->user = $this->createMock(IUser::class); } public function getMappingNode($tag = null, array $writableNodeIds = []) { if ($tag === null) { - $tag = new SystemTag(1, 'Test', true, true); + $tag = new SystemTag('1', 'Test', true, true); } - return new \OCA\DAV\SystemTag\SystemTagMappingNode( + return new SystemTagMappingNode( $tag, - 123, + '123', 'files', $this->user, $this->tagManager, @@ -46,7 +46,7 @@ class SystemTagMappingNodeTest extends \Test\TestCase { } public function testGetters(): void { - $tag = new SystemTag(1, 'Test', true, false); + $tag = new SystemTag('1', 'Test', true, false); $node = $this->getMappingNode($tag); $this->assertEquals('1', $node->getName()); $this->assertEquals($tag, $node->getSystemTag()); @@ -92,24 +92,22 @@ class SystemTagMappingNodeTest extends \Test\TestCase { $node->delete(); } - public function tagNodeDeleteProviderPermissionException() { + public static function tagNodeDeleteProviderPermissionException(): array { return [ [ // cannot unassign invisible tag - new SystemTag(1, 'Original', false, true), + new SystemTag('1', 'Original', false, true), 'Sabre\DAV\Exception\NotFound', ], [ // cannot unassign non-assignable tag - new SystemTag(1, 'Original', true, false), + new SystemTag('1', 'Original', true, false), 'Sabre\DAV\Exception\Forbidden', ], ]; } - /** - * @dataProvider tagNodeDeleteProviderPermissionException - */ + #[\PHPUnit\Framework\Attributes\DataProvider('tagNodeDeleteProviderPermissionException')] public function testDeleteTagExpectedException(ISystemTag $tag, $expectedException): void { $this->tagManager->expects($this->any()) ->method('canUserSeeTag') @@ -140,7 +138,7 @@ class SystemTagMappingNodeTest extends \Test\TestCase { // assuming the tag existed at the time the node was created, // but got deleted concurrently in the database - $tag = new SystemTag(1, 'Test', true, true); + $tag = new SystemTag('1', 'Test', true, true); $this->tagManager->expects($this->once()) ->method('canUserSeeTag') ->with($tag) @@ -152,7 +150,7 @@ class SystemTagMappingNodeTest extends \Test\TestCase { $this->tagMapper->expects($this->once()) ->method('unassignTags') ->with(123, 'files', 1) - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->getMappingNode($tag, [123])->delete(); } diff --git a/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php b/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php index 82aa81674df..594b5e15db6 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagNodeTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,54 +9,48 @@ namespace OCA\DAV\Tests\unit\SystemTag; use OC\SystemTag\SystemTag; +use OCA\DAV\SystemTag\SystemTagNode; use OCP\IUser; use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagAlreadyExistsException; use OCP\SystemTag\TagNotFoundException; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\Forbidden; class SystemTagNodeTest extends \Test\TestCase { - - /** - * @var \OCP\SystemTag\ISystemTagManager|\PHPUnit\Framework\MockObject\MockObject - */ - private $tagManager; - - /** - * @var \OCP\IUser - */ - private $user; + private ISystemTagManager&MockObject $tagManager; + private ISystemTagObjectMapper&MockObject $tagMapper; + private IUser&MockObject $user; protected function setUp(): void { parent::setUp(); - $this->tagManager = $this->getMockBuilder(ISystemTagManager::class) - ->getMock(); - $this->user = $this->getMockBuilder(IUser::class) - ->getMock(); + $this->tagManager = $this->createMock(ISystemTagManager::class); + $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class); + $this->user = $this->createMock(IUser::class); } protected function getTagNode($isAdmin = true, $tag = null) { if ($tag === null) { - $tag = new SystemTag(1, 'Test', true, true); + $tag = new SystemTag('1', 'Test', true, true); } - return new \OCA\DAV\SystemTag\SystemTagNode( + return new SystemTagNode( $tag, $this->user, $isAdmin, - $this->tagManager + $this->tagManager, + $this->tagMapper, ); } - public function adminFlagProvider() { + public static function adminFlagProvider(): array { return [[true], [false]]; } - /** - * @dataProvider adminFlagProvider - */ - public function testGetters($isAdmin): void { + #[\PHPUnit\Framework\Attributes\DataProvider('adminFlagProvider')] + public function testGetters(bool $isAdmin): void { $tag = new SystemTag('1', 'Test', true, true); $node = $this->getTagNode($isAdmin, $tag); $this->assertEquals('1', $node->getName()); @@ -69,33 +64,31 @@ class SystemTagNodeTest extends \Test\TestCase { $this->getTagNode()->setName('2'); } - public function tagNodeProvider() { + public static function tagNodeProvider(): array { return [ // admin [ true, - new SystemTag(1, 'Original', true, true), - ['Renamed', true, true] + new SystemTag('1', 'Original', true, true), + ['Renamed', true, true, null] ], [ true, - new SystemTag(1, 'Original', true, true), - ['Original', false, false] + new SystemTag('1', 'Original', true, true), + ['Original', false, false, null] ], // non-admin [ // renaming allowed false, - new SystemTag(1, 'Original', true, true), - ['Rename', true, true] + new SystemTag('1', 'Original', true, true), + ['Rename', true, true, '0082c9'] ], ]; } - /** - * @dataProvider tagNodeProvider - */ - public function testUpdateTag($isAdmin, ISystemTag $originalTag, $changedArgs): void { + #[\PHPUnit\Framework\Attributes\DataProvider('tagNodeProvider')] + public function testUpdateTag(bool $isAdmin, ISystemTag $originalTag, array $changedArgs): void { $this->tagManager->expects($this->once()) ->method('canUserSeeTag') ->with($originalTag) @@ -106,56 +99,54 @@ class SystemTagNodeTest extends \Test\TestCase { ->willReturn($originalTag->isUserAssignable() || $isAdmin); $this->tagManager->expects($this->once()) ->method('updateTag') - ->with(1, $changedArgs[0], $changedArgs[1], $changedArgs[2]); + ->with(1, $changedArgs[0], $changedArgs[1], $changedArgs[2], $changedArgs[3]); $this->getTagNode($isAdmin, $originalTag) - ->update($changedArgs[0], $changedArgs[1], $changedArgs[2]); + ->update($changedArgs[0], $changedArgs[1], $changedArgs[2], $changedArgs[3]); } - public function tagNodeProviderPermissionException() { + public static function tagNodeProviderPermissionException(): array { return [ [ // changing permissions not allowed - new SystemTag(1, 'Original', true, true), - ['Original', false, true], + new SystemTag('1', 'Original', true, true), + ['Original', false, true, ''], 'Sabre\DAV\Exception\Forbidden', ], [ // changing permissions not allowed - new SystemTag(1, 'Original', true, true), - ['Original', true, false], + new SystemTag('1', 'Original', true, true), + ['Original', true, false, ''], 'Sabre\DAV\Exception\Forbidden', ], [ // changing permissions not allowed - new SystemTag(1, 'Original', true, true), - ['Original', false, false], + new SystemTag('1', 'Original', true, true), + ['Original', false, false, ''], 'Sabre\DAV\Exception\Forbidden', ], [ // changing non-assignable not allowed - new SystemTag(1, 'Original', true, false), - ['Rename', true, false], + new SystemTag('1', 'Original', true, false), + ['Rename', true, false, ''], 'Sabre\DAV\Exception\Forbidden', ], [ // changing non-assignable not allowed - new SystemTag(1, 'Original', true, false), - ['Original', true, true], + new SystemTag('1', 'Original', true, false), + ['Original', true, true, ''], 'Sabre\DAV\Exception\Forbidden', ], [ // invisible tag does not exist - new SystemTag(1, 'Original', false, false), - ['Rename', false, false], + new SystemTag('1', 'Original', false, false), + ['Rename', false, false, ''], 'Sabre\DAV\Exception\NotFound', ], ]; } - /** - * @dataProvider tagNodeProviderPermissionException - */ - public function testUpdateTagPermissionException(ISystemTag $originalTag, $changedArgs, $expectedException = null): void { + #[\PHPUnit\Framework\Attributes\DataProvider('tagNodeProviderPermissionException')] + public function testUpdateTagPermissionException(ISystemTag $originalTag, array $changedArgs, string $expectedException): void { $this->tagManager->expects($this->any()) ->method('canUserSeeTag') ->with($originalTag) @@ -171,7 +162,7 @@ class SystemTagNodeTest extends \Test\TestCase { try { $this->getTagNode(false, $originalTag) - ->update($changedArgs[0], $changedArgs[1], $changedArgs[2]); + ->update($changedArgs[0], $changedArgs[1], $changedArgs[2], $changedArgs[3]); } catch (\Exception $e) { $thrown = $e; } @@ -183,7 +174,7 @@ class SystemTagNodeTest extends \Test\TestCase { public function testUpdateTagAlreadyExists(): void { $this->expectException(\Sabre\DAV\Exception\Conflict::class); - $tag = new SystemTag(1, 'tag1', true, true); + $tag = new SystemTag('1', 'tag1', true, true); $this->tagManager->expects($this->any()) ->method('canUserSeeTag') ->with($tag) @@ -195,15 +186,15 @@ class SystemTagNodeTest extends \Test\TestCase { $this->tagManager->expects($this->once()) ->method('updateTag') ->with(1, 'Renamed', true, true) - ->will($this->throwException(new TagAlreadyExistsException())); - $this->getTagNode(false, $tag)->update('Renamed', true, true); + ->willThrowException(new TagAlreadyExistsException()); + $this->getTagNode(false, $tag)->update('Renamed', true, true, null); } public function testUpdateTagNotFound(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $tag = new SystemTag(1, 'tag1', true, true); + $tag = new SystemTag('1', 'tag1', true, true); $this->tagManager->expects($this->any()) ->method('canUserSeeTag') ->with($tag) @@ -215,15 +206,13 @@ class SystemTagNodeTest extends \Test\TestCase { $this->tagManager->expects($this->once()) ->method('updateTag') ->with(1, 'Renamed', true, true) - ->will($this->throwException(new TagNotFoundException())); - $this->getTagNode(false, $tag)->update('Renamed', true, true); + ->willThrowException(new TagNotFoundException()); + $this->getTagNode(false, $tag)->update('Renamed', true, true, null); } - /** - * @dataProvider adminFlagProvider - */ - public function testDeleteTag($isAdmin): void { - $tag = new SystemTag(1, 'tag1', true, true); + #[\PHPUnit\Framework\Attributes\DataProvider('adminFlagProvider')] + public function testDeleteTag(bool $isAdmin): void { + $tag = new SystemTag('1', 'tag1', true, true); $this->tagManager->expects($isAdmin ? $this->once() : $this->never()) ->method('canUserSeeTag') ->with($tag) @@ -237,25 +226,23 @@ class SystemTagNodeTest extends \Test\TestCase { $this->getTagNode($isAdmin, $tag)->delete(); } - public function tagNodeDeleteProviderPermissionException() { + public static function tagNodeDeleteProviderPermissionException(): array { return [ [ // cannot delete invisible tag - new SystemTag(1, 'Original', false, true), + new SystemTag('1', 'Original', false, true), 'Sabre\DAV\Exception\Forbidden', ], [ // cannot delete non-assignable tag - new SystemTag(1, 'Original', true, false), + new SystemTag('1', 'Original', true, false), 'Sabre\DAV\Exception\Forbidden', ], ]; } - /** - * @dataProvider tagNodeDeleteProviderPermissionException - */ - public function testDeleteTagPermissionException(ISystemTag $tag, $expectedException): void { + #[\PHPUnit\Framework\Attributes\DataProvider('tagNodeDeleteProviderPermissionException')] + public function testDeleteTagPermissionException(ISystemTag $tag, string $expectedException): void { $this->tagManager->expects($this->any()) ->method('canUserSeeTag') ->with($tag) @@ -271,7 +258,7 @@ class SystemTagNodeTest extends \Test\TestCase { public function testDeleteTagNotFound(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $tag = new SystemTag(1, 'tag1', true, true); + $tag = new SystemTag('1', 'tag1', true, true); $this->tagManager->expects($this->any()) ->method('canUserSeeTag') ->with($tag) @@ -279,7 +266,7 @@ class SystemTagNodeTest extends \Test\TestCase { $this->tagManager->expects($this->once()) ->method('deleteTags') ->with('1') - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->getTagNode(true, $tag)->delete(); } } diff --git a/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php b/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php index 51711fc1666..e0c4685c1fb 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -9,8 +10,10 @@ namespace OCA\DAV\Tests\unit\SystemTag; use OC\SystemTag\SystemTag; use OCA\DAV\SystemTag\SystemTagNode; +use OCA\DAV\SystemTag\SystemTagPlugin; use OCA\DAV\SystemTag\SystemTagsByIdCollection; use OCA\DAV\SystemTag\SystemTagsObjectMappingCollection; +use OCP\Files\IRootFolder; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; @@ -18,74 +21,39 @@ use OCP\SystemTag\ISystemTag; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagAlreadyExistsException; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Tree; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; class SystemTagPluginTest extends \Test\TestCase { - public const ID_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::ID_PROPERTYNAME; - public const DISPLAYNAME_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::DISPLAYNAME_PROPERTYNAME; - public const USERVISIBLE_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::USERVISIBLE_PROPERTYNAME; - public const USERASSIGNABLE_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::USERASSIGNABLE_PROPERTYNAME; - public const CANASSIGN_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::CANASSIGN_PROPERTYNAME; - public const GROUPS_PROPERTYNAME = \OCA\DAV\SystemTag\SystemTagPlugin::GROUPS_PROPERTYNAME; - - /** - * @var \Sabre\DAV\Server - */ - private $server; - - /** - * @var \Sabre\DAV\Tree - */ - private $tree; - - /** - * @var \OCP\SystemTag\ISystemTagManager - */ - private $tagManager; - - /** - * @var IGroupManager - */ - private $groupManager; - - /** - * @var IUserSession - */ - private $userSession; - - /** - * @var IUser - */ - private $user; - - /** - * @var \OCA\DAV\SystemTag\SystemTagPlugin - */ - private $plugin; - - /** - * @var ISystemTagObjectMapper - */ - private $tagMapper; + public const ID_PROPERTYNAME = SystemTagPlugin::ID_PROPERTYNAME; + public const DISPLAYNAME_PROPERTYNAME = SystemTagPlugin::DISPLAYNAME_PROPERTYNAME; + public const USERVISIBLE_PROPERTYNAME = SystemTagPlugin::USERVISIBLE_PROPERTYNAME; + public const USERASSIGNABLE_PROPERTYNAME = SystemTagPlugin::USERASSIGNABLE_PROPERTYNAME; + public const CANASSIGN_PROPERTYNAME = SystemTagPlugin::CANASSIGN_PROPERTYNAME; + public const GROUPS_PROPERTYNAME = SystemTagPlugin::GROUPS_PROPERTYNAME; + + private \Sabre\DAV\Server $server; + private \Sabre\DAV\Tree&MockObject $tree; + private ISystemTagManager&MockObject $tagManager; + private IGroupManager&MockObject $groupManager; + private IUserSession&MockObject $userSession; + private IRootFolder&MockObject $rootFolder; + private IUser&MockObject $user; + private ISystemTagObjectMapper&MockObject $tagMapper; + private SystemTagPlugin $plugin; protected function setUp(): void { parent::setUp(); - $this->tree = $this->getMockBuilder(Tree::class) - ->disableOriginalConstructor() - ->getMock(); + $this->tree = $this->createMock(Tree::class); $this->server = new \Sabre\DAV\Server($this->tree); - $this->tagManager = $this->getMockBuilder(ISystemTagManager::class) - ->getMock(); - $this->groupManager = $this->getMockBuilder(IGroupManager::class) - ->getMock(); - $this->user = $this->getMockBuilder(IUser::class) - ->getMock(); - $this->userSession = $this->getMockBuilder(IUserSession::class) - ->getMock(); + $this->tagManager = $this->createMock(ISystemTagManager::class); + $this->groupManager = $this->createMock(IGroupManager::class); + $this->user = $this->createMock(IUser::class); + $this->userSession = $this->createMock(IUserSession::class); $this->userSession ->expects($this->any()) ->method('getUser') @@ -94,22 +62,24 @@ class SystemTagPluginTest extends \Test\TestCase { ->expects($this->any()) ->method('isLoggedIn') ->willReturn(true); - $this->tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class) - ->getMock(); - $this->plugin = new \OCA\DAV\SystemTag\SystemTagPlugin( + $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class); + $this->rootFolder = $this->createMock(IRootFolder::class); + + $this->plugin = new SystemTagPlugin( $this->tagManager, $this->groupManager, $this->userSession, + $this->rootFolder, $this->tagMapper ); $this->plugin->initialize($this->server); } - public function getPropertiesDataProvider() { + public static function getPropertiesDataProvider(): array { return [ [ - new SystemTag(1, 'Test', true, true), + new SystemTag('1', 'Test', true, true), [], [ self::ID_PROPERTYNAME, @@ -127,7 +97,7 @@ class SystemTagPluginTest extends \Test\TestCase { ] ], [ - new SystemTag(1, 'Test', true, false), + new SystemTag('1', 'Test', true, false), [], [ self::ID_PROPERTYNAME, @@ -145,7 +115,7 @@ class SystemTagPluginTest extends \Test\TestCase { ] ], [ - new SystemTag(1, 'Test', true, false), + new SystemTag('1', 'Test', true, false), ['group1', 'group2'], [ self::ID_PROPERTYNAME, @@ -157,7 +127,7 @@ class SystemTagPluginTest extends \Test\TestCase { ] ], [ - new SystemTag(1, 'Test', true, true), + new SystemTag('1', 'Test', true, true), ['group1', 'group2'], [ self::ID_PROPERTYNAME, @@ -172,10 +142,8 @@ class SystemTagPluginTest extends \Test\TestCase { ]; } - /** - * @dataProvider getPropertiesDataProvider - */ - public function testGetProperties(ISystemTag $systemTag, $groups, $requestedProperties, $expectedProperties): void { + #[\PHPUnit\Framework\Attributes\DataProvider('getPropertiesDataProvider')] + public function testGetProperties(ISystemTag $systemTag, array $groups, array $requestedProperties, array $expectedProperties): void { $this->user->expects($this->any()) ->method('getUID') ->willReturn('admin'); @@ -226,7 +194,7 @@ class SystemTagPluginTest extends \Test\TestCase { public function testGetPropertiesForbidden(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); - $systemTag = new SystemTag(1, 'Test', true, false); + $systemTag = new SystemTag('1', 'Test', true, false); $requestedProperties = [ self::ID_PROPERTYNAME, self::GROUPS_PROPERTYNAME, @@ -265,7 +233,7 @@ class SystemTagPluginTest extends \Test\TestCase { } public function testUpdatePropertiesAdmin(): void { - $systemTag = new SystemTag(1, 'Test', true, false); + $systemTag = new SystemTag('1', 'Test', true, false); $this->user->expects($this->any()) ->method('getUID') ->willReturn('admin'); @@ -323,7 +291,7 @@ class SystemTagPluginTest extends \Test\TestCase { public function testUpdatePropertiesForbidden(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); - $systemTag = new SystemTag(1, 'Test', true, false); + $systemTag = new SystemTag('1', 'Test', true, false); $this->user->expects($this->any()) ->method('getUID') ->willReturn('admin'); @@ -364,17 +332,15 @@ class SystemTagPluginTest extends \Test\TestCase { $propPatch->commit(); } - public function createTagInsufficientPermissionsProvider() { + public static function createTagInsufficientPermissionsProvider(): array { return [ [true, false, ''], [false, true, ''], [true, true, 'group1|group2'], ]; } - /** - * @dataProvider createTagInsufficientPermissionsProvider - */ - public function testCreateNotAssignableTagAsRegularUser($userVisible, $userAssignable, $groups): void { + #[\PHPUnit\Framework\Attributes\DataProvider('createTagInsufficientPermissionsProvider')] + public function testCreateNotAssignableTagAsRegularUser(bool $userVisible, bool $userAssignable, string $groups): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->expectExceptionMessage('Not sufficient permissions'); @@ -397,9 +363,7 @@ class SystemTagPluginTest extends \Test\TestCase { } $requestData = json_encode($requestData); - $node = $this->getMockBuilder(SystemTagsByIdCollection::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(SystemTagsByIdCollection::class); $this->tagManager->expects($this->never()) ->method('createTag'); $this->tagManager->expects($this->never()) @@ -410,12 +374,8 @@ class SystemTagPluginTest extends \Test\TestCase { ->with('/systemtags') ->willReturn($node); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request->expects($this->once()) ->method('getPath') @@ -434,7 +394,7 @@ class SystemTagPluginTest extends \Test\TestCase { } public function testCreateTagInByIdCollectionAsRegularUser(): void { - $systemTag = new SystemTag(1, 'Test', true, false); + $systemTag = new SystemTag('1', 'Test', true, false); $requestData = json_encode([ 'name' => 'Test', @@ -442,9 +402,7 @@ class SystemTagPluginTest extends \Test\TestCase { 'userAssignable' => true, ]); - $node = $this->getMockBuilder(SystemTagsByIdCollection::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(SystemTagsByIdCollection::class); $this->tagManager->expects($this->once()) ->method('createTag') ->with('Test', true, true) @@ -455,12 +413,8 @@ class SystemTagPluginTest extends \Test\TestCase { ->with('/systemtags') ->willReturn($node); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request->expects($this->once()) ->method('getPath') @@ -486,7 +440,7 @@ class SystemTagPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - public function createTagProvider() { + public static function createTagProvider(): array { return [ [true, false, ''], [false, false, ''], @@ -494,10 +448,8 @@ class SystemTagPluginTest extends \Test\TestCase { ]; } - /** - * @dataProvider createTagProvider - */ - public function testCreateTagInByIdCollection($userVisible, $userAssignable, $groups): void { + #[\PHPUnit\Framework\Attributes\DataProvider('createTagProvider')] + public function testCreateTagInByIdCollection(bool $userVisible, bool $userAssignable, string $groups): void { $this->user->expects($this->once()) ->method('getUID') ->willReturn('admin'); @@ -507,7 +459,7 @@ class SystemTagPluginTest extends \Test\TestCase { ->with('admin') ->willReturn(true); - $systemTag = new SystemTag(1, 'Test', true, false); + $systemTag = new SystemTag('1', 'Test', true, false); $requestData = [ 'name' => 'Test', @@ -519,9 +471,7 @@ class SystemTagPluginTest extends \Test\TestCase { } $requestData = json_encode($requestData); - $node = $this->getMockBuilder(SystemTagsByIdCollection::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(SystemTagsByIdCollection::class); $this->tagManager->expects($this->once()) ->method('createTag') ->with('Test', $userVisible, $userAssignable) @@ -542,12 +492,8 @@ class SystemTagPluginTest extends \Test\TestCase { ->with('/systemtags') ->willReturn($node); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request->expects($this->once()) ->method('getPath') @@ -573,7 +519,7 @@ class SystemTagPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - public function nodeClassProvider() { + public static function nodeClassProvider(): array { return [ ['\OCA\DAV\SystemTag\SystemTagsByIdCollection'], ['\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection'], @@ -590,7 +536,7 @@ class SystemTagPluginTest extends \Test\TestCase { ->with('admin') ->willReturn(true); - $systemTag = new SystemTag(1, 'Test', true, false); + $systemTag = new SystemTag('1', 'Test', true, false); $requestData = json_encode([ 'name' => 'Test', @@ -598,9 +544,7 @@ class SystemTagPluginTest extends \Test\TestCase { 'userAssignable' => false, ]); - $node = $this->getMockBuilder(SystemTagsObjectMappingCollection::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(SystemTagsObjectMappingCollection::class); $this->tagManager->expects($this->once()) ->method('createTag') @@ -616,12 +560,8 @@ class SystemTagPluginTest extends \Test\TestCase { ->method('createFile') ->with(1); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request->expects($this->once()) ->method('getPath') @@ -651,13 +591,11 @@ class SystemTagPluginTest extends \Test\TestCase { public function testCreateTagToUnknownNode(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $node = $this->getMockBuilder(SystemTagsObjectMappingCollection::class) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock(SystemTagsObjectMappingCollection::class); $this->tree->expects($this->any()) ->method('getNodeForPath') - ->will($this->throwException(new \Sabre\DAV\Exception\NotFound())); + ->willThrowException(new \Sabre\DAV\Exception\NotFound()); $this->tagManager->expects($this->never()) ->method('createTag'); @@ -665,12 +603,8 @@ class SystemTagPluginTest extends \Test\TestCase { $node->expects($this->never()) ->method('createFile'); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request->expects($this->once()) ->method('getPath') @@ -679,10 +613,8 @@ class SystemTagPluginTest extends \Test\TestCase { $this->plugin->httpPost($request, $response); } - /** - * @dataProvider nodeClassProvider - */ - public function testCreateTagConflict($nodeClass): void { + #[\PHPUnit\Framework\Attributes\DataProvider('nodeClassProvider')] + public function testCreateTagConflict(string $nodeClass): void { $this->expectException(\Sabre\DAV\Exception\Conflict::class); $this->user->expects($this->once()) @@ -700,25 +632,19 @@ class SystemTagPluginTest extends \Test\TestCase { 'userAssignable' => false, ]); - $node = $this->getMockBuilder($nodeClass) - ->disableOriginalConstructor() - ->getMock(); + $node = $this->createMock($nodeClass); $this->tagManager->expects($this->once()) ->method('createTag') ->with('Test', true, false) - ->will($this->throwException(new TagAlreadyExistsException('Tag already exists'))); + ->willThrowException(new TagAlreadyExistsException('Tag already exists')); $this->tree->expects($this->any()) ->method('getNodeForPath') ->with('/systemtags') ->willReturn($node); - $request = $this->getMockBuilder(RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $response = $this->getMockBuilder(ResponseInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $request = $this->createMock(RequestInterface::class); + $response = $this->createMock(ResponseInterface::class); $request->expects($this->once()) ->method('getPath') diff --git a/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php b/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php index db55d82adc9..8f7848452fe 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagsByIdCollectionTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,67 +9,66 @@ namespace OCA\DAV\Tests\unit\SystemTag; use OC\SystemTag\SystemTag; +use OCA\DAV\SystemTag\SystemTagsByIdCollection; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; +use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagNotFoundException; +use PHPUnit\Framework\MockObject\MockObject; class SystemTagsByIdCollectionTest extends \Test\TestCase { - - /** - * @var \OCP\SystemTag\ISystemTagManager - */ - private $tagManager; - - /** - * @var \OCP\IUser - */ - private $user; + private ISystemTagManager&MockObject $tagManager; + private IUser&MockObject $user; protected function setUp(): void { parent::setUp(); - $this->tagManager = $this->getMockBuilder(ISystemTagManager::class) - ->getMock(); + $this->tagManager = $this->createMock(ISystemTagManager::class); } - public function getNode($isAdmin = true) { - $this->user = $this->getMockBuilder(IUser::class) - ->getMock(); + public function getNode(bool $isAdmin = true) { + $this->user = $this->createMock(IUser::class); $this->user->expects($this->any()) ->method('getUID') ->willReturn('testuser'); - $userSession = $this->getMockBuilder(IUserSession::class) - ->getMock(); + + /** @var IUserSession&MockObject */ + $userSession = $this->createMock(IUserSession::class); $userSession->expects($this->any()) ->method('getUser') ->willReturn($this->user); - $groupManager = $this->getMockBuilder(IGroupManager::class) - ->getMock(); + + /** @var IGroupManager&MockObject */ + $groupManager = $this->createMock(IGroupManager::class); $groupManager->expects($this->any()) ->method('isAdmin') ->with('testuser') ->willReturn($isAdmin); - return new \OCA\DAV\SystemTag\SystemTagsByIdCollection( + + /** @var ISystemTagObjectMapper&MockObject */ + $tagMapper = $this->createMock(ISystemTagObjectMapper::class); + return new SystemTagsByIdCollection( $this->tagManager, $userSession, - $groupManager + $groupManager, + $tagMapper, ); } - public function adminFlagProvider() { + public static function adminFlagProvider(): array { return [[true], [false]]; } - + public function testForbiddenCreateFile(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); $this->getNode()->createFile('555'); } - + public function testForbiddenCreateDirectory(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); @@ -76,7 +76,7 @@ class SystemTagsByIdCollectionTest extends \Test\TestCase { } public function testGetChild(): void { - $tag = new SystemTag(123, 'Test', true, false); + $tag = new SystemTag('123', 'Test', true, false); $this->tagManager->expects($this->once()) ->method('canUserSeeTag') ->with($tag) @@ -94,35 +94,35 @@ class SystemTagsByIdCollectionTest extends \Test\TestCase { $this->assertEquals($tag, $childNode->getSystemTag()); } - + public function testGetChildInvalidName(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->tagManager->expects($this->once()) ->method('getTagsByIds') ->with(['invalid']) - ->will($this->throwException(new \InvalidArgumentException())); + ->willThrowException(new \InvalidArgumentException()); $this->getNode()->getChild('invalid'); } - + public function testGetChildNotFound(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); $this->tagManager->expects($this->once()) ->method('getTagsByIds') ->with(['444']) - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->getNode()->getChild('444'); } - + public function testGetChildUserNotVisible(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $tag = new SystemTag(123, 'Test', false, false); + $tag = new SystemTag('123', 'Test', false, false); $this->tagManager->expects($this->once()) ->method('getTagsByIds') @@ -133,8 +133,8 @@ class SystemTagsByIdCollectionTest extends \Test\TestCase { } public function testGetChildrenAdmin(): void { - $tag1 = new SystemTag(123, 'One', true, false); - $tag2 = new SystemTag(456, 'Two', true, true); + $tag1 = new SystemTag('123', 'One', true, false); + $tag2 = new SystemTag('456', 'Two', true, true); $this->tagManager->expects($this->once()) ->method('getAllTags') @@ -152,8 +152,8 @@ class SystemTagsByIdCollectionTest extends \Test\TestCase { } public function testGetChildrenNonAdmin(): void { - $tag1 = new SystemTag(123, 'One', true, false); - $tag2 = new SystemTag(456, 'Two', true, true); + $tag1 = new SystemTag('123', 'One', true, false); + $tag2 = new SystemTag('456', 'Two', true, true); $this->tagManager->expects($this->once()) ->method('getAllTags') @@ -178,18 +178,16 @@ class SystemTagsByIdCollectionTest extends \Test\TestCase { $this->assertCount(0, $this->getNode()->getChildren()); } - public function childExistsProvider() { + public static function childExistsProvider(): array { return [ [true, true], [false, false], ]; } - /** - * @dataProvider childExistsProvider - */ - public function testChildExists($userVisible, $expectedResult): void { - $tag = new SystemTag(123, 'One', $userVisible, false); + #[\PHPUnit\Framework\Attributes\DataProvider('childExistsProvider')] + public function testChildExists(bool $userVisible, bool $expectedResult): void { + $tag = new SystemTag('123', 'One', $userVisible, false); $this->tagManager->expects($this->once()) ->method('canUserSeeTag') ->with($tag) @@ -207,19 +205,19 @@ class SystemTagsByIdCollectionTest extends \Test\TestCase { $this->tagManager->expects($this->once()) ->method('getTagsByIds') ->with(['123']) - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->assertFalse($this->getNode()->childExists('123')); } - + public function testChildExistsBadRequest(): void { $this->expectException(\Sabre\DAV\Exception\BadRequest::class); $this->tagManager->expects($this->once()) ->method('getTagsByIds') ->with(['invalid']) - ->will($this->throwException(new \InvalidArgumentException())); + ->willThrowException(new \InvalidArgumentException()); $this->getNode()->childExists('invalid'); } diff --git a/apps/dav/tests/unit/SystemTag/SystemTagsObjectMappingCollectionTest.php b/apps/dav/tests/unit/SystemTag/SystemTagsObjectMappingCollectionTest.php index c2e62f73828..5aea1242e2a 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagsObjectMappingCollectionTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagsObjectMappingCollectionTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,31 +9,29 @@ namespace OCA\DAV\Tests\unit\SystemTag; use OC\SystemTag\SystemTag; +use OCA\DAV\SystemTag\SystemTagsObjectMappingCollection; use OCP\IUser; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; use OCP\SystemTag\TagNotFoundException; +use PHPUnit\Framework\MockObject\MockObject; class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { - private ISystemTagManager $tagManager; - private ISystemTagObjectMapper $tagMapper; - private IUser $user; + private ISystemTagManager&MockObject $tagManager; + private ISystemTagObjectMapper&MockObject $tagMapper; + private IUser&MockObject $user; protected function setUp(): void { parent::setUp(); - $this->tagManager = $this->getMockBuilder(ISystemTagManager::class) - ->getMock(); - $this->tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class) - ->getMock(); - - $this->user = $this->getMockBuilder(IUser::class) - ->getMock(); + $this->tagManager = $this->createMock(ISystemTagManager::class); + $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class); + $this->user = $this->createMock(IUser::class); } - public function getNode(array $writableNodeIds = []) { - return new \OCA\DAV\SystemTag\SystemTagsObjectMappingCollection( - 111, + public function getNode(array $writableNodeIds = []): SystemTagsObjectMappingCollection { + return new SystemTagsObjectMappingCollection( + '111', 'files', $this->user, $this->tagManager, @@ -85,7 +84,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { $this->getNode()->createFile('555'); } - public function permissionsProvider() { + public static function permissionsProvider(): array { return [ // invisible, tag does not exist for user [false, true, '\Sabre\DAV\Exception\PreconditionFailed'], @@ -94,10 +93,8 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { ]; } - /** - * @dataProvider permissionsProvider - */ - public function testAssignTagNoPermission($userVisible, $userAssignable, $expectedException): void { + #[\PHPUnit\Framework\Attributes\DataProvider('permissionsProvider')] + public function testAssignTagNoPermission(bool $userVisible, bool $userAssignable, string $expectedException): void { $tag = new SystemTag('1', 'Test', $userVisible, $userAssignable); $this->tagManager->expects($this->once()) ->method('canUserSeeTag') @@ -132,7 +129,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { $this->tagManager->expects($this->once()) ->method('getTagsByIds') ->with(['555']) - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->getNode()->createFile('555'); } @@ -145,7 +142,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { } public function testGetChild(): void { - $tag = new SystemTag(555, 'TheTag', true, false); + $tag = new SystemTag('555', 'TheTag', true, false); $this->tagManager->expects($this->once()) ->method('canUserSeeTag') ->with($tag) @@ -171,7 +168,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { public function testGetChildNonVisible(): void { $this->expectException(\Sabre\DAV\Exception\NotFound::class); - $tag = new SystemTag(555, 'TheTag', false, false); + $tag = new SystemTag('555', 'TheTag', false, false); $this->tagManager->expects($this->once()) ->method('canUserSeeTag') ->with($tag) @@ -209,7 +206,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { $this->tagMapper->expects($this->once()) ->method('haveTag') ->with([111], 'files', 'badid') - ->will($this->throwException(new \InvalidArgumentException())); + ->willThrowException(new \InvalidArgumentException()); $this->getNode()->getChild('badid'); } @@ -221,15 +218,15 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { $this->tagMapper->expects($this->once()) ->method('haveTag') ->with([111], 'files', '777') - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->getNode()->getChild('777'); } public function testGetChildren(): void { - $tag1 = new SystemTag(555, 'TagOne', true, false); - $tag2 = new SystemTag(556, 'TagTwo', true, true); - $tag3 = new SystemTag(557, 'InvisibleTag', false, true); + $tag1 = new SystemTag('555', 'TagOne', true, false); + $tag2 = new SystemTag('556', 'TagTwo', true, true); + $tag3 = new SystemTag('557', 'InvisibleTag', false, true); $this->tagMapper->expects($this->once()) ->method('getTagIdsForObjects') @@ -264,7 +261,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { } public function testChildExistsWithVisibleTag(): void { - $tag = new SystemTag(555, 'TagOne', true, false); + $tag = new SystemTag('555', 'TagOne', true, false); $this->tagMapper->expects($this->once()) ->method('haveTag') @@ -285,7 +282,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { } public function testChildExistsWithInvisibleTag(): void { - $tag = new SystemTag(555, 'TagOne', false, false); + $tag = new SystemTag('555', 'TagOne', false, false); $this->tagMapper->expects($this->once()) ->method('haveTag') @@ -313,7 +310,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { $this->tagMapper->expects($this->once()) ->method('haveTag') ->with([111], 'files', '555') - ->will($this->throwException(new TagNotFoundException())); + ->willThrowException(new TagNotFoundException()); $this->assertFalse($this->getNode()->childExists('555')); } @@ -325,7 +322,7 @@ class SystemTagsObjectMappingCollectionTest extends \Test\TestCase { $this->tagMapper->expects($this->once()) ->method('haveTag') ->with([111], 'files', '555') - ->will($this->throwException(new \InvalidArgumentException())); + ->willThrowException(new \InvalidArgumentException()); $this->getNode()->childExists('555'); } diff --git a/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php b/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php index b202f340e32..301eb528436 100644 --- a/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php +++ b/apps/dav/tests/unit/SystemTag/SystemTagsObjectTypeCollectionTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -7,66 +8,47 @@ */ namespace OCA\DAV\Tests\unit\SystemTag; +use OCA\DAV\SystemTag\SystemTagsObjectTypeCollection; use OCP\Files\Folder; +use OCP\Files\Node; use OCP\IGroupManager; use OCP\IUser; use OCP\IUserSession; use OCP\SystemTag\ISystemTagManager; use OCP\SystemTag\ISystemTagObjectMapper; +use PHPUnit\Framework\MockObject\MockObject; class SystemTagsObjectTypeCollectionTest extends \Test\TestCase { - - /** - * @var \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection - */ - private $node; - - /** - * @var \OCP\SystemTag\ISystemTagManager - */ - private $tagManager; - - /** - * @var \OCP\SystemTag\ISystemTagObjectMapper - */ - private $tagMapper; - - /** - * @var \OCP\Files\Folder - */ - private $userFolder; + private ISystemTagManager&MockObject $tagManager; + private ISystemTagObjectMapper&MockObject $tagMapper; + private Folder&MockObject $userFolder; + private SystemTagsObjectTypeCollection $node; protected function setUp(): void { parent::setUp(); - $this->tagManager = $this->getMockBuilder(ISystemTagManager::class) - ->getMock(); - $this->tagMapper = $this->getMockBuilder(ISystemTagObjectMapper::class) - ->getMock(); + $this->tagManager = $this->createMock(ISystemTagManager::class); + $this->tagMapper = $this->createMock(ISystemTagObjectMapper::class); - $user = $this->getMockBuilder(IUser::class) - ->getMock(); + $user = $this->createMock(IUser::class); $user->expects($this->any()) ->method('getUID') ->willReturn('testuser'); - $userSession = $this->getMockBuilder(IUserSession::class) - ->getMock(); + $userSession = $this->createMock(IUserSession::class); $userSession->expects($this->any()) ->method('getUser') ->willReturn($user); - $groupManager = $this->getMockBuilder(IGroupManager::class) - ->getMock(); + $groupManager = $this->createMock(IGroupManager::class); $groupManager->expects($this->any()) ->method('isAdmin') ->with('testuser') ->willReturn(true); - $this->userFolder = $this->getMockBuilder(Folder::class) - ->getMock(); + $this->userFolder = $this->createMock(Folder::class); $userFolder = $this->userFolder; $closure = function ($name) use ($userFolder) { - $node = $userFolder->getFirstNodeById(intval($name)); + $node = $userFolder->getFirstNodeById((int)$name); return $node !== null; }; $writeAccessClosure = function ($name) use ($userFolder) { @@ -79,7 +61,7 @@ class SystemTagsObjectTypeCollectionTest extends \Test\TestCase { return false; }; - $this->node = new \OCA\DAV\SystemTag\SystemTagsObjectTypeCollection( + $this->node = new SystemTagsObjectTypeCollection( 'files', $this->tagManager, $this->tagMapper, @@ -108,7 +90,7 @@ class SystemTagsObjectTypeCollectionTest extends \Test\TestCase { $this->userFolder->expects($this->once()) ->method('getFirstNodeById') ->with('555') - ->willReturn($this->createMock(\OCP\Files\Node::class)); + ->willReturn($this->createMock(Node::class)); $childNode = $this->node->getChild('555'); $this->assertInstanceOf('\OCA\DAV\SystemTag\SystemTagsObjectMappingCollection', $childNode); @@ -137,7 +119,7 @@ class SystemTagsObjectTypeCollectionTest extends \Test\TestCase { $this->userFolder->expects($this->once()) ->method('getFirstNodeById') ->with('123') - ->willReturn($this->createMock(\OCP\Files\Node::class)); + ->willReturn($this->createMock(Node::class)); $this->assertTrue($this->node->childExists('123')); } diff --git a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php index 6c380fa3191..ec5d0a9ab5b 100644 --- a/apps/dav/tests/unit/Upload/AssemblyStreamTest.php +++ b/apps/dav/tests/unit/Upload/AssemblyStreamTest.php @@ -7,39 +7,51 @@ */ namespace OCA\DAV\Tests\unit\Upload; +use OCA\DAV\Upload\AssemblyStream; use Sabre\DAV\File; class AssemblyStreamTest extends \Test\TestCase { - /** - * @dataProvider providesNodes() - */ - public function testGetContents($expected, $nodes): void { - $stream = \OCA\DAV\Upload\AssemblyStream::wrap($nodes); + #[\PHPUnit\Framework\Attributes\DataProvider('providesNodes')] + public function testGetContents(string $expected, array $nodeData): void { + $nodes = []; + foreach ($nodeData as $data) { + $nodes[] = $this->buildNode(...$data); + } + $stream = AssemblyStream::wrap($nodes); $content = stream_get_contents($stream); $this->assertEquals($expected, $content); } - /** - * @dataProvider providesNodes() - */ - public function testGetContentsFread($expected, $nodes): void { - $stream = \OCA\DAV\Upload\AssemblyStream::wrap($nodes); + #[\PHPUnit\Framework\Attributes\DataProvider('providesNodes')] + public function testGetContentsFread(string $expected, array $nodeData, int $chunkLength = 3): void { + $nodes = []; + foreach ($nodeData as $data) { + $nodes[] = $this->buildNode(...$data); + } + $stream = AssemblyStream::wrap($nodes); $content = ''; while (!feof($stream)) { - $content .= fread($stream, 3); + $chunk = fread($stream, $chunkLength); + $content .= $chunk; + if ($chunkLength !== 3) { + $this->assertEquals($chunkLength, strlen($chunk)); + } } $this->assertEquals($expected, $content); } - /** - * @dataProvider providesNodes() - */ - public function testSeek($expected, $nodes): void { - $stream = \OCA\DAV\Upload\AssemblyStream::wrap($nodes); + #[\PHPUnit\Framework\Attributes\DataProvider('providesNodes')] + public function testSeek(string $expected, array $nodeData): void { + $nodes = []; + foreach ($nodeData as $data) { + $nodes[] = $this->buildNode(...$data); + } + + $stream = AssemblyStream::wrap($nodes); $offset = floor(strlen($expected) * 0.6); if (fseek($stream, $offset) === -1) { @@ -50,63 +62,75 @@ class AssemblyStreamTest extends \Test\TestCase { $this->assertEquals(substr($expected, $offset), $content); } - public function providesNodes() { - $data8k = $this->makeData(8192); - $dataLess8k = $this->makeData(8191); + public static function providesNodes(): array { + $data8k = self::makeData(8192); + $dataLess8k = self::makeData(8191); $tonofnodes = []; - $tonofdata = ""; + $tonofdata = ''; for ($i = 0; $i < 101; $i++) { - $thisdata = rand(0, 100); // variable length and content + $thisdata = random_int(0, 100); // variable length and content $tonofdata .= $thisdata; - array_push($tonofnodes, $this->buildNode($i, $thisdata)); + $tonofnodes[] = [(string)$i, (string)$thisdata]; } return[ 'one node zero bytes' => [ '', [ - $this->buildNode('0', '') + ['0', ''], ]], 'one node only' => [ '1234567890', [ - $this->buildNode('0', '1234567890') + ['0', '1234567890'], ]], 'one node buffer boundary' => [ $data8k, [ - $this->buildNode('0', $data8k) + ['0', $data8k], ]], 'two nodes' => [ '1234567890', [ - $this->buildNode('1', '67890'), - $this->buildNode('0', '12345') + ['1', '67890'], + ['0', '12345'], ]], 'two nodes end on buffer boundary' => [ $data8k . $data8k, [ - $this->buildNode('1', $data8k), - $this->buildNode('0', $data8k) + ['1', $data8k], + ['0', $data8k], ]], 'two nodes with one on buffer boundary' => [ $data8k . $dataLess8k, [ - $this->buildNode('1', $dataLess8k), - $this->buildNode('0', $data8k) + ['1', $dataLess8k], + ['0', $data8k], ]], 'two nodes on buffer boundary plus one byte' => [ $data8k . 'X' . $data8k, [ - $this->buildNode('1', $data8k), - $this->buildNode('0', $data8k . 'X') + ['1', $data8k], + ['0', $data8k . 'X'], ]], 'two nodes on buffer boundary plus one byte at the end' => [ $data8k . $data8k . 'X', [ - $this->buildNode('1', $data8k . 'X'), - $this->buildNode('0', $data8k) + ['1', $data8k . 'X'], + ['0', $data8k], ]], 'a ton of nodes' => [ $tonofdata, $tonofnodes - ] + ], + 'one read over multiple nodes' => [ + '1234567890', [ + ['0', '1234'], + ['1', '5678'], + ['2', '90'], + ], 10], + 'two reads over multiple nodes' => [ + '1234567890', [ + ['0', '1234'], + ['1', '5678'], + ['2', '90'], + ], 5], ]; } - private function makeData($count) { + private static function makeData(int $count): string { $data = ''; $base = '1234567890'; $j = 0; @@ -120,10 +144,10 @@ class AssemblyStreamTest extends \Test\TestCase { return $data; } - private function buildNode($name, $data) { + private function buildNode(string $name, string $data) { $node = $this->getMockBuilder(File::class) - ->setMethods(['getName', 'get', 'getSize']) - ->getMockForAbstractClass(); + ->onlyMethods(['getName', 'get', 'getSize']) + ->getMock(); $node->expects($this->any()) ->method('getName') diff --git a/apps/dav/tests/unit/Upload/ChunkingPluginTest.php b/apps/dav/tests/unit/Upload/ChunkingPluginTest.php index 87feebf5d09..00ed7657dd3 100644 --- a/apps/dav/tests/unit/Upload/ChunkingPluginTest.php +++ b/apps/dav/tests/unit/Upload/ChunkingPluginTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2019-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2017 ownCloud GmbH @@ -10,40 +11,24 @@ namespace OCA\DAV\Tests\unit\Upload; use OCA\DAV\Connector\Sabre\Directory; use OCA\DAV\Upload\ChunkingPlugin; use OCA\DAV\Upload\FutureFile; +use PHPUnit\Framework\MockObject\MockObject; use Sabre\DAV\Exception\NotFound; use Sabre\HTTP\RequestInterface; use Sabre\HTTP\ResponseInterface; use Test\TestCase; class ChunkingPluginTest extends TestCase { - /** - * @var \Sabre\DAV\Server | \PHPUnit\Framework\MockObject\MockObject - */ - private $server; - - /** - * @var \Sabre\DAV\Tree | \PHPUnit\Framework\MockObject\MockObject - */ - private $tree; - - /** - * @var ChunkingPlugin - */ - private $plugin; - /** @var RequestInterface | \PHPUnit\Framework\MockObject\MockObject */ - private $request; - /** @var ResponseInterface | \PHPUnit\Framework\MockObject\MockObject */ - private $response; + private \Sabre\DAV\Server&MockObject $server; + private \Sabre\DAV\Tree&MockObject $tree; + private ChunkingPlugin $plugin; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; protected function setUp(): void { parent::setUp(); - $this->server = $this->getMockBuilder('\Sabre\DAV\Server') - ->disableOriginalConstructor() - ->getMock(); - $this->tree = $this->getMockBuilder('\Sabre\DAV\Tree') - ->disableOriginalConstructor() - ->getMock(); + $this->server = $this->createMock('\Sabre\DAV\Server'); + $this->tree = $this->createMock('\Sabre\DAV\Tree'); $this->server->tree = $this->tree; $this->plugin = new ChunkingPlugin(); @@ -78,14 +63,10 @@ class ChunkingPluginTest extends TestCase { $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->withConsecutive( - ['source'], - ['target'], - ) - ->willReturnOnConsecutiveCalls( - $sourceNode, - $targetNode, - ); + ->willReturnMap([ + ['source', $sourceNode], + ['target', $targetNode], + ]); $this->response->expects($this->never()) ->method('setStatus'); @@ -98,16 +79,20 @@ class ChunkingPluginTest extends TestCase { ->method('getSize') ->willReturn(4); + $calls = [ + ['source', $sourceNode], + ['target', new NotFound()], + ]; $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->withConsecutive( - ['source'], - ['target'], - ) - ->willReturnOnConsecutiveCalls( - $sourceNode, - $this->throwException(new NotFound()), - ); + ->willReturnCallback(function (string $path) use (&$calls) { + $expected = array_shift($calls); + $this->assertSame($expected[0], $path); + if ($expected[1] instanceof \Throwable) { + throw $expected[1]; + } + return $expected[1]; + }); $this->tree->expects($this->any()) ->method('nodeExists') ->with('target') @@ -132,17 +117,21 @@ class ChunkingPluginTest extends TestCase { ->method('getSize') ->willReturn(4); - + $calls = [ + ['source', $sourceNode], + ['target', new NotFound()], + ]; $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->withConsecutive( - ['source'], - ['target'], - ) - ->willReturnOnConsecutiveCalls( - $sourceNode, - $this->throwException(new NotFound()), - ); + ->willReturnCallback(function (string $path) use (&$calls) { + $expected = array_shift($calls); + $this->assertSame($expected[0], $path); + if ($expected[1] instanceof \Throwable) { + throw $expected[1]; + } + return $expected[1]; + }); + $this->tree->expects($this->any()) ->method('nodeExists') ->with('target') @@ -175,17 +164,21 @@ class ChunkingPluginTest extends TestCase { ->method('getSize') ->willReturn(3); - + $calls = [ + ['source', $sourceNode], + ['target', new NotFound()], + ]; $this->tree->expects($this->exactly(2)) ->method('getNodeForPath') - ->withConsecutive( - ['source'], - ['target'], - ) - ->willReturnOnConsecutiveCalls( - $sourceNode, - $this->throwException(new NotFound()), - ); + ->willReturnCallback(function (string $path) use (&$calls) { + $expected = array_shift($calls); + $this->assertSame($expected[0], $path); + if ($expected[1] instanceof \Throwable) { + throw $expected[1]; + } + return $expected[1]; + }); + $this->request->expects($this->once()) ->method('getHeader') ->with('OC-Total-Length') diff --git a/apps/dav/tests/unit/Upload/FutureFileTest.php b/apps/dav/tests/unit/Upload/FutureFileTest.php index 9ec455a9595..1409df937c0 100644 --- a/apps/dav/tests/unit/Upload/FutureFileTest.php +++ b/apps/dav/tests/unit/Upload/FutureFileTest.php @@ -1,5 +1,6 @@ <?php +declare(strict_types=1); /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. @@ -8,6 +9,7 @@ namespace OCA\DAV\Tests\unit\Upload; use OCA\DAV\Connector\Sabre\Directory; +use OCA\DAV\Upload\FutureFile; class FutureFileTest extends \Test\TestCase { public function testGetContentType(): void { @@ -44,17 +46,17 @@ class FutureFileTest extends \Test\TestCase { public function testDelete(): void { $d = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() - ->setMethods(['delete']) + ->onlyMethods(['delete']) ->getMock(); $d->expects($this->once()) ->method('delete'); - $f = new \OCA\DAV\Upload\FutureFile($d, 'foo.txt'); + $f = new FutureFile($d, 'foo.txt'); $f->delete(); } - + public function testPut(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); @@ -62,7 +64,7 @@ class FutureFileTest extends \Test\TestCase { $f->put(''); } - + public function testSetName(): void { $this->expectException(\Sabre\DAV\Exception\Forbidden::class); @@ -70,13 +72,10 @@ class FutureFileTest extends \Test\TestCase { $f->setName(''); } - /** - * @return \OCA\DAV\Upload\FutureFile - */ - private function mockFutureFile() { + private function mockFutureFile(): FutureFile { $d = $this->getMockBuilder(Directory::class) ->disableOriginalConstructor() - ->setMethods(['getETag', 'getLastModified', 'getChildren']) + ->onlyMethods(['getETag', 'getLastModified', 'getChildren']) ->getMock(); $d->expects($this->any()) @@ -91,6 +90,6 @@ class FutureFileTest extends \Test\TestCase { ->method('getChildren') ->willReturn([]); - return new \OCA\DAV\Upload\FutureFile($d, 'foo.txt'); + return new FutureFile($d, 'foo.txt'); } } diff --git a/apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php b/apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php new file mode 100644 index 00000000000..baae839c8da --- /dev/null +++ b/apps/dav/tests/unit/Upload/UploadAutoMkcolPluginTest.php @@ -0,0 +1,133 @@ +<?php + +declare(strict_types=1); + +/** + * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +namespace OCA\DAV\Tests\unit\Upload; + +use Generator; +use OCA\DAV\Upload\UploadAutoMkcolPlugin; +use PHPUnit\Framework\MockObject\MockObject; +use Sabre\DAV\ICollection; +use Sabre\DAV\INode; +use Sabre\DAV\Server; +use Sabre\DAV\Tree; +use Sabre\HTTP\RequestInterface; +use Sabre\HTTP\ResponseInterface; +use Test\TestCase; + +class UploadAutoMkcolPluginTest extends TestCase { + + private Tree&MockObject $tree; + private RequestInterface&MockObject $request; + private ResponseInterface&MockObject $response; + + public static function dataMissingHeaderShouldReturnTrue(): Generator { + yield 'missing X-NC-WebDAV-Auto-Mkcol header' => [null]; + yield 'empty X-NC-WebDAV-Auto-Mkcol header' => ['']; + yield 'invalid X-NC-WebDAV-Auto-Mkcol header' => ['enable']; + } + + public function testBeforeMethodWithRootNodeNotAnICollectionShouldReturnTrue(): void { + $this->request->method('getHeader')->willReturn('1'); + $this->request->expects(self::once()) + ->method('getPath') + ->willReturn('/non-relevant/path.txt'); + $this->tree->expects(self::once()) + ->method('nodeExists') + ->with('/non-relevant') + ->willReturn(false); + + $mockNode = $this->getMockBuilder(INode::class); + $this->tree->expects(self::once()) + ->method('getNodeForPath') + ->willReturn($mockNode); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + $this->assertTrue($return); + } + + #[\PHPUnit\Framework\Attributes\DataProvider('dataMissingHeaderShouldReturnTrue')] + public function testBeforeMethodWithMissingHeaderShouldReturnTrue(?string $header): void { + $this->request->expects(self::once()) + ->method('getHeader') + ->with('X-NC-WebDAV-Auto-Mkcol') + ->willReturn($header); + + $this->request->expects(self::never()) + ->method('getPath'); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + self::assertTrue($return); + } + + public function testBeforeMethodWithExistingPathShouldReturnTrue(): void { + $this->request->method('getHeader')->willReturn('1'); + $this->request->expects(self::once()) + ->method('getPath') + ->willReturn('/files/user/deep/image.jpg'); + $this->tree->expects(self::once()) + ->method('nodeExists') + ->with('/files/user/deep') + ->willReturn(true); + + $this->tree->expects(self::never()) + ->method('getNodeForPath'); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + self::assertTrue($return); + } + + public function testBeforeMethodShouldSucceed(): void { + $this->request->method('getHeader')->willReturn('1'); + $this->request->expects(self::once()) + ->method('getPath') + ->willReturn('/files/user/my/deep/path/image.jpg'); + $this->tree->expects(self::once()) + ->method('nodeExists') + ->with('/files/user/my/deep/path') + ->willReturn(false); + + $mockNode = $this->createMock(ICollection::class); + $this->tree->expects(self::once()) + ->method('getNodeForPath') + ->with('/files') + ->willReturn($mockNode); + $mockNode->expects(self::exactly(4)) + ->method('childExists') + ->willReturnMap([ + ['user', true], + ['my', true], + ['deep', false], + ['path', false], + ]); + $mockNode->expects(self::exactly(2)) + ->method('createDirectory'); + $mockNode->expects(self::exactly(4)) + ->method('getChild') + ->willReturn($mockNode); + + $return = $this->plugin->beforeMethod($this->request, $this->response); + self::assertTrue($return); + } + + protected function setUp(): void { + parent::setUp(); + + $server = $this->createMock(Server::class); + $this->tree = $this->createMock(Tree::class); + + $server->tree = $this->tree; + $this->plugin = new UploadAutoMkcolPlugin(); + + $this->request = $this->createMock(RequestInterface::class); + $this->response = $this->createMock(ResponseInterface::class); + $server->httpRequest = $this->request; + $server->httpResponse = $this->response; + + $this->plugin->initialize($server); + } +} diff --git a/apps/dav/tests/unit/bootstrap.php b/apps/dav/tests/unit/bootstrap.php index 61dbda35ae2..ee76bb6677b 100644 --- a/apps/dav/tests/unit/bootstrap.php +++ b/apps/dav/tests/unit/bootstrap.php @@ -1,18 +1,21 @@ <?php +declare(strict_types=1); + /** * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors * SPDX-FileCopyrightText: 2016 ownCloud, Inc. * SPDX-License-Identifier: AGPL-3.0-only */ + +use OCP\App\IAppManager; +use OCP\Server; + 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('dav'); +require_once __DIR__ . '/../../../../lib/base.php'; +require_once __DIR__ . '/../../../../tests/autoload.php'; -OC_Hook::clear(); +Server::get(IAppManager::class)->loadApp('dav'); diff --git a/apps/dav/tests/unit/phpunit.xml b/apps/dav/tests/unit/phpunit.xml index 0d08d8fe5d6..c85d07c6fcb 100644 --- a/apps/dav/tests/unit/phpunit.xml +++ b/apps/dav/tests/unit/phpunit.xml @@ -26,4 +26,3 @@ <log type="coverage-clover" target="./clover.xml"/> </logging> </phpunit> - diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-1.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-1.ics new file mode 100644 index 00000000000..e76ac3c9b2f --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-1.ics @@ -0,0 +1,17 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T105946Z +LAST-MODIFIED:20240507T121113Z +DTSTAMP:20240507T121113Z +UID:07514c7b-1014-425c-b1b8-2c35ab0eea1d +SUMMARY:Event A +RRULE:FREQ=YEARLY +DTSTART;TZID=Europe/Berlin:20240101T101500 +DTEND;TZID=Europe/Berlin:20240101T111500 +TRANSP:OPAQUE +X-MOZ-GENERATION:4 +SEQUENCE:2 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-2.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-2.ics new file mode 100644 index 00000000000..fe948321d51 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-2.ics @@ -0,0 +1,17 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T110122Z +LAST-MODIFIED:20240507T121120Z +DTSTAMP:20240507T121120Z +UID:67cf8134-ff10-49a7-913d-acfeda463db6 +SUMMARY:Event B +RRULE:FREQ=YEARLY +DTSTART;TZID=Europe/Berlin:20240101T123000 +DTEND;TZID=Europe/Berlin:20240101T133000 +TRANSP:OPAQUE +X-MOZ-GENERATION:4 +SEQUENCE:2 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-3.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-3.ics new file mode 100644 index 00000000000..de7765b28d2 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-3.ics @@ -0,0 +1,17 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T120352Z +LAST-MODIFIED:20240507T121128Z +DTSTAMP:20240507T121128Z +UID:59090ca1-e52b-447f-8e08-491d1da729fa +SUMMARY:Event C +RRULE:FREQ=YEARLY +DTSTART;TZID=Europe/Berlin:20240101T151000 +DTEND;TZID=Europe/Berlin:20240101T161000 +TRANSP:OPAQUE +X-MOZ-GENERATION:2 +SEQUENCE:1 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-4.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-4.ics new file mode 100644 index 00000000000..b4d2f752c0a --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-4.ics @@ -0,0 +1,17 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T120414Z +LAST-MODIFIED:20240507T121134Z +DTSTAMP:20240507T121134Z +UID:b1814d32-9adf-4518-8535-37f2c037f423 +SUMMARY:Event D +RRULE:FREQ=YEARLY +DTSTART;TZID=Europe/Berlin:20240101T164500 +DTEND;TZID=Europe/Berlin:20240101T171500 +TRANSP:OPAQUE +SEQUENCE:2 +X-MOZ-GENERATION:3 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-5.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-5.ics new file mode 100644 index 00000000000..1cd8b7ebf13 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-5.ics @@ -0,0 +1,14 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T122221Z +LAST-MODIFIED:20240507T122237Z +DTSTAMP:20240507T122237Z +UID:19c4e049-0b09-4101-a2ad-061a837e6a5e +SUMMARY:Cake Tasting +DTSTART;TZID=Europe/Berlin:20240509T151500 +DTEND;TZID=Europe/Berlin:20240509T171500 +TRANSP:OPAQUE +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-6.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-6.ics new file mode 100644 index 00000000000..6c24d534281 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-limit-timerange-6.ics @@ -0,0 +1,15 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T122246Z +LAST-MODIFIED:20240507T175258Z +DTSTAMP:20240507T175258Z +UID:60a7d310-aa7b-4974-8a8a-ff9339367e1d +SUMMARY:Pasta Day +DTSTART;TZID=Europe/Berlin:20240514T123000 +DTEND;TZID=Europe/Berlin:20240514T133000 +TRANSP:OPAQUE +X-MOZ-GENERATION:2 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-missing-start-1.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-missing-start-1.ics new file mode 100644 index 00000000000..a7865eaf5ef --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-missing-start-1.ics @@ -0,0 +1,14 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T122246Z +LAST-MODIFIED:20240507T175258Z +DTSTAMP:20240507T175258Z +UID:39e1b04f-d1cc-4622-bf97-11c38e070f43 +SUMMARY:Missing DTSTART 1 +DTEND;TZID=Europe/Berlin:20240514T133000 +TRANSP:OPAQUE +X-MOZ-GENERATION:2 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/caldav-search-missing-start-2.ics b/apps/dav/tests/unit/test_fixtures/caldav-search-missing-start-2.ics new file mode 100644 index 00000000000..4a33f2b1c8a --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/caldav-search-missing-start-2.ics @@ -0,0 +1,14 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VEVENT +CREATED:20240507T122246Z +LAST-MODIFIED:20240507T175258Z +DTSTAMP:20240507T175258Z +UID:12413feb-4b8c-4e95-ae7f-9ec4f42f3348 +SUMMARY:Missing DTSTART 2 +DTEND;TZID=Europe/Berlin:20240514T133000 +TRANSP:OPAQUE +X-MOZ-GENERATION:2 +END:VEVENT +END:VCALENDAR diff --git a/apps/dav/tests/unit/test_fixtures/example-event-default-expected.ics b/apps/dav/tests/unit/test_fixtures/example-event-default-expected.ics new file mode 100644 index 00000000000..09606ca5ee4 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event-default-expected.ics @@ -0,0 +1,20 @@ +BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Sabre//Sabre VObject 4.5.6//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+UID:RANDOM-UID
+DTSTAMP:20250121T000000Z
+SUMMARY:Example event - open me!
+DTSTART:20250128T100000Z
+DTEND:20250128T110000Z
+DESCRIPTION:Welcome to Nextcloud Calendar!\n\nThis is a sample event - expl
+ ore the flexibility of planning with Nextcloud Calendar by making any edit
+ s you want!\n\nWith Nextcloud Calendar\, you can:\n- Create\, edit\, and m
+ anage events effortlessly.\n- Create multiple calendars and share them wit
+ h teammates\, friends\, or family.\n- Check availability and display your
+ busy times to others.\n- Seamlessly integrate with apps and devices via Ca
+ lDAV.\n- Customize your experience: schedule recurring events\, adjust not
+ ifications and other settings.
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/unit/test_fixtures/example-event-default-expected.ics.license b/apps/dav/tests/unit/test_fixtures/example-event-default-expected.ics.license new file mode 100644 index 00000000000..23e2d6b1908 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event-default-expected.ics.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/tests/unit/test_fixtures/example-event-expected.ics b/apps/dav/tests/unit/test_fixtures/example-event-expected.ics new file mode 100644 index 00000000000..f9dfc37718e --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event-expected.ics @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
+BEGIN:VEVENT
+CREATED:20250128T091147Z
+DTSTAMP:20250128T091507Z
+LAST-MODIFIED:20250128T091507Z
+SEQUENCE:2
+STATUS:CONFIRMED
+SUMMARY:Welcome!
+DESCRIPTION:Welcome!!!
+LOCATION:Test
+UID:RANDOM-UID
+DTSTART:20250128T100000Z
+DTEND:20250128T110000Z
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/unit/test_fixtures/example-event-expected.ics.license b/apps/dav/tests/unit/test_fixtures/example-event-expected.ics.license new file mode 100644 index 00000000000..23e2d6b1908 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event-expected.ics.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/tests/unit/test_fixtures/example-event-with-attendees.ics b/apps/dav/tests/unit/test_fixtures/example-event-with-attendees.ics new file mode 100644 index 00000000000..8018552f2a5 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event-with-attendees.ics @@ -0,0 +1,21 @@ +BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
+BEGIN:VEVENT
+CREATED:20250128T091147Z
+DTSTAMP:20250128T091507Z
+LAST-MODIFIED:20250128T091507Z
+SEQUENCE:2
+UID:3b4df6a8-84df-43d5-baf9-377b43390b70
+DTSTART;VALUE=DATE:20250130
+DTEND;VALUE=DATE:20250131
+STATUS:CONFIRMED
+SUMMARY:Welcome!
+DESCRIPTION:Welcome!!!
+LOCATION:Test
+ATTENDEE;CN=user a;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICI
+ PANT;RSVP=TRUE;LANGUAGE=en;SCHEDULE-STATUS=1.1:mailto:usera@imap.localhost
+ORGANIZER;CN=Admin Account:mailto:admin@imap.localhost
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/unit/test_fixtures/example-event-with-attendees.ics.license b/apps/dav/tests/unit/test_fixtures/example-event-with-attendees.ics.license new file mode 100644 index 00000000000..23e2d6b1908 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event-with-attendees.ics.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/apps/dav/tests/unit/test_fixtures/example-event.ics b/apps/dav/tests/unit/test_fixtures/example-event.ics new file mode 100644 index 00000000000..6fc1848ea52 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event.ics @@ -0,0 +1,18 @@ +BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
+BEGIN:VEVENT
+CREATED:20250128T091147Z
+DTSTAMP:20250128T091507Z
+LAST-MODIFIED:20250128T091507Z
+SEQUENCE:2
+UID:3b4df6a8-84df-43d5-baf9-377b43390b70
+STATUS:CONFIRMED
+SUMMARY:Welcome!
+DESCRIPTION:Welcome!!!
+LOCATION:Test
+DTSTART:20250204T100000Z
+DTEND:20250204T110000Z
+END:VEVENT
+END:VCALENDAR
diff --git a/apps/dav/tests/unit/test_fixtures/example-event.ics.license b/apps/dav/tests/unit/test_fixtures/example-event.ics.license new file mode 100644 index 00000000000..23e2d6b1908 --- /dev/null +++ b/apps/dav/tests/unit/test_fixtures/example-event.ics.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors +SPDX-License-Identifier: AGPL-3.0-or-later |