aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/tests/unit/CalDAV/CalDavBackendTest.php')
-rw-r--r--apps/dav/tests/unit/CalDAV/CalDavBackendTest.php406
1 files changed, 345 insertions, 61 deletions
diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
index ef4cfb2aaab..f9205d5d322 100644
--- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
+++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php
@@ -1,33 +1,10 @@
<?php
+
+declare(strict_types=1);
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- * @copyright Copyright (c) 2017 Georg Ehrke
- *
- * @author Christoph Wurst <christoph@winzerhof-wurst.at>
- * @author dartcafe <github@dartcafe.de>
- * @author Georg Ehrke <oc.list@georgehrke.com>
- * @author Joas Schilling <coding@schilljs.com>
- * @author leith abdulla <online-nextcloud@eleith.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @author Robin Appelman <robin@icewind.nl>
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- * @author Thomas Citharel <nextcloud@tcit.fr>
- * @author Thomas Müller <thomas.mueller@tmit.eu>
- *
- * @license AGPL-3.0
- *
- * This code is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License, version 3,
- * along with this program. If not, see <http://www.gnu.org/licenses/>
- *
+ * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
namespace OCA\DAV\Tests\unit\CalDAV;
@@ -42,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;
@@ -52,8 +30,6 @@ use function time;
* Class CalDavBackendTest
*
* @group DB
- *
- * @package OCA\DAV\Tests\unit\CalDAV
*/
class CalDavBackendTest extends AbstractCalDavBackend {
public function testCalendarOperations(): void {
@@ -83,7 +59,7 @@ class CalDavBackendTest extends AbstractCalDavBackend {
self::assertEmpty($calendars);
}
- public function providesSharingData() {
+ public static function providesSharingData(): array {
return [
[true, true, true, false, [
[
@@ -136,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')
@@ -427,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 = [];
@@ -482,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'], []],
@@ -493,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 {
@@ -571,7 +615,7 @@ EOD;
$this->assertCount(0, $subscriptions);
}
- public function providesSchedulingData() {
+ public static function providesSchedulingData(): array {
$data = <<<EOS
BEGIN:VCALENDAR
VERSION:2.0
@@ -644,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);
@@ -662,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);
@@ -677,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"],
@@ -686,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"],
@@ -830,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();
@@ -932,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],
@@ -1443,7 +1483,7 @@ EOD;
self::assertSame(0, $deleted);
}
- public function testSearchAndExpandRecurrences() {
+ public function testSearchAndExpandRecurrences(): void {
$calendarId = $this->createTestCalendar();
$calendarInfo = [
'id' => $calendarId,
@@ -1598,4 +1638,248 @@ EOD;
self::assertEqualsCanonicalizing([$uri1, $uri3], $changesAfter['modified']);
self::assertEquals([$uri2], $changesAfter['deleted']);
}
+
+ public function testSearchWithLimitAndTimeRange(): void {
+ $calendarId = $this->createTestCalendar();
+ $calendarInfo = [
+ 'id' => $calendarId,
+ 'principaluri' => 'user1',
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ ];
+
+ $testFiles = [
+ __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) {
+ $objectUri = static::getUniqueID('search-limit-timerange-');
+ $calendarData = \file_get_contents($testFile);
+ $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
+ }
+
+ $start = new DateTimeImmutable('2024-05-06T00:00:00Z');
+ $end = $start->add(new DateInterval('P14D'));
+
+ $results = $this->backend->search(
+ $calendarInfo,
+ '',
+ [],
+ [
+ 'timerange' => [
+ 'start' => $start,
+ 'end' => $end,
+ ]
+ ],
+ 4,
+ null,
+ );
+
+ $this->assertCount(2, $results);
+
+ $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
+ $this->assertGreaterThanOrEqual(
+ $start->getTimestamp(),
+ $results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
+ 'Recurrence starting before requested start',
+ );
+
+ $this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
+ $this->assertGreaterThanOrEqual(
+ $start->getTimestamp(),
+ $results[1]['objects'][0]['DTSTART'][0]->getTimestamp(),
+ 'Recurrence starting before requested start',
+ );
+ }
+
+ public function testSearchWithLimitAndTimeRangeShouldNotReturnMoreObjectsThenLimit(): void {
+ $calendarId = $this->createTestCalendar();
+ $calendarInfo = [
+ 'id' => $calendarId,
+ 'principaluri' => 'user1',
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ ];
+
+ $testFiles = [
+ __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) {
+ $objectUri = static::getUniqueID('search-limit-timerange-');
+ $calendarData = \file_get_contents($testFile);
+ $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
+ }
+
+ $start = new DateTimeImmutable('2024-05-06T00:00:00Z');
+ $end = $start->add(new DateInterval('P14D'));
+
+ $results = $this->backend->search(
+ $calendarInfo,
+ '',
+ [],
+ [
+ 'timerange' => [
+ 'start' => $start,
+ 'end' => $end,
+ ]
+ ],
+ 1,
+ null,
+ );
+
+ $this->assertCount(1, $results);
+
+ $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
+ $this->assertGreaterThanOrEqual(
+ $start->getTimestamp(),
+ $results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
+ 'Recurrence starting before requested start',
+ );
+ }
+
+ public function testSearchWithLimitAndTimeRangeShouldReturnObjectsInTheSameOrder(): void {
+ $calendarId = $this->createTestCalendar();
+ $calendarInfo = [
+ 'id' => $calendarId,
+ 'principaluri' => 'user1',
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ ];
+
+ $testFiles = [
+ __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) {
+ $objectUri = static::getUniqueID('search-limit-timerange-');
+ $calendarData = \file_get_contents($testFile);
+ $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
+ }
+
+ $start = new DateTimeImmutable('2024-05-06T00:00:00Z');
+ $end = $start->add(new DateInterval('P14D'));
+
+ $results = $this->backend->search(
+ $calendarInfo,
+ '',
+ [],
+ [
+ 'timerange' => [
+ 'start' => $start,
+ 'end' => $end,
+ ]
+ ],
+ 2,
+ null,
+ );
+
+ $this->assertCount(2, $results);
+
+ $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
+ $this->assertGreaterThanOrEqual(
+ $start->getTimestamp(),
+ $results[0]['objects'][0]['DTSTART'][0]->getTimestamp(),
+ 'Recurrence starting before requested start',
+ );
+
+ $this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
+ $this->assertGreaterThanOrEqual(
+ $start->getTimestamp(),
+ $results[1]['objects'][0]['DTSTART'][0]->getTimestamp(),
+ 'Recurrence starting before requested start',
+ );
+ }
+
+ public function testSearchShouldReturnObjectsInTheSameOrderMissingDate(): void {
+ $calendarId = $this->createTestCalendar();
+ $calendarInfo = [
+ 'id' => $calendarId,
+ 'principaluri' => 'user1',
+ '{http://owncloud.org/ns}owner-principal' => 'user1',
+ ];
+
+ $testFiles = [
+ __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) {
+ $objectUri = static::getUniqueID('search-return-objects-in-same-order-');
+ $calendarData = \file_get_contents($testFile);
+ $this->backend->createCalendarObject($calendarId, $objectUri, $calendarData);
+ }
+
+ $results = $this->backend->search(
+ $calendarInfo,
+ '',
+ [],
+ [],
+ 4,
+ null,
+ );
+
+ $this->assertCount(4, $results);
+
+ $this->assertEquals('Cake Tasting', $results[0]['objects'][0]['SUMMARY'][0]);
+ $this->assertEquals('Pasta Day', $results[1]['objects'][0]['SUMMARY'][0]);
+ $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
+ );
+
+ }
}