service(listStatus: true)->mailboxFetch('INBOX'); $this->assertCount(1, $this->commands); $this->assertStringContainsString('RETURN (STATUS', $this->commands[0]); $this->assertSame(3, $mailbox?->messages()); $this->assertSame(7, $mailbox?->uidValidity()); $this->assertSame(10, $mailbox?->uidNext()); } public function testWithoutListStatusFallsBackToStatus(): void { $mailbox = $this->service(listStatus: false)->mailboxFetch('INBOX'); $this->assertCount(2, $this->commands); $this->assertStringNotContainsString('RETURN', $this->commands[0]); $this->assertStringStartsWith('STATUS', $this->commands[1]); $this->assertSame(3, $mailbox?->messages()); $this->assertSame(7, $mailbox?->uidValidity()); $this->assertSame(10, $mailbox?->uidNext()); } public function testUnknownMailboxIsNull(): void { $this->assertNull($this->service(listStatus: false, exists: false)->mailboxFetch('Missing')); } public function testCollectionsAreResourcesKeyedByName(): void { $service = $this->service(listStatus: true); $list = $service->collectionList(); $collection = $service->collectionFetch('INBOX'); $this->assertSame(['INBOX'], array_keys($list)); $this->assertInstanceOf(CollectionResource::class, $list['INBOX']); $this->assertInstanceOf(CollectionResource::class, $collection); $this->assertSame('INBOX', $collection?->identifier()); $this->assertNull($this->service(listStatus: true, exists: false)->collectionFetch('Missing')); } public function testDelimiterIsReadWithoutListingMailboxes(): void { $this->assertSame('/', $this->service(listStatus: false)->collectionDelimiter()); $this->assertSame(['LIST "" ""'], $this->commands); } public function testCollectionExtantReportsMissingMailboxes(): void { $service = new LiveService(); $live = $this->service(listStatus: true, exists: false); (new \ReflectionProperty(LiveService::class, 'mailService'))->setValue($service, $live); $this->assertSame(['Missing' => false], $service->collectionExtant('Missing')); } private function service(bool $listStatus, bool $exists = true): LiveMailService { $lines = ["* PREAUTH Ready\r\n"]; $connection = $this->createStub(ConnectionInterface::class); $connection->method('readLine')->willReturnCallback(static function () use (&$lines): string { return array_shift($lines) ?? throw new \RuntimeException('Unexpected response read'); }); $connection->method('write')->willReturnCallback(function (string $wire) use (&$lines, $listStatus, $exists): void { [$tag, $command] = explode(' ', trim($wire), 2); $operation = explode(' ', $command)[0]; if ($operation === 'CAPABILITY') { $lines[] = '* CAPABILITY IMAP4rev1' . ($listStatus ? ' LIST-STATUS' : '') . "\r\n"; } else { $this->commands[] = $command; } if ($operation === 'LIST' && $command === 'LIST "" ""') { $lines[] = "* LIST (\\Noselect) \"/\" \"\"\r\n"; $lines[] = "$tag OK Completed\r\n"; return; } if ($operation === 'LIST') { if (!$listStatus && str_contains($command, 'RETURN')) { $lines[] = "$tag BAD Unknown RETURN option\r\n"; return; } if ($exists) { $lines[] = "* LIST (\\HasNoChildren) \"/\" \"INBOX\"\r\n"; if ($listStatus) { $lines[] = "* STATUS \"INBOX\" (MESSAGES 3 UNSEEN 1 UIDNEXT 10 UIDVALIDITY 7)\r\n"; } } } if ($operation === 'STATUS') { $lines[] = "* STATUS \"INBOX\" (MESSAGES 3 UNSEEN 1 UIDNEXT 10 UIDVALIDITY 7)\r\n"; } $lines[] = "$tag OK Completed\r\n"; }); $factory = $this->createStub(ConnectionFactoryInterface::class); $factory->method('create')->willReturn($connection); $client = new Client($factory); $client->connect(new ConnectionConfig('localhost')); return new class($this->createStub(ServiceBase::class), $client) extends LiveMailService { public function __construct(ServiceBase $service, private readonly Client $client) { parent::__construct($service); } public function imapClient(): Client { return $this->client; } }; } }