nextcloud-contacts/tests/unit/Service/SocialApiServiceTest.php

549 lines
17 KiB
PHP

<?php
/**
* @copyright Copyright (c) 2020 Matthias Heinisch <nextcloud@matthiasheinisch.de>
*
* @author Matthias Heinisch <nextcloud@matthiasheinisch.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\Contacts\Service;
use OCA\Contacts\Service\Social\CompositeSocialProvider;
use OCA\Contacts\Service\Social\ISocialProvider;
use OCP\AppFramework\Http;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IResponse;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\Contacts\IManager;
use OCP\IAddressBook;
use OCA\DAV\CardDAV\CardDavBackend;
use OCP\IURLGenerator;
use OCP\IL10N;
use OCP\Util;
use OCP\AppFramework\Utility\ITimeFactory;
use PHPUnit\Framework\MockObject\MockObject;
use ChristophWurst\Nextcloud\Testing\TestCase;
class SocialApiServiceTest extends TestCase {
private $service;
/** @var CompositeSocialProvider|MockObject */
private $socialProvider;
/** @var IManager|MockObject */
private $manager;
/** @var IConfig|MockObject */
private $config;
/** @var IClientService|MockObject */
private $clientService;
/** @var IL10N|MockObject */
private $l10n;
/** @var IURLGenerator|MockObject */
private $urlGen;
/** @var CardDavBackend|MockObject */
private $davBackend;
/** @var ITimeFactory|MockObject */
private $timeFactory;
public function allSocialProfileProviders() {
$body = "the body";
$imageType = "jpg";
$contact = [
'URI' => '3225c0d5-1bd2-43e5-a08c-4e65eaa406b0',
'VERSION' => '4.0'
];
$connector = $this->createMock(ISocialProvider::class);
$connector->method('supportsContact')->willReturn(true);
$connector->method('getImageUrls')->willReturn(["url1"]);
$connectorNoSupport = $this->createMock(ISocialProvider::class);
$connectorNoSupport->method('supportsContact')->willReturn(false);
$connectorNoUrl = $this->createMock(ISocialProvider::class);
$connectorNoUrl->method('supportsContact')->willReturn(true);
$connectorNoUrl->method('getImageUrls')->willReturn([]);
$addressbookEmpty = $this->createMock(IAddressBook::class);
$addressbookEmpty
->method('getUri')
->willReturn('contacts');
$addressbookEmpty
->method('search')
->willReturn(null);
$addressbook = $this->createMock(IAddressBook::class);
$addressbook
->method('getUri')
->willReturn('contacts');
$addressbook
->method('search')
->willReturn([$contact]);
return [
'no address book found' => [null, [], "", "", Http::STATUS_BAD_REQUEST],
'no contact found' => [[$addressbookEmpty], [], "", "", Http::STATUS_PRECONDITION_FAILED],
'no supporting contacts found' => [[$addressbook], [$connectorNoSupport], "", "", Http::STATUS_PRECONDITION_FAILED],
'no url found' => [[$addressbook], [$connectorNoUrl], "", "", Http::STATUS_BAD_REQUEST],
'no image found' => [[$addressbook], [$connector], "", "", Http::STATUS_NOT_FOUND],
'image found' => [[$addressbook], [$connector], $body, $imageType, Http::STATUS_OK]
];
}
public function updateAddressbookProvider() {
return [
'not user enabled' => ['yes', 'no', Http::STATUS_FORBIDDEN],
'not admin allowed' => ['no', 'yes', Http::STATUS_FORBIDDEN],
'not allowed, not enabled' => ['no', 'no', Http::STATUS_FORBIDDEN],
'allowed and enabled' => ['yes', 'yes', Http::STATUS_OK],
];
}
protected function setUp(): void {
parent::setUp();
$this->manager = $this->createMock(IManager::class);
$this->config = $this->createMock(IConfig::class);
$this->socialProvider = $this->createMock(CompositeSocialProvider::class);
$this->clientService = $this->createMock(IClientService::class);
$this->l10n = $this->createMock(IL10N::class);
$this->urlGen = $this->createMock(IURLGenerator::class);
$this->davBackend = $this->createMock(CardDavBackend::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->service = new SocialApiService(
$this->socialProvider,
$this->manager,
$this->config,
$this->clientService,
$this->l10n,
$this->urlGen,
$this->davBackend,
$this->timeFactory
);
}
public function testDeactivatedSocial() {
$this->config
->method('getAppValue')
->willReturn('no');
$result = $this->service->getSupportedNetworks();
$this->assertEmpty($result);
}
/**
* @dataProvider allSocialProfileProviders
*/
public function testUpdateContactWithoutNetwork($addressbooks, $providers, $body, $imageType, $status) {
$this->manager
->method('getUserAddressBooks')
->willReturn($addressbooks);
$this->socialProvider
->method('getSocialConnectors')
->willReturn($providers);
$response = $this->createMock(IResponse::class);
$response
->method('getBody')
->willReturn($body);
$response
->method('getHeader')
->willReturn($imageType);
$client = $this->createMock(IClient::class);
$client
->method('get')
->willReturn($response);
$this->clientService
->method('NewClient')
->willReturn($client);
$result = $this->service
->updateContact(
'contacts',
'3225c0d5-1bd2-43e5-a08c-4e65eaa406b0',
null);
$this->assertEquals($status, $result->getStatus());
}
public function testUpdateContactWithNetworkVersion3() {
$network = "mastodon";
$body = "the body";
$imageType = "jpg";
$addressBookId = "contacts";
$contactId = "3225c0d5-1bd2-43e5-a08c-4e65eaa406b0";
$contact = [
'URI' => $contactId,
'VERSION' => '3.0'
];
$provider = $this->createMock(ISocialProvider::class);
$provider->method('supportsContact')->willReturn(true);
$provider->method('getImageUrls')->willReturn(["url1"]);
$addressbook = $this->createMock(IAddressBook::class);
$addressbook
->method('getUri')
->willReturn('contacts');
$addressbook
->method('search')
->willReturn([$contact]);
$this->manager
->method('getUserAddressBooks')
->willReturn([$addressbook]);
$this->socialProvider
->method('getSocialConnectors')
->willReturn([$provider]);
$this->socialProvider
->method('getSocialConnector')
->willReturn($provider);
$response = $this->createMock(IResponse::class);
$response
->method('getBody')
->willReturn($body);
$response
->method('getHeader')
->willReturn($imageType);
$client = $this->createMock(IClient::class);
$client
->method('get')
->willReturn($response);
$this->clientService
->method('NewClient')
->willReturn($client);
$changes = [
'URI' => $contact['URI'],
'VERSION' => $contact['VERSION'],
'PHOTO;ENCODING=b;TYPE=' . $imageType . ';VALUE=BINARY' => base64_encode($body)
];
$this->socialProvider
->expects($this->once())->method("getSocialConnector")->with($network);
$provider->expects($this->once())->method("supportsContact")->with($contact);
$addressbook->expects($this->once())->method("createOrUpdate")->with($changes, $addressBookId);
$result = $this->service
->updateContact(
$addressBookId,
$contactId,
$network);
$this->assertEquals(Http::STATUS_OK, $result->getStatus());
}
public function testUpdateContactWithNetworkVersion4() {
$network = "mastodon";
$body = "the body";
$imageType = "jpg";
$addressBookId = "contacts";
$contactId = "3225c0d5-1bd2-43e5-a08c-4e65eaa406b0";
$contact = [
'URI' => $contactId,
'VERSION' => '4.0'
];
$provider = $this->createMock(ISocialProvider::class);
$provider->method('supportsContact')->willReturn(true);
$provider->method('getImageUrls')->willReturn(["url1"]);
$addressbook = $this->createMock(IAddressBook::class);
$addressbook
->method('getUri')
->willReturn('contacts');
$addressbook
->method('search')
->willReturn([$contact]);
$this->manager
->method('getUserAddressBooks')
->willReturn([$addressbook]);
$this->socialProvider
->method('getSocialConnectors')
->willReturn([$provider]);
$this->socialProvider
->method('getSocialConnector')
->willReturn($provider);
$response = $this->createMock(IResponse::class);
$response
->method('getBody')
->willReturn($body);
$response
->method('getHeader')
->willReturn($imageType);
$client = $this->createMock(IClient::class);
$client
->method('get')
->willReturn($response);
$this->clientService
->method('NewClient')
->willReturn($client);
$changes = [
'URI' => $contact['URI'],
'VERSION' => $contact['VERSION'],
'PHOTO' => "data:".$imageType.";base64," . base64_encode($body)
];
$this->socialProvider
->expects($this->once())->method("getSocialConnector")->with($network);
$provider->expects($this->once())->method("supportsContact")->with($contact);
$addressbook->expects($this->once())->method("createOrUpdate")->with($changes, $addressBookId);
$result = $this->service
->updateContact(
$addressBookId,
$contactId,
$network);
$this->assertEquals(Http::STATUS_OK, $result->getStatus());
}
protected function setupAddressbooks() {
$validContact1 = [
'UID' => '11111111-1111-1111-1111-111111111111',
'FN' => 'Valid Contact One',
'VERSION' => '4.0',
'X-SOCIALPROFILE' => [['type' => 'someNetwork', 'value' => 'someId1']],
];
$validContact2 = [
'UID' => '22222222-2222-2222-2222-222222222222',
'FN' => 'Valid Contact Two',
'VERSION' => '4.0',
'PHOTO' => "data:someHeader;base64," . base64_encode('someBody'),
'X-SOCIALPROFILE' => [['type' => 'someNetwork', 'value' => 'someId2']],
];
$emptyContact = [
'UID' => '00000000-0000-0000-0000-000000000000',
'FN' => 'Empty Contact',
'VERSION' => '4.0',
];
$invalidContact = [
'UID' => 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
'FN' => 'Invalid Contact',
'VERSION' => '4.0',
'X-SOCIALPROFILE' => [['type' => 'someNetwork', 'value' => 'invalidId']],
];
$addressbook1 = $this->createMock(IAddressBook::class);
$addressbook1->method('getUri')->willReturn('contacts1');
$addressbook2 = $this->createMock(IAddressBook::class);
$addressbook2->method('getUri')->willReturn('contacts2');
if (Util::getVersion()[0] >= 20) {
// TODO: check can be removed as soon as min-dependency of contacts is NCv20
$addressbook1->method('isShared')->willReturn(false);
$addressbook1->method('isSystemAddressBook')->willReturn(false);
$addressbook2->method('isShared')->willReturn(false);
$addressbook2->method('isSystemAddressBook')->willReturn(false);
}
$searchMap1 = [
['', ['UID'], ['types' => true], [$validContact1, $invalidContact]],
['', ['X-SOCIALPROFILE'], ['types' => true], [$validContact1, $invalidContact]],
[$validContact1['UID'], ['UID'], ['types' => true], [$validContact1]],
[$invalidContact['UID'], ['UID'], ['types' => true], [$invalidContact]],
];
$searchMap2 = [
['', ['UID'], ['types' => true], [$validContact2, $emptyContact]],
['', ['X-SOCIALPROFILE'], ['types' => true], [$validContact2]],
[$validContact2['UID'], ['UID'], ['types' => true], [$validContact2]],
[$emptyContact['UID'], ['UID'], ['types' => true], [$emptyContact]],
];
$addressbook1
->method('search')
->will($this->returnValueMap($searchMap1));
$addressbook2
->method('search')
->will($this->returnValueMap($searchMap2));
$this->manager
->method('getUserAddressBooks')
->willReturn([$addressbook1, $addressbook2]);
$providerSupportsMap = [
[$validContact1, true],
[$emptyContact, false],
[$invalidContact, false],
[$validContact2, true]
];
$providerUrlMap = [
[$validContact1, ["url1"]],
[$emptyContact, []],
[$invalidContact, []],
[$validContact2, ["url1"]]
];
$provider = $this->createMock(ISocialProvider::class);
$provider->method('getImageUrls')
->will($this->returnValueMap($providerUrlMap));
$provider->method('supportsContact')
->will($this->returnValueMap($providerSupportsMap));
$this->socialProvider
->method('getSocialConnectors')
->willReturn([$provider]);
$this->socialProvider
->method('getSocialConnector')
->willReturn($provider);
$validResponse = $this->createMock(IResponse::class);
$validResponse
->method('getBody')
->willReturn('someBody');
$validResponse
->method('getHeader')
->willReturn('someHeader');
$client = $this->createMock(IClient::class);
$client
->method('get')
->willReturn($validResponse);
$this->clientService
->method('NewClient')
->willReturn($client);
}
/**
* @dataProvider updateAddressbookProvider
*/
public function testUpdateAddressbooks($syncAllowedByAdmin, $bgSyncEnabledByUser, $expected) {
$this->config
->method('getAppValue')
->willReturn($syncAllowedByAdmin);
$this->config
->method('getUserValue')
->willReturn($bgSyncEnabledByUser);
$this->timeFactory
->method('getTime')
->willReturn(10);
$this->setupAddressbooks();
if ($syncAllowedByAdmin === 'yes' && $bgSyncEnabledByUser === 'yes') {
$this->socialProvider
->expects($this->atLeastOnce())
->method('getSocialConnectors');
}
$this->socialProvider
->expects($this->never())
->method('getSocialConnector');
$result = $this->service->updateAddressbooks('mrstest');
$this->assertEquals($expected, $result->getStatus());
if (($syncAllowedByAdmin === 'yes') && ($bgSyncEnabledByUser === 'yes')) {
$report = $result->getData();
$this->assertArrayHasKey('0', $report);
$this->assertArrayHasKey('updated', $report[0]);
$this->assertContains('Valid Contact One', $report[0]['updated']);
$this->assertArrayHasKey('checked', $report[0]);
$this->assertContains('Valid Contact Two', $report[0]['checked']);
$this->assertArrayHasKey('failed', $report[0]);
$this->assertArrayHasKey('412', $report[0]['failed']);
$this->assertContains('Invalid Contact', $report[0]['failed']['412']);
$this->assertContains('Empty Contact', $report[0]['failed']['412']);
}
}
public function testUpdateAddressbooksTimeout() {
$this->config
->method('getAppValue')
->willReturn('yes');
$this->config
->method('getUserValue')
->willReturn('yes');
$this->timeFactory
->method('getTime')
->willReturnOnConsecutiveCalls(10, 11, 999);
$this->setupAddressbooks();
$result = $this->service->updateAddressbooks('msstest');
$this->assertEquals(Http::STATUS_PARTIAL_CONTENT, $result->getStatus());
$report = $result->getData();
$this->assertArrayHasKey('0', $report);
$this->assertArrayHasKey('stoppedAt', $report[0]);
$this->assertArrayHasKey('addressBook', $report[0]['stoppedAt']);
$this->assertArrayHasKey('contact', $report[0]['stoppedAt']);
}
/**
* @dataProvider updateAddressbookProvider
*/
public function testUpdateAddressbooksContinued($syncAllowedByAdmin, $bgSyncEnabledByUser, $expected) {
$this->config
->method('getAppValue')
->willReturn($syncAllowedByAdmin);
$this->config
->method('getUserValue')
->willReturn($bgSyncEnabledByUser);
$this->timeFactory
->method('getTime')
->willReturn(10);
$this->setupAddressbooks();
$result = $this->service->updateAddressbooks('mrstest', 'contacts2', '22222222-2222-2222-2222-222222222222');
$this->assertEquals($expected, $result->getStatus());
if (($syncAllowedByAdmin === 'yes') && ($bgSyncEnabledByUser === 'yes')) {
$report = $result->getData();
$this->assertArrayHasKey('0', $report);
$this->assertArrayHasKey('updated', $report[0]);
$this->assertNotContains('Valid Contact One', $report[0]['updated']);
$this->assertArrayHasKey('checked', $report[0]);
$this->assertContains('Valid Contact Two', $report[0]['checked']);
}
}
public function testExistsContact() {
$this->setupAddressbooks();
// all good:
$result = $this->service->existsContact('11111111-1111-1111-1111-111111111111', 'contacts1', 'admin');
$this->assertEquals(true, $result);
// wrong address book:
$result = $this->service->existsContact('22222222-2222-2222-2222-222222222222', 'contacts1', 'admin');
$this->assertEquals(false, $result);
// invalid contactId:
$result = $this->service->existsContact('not-existing', 'contacts1', 'admin');
$this->assertEquals(false, $result);
// invalid addressbookId:
$result = $this->service->existsContact('11111111-1111-1111-1111-111111111111', 'not-existing', 'admin');
$this->assertEquals(false, $result);
}
}