generated from Nodarx/template
feat: add CachedService with cached collections and delta
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXT\ProviderImap\Tests\Unit;
|
||||
|
||||
use KTXC\Db\DataStore;
|
||||
use KTXF\Mail\Collection\CollectionRoles;
|
||||
use KTXM\ProviderImap\Client\Mailbox;
|
||||
use KTXM\ProviderImap\Providers\CachedService;
|
||||
use KTXM\ProviderImap\Providers\MessageProperties;
|
||||
use KTXM\ProviderImap\Service\Cache\HarmonizationService;
|
||||
use KTXM\ProviderImap\Service\Cache\MessageDeltaService;
|
||||
use KTXM\ProviderImap\Service\Cache\MessageIngestor;
|
||||
use KTXM\ProviderImap\Service\Live\LiveMailService;
|
||||
use KTXF\Resource\Identifier\EntityIdentifier;
|
||||
use KTXT\ProviderImap\Tests\Support\FakeLiveMailService;
|
||||
use KTXT\ProviderImap\Tests\Support\FakeMailboxStore;
|
||||
use KTXT\ProviderImap\Tests\Support\FakeMessageFileStore;
|
||||
use KTXT\ProviderImap\Tests\Support\FakeMessageStore;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class CachedServiceTest extends TestCase
|
||||
{
|
||||
private FakeMailboxStore $mailboxes;
|
||||
private FakeLiveMailService $live;
|
||||
private CachedService $service;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$dataStore = $this->createStub(DataStore::class);
|
||||
$this->mailboxes = new FakeMailboxStore($dataStore);
|
||||
$messages = new FakeMessageStore($dataStore);
|
||||
$files = new FakeMessageFileStore('/nonexistent');
|
||||
|
||||
$harmonizer = new HarmonizationService(
|
||||
$this->mailboxes,
|
||||
$messages,
|
||||
$files,
|
||||
new MessageIngestor($files, $messages, $this->mailboxes),
|
||||
);
|
||||
|
||||
$this->service = new CachedServiceTestDouble(
|
||||
$this->mailboxes,
|
||||
$harmonizer,
|
||||
new MessageDeltaService($this->mailboxes, $messages),
|
||||
);
|
||||
$this->service->fromStore(['tid' => 'tenant', 'sid' => 'svc']);
|
||||
|
||||
$this->live = new FakeLiveMailService($this->service);
|
||||
$this->live->uidValidity = 7;
|
||||
$this->live->uidNext = 10;
|
||||
$this->live->mailboxList = [new Mailbox('INBOX', '/', []), new Mailbox('Sent', '/', ['\\Sent'])];
|
||||
$this->service->liveMail = $this->live;
|
||||
}
|
||||
|
||||
public function testCollectionListComesFromTheCacheAfterHarmonizingTheList(): void
|
||||
{
|
||||
$list = $this->service->collectionList(null);
|
||||
|
||||
$this->assertSame(['INBOX', 'Sent'], array_keys($list));
|
||||
$this->assertSame(CollectionRoles::Sent, $list['Sent']->getProperties()->getRole());
|
||||
$this->assertCount(1, $this->listCalls());
|
||||
}
|
||||
|
||||
public function testFreshListIsNotHarmonizedAgain(): void
|
||||
{
|
||||
$this->service->collectionList(null);
|
||||
$this->service->collectionList(null);
|
||||
$this->service->collectionFetch('INBOX');
|
||||
|
||||
$this->assertCount(1, $this->listCalls());
|
||||
}
|
||||
|
||||
public function testStaleListIsHarmonizedAgain(): void
|
||||
{
|
||||
$this->mailboxes->clock = time() - CachedService::FRESHNESS_WINDOW - 1;
|
||||
$this->service->collectionList(null);
|
||||
$this->mailboxes->clock = null;
|
||||
|
||||
$this->live->mailboxList[] = new Mailbox('Archive', '/', []);
|
||||
$list = $this->service->collectionList(null);
|
||||
|
||||
$this->assertSame(['INBOX', 'Sent', 'Archive'], array_keys($list));
|
||||
$this->assertCount(2, $this->listCalls());
|
||||
}
|
||||
|
||||
public function testFilteredListGoesToTheServer(): void
|
||||
{
|
||||
$filter = $this->service->collectionListFilter();
|
||||
$filter->condition('role', CollectionRoles::Sent->value);
|
||||
|
||||
$this->service->collectionList(null, $filter);
|
||||
|
||||
$this->assertSame([['collectionList', [null, $filter]]], $this->live->calls);
|
||||
}
|
||||
|
||||
public function testCollectionSignatureIsTheDeltaSignature(): void
|
||||
{
|
||||
$this->live->flags = [1 => [], 2 => []];
|
||||
$this->service->entityDelta('INBOX', '');
|
||||
|
||||
$this->assertSame('7:2', $this->service->collectionFetch('INBOX')?->signature());
|
||||
}
|
||||
|
||||
public function testCollectionExtantUsesTheCache(): void
|
||||
{
|
||||
$this->assertSame(['INBOX' => true, 'Missing' => false], $this->service->collectionExtant('INBOX', 'Missing'));
|
||||
}
|
||||
|
||||
public function testDeltaHarmonizesWhenStaleOnly(): void
|
||||
{
|
||||
$this->live->flags = [1 => []];
|
||||
$first = $this->service->entityDelta('INBOX', '');
|
||||
|
||||
$this->live->flags = [1 => [], 2 => []];
|
||||
$second = $this->service->entityDelta('INBOX', $first->signature);
|
||||
|
||||
$this->assertSame('7:1', $first->signature);
|
||||
$this->assertSame('7:1', $second->signature, 'fresh mailbox is not harmonized again');
|
||||
|
||||
$this->mailboxes->updateState('svc', 'INBOX', ['harmonizedAt' => time() - CachedService::FRESHNESS_WINDOW]);
|
||||
$third = $this->service->entityDelta('INBOX', $first->signature);
|
||||
|
||||
$this->assertSame('7:2', $third->signature);
|
||||
$this->assertSame(['2'], json_decode(json_encode($third->additions), true));
|
||||
}
|
||||
|
||||
public function testDeltaIsAnsweredFromTheCacheWhenTheServerFails(): void
|
||||
{
|
||||
$this->live->flags = [1 => []];
|
||||
$signature = $this->service->entityDelta('INBOX', '')->signature;
|
||||
|
||||
$this->mailboxes->updateState('svc', 'INBOX', ['harmonizedAt' => 0]);
|
||||
$this->live->uidValidity = null; // harmonization throws
|
||||
|
||||
$this->assertSame($signature, $this->service->entityDelta('INBOX', $signature)->signature);
|
||||
}
|
||||
|
||||
public function testWritesGoThroughTheLiveService(): void
|
||||
{
|
||||
$properties = new MessageProperties([]);
|
||||
$properties->setFlag('seen', true);
|
||||
|
||||
$this->service->entityPatch($properties, new EntityIdentifier('imap', 'svc', 'INBOX', '5'));
|
||||
|
||||
$patches = array_values(array_filter($this->live->calls, static fn (array $call): bool => $call[0] === 'entityPatch'));
|
||||
$this->assertCount(1, $patches);
|
||||
[$collection, $added, , $uids] = $patches[0][1];
|
||||
$this->assertSame('INBOX', $collection);
|
||||
$this->assertContains('seen', $added);
|
||||
$this->assertSame([5], $uids);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array>
|
||||
*/
|
||||
private function listCalls(): array
|
||||
{
|
||||
return array_values(array_filter($this->live->calls, static fn (array $call): bool => $call[0] === 'collectionList'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CachedService with a replaceable IMAP side.
|
||||
*/
|
||||
final class CachedServiceTestDouble extends CachedService
|
||||
{
|
||||
public ?LiveMailService $liveMail = null;
|
||||
|
||||
protected function liveMail(): LiveMailService
|
||||
{
|
||||
return $this->liveMail;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user