Преглед на файлове

feat(caldav): expose calendar subscriptions

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
Daniel Kesselberg преди 2 месеца
родител
ревизия
e210043ee9

+ 2 - 0
apps/dav/composer/composer/autoload_classmap.php

@@ -46,7 +46,9 @@ return array(
     'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => $baseDir . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php',
     'OCA\\DAV\\CalDAV\\BirthdayService' => $baseDir . '/../lib/CalDAV/BirthdayService.php',
     'OCA\\DAV\\CalDAV\\CachedSubscription' => $baseDir . '/../lib/CalDAV/CachedSubscription.php',
+    'OCA\\DAV\\CalDAV\\CachedSubscriptionImpl' => $baseDir . '/../lib/CalDAV/CachedSubscriptionImpl.php',
     'OCA\\DAV\\CalDAV\\CachedSubscriptionObject' => $baseDir . '/../lib/CalDAV/CachedSubscriptionObject.php',
+    'OCA\\DAV\\CalDAV\\CachedSubscriptionProvider' => $baseDir . '/../lib/CalDAV/CachedSubscriptionProvider.php',
     'OCA\\DAV\\CalDAV\\CalDavBackend' => $baseDir . '/../lib/CalDAV/CalDavBackend.php',
     'OCA\\DAV\\CalDAV\\Calendar' => $baseDir . '/../lib/CalDAV/Calendar.php',
     'OCA\\DAV\\CalDAV\\CalendarHome' => $baseDir . '/../lib/CalDAV/CalendarHome.php',

+ 2 - 0
apps/dav/composer/composer/autoload_static.php

@@ -61,7 +61,9 @@ class ComposerStaticInitDAV
         'OCA\\DAV\\CalDAV\\BirthdayCalendar\\EnablePlugin' => __DIR__ . '/..' . '/../lib/CalDAV/BirthdayCalendar/EnablePlugin.php',
         'OCA\\DAV\\CalDAV\\BirthdayService' => __DIR__ . '/..' . '/../lib/CalDAV/BirthdayService.php',
         'OCA\\DAV\\CalDAV\\CachedSubscription' => __DIR__ . '/..' . '/../lib/CalDAV/CachedSubscription.php',
+        'OCA\\DAV\\CalDAV\\CachedSubscriptionImpl' => __DIR__ . '/..' . '/../lib/CalDAV/CachedSubscriptionImpl.php',
         'OCA\\DAV\\CalDAV\\CachedSubscriptionObject' => __DIR__ . '/..' . '/../lib/CalDAV/CachedSubscriptionObject.php',
+        'OCA\\DAV\\CalDAV\\CachedSubscriptionProvider' => __DIR__ . '/..' . '/../lib/CalDAV/CachedSubscriptionProvider.php',
         'OCA\\DAV\\CalDAV\\CalDavBackend' => __DIR__ . '/..' . '/../lib/CalDAV/CalDavBackend.php',
         'OCA\\DAV\\CalDAV\\Calendar' => __DIR__ . '/..' . '/../lib/CalDAV/Calendar.php',
         'OCA\\DAV\\CalDAV\\CalendarHome' => __DIR__ . '/..' . '/../lib/CalDAV/CalendarHome.php',

+ 2 - 0
apps/dav/lib/AppInfo/Application.php

@@ -34,6 +34,7 @@ namespace OCA\DAV\AppInfo;
 
 use OCA\DAV\CalDAV\Activity\Backend;
 use OCA\DAV\CalDAV\AppCalendar\AppCalendarPlugin;
+use OCA\DAV\CalDAV\CachedSubscriptionProvider;
 use OCA\DAV\CalDAV\CalendarManager;
 use OCA\DAV\CalDAV\CalendarProvider;
 use OCA\DAV\CalDAV\Reminder\NotificationProvider\AudioProvider;
@@ -207,6 +208,7 @@ class Application extends App implements IBootstrap {
 		$context->registerNotifierService(Notifier::class);
 
 		$context->registerCalendarProvider(CalendarProvider::class);
+		$context->registerCalendarProvider(CachedSubscriptionProvider::class);
 
 		$context->registerUserMigrator(CalendarMigrator::class);
 		$context->registerUserMigrator(ContactsMigrator::class);

+ 1 - 1
apps/dav/lib/CalDAV/AppCalendar/AppCalendarPlugin.php

@@ -68,7 +68,7 @@ class AppCalendarPlugin implements ICalendarProvider {
 		return array_values(
 			array_filter($this->manager->getCalendarsForPrincipal($principalUri, $calendarUris), function ($c) {
 				// We must not provide a wrapper for DAV calendars
-				return ! ($c instanceof \OCA\DAV\CalDAV\CalendarImpl);
+				return ! (($c instanceof \OCA\DAV\CalDAV\CalendarImpl) || ($c instanceof \OCA\DAV\CalDAV\CachedSubscriptionImpl));
 			})
 		);
 	}

+ 5 - 1
apps/dav/lib/CalDAV/CachedSubscription.php

@@ -73,6 +73,11 @@ class CachedSubscription extends \Sabre\CalDAV\Calendar {
 				'principal' => '{DAV:}authenticated',
 				'protected' => true,
 			],
+			[
+				'privilege' => '{DAV:}write-properties',
+				'principal' => $this->getOwner(),
+				'protected' => true,
+			]
 		];
 	}
 
@@ -97,7 +102,6 @@ class CachedSubscription extends \Sabre\CalDAV\Calendar {
 				'principal' => $this->getOwner() . '/calendar-proxy-read',
 				'protected' => true,
 			],
-
 		];
 	}
 

+ 117 - 0
apps/dav/lib/CalDAV/CachedSubscriptionImpl.php

@@ -0,0 +1,117 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2024 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\DAV\CalDAV;
+
+use OCP\Calendar\ICalendar;
+use OCP\Constants;
+
+class CachedSubscriptionImpl implements ICalendar {
+	private CalDavBackend $backend;
+	private CachedSubscription $calendar;
+	/** @var array<string, mixed> */
+	private array $calendarInfo;
+
+	public function __construct(
+		CachedSubscription $calendar,
+		array $calendarInfo,
+		CalDavBackend $backend
+	) {
+		$this->calendar = $calendar;
+		$this->calendarInfo = $calendarInfo;
+		$this->backend = $backend;
+	}
+
+	/**
+	 * @return string defining the technical unique key
+	 * @since 13.0.0
+	 */
+	public function getKey(): string {
+		return (string) $this->calendarInfo['id'];
+	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	public function getUri(): string {
+		return $this->calendarInfo['uri'];
+	}
+
+	/**
+	 * In comparison to getKey() this function returns a human readable (maybe translated) name
+	 * @since 13.0.0
+	 */
+	public function getDisplayName(): ?string {
+		return $this->calendarInfo['{DAV:}displayname'];
+	}
+
+	/**
+	 * Calendar color
+	 * @since 13.0.0
+	 */
+	public function getDisplayColor(): ?string {
+		return $this->calendarInfo['{http://apple.com/ns/ical/}calendar-color'];
+	}
+
+	/**
+	 * @param string $pattern which should match within the $searchProperties
+	 * @param array $searchProperties defines the properties within the query pattern should match
+	 * @param array $options - optional parameters:
+	 * 	['timerange' => ['start' => new DateTime(...), 'end' => new DateTime(...)]]
+	 * @param int|null $limit - limit number of search results
+	 * @param int|null $offset - offset for paging of search results
+	 * @return array an array of events/journals/todos which are arrays of key-value-pairs
+	 * @since 13.0.0
+	 */
+	public function search(string $pattern, array $searchProperties = [], array $options = [], $limit = null, $offset = null): array {
+		return $this->backend->search($this->calendarInfo, $pattern, $searchProperties, $options, $limit, $offset);
+	}
+
+	/**
+	 * @return int build up using \OCP\Constants
+	 * @since 13.0.0
+	 */
+	public function getPermissions(): int {
+		$permissions = $this->calendar->getACL();
+		$result = 0;
+		foreach ($permissions as $permission) {
+			switch ($permission['privilege']) {
+				case '{DAV:}read':
+					$result |= Constants::PERMISSION_READ;
+					break;
+			}
+		}
+
+		return $result;
+	}
+
+	public function isDeleted(): bool {
+		return false;
+	}
+
+	public function getSource(): string {
+		return $this->calendarInfo['source'];
+	}
+}

+ 57 - 0
apps/dav/lib/CalDAV/CachedSubscriptionProvider.php

@@ -0,0 +1,57 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2024 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+namespace OCA\DAV\CalDAV;
+
+use OCP\Calendar\ICalendarProvider;
+
+class CachedSubscriptionProvider implements ICalendarProvider {
+
+	public function __construct(
+		private CalDavBackend $calDavBackend
+	) {
+	}
+
+	public function getCalendars(string $principalUri, array $calendarUris = []): array {
+		$calendarInfos = $this->calDavBackend->getSubscriptionsForUser($principalUri);
+
+		if (count($calendarUris) > 0) {
+			$calendarInfos = array_filter($calendarInfos, fn (array $subscription) => in_array($subscription['uri'], $calendarUris));
+		}
+
+		$calendarInfos = array_values(array_filter($calendarInfos));
+
+		$iCalendars = [];
+		foreach ($calendarInfos as $calendarInfo) {
+			$calendar = new CachedSubscription($this->calDavBackend, $calendarInfo);
+			$iCalendars[] = new CachedSubscriptionImpl(
+				$calendar,
+				$calendarInfo,
+				$this->calDavBackend,
+			);
+		}
+		return $iCalendars;
+	}
+}

+ 7 - 1
apps/dav/lib/CalDAV/CalDavBackend.php

@@ -1882,12 +1882,18 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription
 		$outerQuery = $this->db->getQueryBuilder();
 		$innerQuery = $this->db->getQueryBuilder();
 
+		if (isset($calendarInfo['source'])) {
+			$calendarType = self::CALENDAR_TYPE_SUBSCRIPTION;
+		} else {
+			$calendarType = self::CALENDAR_TYPE_CALENDAR;
+		}
+
 		$innerQuery->selectDistinct('op.objectid')
 			->from($this->dbObjectPropertiesTable, 'op')
 			->andWhere($innerQuery->expr()->eq('op.calendarid',
 				$outerQuery->createNamedParameter($calendarInfo['id'])))
 			->andWhere($innerQuery->expr()->eq('op.calendartype',
-				$outerQuery->createNamedParameter(self::CALENDAR_TYPE_CALENDAR)));
+				$outerQuery->createNamedParameter($calendarType)));
 
 		$outerQuery->select('c.id', 'c.calendardata', 'c.componenttype', 'c.uid', 'c.uri')
 			->from('calendarobjects', 'c')

+ 6 - 8
apps/dav/lib/CalDAV/CalendarHome.php

@@ -53,14 +53,16 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
 	/** @var PluginManager */
 	private $pluginManager;
 
-	/** @var bool */
-	private $returnCachedSubscriptions = false;
-
 	/** @var LoggerInterface */
 	private $logger;
 	private ?array $cachedChildren = null;
 
-	public function __construct(BackendInterface $caldavBackend, $principalInfo, LoggerInterface $logger) {
+	public function __construct(
+		BackendInterface $caldavBackend,
+		array $principalInfo,
+		LoggerInterface $logger,
+		private bool $returnCachedSubscriptions
+	) {
 		parent::__construct($caldavBackend, $principalInfo);
 		$this->l10n = \OC::$server->getL10N('dav');
 		$this->config = \OC::$server->getConfig();
@@ -219,8 +221,4 @@ class CalendarHome extends \Sabre\CalDAV\CalendarHome {
 		$principalUri = $this->principalInfo['uri'];
 		return $this->caldavBackend->calendarSearch($principalUri, $filters, $limit, $offset);
 	}
-
-	public function enableCachedSubscriptionsForThisRequest() {
-		$this->returnCachedSubscriptions = true;
-	}
 }

+ 12 - 1
apps/dav/lib/CalDAV/CalendarRoot.php

@@ -32,6 +32,8 @@ use Sabre\DAVACL\PrincipalBackend;
 class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
 	private LoggerInterface $logger;
 
+	private array $returnCachedSubscriptions = [];
+
 	public function __construct(
 		PrincipalBackend\BackendInterface $principalBackend,
 		Backend\BackendInterface $caldavBackend,
@@ -43,7 +45,12 @@ class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
 	}
 
 	public function getChildForPrincipal(array $principal) {
-		return new CalendarHome($this->caldavBackend, $principal, $this->logger);
+		return new CalendarHome(
+			$this->caldavBackend,
+			$principal,
+			$this->logger,
+			array_key_exists($principal['uri'], $this->returnCachedSubscriptions)
+		);
 	}
 
 	public function getName() {
@@ -56,4 +63,8 @@ class CalendarRoot extends \Sabre\CalDAV\CalendarRoot {
 
 		return parent::getName();
 	}
+
+	public function enableReturnCachedSubscriptions(string $principalUri): void {
+		$this->returnCachedSubscriptions['principals/users/' . $principalUri] = true;
+	}
 }

+ 10 - 10
apps/dav/lib/CalDAV/WebcalCaching/Plugin.php

@@ -26,7 +26,7 @@ declare(strict_types=1);
  */
 namespace OCA\DAV\CalDAV\WebcalCaching;
 
-use OCA\DAV\CalDAV\CalendarHome;
+use OCA\DAV\CalDAV\CalendarRoot;
 use OCP\IRequest;
 use Sabre\DAV\Exception\NotFound;
 use Sabre\DAV\Server;
@@ -71,6 +71,11 @@ class Plugin extends ServerPlugin {
 		if ($magicHeader === 'On') {
 			$this->enabled = true;
 		}
+
+		$isExportRequest = $request->getMethod() === 'GET' && array_key_exists('export', $request->getParams());
+		if ($isExportRequest) {
+			$this->enabled = true;
+		}
 	}
 
 	/**
@@ -85,7 +90,7 @@ class Plugin extends ServerPlugin {
 	 */
 	public function initialize(Server $server) {
 		$this->server = $server;
-		$server->on('beforeMethod:*', [$this, 'beforeMethod']);
+		$server->on('beforeMethod:*', [$this, 'beforeMethod'], 15);
 	}
 
 	/**
@@ -107,16 +112,11 @@ class Plugin extends ServerPlugin {
 			return;
 		}
 
-		// $calendarHomePath will look like: calendars/username
-		$calendarHomePath = $pathParts[0] . '/' . $pathParts[1];
 		try {
-			$calendarHome = $this->server->tree->getNodeForPath($calendarHomePath);
-			if (!($calendarHome instanceof CalendarHome)) {
-				//how did we end up here?
-				return;
+			$calendarRoot = $this->server->tree->getNodeForPath($pathParts[0]);
+			if ($calendarRoot instanceof CalendarRoot) {
+				$calendarRoot->enableReturnCachedSubscriptions($pathParts[1]);
 			}
-
-			$calendarHome->enableCachedSubscriptionsForThisRequest();
 		} catch (NotFound $ex) {
 			return;
 		}

+ 2 - 0
apps/dav/lib/Connector/Sabre/DavAclPlugin.php

@@ -27,6 +27,7 @@
  */
 namespace OCA\DAV\Connector\Sabre;
 
+use OCA\DAV\CalDAV\CachedSubscription;
 use OCA\DAV\CalDAV\Calendar;
 use OCA\DAV\CardDAV\AddressBook;
 use Sabre\CalDAV\Principal\User;
@@ -61,6 +62,7 @@ class DavAclPlugin extends \Sabre\DAVACL\Plugin {
 					$type = 'Addressbook';
 					break;
 				case Calendar::class:
+				case CachedSubscription::class:
 					$type = 'Calendar';
 					break;
 				default:

+ 96 - 0
apps/dav/tests/unit/CalDAV/CachedSubscriptionImplTest.php

@@ -0,0 +1,96 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2024 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\Tests\unit\CalDAV;
+
+use OCA\DAV\CalDAV\CachedSubscription;
+use OCA\DAV\CalDAV\CachedSubscriptionImpl;
+use OCA\DAV\CalDAV\CalDavBackend;
+use Test\TestCase;
+
+class CachedSubscriptionImplTest extends TestCase {
+	private CachedSubscription $cachedSubscription;
+	private array $cachedSubscriptionInfo;
+	private CachedSubscriptionImpl $cachedSubscriptionImpl;
+	private CalDavBackend $backend;
+
+	protected function setUp(): void {
+		parent::setUp();
+
+		$this->cachedSubscription = $this->createMock(CachedSubscription::class);
+		$this->cachedSubscriptionInfo = [
+			'id' => 'fancy_id_123',
+			'{DAV:}displayname' => 'user readable name 123',
+			'{http://apple.com/ns/ical/}calendar-color' => '#AABBCC',
+			'uri' => '/this/is/a/uri',
+			'source' => 'https://test.localhost/calendar1',
+		];
+		$this->backend = $this->createMock(CalDavBackend::class);
+
+		$this->cachedSubscriptionImpl = new CachedSubscriptionImpl(
+			$this->cachedSubscription,
+			$this->cachedSubscriptionInfo,
+			$this->backend
+		);
+	}
+
+	public function testGetKey(): void {
+		$this->assertEquals($this->cachedSubscriptionImpl->getKey(), 'fancy_id_123');
+	}
+
+	public function testGetDisplayname(): void {
+		$this->assertEquals($this->cachedSubscriptionImpl->getDisplayName(), 'user readable name 123');
+	}
+
+	public function testGetDisplayColor(): void {
+		$this->assertEquals($this->cachedSubscriptionImpl->getDisplayColor(), '#AABBCC');
+	}
+
+	public function testGetSource(): void {
+		$this->assertEquals($this->cachedSubscriptionImpl->getSource(), 'https://test.localhost/calendar1');
+	}
+
+	public function testSearch(): void {
+		$this->backend->expects($this->once())
+			->method('search')
+			->with($this->cachedSubscriptionInfo, 'abc', ['def'], ['ghi'], 42, 1337)
+			->willReturn(['SEARCHRESULTS']);
+
+		$result = $this->cachedSubscriptionImpl->search('abc', ['def'], ['ghi'], 42, 1337);
+		$this->assertEquals($result, ['SEARCHRESULTS']);
+	}
+
+	public function testGetPermissionRead(): void {
+		$this->cachedSubscription->expects($this->once())
+			->method('getACL')
+			->with()
+			->willReturn([
+				['privilege' => '{DAV:}read']
+			]);
+
+		$this->assertEquals(1, $this->cachedSubscriptionImpl->getPermissions());
+	}
+}

+ 88 - 0
apps/dav/tests/unit/CalDAV/CachedSubscriptionProviderTest.php

@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+/**
+ * @copyright 2024 Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @author Daniel Kesselberg <mail@danielkesselberg.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * 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
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\DAV\Tests\unit\CalDAV;
+
+use OCA\DAV\CalDAV\CachedSubscriptionImpl;
+use OCA\DAV\CalDAV\CachedSubscriptionProvider;
+use OCA\DAV\CalDAV\CalDavBackend;
+use Test\TestCase;
+
+class CachedSubscriptionProviderTest extends TestCase {
+
+	private CalDavBackend $backend;
+	private CachedSubscriptionProvider $provider;
+
+	protected function setUp(): void {
+		parent::setUp();
+
+		$this->backend = $this->createMock(CalDavBackend::class);
+		$this->backend
+			->expects(self::once())
+			->method('getSubscriptionsForUser')
+			->with('user-principal-123')
+			->willReturn([
+				[
+					'id' => 'subscription-1',
+					'uri' => 'subscription-1',
+					'principaluris' => 'user-principal-123',
+					'source' => 'https://localhost/subscription-1',
+					// A subscription array has actually more properties.
+				],
+				[
+					'id' => 'subscription-2',
+					'uri' => 'subscription-2',
+					'principaluri' => 'user-principal-123',
+					'source' => 'https://localhost/subscription-2',
+					// A subscription array has actually more properties.
+				]
+			]);
+
+		$this->provider = new CachedSubscriptionProvider($this->backend);
+	}
+
+	public function testGetCalendars() {
+		$calendars = $this->provider->getCalendars(
+			'user-principal-123',
+			[]
+		);
+
+		$this->assertCount(2, $calendars);
+		$this->assertInstanceOf(CachedSubscriptionImpl::class, $calendars[0]);
+		$this->assertInstanceOf(CachedSubscriptionImpl::class, $calendars[1]);
+	}
+
+	public function testGetCalendarsFilterByUri() {
+		$calendars = $this->provider->getCalendars(
+			'user-principal-123',
+			['subscription-1']
+		);
+
+		$this->assertCount(1, $calendars);
+		$this->assertInstanceOf(CachedSubscriptionImpl::class, $calendars[0]);
+		$this->assertEquals('subscription-1', $calendars[0]->getUri());
+	}
+}

+ 5 - 0
apps/dav/tests/unit/CalDAV/CachedSubscriptionTest.php

@@ -61,6 +61,11 @@ class CachedSubscriptionTest extends \Test\TestCase {
 				'principal' => '{DAV:}authenticated',
 				'protected' => true,
 			],
+			[
+				'privilege' => '{DAV:}write-properties',
+				'principal' => 'user1',
+				'protected' => 'true'
+			]
 		], $calendar->getACL());
 	}
 

+ 124 - 2
apps/dav/tests/unit/CalDAV/CalendarHomeTest.php

@@ -26,6 +26,7 @@
 namespace OCA\DAV\Tests\unit\CalDAV;
 
 use OCA\DAV\AppInfo\PluginManager;
+use OCA\DAV\CalDAV\CachedSubscription;
 use OCA\DAV\CalDAV\CalDavBackend;
 use OCA\DAV\CalDAV\CalendarHome;
 use OCA\DAV\CalDAV\Integration\ExternalCalendar;
@@ -35,6 +36,7 @@ use OCA\DAV\CalDAV\Trashbin\TrashbinHome;
 use PHPUnit\Framework\MockObject\MockObject;
 use Psr\Log\LoggerInterface;
 use Sabre\CalDAV\Schedule\Inbox;
+use Sabre\CalDAV\Subscriptions\Subscription;
 use Sabre\DAV\MkCol;
 use Test\TestCase;
 
@@ -68,13 +70,13 @@ class CalendarHomeTest extends TestCase {
 		$this->calendarHome = new CalendarHome(
 			$this->backend,
 			$this->principalInfo,
-			$this->logger
+			$this->logger,
+			false
 		);
 
 		// Replace PluginManager with our mock
 		$reflection = new \ReflectionClass($this->calendarHome);
 		$reflectionProperty = $reflection->getProperty('pluginManager');
-		$reflectionProperty->setAccessible(true);
 		$reflectionProperty->setValue($this->calendarHome, $this->pluginManager);
 	}
 
@@ -249,4 +251,124 @@ class CalendarHomeTest extends TestCase {
 		$actual = $this->calendarHome->getChild('app-generated--calendar_plugin_2--calendar-uri-from-backend');
 		$this->assertEquals($externalCalendarMock, $actual);
 	}
+
+	public function testGetChildrenSubscriptions(): void {
+		$this->backend
+			->expects(self::once())
+			->method('getCalendarsForUser')
+			->with('user-principal-123')
+			->willReturn([]);
+
+		$this->backend
+			->expects(self::once())
+			->method('getSubscriptionsForUser')
+			->with('user-principal-123')
+			->willReturn([
+				[
+					'id' => 'subscription-1',
+					'uri' => 'subscription-1',
+					'principaluri' => 'user-principal-123',
+					'source' => 'https://localhost/subscription-1',
+					// A subscription array has actually more properties.
+				],
+				[
+					'id' => 'subscription-2',
+					'uri' => 'subscription-2',
+					'principaluri' => 'user-principal-123',
+					'source' => 'https://localhost/subscription-2',
+					// A subscription array has actually more properties.
+				]
+			]);
+
+		/*
+		 * @FIXME: PluginManager should be injected via constructor.
+		 */
+
+		$pluginManager = $this->createMock(PluginManager::class);
+		$pluginManager
+			->expects(self::once())
+			->method('getCalendarPlugins')
+			->with()
+			->willReturn([]);
+
+		$calendarHome = new CalendarHome(
+			$this->backend,
+			$this->principalInfo,
+			$this->logger,
+			false
+		);
+
+		$reflection = new \ReflectionClass($calendarHome);
+		$reflectionProperty = $reflection->getProperty('pluginManager');
+		$reflectionProperty->setValue($calendarHome, $pluginManager);
+
+		$actual = $calendarHome->getChildren();
+
+		$this->assertCount(5, $actual);
+		$this->assertInstanceOf(Inbox::class, $actual[0]);
+		$this->assertInstanceOf(Outbox::class, $actual[1]);
+		$this->assertInstanceOf(TrashbinHome::class, $actual[2]);
+		$this->assertInstanceOf(Subscription::class, $actual[3]);
+		$this->assertInstanceOf(Subscription::class, $actual[4]);
+	}
+
+	public function testGetChildrenCachedSubscriptions(): void {
+		$this->backend
+			->expects(self::once())
+			->method('getCalendarsForUser')
+			->with('user-principal-123')
+			->willReturn([]);
+
+		$this->backend
+			->expects(self::once())
+			->method('getSubscriptionsForUser')
+			->with('user-principal-123')
+			->willReturn([
+				[
+					'id' => 'subscription-1',
+					'uri' => 'subscription-1',
+					'principaluris' => 'user-principal-123',
+					'source' => 'https://localhost/subscription-1',
+					// A subscription array has actually more properties.
+				],
+				[
+					'id' => 'subscription-2',
+					'uri' => 'subscription-2',
+					'principaluri' => 'user-principal-123',
+					'source' => 'https://localhost/subscription-2',
+					// A subscription array has actually more properties.
+				]
+			]);
+
+		/*
+		 * @FIXME: PluginManager should be injected via constructor.
+		 */
+
+		$pluginManager = $this->createMock(PluginManager::class);
+		$pluginManager
+			->expects(self::once())
+			->method('getCalendarPlugins')
+			->with()
+			->willReturn([]);
+
+		$calendarHome = new CalendarHome(
+			$this->backend,
+			$this->principalInfo,
+			$this->logger,
+			true
+		);
+
+		$reflection = new \ReflectionClass($calendarHome);
+		$reflectionProperty = $reflection->getProperty('pluginManager');
+		$reflectionProperty->setValue($calendarHome, $pluginManager);
+
+		$actual = $calendarHome->getChildren();
+
+		$this->assertCount(5, $actual);
+		$this->assertInstanceOf(Inbox::class, $actual[0]);
+		$this->assertInstanceOf(Outbox::class, $actual[1]);
+		$this->assertInstanceOf(TrashbinHome::class, $actual[2]);
+		$this->assertInstanceOf(CachedSubscription::class, $actual[3]);
+		$this->assertInstanceOf(CachedSubscription::class, $actual[4]);
+	}
 }

+ 0 - 3
apps/dav/tests/unit/CalDAV/CalendarImplTest.php

@@ -39,9 +39,6 @@ use Sabre\VObject\Component\VEvent;
 use Sabre\VObject\ITip\Message;
 use Sabre\VObject\Reader;
 
-/**
- * @group DB
- */
 class CalendarImplTest extends \Test\TestCase {
 	/** @var CalendarImpl */
 	private $calendarImpl;

+ 49 - 2
apps/dav/tests/unit/CalDAV/WebcalCaching/PluginTest.php

@@ -48,17 +48,64 @@ class PluginTest extends \Test\TestCase {
 		$this->assertEquals(false, $plugin->isCachingEnabledForThisRequest());
 	}
 
-	public function testEnabled(): void {
+	public function testEnabledUserAgent(): void {
 		$request = $this->createMock(IRequest::class);
 		$request->expects($this->once())
 			->method('isUserAgent')
 			->with(Plugin::ENABLE_FOR_CLIENTS)
-			->willReturn(false);
+			->willReturn(true);
+		$request->expects($this->once())
+			->method('getHeader')
+			->with('X-NC-CalDAV-Webcal-Caching')
+			->willReturn('');
+		$request->expects($this->once())
+			->method('getMethod')
+			->willReturn('REPORT');
+		$request->expects($this->never())
+			->method('getParams');
+
+		$plugin = new Plugin($request);
 
+		$this->assertEquals(true, $plugin->isCachingEnabledForThisRequest());
+	}
+
+	public function testEnabledWebcalCachingHeader(): void {
+		$request = $this->createMock(IRequest::class);
+		$request->expects($this->once())
+			->method('isUserAgent')
+			->with(Plugin::ENABLE_FOR_CLIENTS)
+			->willReturn(false);
 		$request->expects($this->once())
 			->method('getHeader')
 			->with('X-NC-CalDAV-Webcal-Caching')
 			->willReturn('On');
+		$request->expects($this->once())
+			->method('getMethod')
+			->willReturn('REPORT');
+		$request->expects($this->never())
+			->method('getParams');
+
+		$plugin = new Plugin($request);
+
+		$this->assertEquals(true, $plugin->isCachingEnabledForThisRequest());
+	}
+
+	public function testEnabledExportRequest(): void {
+		$request = $this->createMock(IRequest::class);
+		$request->expects($this->once())
+			->method('isUserAgent')
+			->with(Plugin::ENABLE_FOR_CLIENTS)
+			->willReturn(false);
+		$request->expects($this->once())
+			->method('getHeader')
+			->with('X-NC-CalDAV-Webcal-Caching')
+			->willReturn('');
+		$request->expects($this->once())
+			->method('getMethod')
+			->willReturn('GET');
+		$request->expects($this->once())
+			->method('getParams')
+			->willReturn(['export' => '']);
 
 		$plugin = new Plugin($request);