aboutsummaryrefslogtreecommitdiffstats
path: root/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'apps/dav/tests/unit/CardDAV/SyncServiceTest.php')
-rw-r--r--apps/dav/tests/unit/CardDAV/SyncServiceTest.php518
1 files changed, 406 insertions, 112 deletions
diff --git a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
index d22a246bbec..77caed336f4 100644
--- a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
+++ b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php
@@ -1,112 +1,335 @@
<?php
+
/**
- * @copyright Copyright (c) 2016, ownCloud, Inc.
- *
- * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
- * @author Bjoern Schiessle <bjoern@schiessle.org>
- * @author Joas Schilling <coding@schilljs.com>
- * @author Morris Jobke <hey@morrisjobke.de>
- * @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: 2016-2024 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
+ * SPDX-License-Identifier: AGPL-3.0-only
*/
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\ILogger;
+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 {
- public function testEmptySync() {
- $backend = $this->getBackendMock(0, 0, 0);
- $ss = $this->getSyncServiceMock($backend, []);
- $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []);
- $this->assertEquals('sync-token-1', $return);
+ 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 testSyncWithNewElement() {
- $backend = $this->getBackendMock(1, 0, 0);
- $backend->method('getCard')->willReturn(false);
+ public function testEmptySync(): void {
+ $this->backend->expects($this->exactly(0))
+ ->method('createCard');
+ $this->backend->expects($this->exactly(0))
+ ->method('updateCard');
+ $this->backend->expects($this->exactly(0))
+ ->method('deleteCard');
- $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: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')
+ ->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 testSyncWithUpdatedElement() {
- $backend = $this->getBackendMock(0, 1, 0);
- $backend->method('getCard')->willReturn(true);
+ public function testSyncWithNewElement(): void {
+ $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);
+
+
+ $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>&quot;2df155fa5c2a24cd7f750353fc63f037&quot;</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>';
- $ss = $this->getSyncServiceMock($backend, ['0' => [200 => '']]);
- $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []);
- $this->assertEquals('sync-token-1', $return);
+ $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 testSyncWithDeletedElement() {
- $backend = $this->getBackendMock(0, 0, 1);
+ public function testSyncWithUpdatedElement(): void {
+ $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>&quot;2df155fa5c2a24cd7f750353fc63f037&quot;</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,
+ ));
+
+ $this->client
+ ->method('get')
+ ->willReturn($getResponse);
- $ss = $this->getSyncServiceMock($backend, ['0' => [404 => '']]);
- $return = $ss->syncRemoteAddressBook('', 'system', 'system', '1234567890', null, '1', 'principals/system/system', []);
- $this->assertEquals('sync-token-1', $return);
+ $token = $this->service->syncRemoteAddressBook(
+ '',
+ 'system',
+ 'system',
+ '1234567890',
+ null,
+ '1',
+ 'principals/system/system',
+ []
+ )[0];
+
+ $this->assertEquals('http://sabre.io/ns/sync/3', $token);
}
- public function testEnsureSystemAddressBookExists() {
- /** @var CardDavBackend | \PHPUnit\Framework\MockObject\MockObject $backend */
- $backend = $this->getMockBuilder(CardDavBackend::class)->disableOriginalConstructor()->getMock();
+ public function testSyncWithDeletedElement(): void {
+ $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
+ ));
+
+ $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&MockObject $backend */
+ $backend = $this->createMock(CardDavBackend::class);
$backend->expects($this->exactly(1))->method('createAddressBook');
- $backend->expects($this->at(0))->method('getAddressBooksByUri')->willReturn(null);
- $backend->expects($this->at(1))->method('getAddressBooksByUri')->willReturn([]);
+ $backend->expects($this->exactly(2))
+ ->method('getAddressBooksByUri')
+ ->willReturnOnConsecutiveCalls(
+ null,
+ [],
+ );
- /** @var IUserManager $userManager */
- $userManager = $this->getMockBuilder(IUserManager::class)->disableOriginalConstructor()->getMock();
- $logger = $this->getMockBuilder(ILogger::class)->disableOriginalConstructor()->getMock();
+ $userManager = $this->createMock(IUserManager::class);
+ $dbConnection = $this->createMock(IDBConnection::class);
+ $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, $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) {
- /** @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(ILogger::class)->disableOriginalConstructor()->getMock();
+ $logger = $this->getMockBuilder(LoggerInterface::class)->disableOriginalConstructor()->getMock();
$backend->expects($this->exactly($createCalls))->method('createCard');
$backend->expects($this->exactly($updateCalls))->method('updateCard');
@@ -120,11 +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();
-
- /** @var IUser | \PHPUnit\Framework\MockObject\MockObject $user */
- $user = $this->getMockBuilder(IUser::class)->disableOriginalConstructor()->getMock();
+ $userManager = $this->createMock(IUserManager::class);
+ $dbConnection = $this->createMock(IDBConnection::class);
+ $user = $this->createMock(IUser::class);
$user->method('getBackendClassName')->willReturn('unittest');
$user->method('getUID')->willReturn('test-user');
$user->method('getCloudId')->willReturn('cloudId');
@@ -135,7 +356,10 @@ class SyncServiceTest extends TestCase {
->method('createCardFromUser')
->willReturn($this->createMock(VCard::class));
- $ss = new SyncService($backend, $userManager, $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);
@@ -143,44 +367,114 @@ class SyncServiceTest extends TestCase {
$ss->deleteUser($user);
}
- /**
- * @param int $createCount
- * @param int $updateCount
- * @param int $deleteCount
- * @return \PHPUnit\Framework\MockObject\MockObject
- */
- 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',
+ []
+ );
+ }
+
+ #[\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',
+ []
+ );
}
- /**
- * @param $backend
- * @param $response
- * @return SyncService|\PHPUnit\Framework\MockObject\MockObject
- */
- private function getSyncServiceMock($backend, $response) {
- $userManager = $this->getMockBuilder(IUserManager::class)->disableOriginalConstructor()->getMock();
- $logger = $this->getMockBuilder(ILogger::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, $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;
+ 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'],
+ ];
}
}