generated from Nodarx/template
feat: harmonize mailbox list and track harmonization state
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace KTXT\ProviderImap\Tests\Unit;
|
||||
|
||||
use Generator;
|
||||
use KTXC\Db\Collection;
|
||||
use KTXC\Db\DataStore;
|
||||
use KTXF\Resource\Filter\IFilter;
|
||||
use KTXF\Resource\Sort\ISort;
|
||||
use KTXM\ProviderImap\Client\Mailbox;
|
||||
use KTXM\ProviderImap\Providers\CollectionResource;
|
||||
use KTXM\ProviderImap\Providers\Service;
|
||||
use KTXM\ProviderImap\Service\Cache\HarmonizationService;
|
||||
use KTXM\ProviderImap\Service\Live\LiveMailService;
|
||||
use KTXM\ProviderImap\Stores\MailboxStore;
|
||||
use KTXM\ProviderImap\Stores\MessageFileStore;
|
||||
use KTXM\ProviderImap\Stores\MessageStore;
|
||||
use LogicException;
|
||||
use MongoDB\UpdateResult;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class HarmonizationServiceTest extends TestCase
|
||||
{
|
||||
private array $calls = [];
|
||||
|
||||
public function testMailboxesAreAddedUpdatedAndRemoved(): void
|
||||
{
|
||||
$service = $this->harmonizer(
|
||||
cached: ['INBOX' => [], 'Old' => []],
|
||||
remote: ['INBOX', 'Reports'],
|
||||
);
|
||||
|
||||
$result = $service->harmonizeMailboxes();
|
||||
|
||||
$this->assertSame(['added' => ['Reports'], 'updated' => ['INBOX'], 'removed' => ['Old']], $result);
|
||||
$this->assertSame([
|
||||
['upsert', 'tenant', 'svc', 'INBOX'],
|
||||
['upsert', 'tenant', 'svc', 'Reports'],
|
||||
['messages.deleteByMailbox', 'svc', 'Old'],
|
||||
['files.deleteByMailbox', 'tenant', 'svc', 'Old'],
|
||||
['mailboxes.delete', 'svc', 'Old'],
|
||||
], $this->calls);
|
||||
}
|
||||
|
||||
public function testNothingIsPurgedWhenTheServerListHasNoInbox(): void
|
||||
{
|
||||
$service = $this->harmonizer(
|
||||
cached: ['INBOX' => [], 'Reports' => []],
|
||||
remote: [],
|
||||
);
|
||||
|
||||
$result = $service->harmonizeMailboxes();
|
||||
|
||||
$this->assertSame(['added' => [], 'updated' => [], 'removed' => []], $result);
|
||||
$this->assertSame([], $this->calls);
|
||||
}
|
||||
|
||||
public function testUnboundServiceIsRejected(): void
|
||||
{
|
||||
$unbound = new HarmonizationService(
|
||||
$this->createStub(MailboxStore::class),
|
||||
$this->createStub(MessageStore::class),
|
||||
$this->createStub(MessageFileStore::class),
|
||||
);
|
||||
|
||||
$this->expectException(LogicException::class);
|
||||
$unbound->harmonizeMailboxes();
|
||||
}
|
||||
|
||||
public function testLockIsTakenOnlyWhenFreeExpiredOrOwned(): void
|
||||
{
|
||||
$collection = $this->createMock(Collection::class);
|
||||
$collection->expects($this->exactly(2))
|
||||
->method('updateOne')
|
||||
->willReturnCallback(function (array $filter, array $update) use (&$matched): UpdateResult {
|
||||
$this->assertSame(['lock' => null], $filter['$or'][0]);
|
||||
$this->assertArrayHasKey('$lte', $filter['$or'][1]['lock.expiresAt']);
|
||||
$this->assertSame(['lock.owner' => 'run-1'], $filter['$or'][2]);
|
||||
$this->assertSame('run-1', $update['$set']['lock']['owner']);
|
||||
$result = $this->createStub(UpdateResult::class);
|
||||
$result->method('getMatchedCount')->willReturn(array_shift($matched));
|
||||
return $result;
|
||||
});
|
||||
$matched = [1, 0];
|
||||
|
||||
$store = new MailboxStore($this->dataStore($collection));
|
||||
|
||||
$this->assertTrue($store->acquireLock('svc', 'INBOX', 'run-1', 300));
|
||||
$this->assertFalse($store->acquireLock('svc', 'INBOX', 'run-1', 300));
|
||||
}
|
||||
|
||||
public function testStateFillsDefaultsAndIgnoresOtherFields(): void
|
||||
{
|
||||
$collection = $this->createStub(Collection::class);
|
||||
$collection->method('findOne')->willReturn([
|
||||
'sid' => 'svc', 'name' => 'INBOX', 'uidValidity' => 7, 'lock' => ['owner' => 'x'],
|
||||
]);
|
||||
|
||||
$state = (new MailboxStore($this->dataStore($collection)))->state('svc', 'INBOX');
|
||||
|
||||
$this->assertSame([...MailboxStore::STATE_DEFAULTS, 'uidValidity' => 7], $state);
|
||||
}
|
||||
|
||||
public function testNewMailboxStartsWithDefaultState(): void
|
||||
{
|
||||
$collection = $this->createMock(Collection::class);
|
||||
$collection->expects($this->once())
|
||||
->method('updateOne')
|
||||
->with(
|
||||
$this->anything(),
|
||||
$this->callback(fn (array $update): bool => $update['$setOnInsert'] === MailboxStore::STATE_DEFAULTS
|
||||
&& array_intersect_key($update['$set'], MailboxStore::STATE_DEFAULTS) === []),
|
||||
['upsert' => true],
|
||||
);
|
||||
|
||||
$mailbox = (new CollectionResource('imap', 'svc'))->fromImap(new Mailbox('INBOX', '/', []));
|
||||
(new MailboxStore($this->dataStore($collection)))->upsert('tenant', 'svc', $mailbox);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array> $cached
|
||||
* @param string[] $remote
|
||||
*/
|
||||
private function harmonizer(array $cached, array $remote): HarmonizationService
|
||||
{
|
||||
$mailboxes = $this->createStub(MailboxStore::class);
|
||||
$mailboxes->method('list')->willReturn($cached);
|
||||
$mailboxes->method('upsert')->willReturnCallback(function (string $tid, string $sid, $collection): void {
|
||||
$this->calls[] = ['upsert', $tid, $sid, $collection->identifier()];
|
||||
});
|
||||
$mailboxes->method('delete')->willReturnCallback(function (string $sid, string $name): void {
|
||||
$this->calls[] = ['mailboxes.delete', $sid, $name];
|
||||
});
|
||||
|
||||
$messages = $this->createStub(MessageStore::class);
|
||||
$messages->method('deleteByMailbox')->willReturnCallback(function (string $sid, string $name): void {
|
||||
$this->calls[] = ['messages.deleteByMailbox', $sid, $name];
|
||||
});
|
||||
|
||||
$files = $this->createStub(MessageFileStore::class);
|
||||
$files->method('deleteByMailbox')->willReturnCallback(function (string $tid, string $sid, string $name): void {
|
||||
$this->calls[] = ['files.deleteByMailbox', $tid, $sid, $name];
|
||||
});
|
||||
|
||||
$service = (new Service())->fromStore(['tid' => 'tenant', 'sid' => 'svc']);
|
||||
$live = new HarmonizationServiceTestLiveStub($service);
|
||||
$live->mailboxes = array_map(static fn (string $name): Mailbox => new Mailbox($name, '/', []), $remote);
|
||||
|
||||
return (new HarmonizationService($mailboxes, $messages, $files))->for($service, $live);
|
||||
}
|
||||
|
||||
private function dataStore(Collection $collection): DataStore
|
||||
{
|
||||
$dataStore = $this->createStub(DataStore::class);
|
||||
$dataStore->method('selectCollection')->willReturn($collection);
|
||||
return $dataStore;
|
||||
}
|
||||
}
|
||||
|
||||
final class HarmonizationServiceTestLiveStub extends LiveMailService
|
||||
{
|
||||
/** @var Mailbox[] */
|
||||
public array $mailboxes = [];
|
||||
|
||||
public function collectionList(?string $location = null, IFilter|null $filter = null, ISort|null $sort = null, string $depth = '*'): Generator
|
||||
{
|
||||
foreach ($this->mailboxes as $mailbox) {
|
||||
yield $mailbox->name() => $mailbox;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user