createStub(ConnectionInterface::class)); $context->setSelectedMailbox('INBOX'); return $context; } private function command(string $name): CommandInterface { return match ($name) { 'COPY' => new CopyCommand(MessageTarget::uid(42), 'Archive'), 'MOVE' => new MoveCommand(MessageTarget::uid(42), 'Archive'), 'APPEND' => new AppendCommand('Archive', 'body'), }; } public static function failures(): iterable { foreach (['COPY', 'MOVE', 'APPEND'] as $command) { foreach (['NO', 'BAD'] as $status) { foreach ([ ['[trycreate] Create destination first', ['name' => 'TRYCREATE', 'arguments' => [], 'text' => 'Create destination first']], ['[LIMIT 10] Too many messages', ['name' => 'LIMIT', 'arguments' => ['10'], 'text' => 'Too many messages']], ['Denied', null], ['[Malformed', null], ] as [$text, $code]) { yield [$command, $status, $text, $code]; } } } } #[DataProvider('failures')] public function testServerFailurePreservesStructuredDetails(string $command, string $status, string $text, ?array $code): void { $responses = new ResponseStream(static function () use ($status, $text): \Generator { yield new TaggedResponse('A1', $status, $text, "A1 $status $text"); }); try { $this->command($command)->handle($responses, $this->context()); $this->fail('Expected server failure'); } catch (ImapException $exception) { $this->assertInstanceOf(CommandFailedException::class, $exception); $this->assertSame($command . ' failed: ' . $text, $exception->getMessage()); $this->assertSame($command, $exception->command()); $this->assertSame($status, $exception->status()); $this->assertSame($text, $exception->text()); $this->assertSame($code, $exception->responseCode()); } } public function testMissingCompletionRemainsAProtocolError(): void { foreach (['COPY', 'MOVE', 'APPEND'] as $command) { $responses = new ResponseStream(static function (): \Generator { yield from []; }); try { $this->command($command)->handle($responses, $this->context()); $this->fail('Expected missing completion failure'); } catch (ImapException $exception) { $this->assertNotInstanceOf(CommandFailedException::class, $exception); $this->assertSame($command . ' did not receive a tagged completion response.', $exception->getMessage()); } } } public function testSuccessfulAppendStillReturnsAssignedUid(): void { $responses = new ResponseStream(static function (): \Generator { yield new TaggedResponse('A1', 'OK', '[APPENDUID 7 42] Done', 'A1 OK [APPENDUID 7 42] Done'); }); $this->assertSame(42, $this->command('APPEND')->handle($responses, $this->context())); } }