refactor: use bus batch in davclient (#5542)

This commit is contained in:
Alexis Saettler
2021-10-03 19:00:57 +02:00
committed by GitHub
parent e8ca11ad09
commit 8394386345
32 changed files with 1219 additions and 396 deletions
+1
View File
@@ -41,6 +41,7 @@ class Kernel extends ConsoleKernel
*/
protected function schedule(Schedule $schedule)
{
$this->scheduleCommand($schedule, 'queue:prune-batches', 'daily');
$this->scheduleCommand($schedule, 'send:reminders', 'hourly');
$this->scheduleCommand($schedule, 'send:stay_in_touch', 'hourly');
$this->scheduleCommand($schedule, 'monica:davclients', 'hourly');
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Jobs\Dav;
use Illuminate\Support\Arr;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Sabre\CardDAV\Plugin as CardDAVPlugin;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class GetMultipleVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var AddressBookSubscription
*/
private $subscription;
/**
* @var array
*/
private $hrefs;
/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param array $hrefs
* @return void
*/
public function __construct(AddressBookSubscription $subscription, array $hrefs)
{
$this->subscription = $subscription->withoutRelations();
$this->hrefs = $hrefs;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
$datas = app(DavClient::class)->addressbookMultiget($this->subscription->getRequest(), [
'{DAV:}getetag',
$this->getAddressDataProperty(),
], $this->hrefs);
collect($datas)
->filter(function (array $contact): bool {
return isset($contact[200]);
})
->each(function (array $contact, $href) {
$this->updateVCard($contact, $href);
});
}
/**
* Update the contact.
*
* @param array $contact
* @param string $href
* @return void
*/
private function updateVCard(array $contact, $href): void
{
$card = Arr::get($contact, '200.{'.CardDAVPlugin::NS_CARDDAV.'}address-data');
if ($card !== null) {
$dto = new ContactUpdateDto($href, Arr::get($contact, '200.{DAV:}getetag'), $card);
if (($batch = $this->batch()) !== null) {
$batch->add([
new UpdateVCard($this->subscription->user, $this->subscription->addressbook->name, $dto),
]);
}
}
}
/**
* Get data for address-data property.
*
* @return array
*/
private function getAddressDataProperty(): array
{
$addressDataAttributes = Arr::get($this->subscription->capabilities, 'addressData', [
'content-type' => 'text/vcard',
'version' => '4.0',
]);
return [
'name' => '{'.CardDAVPlugin::NS_CARDDAV.'}address-data',
'value' => null,
'attributes' => $addressDataAttributes,
];
}
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Jobs\Dav;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Model\ContactDto;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class GetVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var AddressBookSubscription
*/
private $subscription;
/**
* @var ContactDto
*/
private $contact;
/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param ContactDto $contact
* @return void
*/
public function __construct(AddressBookSubscription $subscription, ContactDto $contact)
{
$this->subscription = $subscription->withoutRelations();
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
Log::info(__CLASS__.' '.$this->contact->uri);
$response = $this->subscription->getRequest()
->get($this->contact->uri);
$response->throw();
$this->chainUpdateVCard($response->body());
}
private function chainUpdateVCard(string $card): void
{
$dto = new ContactUpdateDto($this->contact->uri, $this->contact->etag, $card);
if (($batch = $this->batch()) !== null) {
$batch->add([
new UpdateVCard($this->subscription->user, $this->subscription->addressbook->name, $dto),
]);
}
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace App\Jobs\Dav;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Model\ContactPushDto;
class PushVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var AddressBookSubscription
*/
private $subscription;
/**
* @var ContactPushDto
*/
private $contact;
/**
* Create a new job instance.
*
* @param AddressBookSubscription $subscription
* @param ContactPushDto $contact
* @return void
*/
public function __construct(AddressBookSubscription $subscription, ContactPushDto $contact)
{
$this->subscription = $subscription->withoutRelations();
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
Log::info(__CLASS__.' '.$this->contact->uri);
$headers = [];
if ($this->contact->mode === 1) {
$headers['If-Match'] = $this->contact->etag;
} elseif ($this->contact->mode === 2) {
$headers['If-Match'] = '*';
}
$response = $this->subscription->getRequest()
->withHeaders($headers)
->put($this->contact->uri, [$this->contact->card]);
$response->throw();
if (! empty($etag = $response->header('Etag')) && $etag !== $this->contact->etag) {
Log::warning(__CLASS__.' wrong etag when updating contact. Expected '.$this->contact->etag.', get '.$etag);
}
}
}
+69
View File
@@ -0,0 +1,69 @@
<?php
namespace App\Jobs\Dav;
use App\Models\User\User;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Support\Facades\Log;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
use App\Http\Controllers\DAV\Backend\CardDAV\CardDAVBackend;
class UpdateVCard implements ShouldQueue
{
use Batchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* @var User
*/
private $user;
/**
* @var string
*/
private $addressBookName;
/**
* @var ContactUpdateDto
*/
private $contact;
/**
* Create a new job instance.
*
* @param User $user
* @param string $addressBookName
* @param ContactUpdateDto $contact
* @return void
*/
public function __construct(User $user, string $addressBookName, ContactUpdateDto $contact)
{
$this->user = $user->withoutRelations();
$this->addressBookName = $addressBookName;
$this->contact = $contact;
}
/**
* Update the Last Consulted At field for the given contact.
*
* @return void
*/
public function handle(): void
{
if (! $this->batching()) {
return;
}
Log::info(__CLASS__.' update '.$this->contact->uri);
$backend = new CardDAVBackend($this->user);
$newtag = $backend->updateCard($this->addressBookName, $this->contact->uri, $this->contact->card);
if ($newtag !== $this->contact->etag) {
Log::warning(__CLASS__.' wrong etag when updating contact. Expected '.$this->contact->etag.', get '.$newtag);
}
}
}
@@ -5,8 +5,10 @@ namespace App\Models\Account;
use App\Models\User\User;
use function safe\json_decode;
use function safe\json_encode;
use Illuminate\Support\Facades\Http;
use App\Models\ModelBinding as Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Factories\HasFactory;
@@ -149,4 +151,16 @@ class AddressBookSubscription extends Model
{
return $query->where('active', 1);
}
/**
* Get a pending request.
*
* @return PendingRequest
*/
public function getRequest(): PendingRequest
{
return Http::withBasicAuth($this->username, $this->password)
->baseUrl($this->uri)
->withUserAgent('Monica DavClient '.config('monica.app_version'));
}
}
@@ -92,6 +92,6 @@ class CreateAddressBookSubscription extends BaseService
'password',
]);
return new DavClient($settings, $client);
return app(DavClient::class)->init($settings, $client);
}
}
@@ -78,10 +78,11 @@ class SynchronizeAddressBook extends BaseService
private function getDavClient(AddressBookSubscription $subscription, ?GuzzleClient $client): DavClient
{
return new DavClient([
'base_uri' => $subscription->uri,
'username' => $subscription->username,
'password' => $subscription->password,
], $client);
return app(DavClient::class)
->init([
'base_uri' => $subscription->uri,
'username' => $subscription->username,
'password' => $subscription->password,
], $client);
}
}
@@ -2,11 +2,8 @@
namespace App\Services\DavClient\Utils;
use GuzzleHttp\Psr7\Request;
use App\Jobs\Dav\PushVCard;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use IlluminateAgnostic\Collection\Support\Arr;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
@@ -25,72 +22,37 @@ class AddressBookContactsPush
* @param SyncDto $sync
* @param Collection<array-key, ContactDto> $changes
* @param array<array-key, string>|null $localChanges
* @param Collection<array-key, ContactPushDto>|null $missed
* @return PromiseInterface
* @return Collection
*/
public function execute(SyncDto $sync, Collection $changes, ?array $localChanges, ?Collection $missed = null): PromiseInterface
public function execute(SyncDto $sync, Collection $changes, ?array $localChanges): Collection
{
$this->sync = $sync;
$commands = $this->preparePushChanges($changes, $localChanges, $missed)
->filter(function ($command) {
return $command !== null;
$changes = $this->preparePushChangedContacts($changes, Arr::get($localChanges, 'modified', []));
$added = $this->preparePushAddedContacts(Arr::get($localChanges, 'added', []));
return $changes->union($added)
->filter(function ($c) {
return $c !== null;
});
$requests = $commands->pluck('request')->toArray();
return $this->sync->client->requestPool($requests, [
'concurrency' => 25,
'fulfilled' => function (ResponseInterface $response, int $index) use ($commands) {
/** @var ContactPushDto $command */
$command = $commands[$index];
Log::info(__CLASS__.' pushContacts: PUT '.$command->uri);
$etags = $response->getHeader('Etag');
if (! empty($etags) && $etags[0] !== $command->etag) {
Log::warning(__CLASS__.' pushContacts: wrong etag when updating contact. Expected '.$command->etag.', get '.$etags[0]);
}
},
]);
}
/**
* Get list of requests to push contacts that have changed.
*
* @param Collection<array-key, ContactDto> $changes
* @param array<array-key, string>|null $localChanges
* @param Collection<array-key, ContactPushDto>|null $missed
* @return Collection<mixed, ?ContactPushDto>
*/
private function preparePushChanges(Collection $changes, ?array $localChanges, ?Collection $missed = null): Collection
{
$requestsChanges = $this->preparePushChangedContacts($changes, Arr::get($localChanges, 'modified', []));
$requestsAdded = $this->preparePushAddedContacts(Arr::get($localChanges, 'added', []));
return $requestsChanges
->union($requestsAdded)
->union($missed ?? []);
}
/**
* Get list of requests to push new contacts.
*
* @param array $contacts
* @return Collection<mixed, ?ContactPushDto>
* @return Collection
*/
private function preparePushAddedContacts(array $contacts): Collection
{
// All added contact must be pushed
return collect($contacts)
->map(function (string $uri): ?ContactPushDto {
->map(function (string $uri): ?PushVCard {
$card = $this->sync->backend->getCard($this->sync->addressBookName(), $uri);
if ($card === false) {
return null;
}
return new ContactPushDto($uri, $card['etag'], new Request('PUT', $uri, [], $card['carddata']));
return $card !== false
? new PushVCard($this->sync->subscription, new ContactPushDto($uri, $card['etag'], $card['carddata'], 0))
: null;
});
}
@@ -99,7 +61,7 @@ class AddressBookContactsPush
*
* @param Collection<array-key, ContactDto> $changes
* @param array $contacts
* @return Collection<mixed, ?ContactPushDto>
* @return Collection
*/
private function preparePushChangedContacts(Collection $changes, array $contacts): Collection
{
@@ -113,14 +75,12 @@ class AddressBookContactsPush
$uuid = $this->sync->backend->getUuid($uri);
return $refreshIds->contains($uuid);
})->map(function (string $uri): ?ContactPushDto {
})->map(function (string $uri): ?PushVCard {
$card = $this->sync->backend->getCard($this->sync->addressBookName(), $uri);
if ($card === false) {
return null;
}
return new ContactPushDto($uri, $card['etag'], new Request('PUT', $uri, ['If-Match' => $card['etag']], $card['carddata']));
return $card !== false
? new PushVCard($this->sync->subscription, new ContactPushDto($uri, $card['etag'], $card['carddata'], 1))
: null;
});
}
}
@@ -2,10 +2,9 @@
namespace App\Services\DavClient\Utils;
use GuzzleHttp\Psr7\Request;
use App\Jobs\Dav\PushVCard;
use App\Models\Contact\Contact;
use Illuminate\Support\Collection;
use GuzzleHttp\Promise\PromiseInterface;
use IlluminateAgnostic\Collection\Support\Arr;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
@@ -25,16 +24,17 @@ class AddressBookContactsPushMissed
* @param array<array-key, string>|null $localChanges
* @param Collection<array-key, ContactDto> $distContacts
* @param Collection<array-key, Contact> $localContacts
* @return PromiseInterface
* @return Collection
*/
public function execute(SyncDto $sync, ?array $localChanges, Collection $distContacts, Collection $localContacts): PromiseInterface
public function execute(SyncDto $sync, ?array $localChanges, Collection $distContacts, Collection $localContacts): Collection
{
$this->sync = $sync;
$missed = $this->preparePushMissedContacts(Arr::get($localChanges, 'added', []), $distContacts, $localContacts);
$missings = $this->preparePushMissedContacts(Arr::get($localChanges, 'added', []), $distContacts, $localContacts);
return app(AddressBookContactsPush::class)
->execute($sync, collect(), $localChanges, $missed);
->execute($sync, collect(), $localChanges)
->union($missings);
}
/**
@@ -43,7 +43,7 @@ class AddressBookContactsPushMissed
* @param array<array-key, string> $added
* @param Collection<array-key, ContactDto> $distContacts
* @param Collection<array-key, Contact> $localContacts
* @return Collection<array-key, ContactPushDto>
* @return Collection
*/
private function preparePushMissedContacts(array $added, Collection $distContacts, Collection $localContacts): Collection
{
@@ -61,11 +61,10 @@ class AddressBookContactsPushMissed
->filter(function (Contact $contact) use ($distUuids, $addedUuids) {
return ! $distUuids->contains($contact->uuid)
&& ! $addedUuids->contains($contact->uuid);
})->map(function (Contact $contact): ContactPushDto {
})->map(function (Contact $contact): PushVCard {
$card = $this->sync->backend->prepareCard($contact);
return new ContactPushDto($card['uri'], $card['etag'], new Request('PUT', $card['uri'], ['If-Match' => '*'], $card['carddata']));
})
->values();
return new PushVCard($this->sync->subscription, new ContactPushDto($card['uri'], $card['etag'], $card['carddata'], 2));
});
}
}
@@ -2,16 +2,11 @@
namespace App\Services\DavClient\Utils;
use Illuminate\Support\Arr;
use GuzzleHttp\Psr7\Request;
use App\Jobs\Dav\GetVCard;
use App\Jobs\Dav\GetMultipleVCard;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Sabre\CardDAV\Plugin as CardDAVPlugin;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Traits\HasCapability;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class AddressBookContactsUpdater
{
@@ -27,9 +22,9 @@ class AddressBookContactsUpdater
*
* @param SyncDto $sync
* @param Collection<array-key, \App\Services\DavClient\Utils\Model\ContactDto> $refresh
* @return PromiseInterface
* @return Collection
*/
public function execute(SyncDto $sync, Collection $refresh): PromiseInterface
public function execute(SyncDto $sync, Collection $refresh): Collection
{
$this->sync = $sync;
@@ -42,98 +37,27 @@ class AddressBookContactsUpdater
* Get contacts data with addressbook-multiget request.
*
* @param Collection<array-key, \App\Services\DavClient\Utils\Model\ContactDto> $refresh
* @return PromiseInterface
* @return Collection
*/
private function refreshMultigetContacts(Collection $refresh): PromiseInterface
private function refreshMultigetContacts(Collection $refresh): Collection
{
$hrefs = $refresh->pluck('uri');
$hrefs = $refresh->pluck('uri')->toArray();
return $this->sync->client->addressbookMultigetAsync('', [
'{DAV:}getetag',
$this->getAddressDataProperty(),
], $hrefs)
->then(function ($datas) {
return collect($datas)
->filter(function (array $contact): bool {
return isset($contact[200]);
})
->map(function (array $contact, $href): ContactUpdateDto {
return new ContactUpdateDto(
$href,
Arr::get($contact, '200.{DAV:}getetag'),
Arr::get($contact, '200.{'.CardDAVPlugin::NS_CARDDAV.'}address-data')
);
});
})->then(function (Collection $contacts) {
$contacts->each(function (ContactUpdateDto $contact) {
$this->syncLocalContact($contact);
});
});
}
/**
* Get data for address-data property.
*
* @return array
*/
private function getAddressDataProperty(): array
{
$addressDataAttributes = Arr::get($this->sync->subscription->capabilities, 'addressData', [
'content-type' => 'text/vcard',
'version' => '4.0',
return collect([
new GetMultipleVCard($this->sync->subscription, $hrefs),
]);
return [
'name' => '{'.CardDAVPlugin::NS_CARDDAV.'}address-data',
'value' => null,
'attributes' => $addressDataAttributes,
];
}
/**
* Get contacts data with request.
*
* @param Collection<array-key, \App\Services\DavClient\Utils\Model\ContactDto> $requests
* @return PromiseInterface
* @return Collection
*/
private function refreshSimpleGetContacts(Collection $requests): PromiseInterface
private function refreshSimpleGetContacts(Collection $requests): Collection
{
$inputs = $requests->map(function ($contact) {
return new Request('GET', $contact->uri);
})->toArray();
return $this->sync->client->requestPool($inputs, [
'concurrency' => 25,
'fulfilled' => function (ResponseInterface $response, $index) use ($requests) {
if ($response->getStatusCode() === 200) {
/** @var \App\Services\DavClient\Utils\Model\ContactDto $request */
$request = $requests[$index];
$this->syncLocalContact(new ContactUpdateDto(
$request->uri,
$request->etag,
$response->getBody()->detach(),
));
}
},
]);
}
/**
* Save contact to local storage.
*
* @param ContactUpdateDto $contact
*/
private function syncLocalContact(ContactUpdateDto $contact): void
{
if ($contact->card !== null) {
Log::info(__CLASS__.' syncLocalContact: update '.$contact->uri);
$newtag = $this->sync->backend->updateCard($this->sync->addressBookName(), $contact->uri, $contact->card);
if ($newtag !== $contact->etag) {
Log::warning(__CLASS__.' syncLocalContact: wrong etag when updating contact. Expected '.$contact->etag.', get '.$newtag);
}
}
return $requests->map(function ($contact): GetVCard {
return new GetVCard($this->sync->subscription, $contact);
});
}
}
@@ -3,7 +3,6 @@
namespace App\Services\DavClient\Utils;
use Illuminate\Support\Collection;
use GuzzleHttp\Promise\PromiseInterface;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
@@ -20,9 +19,9 @@ class AddressBookContactsUpdaterMissed
* @param SyncDto $sync
* @param Collection<array-key, \App\Models\Contact\Contact> $localContacts
* @param Collection<array-key, \App\Services\DavClient\Utils\Model\ContactDto> $distContacts
* @return PromiseInterface
* @return Collection
*/
public function execute(SyncDto $sync, Collection $localContacts, Collection $distContacts): PromiseInterface
public function execute(SyncDto $sync, Collection $localContacts, Collection $distContacts): Collection
{
$this->sync = $sync;
@@ -2,11 +2,12 @@
namespace App\Services\DavClient\Utils;
use Illuminate\Bus\Batch;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use GuzzleHttp\Promise\Each;
use GuzzleHttp\Promise\Promise;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Bus;
use GuzzleHttp\Promise\PromiseInterface;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
@@ -44,27 +45,28 @@ class AddressBookSynchronizer
$localChanges = $this->sync->backend->getChangesForAddressBook($this->sync->addressBookName(), (string) $this->sync->subscription->localSyncToken, 1);
// Get distant changes to sync
$promise = $this->getDistantChanges();
$changes = $this->getDistantChanges();
// Get distant contacts
$batch = app(AddressBookContactsUpdater::class)
->execute($this->sync, $changes);
$chain = [];
$chain[] = $promise->then(function (Collection $changes) {
// Get distant contacts
return app(AddressBookContactsUpdater::class)
->execute($this->sync, $changes);
});
if (! $this->sync->subscription->readonly) {
$chain[] = $promise->then(function (Collection $changes) use ($localChanges) {
return app(AddressBookContactsPush::class)
->execute($this->sync, $changes, $localChanges);
});
$batch->union(
app(AddressBookContactsPush::class)
->execute($this->sync, $changes, $localChanges)
);
}
Each::of($chain)->wait();
Bus::batch($batch)
->then(function (Batch $batch) {
$token = $this->sync->backend->getCurrentSyncToken($this->sync->addressBookName());
$token = $this->sync->backend->getCurrentSyncToken($this->sync->addressBookName());
$this->sync->subscription->localSyncToken = $token->id;
$this->sync->subscription->save();
$this->sync->subscription->localSyncToken = $token->id;
$this->sync->subscription->save();
})
->allowFailures()
->dispatch();
}
/**
@@ -79,31 +81,30 @@ class AddressBookSynchronizer
$localContacts = $this->sync->backend->getObjects($this->sync->addressBookName());
// Get distant changes to sync
$promise = $this->getAllContactsEtag();
$distContacts = $this->getAllContactsEtag();
$chain = [];
$chain[] = $promise->then(function (Collection $distContacts) use ($localContacts) {
// Get missed contacts
return app(AddressBookContactsUpdaterMissed::class)
->execute($this->sync, $localContacts, $distContacts);
});
// Get missed contacts
$batch = app(AddressBookContactsUpdaterMissed::class)
->execute($this->sync, $localContacts, $distContacts);
if (! $this->sync->subscription->readonly) {
$chain[] = $promise->then(function ($distContacts) use ($localChanges, $localContacts) {
return app(AddressBookContactsPushMissed::class)
->execute($this->sync, $localChanges, $distContacts, $localContacts);
});
$batch->union(
app(AddressBookContactsPushMissed::class)
->execute($this->sync, $localChanges, $distContacts, $localContacts)
);
}
Each::of($chain)->wait();
Bus::batch($batch)
->allowFailures()
->dispatch();
}
/**
* Get distant changes to sync.
*
* @return PromiseInterface
* @return Collection
*/
private function getDistantChanges(): PromiseInterface
private function getDistantChanges(): Collection
{
return $this->getDistantEtags()
->then(function ($collection) {
@@ -114,7 +115,8 @@ class AddressBookSynchronizer
->map(function ($contact, $href): ContactDto {
return new ContactDto($href, Arr::get($contact, '200.{DAV:}getetag'));
});
});
})
->wait();
}
/**
@@ -204,12 +206,12 @@ class AddressBookSynchronizer
/**
* Get all contacts etag.
*
* @return PromiseInterface
* @return Collection
*/
private function getAllContactsEtag(): PromiseInterface
private function getAllContactsEtag(): Collection
{
if (! $this->hasCapability('addressbookQuery')) {
return $this->emptyPromise();
return collect();
}
return $this->sync->client->addressbookQueryAsync('', '{DAV:}getetag')
@@ -221,7 +223,8 @@ class AddressBookSynchronizer
->map(function ($contact, $href): ContactDto {
return new ContactDto($href, Arr::get($contact, '200.{DAV:}getetag'));
});
});
})
->wait();
}
/**
+53 -52
View File
@@ -13,44 +13,43 @@ use GuzzleHttp\Client as GuzzleClient;
use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use GuzzleHttp\Exception\ClientException;
use Illuminate\Http\Client\PendingRequest;
use Sabre\CardDAV\Plugin as CardDAVPlugin;
class DavClient
{
/**
* The xml service.
*
* @var Service
*/
public $xml;
/**
* @var GuzzleClient
*/
protected $client;
/**
* Create a new client.
* Initialize the client.
*
* @param array $settings
* @param GuzzleClient $client
* @return DavClient
*/
public function __construct(array $settings, GuzzleClient $client = null)
public static function init(array $settings, GuzzleClient $client = null): DavClient
{
if (is_null($client) && ! isset($settings['base_uri'])) {
throw new \InvalidArgumentException('A baseUri must be provided');
}
$this->client = is_null($client) ? new GuzzleClient([
'base_uri' => $settings['base_uri'],
'auth' => [
$settings['username'],
$settings['password'],
],
'verify' => ! App::environment('local'),
]) : $client;
$me = new self();
$this->xml = new Service();
$me->client = is_null($client)
? new GuzzleClient([
'base_uri' => $settings['base_uri'],
'auth' => [
$settings['username'],
$settings['password'],
],
'verify' => ! App::environment('local'),
])
: $client;
return $me;
}
/**
@@ -138,7 +137,7 @@ class DavClient
{
$baseUri = $this->client->getConfig('base_uri');
return is_null($path) ? $baseUri : $baseUri->withPath($path);
return (string) (is_null($path) ? $baseUri : $baseUri->withPath($path));
}
/**
@@ -210,14 +209,14 @@ class DavClient
public function propFindAsync(string $url, $properties, int $depth = 0, array $options = []): PromiseInterface
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$root = $this->addElementNS($dom, 'DAV:', 'd:propfind');
$prop = $this->addElement($dom, $root, 'd:prop');
$root = self::addElementNS($dom, 'DAV:', 'd:propfind');
$prop = self::addElement($dom, $root, 'd:prop');
$namespaces = [
'DAV:' => 'd',
];
$this->fetchProperties($dom, $prop, $properties, $namespaces);
self::fetchProperties($dom, $prop, $properties, $namespaces);
$body = $dom->saveXML();
@@ -225,7 +224,7 @@ class DavClient
'Depth' => $depth,
'Content-Type' => 'application/xml; charset=utf-8',
], $body, $options)->then(function (ResponseInterface $response) use ($depth): array {
$result = $this->parseMultiStatus((string) $response->getBody());
$result = self::parseMultiStatus((string) $response->getBody());
// If depth was 0, we only return the top item value
if ($depth === 0) {
@@ -254,18 +253,18 @@ class DavClient
public function syncCollectionAsync(string $url, $properties, string $syncToken, array $options = []): PromiseInterface
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$root = $this->addElementNS($dom, 'DAV:', 'd:sync-collection');
$root = self::addElementNS($dom, 'DAV:', 'd:sync-collection');
$this->addElement($dom, $root, 'd:sync-token', $syncToken);
$this->addElement($dom, $root, 'd:sync-level', '1');
self::addElement($dom, $root, 'd:sync-token', $syncToken);
self::addElement($dom, $root, 'd:sync-level', '1');
$prop = $this->addElement($dom, $root, 'd:prop');
$prop = self::addElement($dom, $root, 'd:prop');
$namespaces = [
'DAV:' => 'd',
];
$this->fetchProperties($dom, $prop, $properties, $namespaces);
self::fetchProperties($dom, $prop, $properties, $namespaces);
$body = $dom->saveXML();
@@ -273,7 +272,7 @@ class DavClient
'Depth' => '0',
'Content-Type' => 'application/xml; charset=utf-8',
], $body, $options)->then(function (ResponseInterface $response) {
return $this->parseMultiStatus((string) $response->getBody());
return self::parseMultiStatus((string) $response->getBody());
});
}
@@ -283,37 +282,38 @@ class DavClient
* @param string $url
* @param array|string $properties
* @param iterable $contacts
* @return PromiseInterface
* @return array
*
* @see https://datatracker.ietf.org/doc/html/rfc6352#section-8.7
*/
public function addressbookMultigetAsync(string $url, $properties, iterable $contacts, array $options = []): PromiseInterface
public static function addressbookMultiget(PendingRequest $request, $properties, iterable $contacts, string $url = '', array $options = []): array
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$root = $this->addElementNS($dom, CardDAVPlugin::NS_CARDDAV, 'card:addressbook-multiget');
$root = self::addElementNS($dom, CardDAVPlugin::NS_CARDDAV, 'card:addressbook-multiget');
$dom->createAttributeNS('DAV:', 'd:e');
$prop = $this->addElement($dom, $root, 'd:prop');
$prop = self::addElement($dom, $root, 'd:prop');
$namespaces = [
'DAV:' => 'd',
CardDAVPlugin::NS_CARDDAV => 'card',
];
$this->fetchProperties($dom, $prop, $properties, $namespaces);
self::fetchProperties($dom, $prop, $properties, $namespaces);
foreach ($contacts as $contact) {
$this->addElement($dom, $root, 'd:href', $contact);
self::addElement($dom, $root, 'd:href', $contact);
}
$body = $dom->saveXML();
return $this->requestAsync('REPORT', $url, [
'Depth' => '1',
'Content-Type' => 'application/xml; charset=utf-8',
], $body, $options)->then(function (ResponseInterface $response) {
return $this->parseMultiStatus((string) $response->getBody());
});
$response = $request->withHeaders(['Depth' => '1'])
->withBody($body, 'application/xml; charset=utf-8')
->send('REPORT', $url, $options);
$response->throw();
return self::parseMultiStatus($response->body());
}
/**
@@ -328,17 +328,17 @@ class DavClient
public function addressbookQueryAsync(string $url, $properties, array $options = []): PromiseInterface
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$root = $this->addElementNS($dom, CardDAVPlugin::NS_CARDDAV, 'card:addressbook-query');
$root = self::addElementNS($dom, CardDAVPlugin::NS_CARDDAV, 'card:addressbook-query');
$dom->createAttributeNS('DAV:', 'd:e');
$prop = $this->addElement($dom, $root, 'd:prop');
$prop = self::addElement($dom, $root, 'd:prop');
$namespaces = [
'DAV:' => 'd',
CardDAVPlugin::NS_CARDDAV => 'card',
];
$this->fetchProperties($dom, $prop, $properties, $namespaces);
self::fetchProperties($dom, $prop, $properties, $namespaces);
$body = $dom->saveXML();
@@ -346,7 +346,7 @@ class DavClient
'Depth' => '1',
'Content-Type' => 'application/xml; charset=utf-8',
], $body, $options)->then(function (ResponseInterface $response) {
return $this->parseMultiStatus((string) $response->getBody());
return self::parseMultiStatus((string) $response->getBody());
});
}
@@ -374,7 +374,7 @@ class DavClient
* @param array $namespaces
* @return void
*/
private function fetchProperties(\DOMDocument $dom, \DOMNode $prop, $properties, array $namespaces)
private static function fetchProperties(\DOMDocument $dom, \DOMNode $prop, $properties, array $namespaces)
{
if (is_string($properties)) {
$properties = [$properties];
@@ -522,7 +522,7 @@ class DavClient
{
$propPatch = new PropPatch();
$propPatch->properties = $properties;
$body = $this->xml->write(
$body = (new Service())->write(
'{DAV:}propertyupdate',
$propPatch
);
@@ -533,7 +533,7 @@ class DavClient
if ($response->getStatusCode() === 207) {
// If it's a 207, the request could still have failed, but the
// information is hidden in the response body.
$result = $this->parseMultiStatus((string) $response->getBody());
$result = self::parseMultiStatus((string) $response->getBody());
$errorProperties = [];
foreach ($result as $statusList) {
@@ -649,9 +649,10 @@ class DavClient
*
* @see https://datatracker.ietf.org/doc/html/rfc4918#section-9.2.1
*/
private function parseMultiStatus(string $body): array
private static function parseMultiStatus(string $body): array
{
$multistatus = $this->xml->expect('{DAV:}multistatus', $body);
$multistatus = (new Service())
->expect('{DAV:}multistatus', $body);
$result = [];
@@ -675,7 +676,7 @@ class DavClient
* @param string $qualifiedName
* @return \DOMNode
*/
private function addElementNS(\DOMDocument $dom, ?string $namespace, string $qualifiedName): \DOMNode
private static function addElementNS(\DOMDocument $dom, ?string $namespace, string $qualifiedName): \DOMNode
{
return $dom->appendChild($dom->createElementNS($namespace, $qualifiedName));
}
@@ -689,7 +690,7 @@ class DavClient
* @param string|null $value
* @return \DOMNode
*/
private function addElement(\DOMDocument $dom, \DOMNode $root, string $name, ?string $value = null): \DOMNode
private static function addElement(\DOMDocument $dom, \DOMNode $root, string $name, ?string $value = null): \DOMNode
{
return $root->appendChild($dom->createElement($name, $value));
}
@@ -2,26 +2,24 @@
namespace App\Services\DavClient\Utils\Model;
use GuzzleHttp\Psr7\Request;
class ContactPushDto extends ContactDto
class ContactPushDto extends ContactUpdateDto
{
/**
* @var Request
* @var int
*/
public $request;
public $mode;
/**
* Create a new ContactPushDto.
*
* @param string $uri
* @param string $etag
* @param Request $request
* @param string|resource $card
* @param int $mode
*/
public function __construct(string $uri, string $etag, Request $request)
public function __construct(string $uri, string $etag, $card, int $mode)
{
$this->uri = $uri;
$this->etag = $etag;
$this->request = $request;
parent::__construct($uri, $etag, $card);
$this->mode = $mode;
}
}
@@ -2,10 +2,13 @@
namespace App\Services\DavClient\Utils\Model;
use function Safe\fclose;
use function Safe\stream_get_contents;
class ContactUpdateDto extends ContactDto
{
/**
* @var string|resource
* @var string
*/
public $card;
@@ -18,8 +21,24 @@ class ContactUpdateDto extends ContactDto
*/
public function __construct(string $uri, string $etag, $card)
{
$this->uri = $uri;
$this->etag = $etag;
$this->card = $card;
parent::__construct($uri, $etag);
$this->card = self::transformCard($card);
}
/**
* Transform card.
*
* @param string|resource $card
* @return string
*/
protected static function transformCard($card): string
{
if (is_resource($card)) {
$card = tap(stream_get_contents($card), function () use ($card) {
fclose($card);
});
}
return $card;
}
}
@@ -0,0 +1,39 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateJobBatchesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->text('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('job_batches');
}
}
+1 -1
View File
@@ -359,7 +359,7 @@ class DavTester extends TestCase
"</card:addressbook-multiget>\n", 'REPORT');
}
public function multistatusHeader()
public static function multistatusHeader()
{
return '<d:multistatus xmlns:d="DAV:" xmlns:card="urn:ietf:params:xml:ns:carddav">';
}
+16
View File
@@ -44,6 +44,22 @@ abstract class TestCase extends BaseTestCase
$property->setValue($object, $value);
}
/**
* Get protected/private property of a class.
*
* @param object &$object
* @param string $propertyName
* @return mixed
*/
public function getPrivateValue(&$object, string $propertyName)
{
$reflection = new \ReflectionClass(get_class($object));
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
return $property->getValue($object);
}
/**
* Test that the response contains an ObjectDeleted response.
*
@@ -0,0 +1,172 @@
<?php
namespace Tests\Unit\Jobs\Dav;
use Tests\TestCase;
use App\Models\User\User;
use Mockery\MockInterface;
use Tests\Api\DAV\CardEtag;
use App\Jobs\Dav\UpdateVCard;
use App\Models\Contact\Contact;
use Illuminate\Bus\PendingBatch;
use App\Jobs\Dav\GetMultipleVCard;
use Illuminate\Support\Facades\Bus;
use Sabre\CardDAV\Plugin as CardDAVPlugin;
use Illuminate\Bus\DatabaseBatchRepository;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class GetMultipleVCardTest extends TestCase
{
use DatabaseTransactions;
use CardEtag;
/** @test */
public function it_get_cards()
{
$fake = Bus::fake();
$user = factory(User::class)->create();
$addressBookSubscription = AddressBookSubscription::factory()->create([
'account_id' => $user->account_id,
'user_id' => $user->id,
]);
$contact = new Contact();
$contact->forceFill([
'first_name' => 'Test',
'uuid' => 'affacde9-b2fe-4371-9acb-6612aaee6971',
'updated_at' => now(),
]);
$card = $this->getCard($contact);
$etag = $this->getEtag($contact, true);
$this->mock(DavClient::class, function (MockInterface $mock) use ($card, $etag) {
$mock->shouldReceive('addressbookMultiget')
->once()
->withArgs(function ($request, $properties, $contacts) {
$this->assertEquals([
'{DAV:}getetag',
[
'name' => '{'.CardDAVPlugin::NS_CARDDAV.'}address-data',
'value' => null,
'attributes' => [
'content-type' => 'text/vcard',
'version' => '4.0',
],
],
], $properties);
$this->assertEquals(['https://test/dav/uri'], $contacts);
return true;
})
->andReturn([
'https://test/dav/uri' => [
200 => [
'{'.CardDAVPlugin::NS_CARDDAV.'}address-data' => $card,
'{DAV:}getetag' => $etag,
],
],
]);
});
$pendingBatch = $fake->batch([
$job = new GetMultipleVCard($addressBookSubscription, ['https://test/dav/uri']),
]);
$batch = $pendingBatch->dispatch();
$fake->assertBatched(function (PendingBatch $pendingBatch) {
$this->assertCount(1, $pendingBatch->jobs);
$this->assertInstanceOf(GetMultipleVCard::class, $pendingBatch->jobs->first());
return true;
});
$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
$job->withBatchId($batch->id)->handle();
$fake->assertDispatched(function (UpdateVCard $updateVCard) use ($etag, $card) {
$dto = $this->getPrivateValue($updateVCard, 'contact');
$this->assertEquals('https://test/dav/uri', $dto->uri);
$this->assertEquals($etag, $dto->etag);
$this->assertEquals($card, $dto->card);
return true;
});
}
/** @test */
public function it_get_cards_mock_http()
{
$fake = Bus::fake();
$user = factory(User::class)->create();
$addressBookSubscription = AddressBookSubscription::factory()->create([
'account_id' => $user->account_id,
'user_id' => $user->id,
]);
$contact = new Contact();
$contact->forceFill([
'first_name' => 'Test',
'uuid' => 'affacde9-b2fe-4371-9acb-6612aaee6971',
'updated_at' => now(),
]);
$card = $this->getCard($contact);
$etag = $this->getEtag($contact, true);
$this->mock(DavClient::class, function (MockInterface $mock) use ($card, $etag) {
$mock->shouldReceive('addressbookMultiget')
->once()
->withArgs(function ($request, $properties, $contacts) {
$this->assertEquals([
'{DAV:}getetag',
[
'name' => '{'.CardDAVPlugin::NS_CARDDAV.'}address-data',
'value' => null,
'attributes' => [
'content-type' => 'text/vcard',
'version' => '4.0',
],
],
], $properties);
$this->assertEquals(['https://test/dav/uri'], $contacts);
return true;
})
->andReturn([
'https://test/dav/uri' => [
200 => [
'{'.CardDAVPlugin::NS_CARDDAV.'}address-data' => $card,
'{DAV:}getetag' => $etag,
],
],
]);
});
$pendingBatch = $fake->batch([
$job = new GetMultipleVCard($addressBookSubscription, ['https://test/dav/uri']),
]);
$batch = $pendingBatch->dispatch();
$fake->assertBatched(function (PendingBatch $pendingBatch) {
$this->assertCount(1, $pendingBatch->jobs);
$this->assertInstanceOf(GetMultipleVCard::class, $pendingBatch->jobs->first());
return true;
});
$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
$job->withBatchId($batch->id)->handle();
$fake->assertDispatched(function (UpdateVCard $updateVCard) use ($etag, $card) {
$dto = $this->getPrivateValue($updateVCard, 'contact');
$this->assertEquals('https://test/dav/uri', $dto->uri);
$this->assertEquals($etag, $dto->etag);
$this->assertEquals($card, $dto->card);
return true;
});
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace Tests\Unit\Jobs\Dav;
use Tests\TestCase;
use App\Models\User\User;
use App\Jobs\Dav\GetVCard;
use Tests\Api\DAV\CardEtag;
use App\Jobs\Dav\UpdateVCard;
use App\Models\Contact\Contact;
use Illuminate\Bus\PendingBatch;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Http;
use Illuminate\Bus\DatabaseBatchRepository;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Model\ContactDto;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class GetVCardTest extends TestCase
{
use DatabaseTransactions;
use CardEtag;
/** @test */
public function it_get_card()
{
$fake = Bus::fake();
$user = factory(User::class)->create();
$addressBookSubscription = AddressBookSubscription::factory()->create([
'account_id' => $user->account_id,
'user_id' => $user->id,
]);
$contact = new Contact();
$contact->forceFill([
'first_name' => 'Test',
'uuid' => 'affacde9-b2fe-4371-9acb-6612aaee6971',
'updated_at' => now(),
]);
$card = $this->getCard($contact);
$etag = $this->getEtag($contact, true);
Http::fake([
'https://test/dav/uri' => Http::response($card, 200),
]);
$pendingBatch = $fake->batch([
$job = new GetVCard($addressBookSubscription, new ContactDto('https://test/dav/uri', $etag)),
]);
$batch = $pendingBatch->dispatch();
$fake->assertBatched(function (PendingBatch $pendingBatch) {
$this->assertCount(1, $pendingBatch->jobs);
$this->assertInstanceOf(GetVCard::class, $pendingBatch->jobs->first());
return true;
});
$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
$job->withBatchId($batch->id)->handle();
$fake->assertDispatched(function (UpdateVCard $updateVCard) use ($etag, $card) {
$dto = $this->getPrivateValue($updateVCard, 'contact');
$this->assertEquals('https://test/dav/uri', $dto->uri);
$this->assertEquals($etag, $dto->etag);
$this->assertEquals($card, $dto->card);
return true;
});
}
}
+85
View File
@@ -0,0 +1,85 @@
<?php
namespace Tests\Unit\Jobs\Dav;
use Tests\TestCase;
use App\Models\User\User;
use App\Jobs\Dav\PushVCard;
use Tests\Api\DAV\CardEtag;
use App\Models\Contact\Contact;
use Illuminate\Bus\PendingBatch;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Http;
use Illuminate\Bus\DatabaseBatchRepository;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Model\ContactPushDto;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class PushVCardTest extends TestCase
{
use DatabaseTransactions;
use CardEtag;
/**
* @test
* @dataProvider modes
*/
public function it_push_card($mode, $ifmatch)
{
$fake = Bus::fake();
$user = factory(User::class)->create();
$addressBookSubscription = AddressBookSubscription::factory()->create([
'account_id' => $user->account_id,
'user_id' => $user->id,
'uri' => 'https://test/dav',
]);
$contact = new Contact();
$contact->forceFill([
'first_name' => 'John',
'last_name' => 'Doe',
'uuid' => 'affacde9-b2fe-4371-9acb-6612aaee6971',
'updated_at' => '2021-09-01',
]);
$card = $this->getCard($contact);
$etag = $this->getEtag($contact, true);
if ($ifmatch == ['etag']) {
$ifmatch = [$etag];
}
Http::fake(function (Request $request, $options) use ($card, $ifmatch) {
$this->assertEquals('https://test/dav/uri', $request->url());
$this->assertEquals('PUT', $request->method());
$this->assertEquals($ifmatch, $request->header('If-Match'));
return Http::response($card, 200);
});
$pendingBatch = $fake->batch([
$job = new PushVCard($addressBookSubscription, new ContactPushDto('uri', $etag, $card, $mode)),
]);
$batch = $pendingBatch->dispatch();
$fake->assertBatched(function (PendingBatch $pendingBatch) {
$this->assertCount(1, $pendingBatch->jobs);
$this->assertInstanceOf(PushVCard::class, $pendingBatch->jobs->first());
return true;
});
$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
$job->withBatchId($batch->id)->handle();
}
public function modes(): array
{
return [
[0, []],
[1, ['etag']],
[2, ['*']],
];
}
}
+62
View File
@@ -0,0 +1,62 @@
<?php
namespace Tests\Unit\Jobs\Dav;
use Tests\TestCase;
use App\Models\User\User;
use Tests\Api\DAV\CardEtag;
use App\Jobs\Dav\UpdateVCard;
use App\Models\Contact\Contact;
use Illuminate\Bus\PendingBatch;
use App\Models\Account\AddressBook;
use Illuminate\Support\Facades\Bus;
use Illuminate\Bus\DatabaseBatchRepository;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class UpdateVCardTest extends TestCase
{
use DatabaseTransactions;
use CardEtag;
/** @test */
public function it_create_a_contact()
{
$fake = Bus::fake();
$user = factory(User::class)->create();
$addressBook = AddressBook::factory()->create([
'account_id' => $user->account_id,
'user_id' => $user->id,
]);
$contact = new Contact();
$contact->forceFill([
'first_name' => 'Test',
'uuid' => 'affacde9-b2fe-4371-9acb-6612aaee6971',
'updated_at' => now(),
]);
$card = $this->getCard($contact);
$etag = $this->getEtag($contact, true);
$pendingBatch = $fake->batch([
$job = new UpdateVCard($user, $addressBook->name, new ContactUpdateDto('https://test/dav/uricontact1', $etag, $card)),
]);
$batch = $pendingBatch->dispatch();
$fake->assertBatched(function (PendingBatch $pendingBatch) {
$this->assertCount(1, $pendingBatch->jobs);
$this->assertInstanceOf(UpdateVCard::class, $pendingBatch->jobs->first());
return true;
});
$batch = app(DatabaseBatchRepository::class)->store($pendingBatch);
$job->withBatchId($batch->id)->handle();
$this->assertDatabaseHas('contacts', [
'first_name' => 'Test',
'uuid' => 'affacde9-b2fe-4371-9acb-6612aaee6971',
]);
}
}
@@ -4,15 +4,16 @@ namespace Tests\Unit\Services\DavClient\Utils;
use Tests\TestCase;
use Mockery\MockInterface;
use App\Jobs\Dav\PushVCard;
use Tests\Api\DAV\CardEtag;
use Tests\Helpers\DavTester;
use GuzzleHttp\Psr7\Response;
use App\Models\User\SyncToken;
use App\Models\Contact\Contact;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
use App\Services\DavClient\Utils\Model\ContactPushDto;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Http\Controllers\DAV\Backend\CardDAV\CardDAVBackend;
use App\Services\DavClient\Utils\AddressBookContactsPushMissed;
@@ -48,7 +49,7 @@ class AddressBookContactsPushMissedTest extends TestCase
$mock->shouldReceive('getUuid')
->once()
->withArgs(function ($uri) {
$this->assertEquals('https://test/dav/uuid6', $uri);
$this->assertEquals('uuid6', $uri);
return true;
})
@@ -62,22 +63,25 @@ class AddressBookContactsPushMissedTest extends TestCase
})
->andReturn([
'carddata' => $card,
'uri' => 'https://test/dav/uuid3',
'uri' => 'uuid3',
'etag' => $etag,
]);
});
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addResponse('https://test/dav/uuid3', new Response(200, ['Etag' => $etag]), $card, 'PUT', ['If-Match' => '*']);
$tester = new DavTester();
$client = app(DavClient::class)->init([], $tester->getClient());
$client = new DavClient([], $tester->getClient());
(new AddressBookContactsPushMissed())
$batchs = (new AddressBookContactsPushMissed())
->execute(new SyncDto($subscription, $client, $backend), [], collect([
'https://test/dav/uuid6' => new ContactDto('https://test/dav/uuid6', $etag),
]), collect([$contact]))
->wait();
'uuid6' => new ContactDto('uuid6', $etag),
]), collect([$contact]));
$tester->assert();
$this->assertCount(1, $batchs);
$batch = $batchs->first();
$this->assertInstanceOf(PushVCard::class, $batch);
$dto = $this->getPrivateValue($batch, 'contact');
$this->assertInstanceOf(ContactPushDto::class, $dto);
$this->assertEquals('uuid3', $dto->uri);
$this->assertEquals(2, $dto->mode);
}
}
@@ -4,16 +4,17 @@ namespace Tests\Unit\Services\DavClient\Utils;
use Tests\TestCase;
use Mockery\MockInterface;
use App\Jobs\Dav\PushVCard;
use Illuminate\Support\Str;
use Tests\Api\DAV\CardEtag;
use Tests\Helpers\DavTester;
use GuzzleHttp\Psr7\Response;
use App\Models\User\SyncToken;
use App\Models\Contact\Contact;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use App\Services\DavClient\Utils\Model\SyncDto;
use App\Services\DavClient\Utils\Model\ContactDto;
use App\Services\DavClient\Utils\Model\ContactPushDto;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Services\DavClient\Utils\AddressBookContactsPush;
use App\Http\Controllers\DAV\Backend\CardDAV\CardDAVBackend;
@@ -48,7 +49,7 @@ class AddressBookContactsPushTest extends TestCase
$backend = $this->mock(CardDAVBackend::class, function (MockInterface $mock) use ($card, $etag) {
$mock->shouldReceive('getCard')
->withArgs(function ($name, $uri) {
$this->assertEquals($uri, 'https://test/dav/uricontact2');
$this->assertEquals($uri, 'uricontact2');
return true;
})
@@ -65,20 +66,23 @@ class AddressBookContactsPushTest extends TestCase
->andReturn('uricontact1');
});
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addResponse('https://test/dav/uricontact2', new Response(200, ['Etag' => $etag]), $card, 'PUT');
$tester = new DavTester();
$client = app(DavClient::class)->init([], $tester->getClient());
$client = new DavClient([], $tester->getClient());
(new AddressBookContactsPush())
$batchs = (new AddressBookContactsPush())
->execute(new SyncDto($subscription, $client, $backend), collect([
'https://test/dav/uricontact1' => new ContactDto('https://test/dav/uricontact1', $etag),
]), [
'added' => ['https://test/dav/uricontact2'],
])
->wait();
'added' => ['uricontact2'],
]);
$tester->assert();
$this->assertCount(1, $batchs);
$batch = $batchs->first();
$this->assertInstanceOf(PushVCard::class, $batch);
$dto = $this->getPrivateValue($batch, 'contact');
$this->assertInstanceOf(ContactPushDto::class, $dto);
$this->assertEquals('uricontact2', $dto->uri);
$this->assertEquals(0, $dto->mode);
}
/** @test */
@@ -106,7 +110,7 @@ class AddressBookContactsPushTest extends TestCase
$backend = $this->mock(CardDAVBackend::class, function (MockInterface $mock) use ($card, $etag) {
$mock->shouldReceive('getUuid')
->withArgs(function ($uri) {
$this->assertStringStartsWith('https://test/dav/uricontact', $uri);
$this->assertStringContainsString('uricontact', $uri);
return true;
})
@@ -115,7 +119,7 @@ class AddressBookContactsPushTest extends TestCase
});
$mock->shouldReceive('getCard')
->withArgs(function ($name, $uri) {
$this->assertEquals($uri, 'https://test/dav/uricontact2');
$this->assertEquals($uri, 'uricontact2');
return true;
})
@@ -125,19 +129,22 @@ class AddressBookContactsPushTest extends TestCase
]);
});
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addResponse('https://test/dav/uricontact2', new Response(200, ['Etag' => $etag]), $card, 'PUT', ['If-Match' => $etag]);
$tester = new DavTester();
$client = app(DavClient::class)->init([], $tester->getClient());
$client = new DavClient([], $tester->getClient());
(new AddressBookContactsPush())
$batchs = (new AddressBookContactsPush())
->execute(new SyncDto($subscription, $client, $backend), collect([
'https://test/dav/uricontact1' => new ContactDto('https://test/dav/uricontact1', $etag),
]), [
'modified' => ['https://test/dav/uricontact2'],
])
->wait();
'modified' => ['uricontact2'],
]);
$tester->assert();
$this->assertCount(1, $batchs);
$batch = $batchs->first();
$this->assertInstanceOf(PushVCard::class, $batch);
$dto = $this->getPrivateValue($batch, 'contact');
$this->assertInstanceOf(ContactPushDto::class, $dto);
$this->assertEquals('uricontact2', $dto->uri);
$this->assertEquals(1, $dto->mode);
}
}
@@ -8,6 +8,7 @@ use Tests\Api\DAV\CardEtag;
use Tests\Helpers\DavTester;
use App\Models\User\SyncToken;
use App\Models\Contact\Contact;
use App\Jobs\Dav\GetMultipleVCard;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use App\Services\DavClient\Utils\Model\SyncDto;
@@ -61,21 +62,22 @@ class AddressBookContactsUpdaterMissedTest extends TestCase
->andReturn($etag);
});
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addressMultiGet($etag, $card, 'https://test/dav/uuid2');
$tester = new DavTester();
$client = app(DavClient::class)->init([], $tester->getClient());
$client = new DavClient([], $tester->getClient());
(new AddressBookContactsUpdaterMissed())
$batchs = (new AddressBookContactsUpdaterMissed())
->execute(new SyncDto($subscription, $client, $backend), collect([
[
'uuid' => 'uuid1',
],
]), collect([
'https://test/dav/uuid2' => new ContactDto('https://test/dav/uuid2', $etag),
]))
->wait();
]));
$tester->assert();
$this->assertCount(1, $batchs);
$batch = $batchs->first();
$this->assertInstanceOf(GetMultipleVCard::class, $batch);
$hrefs = $this->getPrivateValue($batch, 'hrefs');
$this->assertEquals(['https://test/dav/uuid2'], $hrefs);
}
}
@@ -3,12 +3,13 @@
namespace Tests\Unit\Services\DavClient\Utils;
use Tests\TestCase;
use App\Jobs\Dav\GetVCard;
use Mockery\MockInterface;
use Tests\Api\DAV\CardEtag;
use Tests\Helpers\DavTester;
use GuzzleHttp\Psr7\Response;
use App\Models\User\SyncToken;
use App\Models\Contact\Contact;
use App\Jobs\Dav\GetMultipleVCard;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use App\Services\DavClient\Utils\Model\SyncDto;
@@ -55,18 +56,19 @@ class AddressBookContactsUpdaterTest extends TestCase
->andReturn($etag);
});
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addressMultiGet($etag, $card, 'https://test/dav/uuid2');
$tester = new DavTester();
$client = app(DavClient::class)->init([], $tester->getClient());
$client = new DavClient([], $tester->getClient());
(new AddressBookContactsUpdater())
$batchs = (new AddressBookContactsUpdater())
->execute(new SyncDto($subscription, $client, $backend), collect([
'https://test/dav/uuid2' => new ContactDto('https://test/dav/uuid2', $etag),
]))
->wait();
]));
$tester->assert();
$this->assertCount(1, $batchs);
$batch = $batchs->first();
$this->assertInstanceOf(GetMultipleVCard::class, $batch);
$hrefs = $this->getPrivateValue($batch, 'hrefs');
$this->assertEquals(['https://test/dav/uuid2'], $hrefs);
}
/** @test */
@@ -121,17 +123,19 @@ class AddressBookContactsUpdaterTest extends TestCase
->andReturn($etag);
});
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addResponse('https://test/dav/uuid2', new Response(200, [], $card), null, 'GET');
$tester = new DavTester();
$client = app(DavClient::class)->init([], $tester->getClient());
$client = new DavClient([], $tester->getClient());
(new AddressBookContactsUpdater())
$batchs = (new AddressBookContactsUpdater())
->execute(new SyncDto($subscription, $client, $backend), collect([
'https://test/dav/uuid2' => new ContactDto('https://test/dav/uuid2', $etag),
]))
->wait();
]));
$tester->assert();
$this->assertCount(1, $batchs);
$batch = $batchs->first();
$this->assertInstanceOf(GetVCard::class, $batch);
$dto = $this->getPrivateValue($batch, 'contact');
$this->assertInstanceOf(ContactDto::class, $dto);
$this->assertEquals('https://test/dav/uuid2', $dto->uri);
}
}
@@ -21,7 +21,7 @@ class AddressBookGetterTest extends TestCase
->addressBookBaseUri()
->capabilities()
->displayName();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = (new AddressBookGetter())
->execute($client);
@@ -47,7 +47,7 @@ class AddressBookGetterTest extends TestCase
$tester = (new DavTester())
->serviceUrl()
->optionsFail();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$this->expectException(DavServerNotCompliantException::class);
(new AddressBookGetter())
@@ -62,7 +62,7 @@ class AddressBookGetterTest extends TestCase
->optionsOk()
->userPrincipalEmpty();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$this->expectException(DavServerNotCompliantException::class);
(new AddressBookGetter())
@@ -78,7 +78,7 @@ class AddressBookGetterTest extends TestCase
->userPrincipal()
->addressbookEmpty();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$this->expectException(DavServerNotCompliantException::class);
(new AddressBookGetter())
@@ -94,7 +94,7 @@ class AddressBookGetterTest extends TestCase
->userPrincipal()
->addressbookHome()
->resourceTypeHomeOnly();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$this->expectException(DavClientException::class);
(new AddressBookGetter())
@@ -9,7 +9,9 @@ use Tests\Helpers\DavTester;
use GuzzleHttp\Psr7\Response;
use App\Models\User\SyncToken;
use App\Models\Contact\Contact;
use GuzzleHttp\Promise\Promise;
use Illuminate\Bus\PendingBatch;
use App\Jobs\Dav\GetMultipleVCard;
use Illuminate\Support\Facades\Bus;
use App\Models\Account\AddressBookSubscription;
use App\Services\DavClient\Utils\Dav\DavClient;
use App\Services\DavClient\Utils\Model\SyncDto;
@@ -28,6 +30,8 @@ class AddressBookSynchronizerTest extends TestCase
/** @test */
public function it_sync_empty_changes()
{
Bus::fake();
$this->mock(AddressBookContactsUpdater::class, function (MockInterface $mock) {
$mock->shouldReceive('execute')
->once()
@@ -39,7 +43,7 @@ class AddressBookSynchronizerTest extends TestCase
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'))
->getSynctoken($subscription->syncToken);
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
(new AddressBookSynchronizer())
->execute(new SyncDto($subscription, $client, $backend));
@@ -50,6 +54,8 @@ class AddressBookSynchronizerTest extends TestCase
/** @test */
public function it_sync_no_changes()
{
Bus::fake();
$this->mock(AddressBookContactsUpdater::class, function (MockInterface $mock) {
$mock->shouldReceive('execute')
->once()
@@ -63,7 +69,7 @@ class AddressBookSynchronizerTest extends TestCase
$tester->getSynctoken('"test21"')
->getSyncCollection('test20');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
(new AddressBookSynchronizer())
->execute(new SyncDto($subscription, $client, $backend));
@@ -74,6 +80,8 @@ class AddressBookSynchronizerTest extends TestCase
/** @test */
public function it_sync_changes_added_local_contact()
{
Bus::fake();
$subscription = $this->getSubscription();
$backend = new CardDAVBackend($subscription->user);
@@ -87,7 +95,7 @@ class AddressBookSynchronizerTest extends TestCase
$tester->getSynctoken('"token"')
->getSyncCollection('token', '"test2"');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$sync = new SyncDto($subscription, $client, $backend);
$this->mock(AddressBookContactsUpdater::class, function (MockInterface $mock) use ($sync) {
@@ -100,9 +108,7 @@ class AddressBookSynchronizerTest extends TestCase
return true;
})
->andReturn(new Promise(function () {
return true;
}));
->andReturn(collect());
});
(new AddressBookSynchronizer())
@@ -111,9 +117,48 @@ class AddressBookSynchronizerTest extends TestCase
$tester->assert();
}
/** @test */
public function it_sync_changes_added_local_contact_batched()
{
Bus::fake();
$subscription = $this->getSubscription();
$backend = new CardDAVBackend($subscription->user);
$contact = factory(Contact::class)->create([
'account_id' => $subscription->account_id,
'address_book_id' => $subscription->address_book_id,
'uuid' => 'd403af1c-8492-4e9b-9833-cf18c795dfa9',
]);
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->getSynctoken('"token"')
->getSyncCollection('token', '"test2"');
$client = app(DavClient::class)->init([], $tester->getClient());
$sync = new SyncDto($subscription, $client, $backend);
(new AddressBookSynchronizer())
->execute($sync);
$tester->assert();
Bus::assertBatched(function (PendingBatch $batch) {
$this->assertCount(1, $batch->jobs);
$job = $batch->jobs[0];
$this->assertInstanceOf(GetMultipleVCard::class, $job);
$this->assertEquals(['https://test/dav/addressbooks/user@test.com/contacts/uuid'], $this->getPrivateValue($job, 'hrefs'));
return true;
});
}
/** @test */
public function it_forcesync_changes_added_local_contact()
{
Bus::fake();
$subscription = $this->getSubscription();
$backend = new CardDAVBackend($subscription->user);
@@ -142,7 +187,7 @@ class AddressBookSynchronizerTest extends TestCase
'</d:prop>'.
"</card:addressbook-query>\n", 'REPORT');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$sync = new SyncDto($subscription, $client, $backend);
$this->mock(AddressBookContactsUpdaterMissed::class, function (MockInterface $mock) use ($sync, $contact, $etag) {
@@ -156,16 +201,12 @@ class AddressBookSynchronizerTest extends TestCase
return true;
})
->andReturn(new Promise(function () {
return true;
}));
->andReturn(collect());
});
$this->mock(AddressBookContactsPushMissed::class, function (MockInterface $mock) {
$mock->shouldReceive('execute')
->once()
->andReturn(new Promise(function () {
return true;
}));
->andReturn(collect());
});
(new AddressBookSynchronizer())
@@ -174,6 +215,58 @@ class AddressBookSynchronizerTest extends TestCase
$tester->assert();
}
/** @test */
public function it_forcesync_changes_added_local_contact_batched()
{
Bus::fake();
$subscription = $this->getSubscription();
$backend = new CardDAVBackend($subscription->user);
$contact = factory(Contact::class)->create([
'account_id' => $subscription->account_id,
'address_book_id' => $subscription->address_book_id,
'uuid' => 'd403af1c-8492-4e9b-9833-cf18c795dfa9',
]);
$etag = $this->getEtag($contact, true);
$tester = (new DavTester('https://test/dav/addressbooks/user@test.com/contacts/'));
$tester->addResponse('https://test/dav/addressbooks/user@test.com/contacts/', new Response(200, [], $tester->multistatusHeader().
'<d:response>'.
'<d:href>https://test/dav/uuid1</d:href>'.
'<d:propstat>'.
'<d:prop>'.
"<d:getetag>$etag</d:getetag>".
'</d:prop>'.
'<d:status>HTTP/1.1 200 OK</d:status>'.
'</d:propstat>'.
'</d:response>'.
'</d:multistatus>'), '<?xml version="1.0" encoding="UTF-8"?>'."\n".
'<card:addressbook-query xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:d="DAV:">'.
'<d:prop>'.
'<d:getetag/>'.
'</d:prop>'.
"</card:addressbook-query>\n", 'REPORT');
$client = app(DavClient::class)->init([], $tester->getClient());
$sync = new SyncDto($subscription, $client, $backend);
(new AddressBookSynchronizer())
->execute($sync, true);
$tester->assert();
Bus::assertBatched(function (PendingBatch $batch) {
$this->assertCount(1, $batch->jobs);
$job = $batch->jobs[0];
$this->assertInstanceOf(GetMultipleVCard::class, $job);
$this->assertEquals(['https://test/dav/uuid1'], $this->getPrivateValue($job, 'hrefs'));
return true;
});
}
private function getSubscription()
{
$subscription = AddressBookSubscription::factory()->create();
@@ -5,6 +5,8 @@ namespace Tests\Unit\Services\DavClient\Utils\Dav;
use Tests\TestCase;
use Tests\Helpers\DavTester;
use GuzzleHttp\Psr7\Response;
use Illuminate\Http\Client\Request;
use Illuminate\Support\Facades\Http;
use GuzzleHttp\Exception\ServerException;
use App\Services\DavClient\Utils\Dav\DavClient;
use Illuminate\Foundation\Testing\DatabaseTransactions;
@@ -14,29 +16,17 @@ class DavClientTest extends TestCase
{
use DatabaseTransactions;
/** @test */
public function it_creates_client()
{
$client = new DavClient([
'base_uri' => 'test',
'username' => 'user',
'password' => 'pass',
]);
$this->assertInstanceOf(\Sabre\DAV\Xml\Service::class, $client->xml);
}
/** @test */
public function it_fails_if_no_baseuri()
{
$this->expectException(\InvalidArgumentException::class);
new DavClient([]);
app(DavClient::class)->init([]);
}
/** @test */
public function it_accept_guzzle_client()
{
$client = new DavClient([], new \GuzzleHttp\Client());
$client = app(DavClient::class)->init([], new \GuzzleHttp\Client());
$this->assertInstanceOf(DavClient::class, $client);
}
@@ -47,7 +37,7 @@ class DavClientTest extends TestCase
->addResponse('https://test', new Response(200, []), null, 'OPTIONS')
->addResponse('https://test', new Response(200, ['Dav' => 'test']), null, 'OPTIONS')
->addResponse('https://test', new Response(200, ['Dav' => ' test ']), null, 'OPTIONS');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->options();
$this->assertEquals([], $result);
@@ -66,7 +56,7 @@ class DavClientTest extends TestCase
{
$tester = (new DavTester())
->serviceUrl();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->getServiceUrl();
@@ -80,7 +70,7 @@ class DavClientTest extends TestCase
$tester = (new DavTester())
->addResponse('https://test/.well-known/carddav', new Response(200), null, 'GET')
->nonStandardServiceUrl();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->getServiceUrl();
@@ -94,7 +84,7 @@ class DavClientTest extends TestCase
$tester = (new DavTester())
->addResponse('https://test/.well-known/carddav', new Response(404), null, 'GET')
->nonStandardServiceUrl();
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->getServiceUrl();
@@ -107,7 +97,7 @@ class DavClientTest extends TestCase
{
$tester = (new DavTester())
->addResponse('https://test/.well-known/carddav', new Response(500), null, 'GET');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$this->expectException(ServerException::class);
$client->getServiceUrl();
@@ -117,7 +107,7 @@ class DavClientTest extends TestCase
public function it_get_base_uri()
{
$tester = (new DavTester());
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->getBaseUri();
@@ -132,7 +122,7 @@ class DavClientTest extends TestCase
public function it_set_base_uri()
{
$tester = (new DavTester());
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->setBaseUri('https://new')
->getBaseUri();
@@ -162,7 +152,7 @@ class DavClientTest extends TestCase
'</d:prop>'.
"</d:propfind>\n", 'PROPFIND');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->propFind('https://test/test', ['{DAV:}test']);
@@ -194,7 +184,7 @@ class DavClientTest extends TestCase
'</d:prop>'.
"</d:propfind>\n", 'PROPFIND');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->getProperty('{DAV:}test', 'https://test/test');
@@ -231,7 +221,7 @@ class DavClientTest extends TestCase
'</d:prop>'.
"</d:propfind>\n", 'PROPFIND');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->getSupportedReportSet();
@@ -265,7 +255,7 @@ class DavClientTest extends TestCase
'</d:prop>'.
"</d:sync-collection>\n", 'REPORT');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->syncCollectionAsync('https://test/test', ['{DAV:}test'], '')
->wait();
@@ -308,7 +298,7 @@ class DavClientTest extends TestCase
'</d:prop>'.
"</d:sync-collection>\n", 'REPORT');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->syncCollectionAsync('https://test/test', ['{DAV:}test'], '"00000-abcd0"')
->wait();
@@ -328,9 +318,8 @@ class DavClientTest extends TestCase
/** @test */
public function it_run_addressbook_multiget_report()
{
$tester = (new DavTester());
$tester->addResponse('https://test/test', new Response(200, [], $tester->multistatusHeader().
Http::fake([
'https://test/*' => Http::response(DavTester::multistatusHeader().
'<d:response>'.
'<d:href>href</d:href>'.
'<d:propstat>'.
@@ -341,22 +330,27 @@ class DavClientTest extends TestCase
'<d:status>HTTP/1.1 200 OK</d:status>'.
'</d:propstat>'.
'</d:response>'.
'</d:multistatus>'), '<?xml version="1.0" encoding="UTF-8"?>'."\n".
'<card:addressbook-multiget xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:d="DAV:">'.
'<d:prop>'.
'<d:test/>'.
'</d:prop>'.
'<d:href>https://test/contacts/1</d:href>'.
"</card:addressbook-multiget>\n", 'REPORT');
'</d:multistatus>'),
]);
$client = new DavClient([], $tester->getClient());
$result = $client->addressbookMultigetAsync('https://test/test', ['{DAV:}test'], [
$result = app(DavClient::class)->addressbookMultiget(Http::baseUrl('https://test/test'), ['{DAV:}test'], [
'https://test/contacts/1',
])
->wait();
]);
Http::assertSent(function (Request $request) {
$this->assertEquals('https://test/test/', $request->url());
$this->assertEquals('REPORT', $request->method());
$this->assertEquals('<?xml version="1.0" encoding="UTF-8"?>'."\n".
'<card:addressbook-multiget xmlns:card="urn:ietf:params:xml:ns:carddav" xmlns:d="DAV:">'.
'<d:prop>'.
'<d:test/>'.
'</d:prop>'.
'<d:href>https://test/contacts/1</d:href>'.
"</card:addressbook-multiget>\n", $request->body());
return true;
});
$tester->assert();
$this->assertEquals([
'href' => [
200 => [
@@ -390,7 +384,7 @@ class DavClientTest extends TestCase
'</d:prop>'.
"</card:addressbook-query>\n", 'REPORT');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->addressbookQueryAsync('https://test/test', ['{DAV:}test'])
->wait();
@@ -430,7 +424,7 @@ class DavClientTest extends TestCase
' </d:set>'."\n".
"</d:propertyupdate>\n", 'PROPPATCH');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$result = $client->propPatchAsync('https://test/test', ['{DAV:}test' => 'value'])
->wait();
@@ -470,11 +464,11 @@ class DavClientTest extends TestCase
' </d:set>'."\n".
"</d:propertyupdate>\n", 'PROPPATCH');
$client = new DavClient([], $tester->getClient());
$client = app(DavClient::class)->init([], $tester->getClient());
$this->expectException(DavClientException::class);
$this->expectExceptionMessage('PROPPATCH failed. The following properties errored: {DAV:}test (405), {DAV:}excerpt (500)');
$result = $client->propPatchAsync('https://test/test', [
$client->propPatchAsync('https://test/test', [
'{DAV:}test' => 'value',
'{DAV:}excerpt' => 'value',
])
@@ -0,0 +1,31 @@
<?php
namespace Tests\Unit\Services\DavClient\Utils\Model;
use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class ContactUpdateDtoTest extends TestCase
{
use DatabaseTransactions;
/** @test */
public function it_create_dto_string()
{
$dto = new ContactUpdateDto('uri', 'etag', 'card');
$this->assertEquals('uri', $dto->uri);
$this->assertEquals('etag', $dto->etag);
$this->assertEquals('card', $dto->card);
}
/** @test */
public function it_create_dto_resource()
{
$resource = fopen(__DIR__.'/stub.vcf', 'r');
$dto = new ContactUpdateDto('uri', 'etag', $resource);
$this->assertEquals('uri', $dto->uri);
$this->assertEquals('etag', $dto->etag);
$this->assertEquals('card', $dto->card);
}
}
@@ -0,0 +1 @@
card