diff --git a/src/AsyncConnectionManager.php b/src/AsyncConnectionManager.php index 25fd0cb..09f2b32 100644 --- a/src/AsyncConnectionManager.php +++ b/src/AsyncConnectionManager.php @@ -36,7 +36,7 @@ public function connect(): PromiseInterface if ($this->isConnected() && !$this->isDisconnecting()) { $this->logger->debug('Connected'); - return resolve(new AsyncConnectionResult($this->writer, false)); + return resolve(AsyncConnectionResult::success($this->writer, false)); } if ($this->isConnecting()) { @@ -50,44 +50,48 @@ public function connect(): PromiseInterface $waitUntilDisconnectEnds = $this->disconnectionPromise->promise(); } else { - $waitUntilDisconnectEnds = resolve(true); + $waitUntilDisconnectEnds = resolve(AsyncDisconnectionResult::success()); } $this->connectionPromise = new Deferred(); - $doAfterFailedDisconnect = function (Throwable $e) { - $this->logger->debug('Disconnection failed. No need to reconnect now.'); - $this->connectionPromise->resolve(null); - $this->connectionPromise = null; + return $waitUntilDisconnectEnds->then(function (AsyncDisconnectionResult $result) { + if (!$result->isDisconnected()) { + $this->logger->debug('Disconnection failed. No need to reconnect now.'); + $result = AsyncConnectionResult::success($this->writer, false); + $this->connectionPromise->resolve($result); - return resolve(new AsyncConnectionResult($this->writer, false)); - }; + return resolve($result); + } - return $waitUntilDisconnectEnds->then(function () { $this->logger->debug('Connecting...'); - return $this->asyncConnector->connect()->then( - function (AsyncConnectionWriter $writer) { - $this->logger->debug('Connecting succeeded'); - $this->writer = $writer; - $this->connectionPromise->resolve(null); - $this->connectionPromise = null; - - return resolve(new AsyncConnectionResult($writer, true)); - }, - function (Throwable $e): void { + return $this->asyncConnector->connect() + ->then( + function (AsyncConnectionWriter $writer) { + $this->logger->debug('Connecting succeeded'); + $this->writer = $writer; + $result = AsyncConnectionResult::success($writer, true); + $this->connectionPromise->resolve($result); + + return resolve($result); + }, + ) + ->catch(function (Throwable $e): PromiseInterface { $this->logger->error('Connecting failed'); - $this->connectionPromise->reject($e); - $this->connectionPromise = null; + $this->connectionPromise->resolve(AsyncConnectionResult::failure($e)); if ($e instanceof TimeoutException) { - throw new AsyncConnectionTimeoutException($e->getMessage(), $e->getCode(), $e); + $exception = new AsyncConnectionTimeoutException($e->getMessage(), $e->getCode(), $e); + + return resolve(AsyncConnectionResult::failure($exception)); } - throw $e; - }, - ); - }, $doAfterFailedDisconnect); + return resolve(AsyncConnectionResult::failure($e)); + }); + })->finally(function (): void { + $this->connectionPromise = null; + }); } public function disconnect(): PromiseInterface @@ -101,43 +105,47 @@ public function disconnect(): PromiseInterface if (!$this->isConnected() && !$this->isConnecting()) { $this->logger->debug('Not connected'); - return resolve('Not connected.'); + return resolve(AsyncDisconnectionResult::success()); } if ($this->isConnecting()) { $this->logger->debug('Connection in progress. Waiting ...'); $waitUntilFinished = $this->connectionPromise->promise(); - } else { - $waitUntilFinished = resolve(true); + } elseif ($this->isConnected()) { + $waitUntilFinished = resolve(AsyncConnectionResult::success($this->writer)); } $this->disconnectionPromise = new Deferred(); - return $waitUntilFinished->then(function () { + return $waitUntilFinished->then(function (AsyncConnectionResult $connectionResult) { + if (!$connectionResult->isConnected()) { + $this->logger->error('Connection failed. No need to disconnect now.'); + + return resolve(AsyncDisconnectionResult::success()); + } $this->logger->debug('Disconnection started'); return $this->asyncConnector->disconnect($this->writer) - ->then(function ($value) { + ->then(function () { $this->logger->debug('Disconnection succeeded'); - $this->disconnectionPromise->resolve(null); - $this->disconnectionPromise = null; $this->writer = null; - return resolve($value); - }, function (Throwable $e): void { + $result = AsyncDisconnectionResult::success(); + $this->disconnectionPromise->resolve($result); + + return resolve($result); + }) + ->catch(function (Throwable $e) { $this->logger->debug('Disconnection failed'); - $this->disconnectionPromise->reject($e); - $this->disconnectionPromise = null; - throw $e; - }); - }, function (Throwable $e) { - $this->logger->error('Connection failed. No need to disconnect now.'); - $this->disconnectionPromise->resolve(null); - $this->disconnectionPromise = null; + $result = AsyncDisconnectionResult::failure($e); + $this->disconnectionPromise->resolve($result); - return resolve(true); + return resolve($result); + })->finally(function (): void { + $this->disconnectionPromise = null; + }); }); } diff --git a/src/AsyncConnectionResult.php b/src/AsyncConnectionResult.php index 44f6f5c..0d75bcd 100644 --- a/src/AsyncConnectionResult.php +++ b/src/AsyncConnectionResult.php @@ -2,27 +2,48 @@ namespace AsyncConnection; +use Throwable; + class AsyncConnectionResult { - private AsyncConnectionWriter $asyncConnectionWriter; + private function __construct( + private bool $isSuccess, + private ?AsyncConnectionWriter $asyncConnectionWriter = null, + private ?bool $connectionRequest = null, // false = already existing connection returned + private ?Throwable $error = null + ) + { + } - private bool $connectionRequest; // false = already existing connection returned + public static function success(AsyncConnectionWriter $writer, ?bool $connectionRequest = null): self + { + return new self(true, $writer, $connectionRequest); + } - public function __construct(AsyncConnectionWriter $asyncConnectionWriter, bool $connectionRequest) + public static function failure(Throwable $error): self { - $this->asyncConnectionWriter = $asyncConnectionWriter; - $this->connectionRequest = $connectionRequest; + return new self(false, null, null, $error); } - public function getWriter(): AsyncConnectionWriter + public function isConnected(): bool + { + return $this->isSuccess; + } + + public function getWriter(): ?AsyncConnectionWriter { return $this->asyncConnectionWriter; } - public function hasConnectedToServer(): bool + public function newServerRequestWasSent(): ?bool + { + return $this->connectionRequest === true; + } + + public function getError(): ?Throwable { - return $this->connectionRequest; + return $this->error; } } diff --git a/src/AsyncDisconnectionResult.php b/src/AsyncDisconnectionResult.php new file mode 100644 index 0000000..c48f60e --- /dev/null +++ b/src/AsyncDisconnectionResult.php @@ -0,0 +1,37 @@ +isSuccess; + } + + public function getError(): ?Throwable + { + return $this->error; + } + +} diff --git a/src/AsyncMessageQueueManager.php b/src/AsyncMessageQueueManager.php index dec2e9b..0fa03c7 100644 --- a/src/AsyncMessageQueueManager.php +++ b/src/AsyncMessageQueueManager.php @@ -10,6 +10,7 @@ use function array_slice; use function array_values; use function count; +use function React\Promise\reject; use function React\Promise\resolve; use function sprintf; use function time; @@ -121,21 +122,23 @@ function () use ($requestsCounter) { }, )->then( function (AsyncConnectionResult $result) use ($requestsCounter) { - $this->log('connected', $requestsCounter); + if ($result->isConnected()) { + $this->log('connected', $requestsCounter); - return $this->minIntervalPromise->then(static fn () => resolve($result)); - }, - function (Throwable $exception) use ($requestsCounter): void { + return $this->minIntervalPromise->then(static fn () => resolve($result)); + } + + $exception = $result->getError(); $this->log('connection failed', $requestsCounter); $this->finishWithError($exception, $requestsCounter); - throw $exception; + return reject($exception); }, )->then( fn (AsyncConnectionResult $result) => $this->asyncMessageSender->sendMessage($result->getWriter(), $message)->then(static fn () => resolve($result)), )->then( function (AsyncConnectionResult $result) use ($requestsCounter): void { - if ($result->hasConnectedToServer()) { + if ($result->newServerRequestWasSent()) { self::$sentMessagesCount = 1; } else { self::$sentMessagesCount++; @@ -144,14 +147,14 @@ function (AsyncConnectionResult $result) use ($requestsCounter): void { $this->lastSentMessageTime = time(); $this->finishWithSuccess($requestsCounter); }, - function (Throwable $exception) use ($requestsCounter): void { + function (Throwable $exception) use ($requestsCounter): PromiseInterface { $this->log('sending failed', $requestsCounter); $this->forceReconnect = true; $this->finishWithError($exception, $requestsCounter); - throw $exception; + return reject($exception); }, - ); + )->catch(static fn (Throwable $e) => reject($e)); } public function getQueuedMessagesCount(): int diff --git a/src/AsyncResult.php b/src/AsyncResult.php new file mode 100644 index 0000000..8630a61 --- /dev/null +++ b/src/AsyncResult.php @@ -0,0 +1,42 @@ +error; + } + + public function isSuccess(): bool + { + return $this->isSuccess; + } + + public function isFailure(): bool + { + return !$this->isSuccess(); + } + +} diff --git a/src/Smtp/AsyncSmtpConnectionWriter.php b/src/Smtp/AsyncSmtpConnectionWriter.php index d11aa51..9e783a7 100644 --- a/src/Smtp/AsyncSmtpConnectionWriter.php +++ b/src/Smtp/AsyncSmtpConnectionWriter.php @@ -4,7 +4,7 @@ use AsyncConnection\AsyncConnectionWriter; use AsyncConnection\AsyncMessage; -use Nette\Mail\Message; +use AsyncConnection\AsyncResult; use Psr\Log\LoggerInterface; use React\Promise\Deferred; use React\Promise\PromiseInterface; @@ -15,7 +15,6 @@ use function implode; use function in_array; use function preg_match; -use function React\Promise\reject; use function React\Promise\resolve; use function sprintf; use function trim; @@ -23,6 +22,8 @@ class AsyncSmtpConnectionWriter implements AsyncConnectionWriter { + private const EOL = "\r\n"; + /** @var array> */ private array $expectedResponses = []; @@ -38,7 +39,7 @@ public function __construct( ) { if (!$connection->isReadable() || !$connection->isWritable()) { - throw new InvalidSmtpConnectionException(); + throw new InvalidSmtpConnectionException('SMTP connection stream is not readable or/and not writable.'); } $connection->on('data', function ($data): void { @@ -73,7 +74,7 @@ public function write(AsyncMessage $message): PromiseInterface if (!$this->isValid()) { $this->logger->error('stream not valid'); - return reject(new InvalidSmtpConnectionException()); + throw new InvalidSmtpConnectionException('Stream is not valid.'); } if ($message instanceof AsyncDoubleResponseMessage) { @@ -92,14 +93,21 @@ public function write(AsyncMessage $message): PromiseInterface $message->getTextReplacement(), ]; - $this->connection->write(sprintf('%s%s', $message->getText(), Message::EOL)); + $result = $this->connection->write(sprintf('%s%s', $message->getText(), self::EOL)); + if ($result === false) { + throw new InvalidSmtpConnectionException('Write failed.'); + } return $firstResponse->promise() - ->then(static fn () => $secondResponse->promise()); + ->then(static fn (AsyncResult $result) => $result->isSuccess() ? $secondResponse->promise() : resolve($result)) + ->catch(static fn (Throwable $e) => resolve(AsyncResult::failure($e))); } $deferred = new Deferred(); - $this->connection->write(sprintf('%s%s', $message->getText(), Message::EOL)); + $result = $this->connection->write(sprintf('%s%s', $message->getText(), self::EOL)); + if ($result === false) { + throw new InvalidSmtpConnectionException('Write failed.'); + } if ($message instanceof AsyncSingleResponseMessage) { $this->expectedResponses[] = [ @@ -110,7 +118,7 @@ public function write(AsyncMessage $message): PromiseInterface ]; } else { - $deferred->resolve(null); + $deferred->resolve(AsyncResult::success()); } return $deferred->promise(); @@ -130,13 +138,14 @@ private function processDataResponse(string $data): void if (preg_match('~^[\d]{3}$~i', $data) !== 1 && preg_match('~^[\d]{3}[^\d]+~i', $data) !== 1) { - $deferred->reject(new AsyncSmtpConnectionException(sprintf('Unexpected response format: %s.', $data))); + $exception = new AsyncSmtpConnectionException(sprintf('Unexpected response format: %s.', $data)); + $deferred->resolve(AsyncResult::failure($exception)); } $code = (int) $data; if (in_array($code, $expectedCodes, true)) { $this->logger->debug('code OK'); - $deferred->resolve(null); + $deferred->resolve(AsyncResult::success()); } else { $this->logger->debug('code WRONG'); @@ -154,10 +163,10 @@ private function processDataResponse(string $data): void ? new TooManyMessagesException($errorMessage) : new AsyncSmtpConnectionException($errorMessage); - $deferred->reject($exception); + $deferred->resolve(AsyncResult::failure($exception)); } - $this->dataProcessingPromise->resolve(null); + $this->dataProcessingPromise->resolve(AsyncResult::success()); $this->dataProcessingPromise = null; } @@ -172,7 +181,7 @@ private function processNonDataResponse( return; } - $dataProcessingPromise = $this->dataProcessingPromise !== null ? $this->dataProcessingPromise->promise() : resolve(null); + $dataProcessingPromise = $this->dataProcessingPromise !== null ? $this->dataProcessingPromise->promise() : resolve(AsyncResult::success()); $dataProcessingPromise->then(function () use ($exceptionMessage, $previousException): void { if (count($this->expectedResponses) <= 0) { return; @@ -184,7 +193,8 @@ private function processNonDataResponse( continue; } - $deferred->reject(new AsyncSmtpConnectionException($exceptionMessage, 0, $previousException)); + $exception = new AsyncSmtpConnectionException($exceptionMessage, 0, $previousException); + $deferred->resolve(AsyncResult::failure($exception)); } }); } diff --git a/src/Smtp/AsyncSmtpConnector.php b/src/Smtp/AsyncSmtpConnector.php index 61c5b04..e9d2859 100644 --- a/src/Smtp/AsyncSmtpConnector.php +++ b/src/Smtp/AsyncSmtpConnector.php @@ -4,10 +4,13 @@ use AsyncConnection\AsyncConnectionWriter; use AsyncConnection\AsyncConnector; +use AsyncConnection\AsyncMessage; +use AsyncConnection\AsyncResult; use React\Promise\PromiseInterface; use React\Socket\ConnectionInterface; use React\Socket\ConnectorInterface; use function base64_encode; +use function React\Promise\reject; use function React\Promise\resolve; use function sprintf; @@ -38,12 +41,17 @@ public function connect(): PromiseInterface $writer = $this->asyncSmtpWriterFactory->create($connection); return $this->greetServer($writer); - })->then(fn ($writer) => $this->loginToServer($writer))->then(static fn ($writer) => resolve($writer)); + })->then(fn ($writer) => $this->loginToServer($writer)); } public function disconnect(AsyncConnectionWriter $writer): PromiseInterface { - return $writer->write(new AsyncSingleResponseMessage('QUIT', [SmtpCode::DISCONNECTING])); + return $this->write($writer, new AsyncSingleResponseMessage('QUIT', [SmtpCode::DISCONNECTING])); + } + + private function write(AsyncConnectionWriter $writer, AsyncMessage $message): PromiseInterface + { + return $writer->write($message)->then(static fn (AsyncResult $result) => $result->isSuccess() ? resolve($writer) : reject($result->getError())); } private function greetServer(AsyncSmtpConnectionWriter $writer): PromiseInterface @@ -51,27 +59,27 @@ private function greetServer(AsyncSmtpConnectionWriter $writer): PromiseInterfac $self = $this->smtpSettings->getHello(); $ehloMessage = new AsyncDoubleResponseMessage(sprintf('EHLO %s', $self), [SmtpCode::SERVICE_READY], [SmtpCode::OK]); - return $writer->write($ehloMessage) - ->otherwise(static function (AsyncSmtpConnectionException $e) use ($self, $writer) { + return $this->write($writer, $ehloMessage) + ->catch(function (AsyncSmtpConnectionException $e) use ($self, $writer) { $heloMessage = new AsyncDoubleResponseMessage(sprintf('HELO %s', $self), [SmtpCode::SERVICE_READY], [SmtpCode::OK]); - return $writer->write($heloMessage); - })->then(static fn () => resolve($writer)); + return $this->write($writer, $heloMessage); + }); } private function loginToServer(AsyncSmtpConnectionWriter $writer): PromiseInterface { if ($this->smtpSettings->getUsername() !== null && $this->smtpSettings->getPassword() !== null) { - return $writer->write(new AsyncSingleResponseMessage('AUTH LOGIN', [SmtpCode::AUTH_CONTINUE])) - ->then(function () use ($writer) { + return $this->write($writer, new AsyncSingleResponseMessage('AUTH LOGIN', [SmtpCode::AUTH_CONTINUE])) + ->then(function ($writer) { $usernameMessage = new AsyncSingleResponseMessage(base64_encode($this->smtpSettings->getUsername()), [SmtpCode::AUTH_CONTINUE], 'credentials'); - return $writer->write($usernameMessage); - })->then(function () use ($writer) { + return $this->write($writer, $usernameMessage); + })->then(function ($writer) { $passwordMessage = new AsyncSingleResponseMessage(base64_encode($this->smtpSettings->getPassword()), [SmtpCode::AUTH_OK], 'credentials'); - return $writer->write($passwordMessage); - })->then(static fn () => resolve($writer)); + return $this->write($writer, $passwordMessage); + }); } return resolve($writer); diff --git a/src/Smtp/AsyncSmtpMessageSender.php b/src/Smtp/AsyncSmtpMessageSender.php index f7213f7..1582842 100644 --- a/src/Smtp/AsyncSmtpMessageSender.php +++ b/src/Smtp/AsyncSmtpMessageSender.php @@ -5,6 +5,7 @@ use AsyncConnection\AsyncConnectionWriter; use AsyncConnection\AsyncMessage; use AsyncConnection\AsyncMessageSender; +use AsyncConnection\AsyncResult; use InvalidArgumentException; use Nette\Mail\Message; use React\Promise\PromiseInterface; @@ -12,6 +13,7 @@ use function array_merge; use function key; use function preg_replace; +use function React\Promise\reject; use function React\Promise\resolve; use function sprintf; @@ -28,8 +30,8 @@ public function sendMessage(AsyncConnectionWriter $writer, AsyncMessage $message $mailFromMessage = new AsyncSingleResponseMessage(sprintf('MAIL FROM:<%s>', $from), [SmtpCode::OK]); - return $writer->write($mailFromMessage) - ->then(static function () use ($message, $writer) { + return $this->write($writer, $mailFromMessage) + ->then(function ($writer) use ($message) { $recipients = array_merge( (array) $message->getHeader('To'), (array) $message->getHeader('Cc'), @@ -38,24 +40,29 @@ public function sendMessage(AsyncConnectionWriter $writer, AsyncMessage $message $previousPromise = resolve(null); foreach (array_keys($recipients, null, true) as $email) { - $previousPromise = $previousPromise->then(static function () use ($email, $writer) { + $previousPromise = $previousPromise->then(function () use ($email, $writer) { $message = sprintf('RCPT TO:<%s>', $email); $recipientMessage = new AsyncSingleResponseMessage($message, [SmtpCode::OK, SmtpCode::FORWARD]); - return $writer->write($recipientMessage); + return $this->write($writer, $recipientMessage); }); } return $previousPromise; }) - ->then(static fn () => $writer->write(new AsyncSingleResponseMessage('DATA', [SmtpCode::START_MAIL]))) - ->then(static function () use ($message, $writer) { + ->then(fn ($writer) => $this->write($writer, new AsyncSingleResponseMessage('DATA', [SmtpCode::START_MAIL]))) + ->then(function ($writer) use ($message) { $data = $message->generateMessage(); $data = preg_replace('#^\.#m', '..', $data); - return $writer->write(new AsyncZeroResponseMessage($data)); + return $this->write($writer, new AsyncZeroResponseMessage($data)); }) - ->then(static fn () => $writer->write(new AsyncSingleResponseMessage('.', [SmtpCode::OK]))); + ->then(fn ($writer) => $this->write($writer, new AsyncSingleResponseMessage('.', [SmtpCode::OK]))); + } + + private function write(AsyncConnectionWriter $writer, AsyncMessage $message): PromiseInterface + { + return $writer->write($message)->then(static fn (AsyncResult $result) => $result->isSuccess() ? resolve($writer) : reject($result->getError())); } } diff --git a/src/Smtp/InvalidSmtpConnectionException.php b/src/Smtp/InvalidSmtpConnectionException.php index 4ebe924..0796754 100644 --- a/src/Smtp/InvalidSmtpConnectionException.php +++ b/src/Smtp/InvalidSmtpConnectionException.php @@ -7,9 +7,9 @@ class InvalidSmtpConnectionException extends AsyncConnectionException { - public function __construct() + public function __construct(string $message) { - parent::__construct('SMTP connection stream is not readable or/and not writable.'); + parent::__construct($message); } } diff --git a/tests/AsyncConnectionManagerTest.php b/tests/AsyncConnectionManagerTest.php index cc007d8..5ad36f9 100644 --- a/tests/AsyncConnectionManagerTest.php +++ b/tests/AsyncConnectionManagerTest.php @@ -35,11 +35,12 @@ public function testConnectWhenNotConnected(): void $connectorMock = $this->getConnector(); $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $exception): void { - $this->setException($exception); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); @@ -53,17 +54,18 @@ public function testConnectWhenConnected(): void $manager->connect()->then( function () use ($manager): void { $manager->connect()->then( - function (): void { - try { - $this->setException(false); - - } catch (Throwable $e) { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + try { + $this->setException(false); + + } catch (Throwable $e) { + $this->setException($e); + } + } else { + $this->setException($result->getError()); } }, - function (Throwable $e): void { - $this->setException($e); - }, ); }, function (Throwable $e): void { @@ -88,11 +90,12 @@ public function testConnectingWhenConnectedToInvalidStream(): void function () use ($manager): void { $this->streamIsValid = false; $manager->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); }, @@ -109,19 +112,20 @@ public function testConnectWhenConnecting(): void $connectorMock = $this->getConnector(1, 0, 2); $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - static function (): void { - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if (!$result->isConnected()) { + $this->setException($result->getError()); + } }, ); $manager->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); @@ -135,19 +139,20 @@ public function testConnectWhenDisconnecting(): void $manager->connect()->then( function () use ($manager): void { $manager->disconnect()->then( - static function (): void { - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncDisconnectionResult $result): void { + if (!$result->isDisconnected()) { + $this->setException($result->getError()); + } }, ); $manager->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); }, @@ -164,18 +169,20 @@ public function testDisconnectWhenConnected(): void $connectorMock = $this->getConnector(1, 1); $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - function () use ($manager): void { - $manager->disconnect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); - }, - ); - }, - function (Throwable $exception): void { - $this->setException($exception); + function (AsyncConnectionResult $result) use ($manager): void { + if ($result->isConnected()) { + $manager->disconnect()->then( + function (AsyncDisconnectionResult $result): void { + if ($result->isDisconnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } + }, + ); + } else { + $this->setException($result->getError()); + } }, ); @@ -187,12 +194,11 @@ public function testDisconnectWhenNotConnected(): void $connectorMock = $this->getConnector(0, 0); $manager = $this->getConnectionManager($connectorMock); $manager->disconnect()->then( - function ($message): void { - try { - $this->assertSame('Not connected.', $message); + function (AsyncDisconnectionResult $result): void { + if ($result->isDisconnected()) { $this->setException(false); - } catch (Throwable $e) { - $this->setException($e); + } else { + $this->setException($result->getError()); } }, function (Throwable $e): void { @@ -208,19 +214,20 @@ public function testDisconnectWhenConnecting(): void $connectorMock = $this->getConnector(1, 1, 2); $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - static function (): void { - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if (!$result->isConnected()) { + $this->setException($result->getError()); + } }, ); $manager->disconnect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncDisconnectionResult $result): void { + if ($result->isDisconnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); @@ -232,33 +239,37 @@ public function testDisconnectWhenDisconnecting(): void $connectorMock = $this->getConnector(1, 1, 0, 2); $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - function () use ($manager): void { - $manager->disconnect()->then( - static function (): void { - }, - function (Throwable $e): void { - $this->setException($e); - }, - ); - - $manager->disconnect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); - }, - ); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result) use ($manager): void { + if ($result->isConnected()) { + $manager->disconnect()->then( + function (AsyncDisconnectionResult $result): void { + if ($result->isDisconnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } + }, + ); + + $manager->disconnect()->then( + function (AsyncDisconnectionResult $result): void { + if ($result->isDisconnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } + }, + ); + } else { + $this->setException($e); + } }, ); $this->runSuccessfulTest($this->loop); } - public function testConnectionFailureReturnsRejectedPromise(): void + public function testConnectionFailureReturnValue(): void { $connectorMock = $this->createMock(AsyncConnector::class); $connectorMock->method('connect') @@ -266,11 +277,12 @@ public function testConnectionFailureReturnsRejectedPromise(): void $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); @@ -287,11 +299,12 @@ public function testTimeoutCausesPromiseRejection(): void $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); @@ -301,7 +314,7 @@ function (Throwable $e): void { }); } - public function testDisconnectFailureReturnsRejectedPromise(): void + public function testDisconnectFailure(): void { $writerMock = $this->createMock(AsyncConnectionWriter::class); $writerMock->method('isValid')->willReturn(true); @@ -315,15 +328,18 @@ public function testDisconnectFailureReturnsRejectedPromise(): void $manager = $this->getConnectionManager($connectorMock); $manager->connect()->then( - function () use ($manager): void { - $manager->disconnect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); - }, - ); + function (AsyncConnectionResult $result) use ($manager): void { + if ($result->isConnected()) { + $manager->disconnect()->then( + function (AsyncDisconnectionResult $result): void { + if ($result->isDisconnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } + }, + ); + } }, ); diff --git a/tests/AsyncMessageQueueManagerTest.php b/tests/AsyncMessageQueueManagerTest.php index a9087ba..484e633 100644 --- a/tests/AsyncMessageQueueManagerTest.php +++ b/tests/AsyncMessageQueueManagerTest.php @@ -67,7 +67,7 @@ public function testDisconnectingWhenTimeSinceLastSentMailExceedsLimit(): void public function testSuccessfulSendingReturnsPromise(): void { $this->connectionManagerMock->method('connect') - ->willReturn(resolve(new AsyncConnectionResult($this->writerMock, true))); + ->willReturn(resolve(AsyncConnectionResult::success($this->writerMock, true))); $this->senderMock->method('sendMessage') ->willReturn(resolve(null)); @@ -78,7 +78,7 @@ public function testSuccessfulSendingReturnsPromise(): void public function testFailedSendingWithExpectedError(): void { $this->connectionManagerMock->method('connect') - ->willReturn(resolve(new AsyncConnectionResult($this->writerMock, true))); + ->willReturn(resolve(AsyncConnectionResult::success($this->writerMock, true))); $this->senderMock->method('sendMessage') ->willReturn(reject(new AsyncConnectionException('Sending failed'))); @@ -99,7 +99,7 @@ public function testFailedSendingWithExpectedError(): void public function testFailedSendingWithUnexpectedError(): void { $this->connectionManagerMock->method('connect') - ->willReturn(resolve(new AsyncConnectionResult($this->writerMock, true))); + ->willReturn(resolve(AsyncConnectionResult::success($this->writerMock, true))); $this->senderMock->method('sendMessage') ->willReturn(reject(new Exception('Unexpected error'))); @@ -119,7 +119,7 @@ public function testFailedSendingWithUnexpectedError(): void public function testFailedConnectionWithUnexpectedError(): void { $this->connectionManagerMock->method('connect') - ->willReturn(reject(new Exception('Unexpected error'))); + ->willThrowException(new Exception('Unexpected error')); $this->senderMock->method('sendMessage') ->willReturn(resolve(null)); @@ -139,7 +139,7 @@ public function testFailedConnectionWithUnexpectedError(): void public function testFailedConnectionWithExpectedError(): void { $this->connectionManagerMock->method('connect') - ->willReturn(reject(new AsyncConnectionException('Connection failed'))); + ->willReturn(resolve(AsyncConnectionResult::failure(new AsyncConnectionException('Connection failed')))); $this->senderMock->method('sendMessage') ->willReturn(resolve(null)); @@ -163,11 +163,11 @@ public function testMultipleRequestsAreProcessedInQueue(): void ->willReturnCallback(function (): PromiseInterface { $this->connectsCount++; - return resolve(new AsyncConnectionResult($this->writerMock, $this->connectsCount === 1)); + return resolve(AsyncConnectionResult::success($this->writerMock, $this->connectsCount === 1)); }); $this->senderMock->method('sendMessage') - ->will($this->returnCallback( + ->willReturnCallback( function (): PromiseInterface { $deferred = new Deferred(); @@ -177,7 +177,7 @@ function (): PromiseInterface { return $deferred->promise(); }, - )); + ); $this->exceptions = []; @@ -186,10 +186,9 @@ function (): PromiseInterface { function (): void { $this->exceptions['first'] = false; }, - function (Throwable $e): void { - $this->exceptions['first'] = $e; - }, - ); + )->catch(function (Throwable $e): void { + $this->exceptions['first'] = $e; + }); $this->assertSame(1, $manager->getQueuedMessagesCount(), 'Unexpected queued messages count.'); $this->assertSame(0, $manager->getSentMessagesCount(), 'Unexpected sent messages count'); @@ -250,7 +249,7 @@ function (Throwable $e): void { public function testSendingWhenPreviousRequestSucceeded(): void { $this->connectionManagerMock->method('connect') - ->willReturn(resolve(new AsyncConnectionResult($this->writerMock, true))); + ->willReturn(resolve(AsyncConnectionResult::success($this->writerMock, true))); $this->senderMock->method('sendMessage') ->willReturn(resolve(null)); @@ -261,20 +260,18 @@ public function testSendingWhenPreviousRequestSucceeded(): void ->then( static function (): void { }, - function (Throwable $e): void { - $this->setException($e); - }, - ); + )->catch(function (Throwable $exception): void { + $this->setException($exception); + }); $manager->send(new SimpleAsyncMessage('message')) ->then( function (): void { $this->setException(false); }, - function (Throwable $e): void { - $this->setException($e); - }, - ); + )->catch(function (Throwable $exception): void { + $this->setException($exception); + }); $this->runSuccessfulTest($this->loop); } @@ -287,12 +284,12 @@ public function testSendingWhenPreviousConnectionFailed(): void $this->connectionManagerMock->method('connect') ->willReturnCallback(function (): PromiseInterface { if (count($this->exceptions) === 0) { - return reject(new Exception('Unexpected error')); + return resolve(AsyncConnectionResult::failure(new Exception('Unexpected error'))); } $this->connectsCount++; - return resolve(new AsyncConnectionResult($this->writerMock, true)); + return resolve(AsyncConnectionResult::success($this->writerMock, true)); }); $this->senderMock->method('sendMessage') @@ -314,11 +311,11 @@ public function testSendingWhenPreviousSendingFailed(): void ->willReturnCallback(function (): PromiseInterface { $this->connectsCount++; - return resolve(new AsyncConnectionResult($this->writerMock, $this->connectsCount === 1)); + return resolve(AsyncConnectionResult::success($this->writerMock, $this->connectsCount === 1)); }); $this->connectionManagerMock->method('disconnect') - ->willReturn(resolve(null)); + ->willReturn(resolve(AsyncDisconnectionResult::success())); $this->senderMock->method('sendMessage') ->willReturnCallback(fn (): PromiseInterface => $this->connectsCount === 1 @@ -366,10 +363,9 @@ private function runSuccessfulSendingTest( function (): void { $this->setException(false); }, - function (Throwable $exception): void { - $this->setException($exception); - }, - ); + )->catch(function (Throwable $exception): void { + $this->setException($exception); + }); $this->runSuccessfulTest($this->loop, $assertOnSuccess); } @@ -386,10 +382,9 @@ private function runFailedSendingTest( function (): void { $this->setException(false); }, - function (Throwable $exception): void { - $this->setException($exception); - }, - ); + )->catch(function (Throwable $exception): void { + $this->setException($exception); + }); $this->runFailedTest($this->loop, $assertOnFail, $errorMessage); } @@ -405,15 +400,15 @@ private function runDisconnectionTest( $this->connectionManagerMock ->method('connect') // intentionally setting $connectionRequest = false (otherwise sentMessagesCounter will be reset) - ->willReturn(resolve(new AsyncConnectionResult($this->writerMock, false))); + ->willReturn(resolve(AsyncConnectionResult::success($this->writerMock, false))); $this->connectionManagerMock ->method('disconnect') - ->will($this->returnCallback(function () { + ->willReturnCallback(function () { $this->disconnectsCount++; return resolve(null); - })); + }); $this->disconnectsCount = 0; $this->connectsCount = 0; @@ -444,10 +439,9 @@ function (Throwable $e): void { $this->setException($e); } }, - function (Throwable $e): void { - $this->setException($e); - }, - ); + )->catch(function (Throwable $exception): void { + $this->setException($exception); + }); $asserts = function () use ($manager): void { $this->assertSame(1, $this->disconnectsCount, 'Unexpected total disconnects count.'); @@ -465,20 +459,18 @@ private function runTwoRequestsTest(Closure $asserts): void function (): void { $this->exceptions['first'] = false; }, - function (Throwable $e): void { - $this->exceptions['first'] = $e; - }, - ); + )->catch(function (Throwable $e): void { + $this->exceptions['first'] = $e; + }); $manager->send(new SimpleAsyncMessage('message')) ->then( function (): void { $this->exceptions['second'] = false; }, - function (Throwable $e): void { - $this->exceptions['second'] = $e; - }, - ); + )->catch(function (Throwable $e): void { + $this->exceptions['second'] = $e; + }); $startTime = time(); $this->loop->addPeriodicTimer($this->getTimerInterval(), function () use ($startTime, $asserts, $manager): void { diff --git a/tests/AsyncTestTrait.php b/tests/AsyncTestTrait.php index 006b0ef..2bbbae6 100644 --- a/tests/AsyncTestTrait.php +++ b/tests/AsyncTestTrait.php @@ -7,6 +7,7 @@ use Psr\Log\NullLogger; use React\EventLoop\LoopInterface; use Throwable; +use function dump; use function sprintf; use function time; diff --git a/tests/Smtp/AsyncSmtpConnectionIntegrationTest.php b/tests/Smtp/AsyncSmtpConnectionIntegrationTest.php index d0be614..4145d85 100644 --- a/tests/Smtp/AsyncSmtpConnectionIntegrationTest.php +++ b/tests/Smtp/AsyncSmtpConnectionIntegrationTest.php @@ -6,6 +6,7 @@ use AsyncConnection\AsyncConnectionException; use AsyncConnection\AsyncConnectionManager; +use AsyncConnection\AsyncConnectionResult; use AsyncConnection\AsyncTestTrait; use AsyncConnection\Connector\ConnectorFactory; use AsyncConnection\IntegrationTestCase; @@ -99,11 +100,12 @@ public function testSubsequentConnectionRequests(): void $connection->connect()->then( function () use ($connection): void { $connection->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); }, @@ -119,11 +121,12 @@ private function successfulConnectionTest(SmtpSettings $settings): void { $connection = $this->createConnectionManager($settings); $connection->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); @@ -138,11 +141,12 @@ private function failedConnectionTest( { $connection = $this->createConnectionManager($settings); $connection->connect()->then( - function (): void { - $this->setException(false); - }, - function (Throwable $e): void { - $this->setException($e); + function (AsyncConnectionResult $result): void { + if ($result->isConnected()) { + $this->setException(false); + } else { + $this->setException($result->getError()); + } }, ); diff --git a/tests/Smtp/AsyncSmtpConnectionWriterTest.php b/tests/Smtp/AsyncSmtpConnectionWriterTest.php index 32ab9df..12c714a 100644 --- a/tests/Smtp/AsyncSmtpConnectionWriterTest.php +++ b/tests/Smtp/AsyncSmtpConnectionWriterTest.php @@ -5,6 +5,7 @@ namespace AsyncConnection\Smtp; use AsyncConnection\AsyncMessage; +use AsyncConnection\AsyncResult; use AsyncConnection\AsyncTestTrait; use AsyncConnection\TestCase; use Closure; @@ -69,13 +70,25 @@ public function testInvalidStreamThrowsException(): void new AsyncSmtpConnectionWriter($connectionMock, $this->logger); } + public function testFailedWriteThrowsException(): void + { + $this->expectException(InvalidSmtpConnectionException::class); + $this->expectExceptionMessage('Write failed.'); + + $connectionMock = $this->createConnectionMock(); + $connectionMock->method('write')->willReturn(false); + + $writer = new AsyncSmtpConnectionWriter($connectionMock, $this->logger); + $writer->write(new AsyncSingleResponseMessage('AUTH LOGIN', [334])); + } + public function testUnexpectedConnectionEndFromServer(): void { $writer = new AsyncSmtpConnectionWriter($this->createConnectionMock(), $this->logger); $writer->write(new AsyncSingleResponseMessage('AUTH LOGIN', [334])) ->then( - function (): void { - $this->exception = false; + function (AsyncResult $result): void { + $this->exception = $result->isSuccess() ? false : $result->getError(); }, function (Throwable $exception): void { $this->exception = $exception; @@ -91,8 +104,8 @@ public function testUnexpectedConnectionClosedByServer(): void $writer = new AsyncSmtpConnectionWriter($this->createConnectionMock(), $this->logger); $writer->write(new AsyncSingleResponseMessage('AUTH LOGIN', [334])) ->then( - function (): void { - $this->exception = false; + function (AsyncResult $result): void { + $this->exception = $result->isSuccess() ? false : $result->getError(); }, function (Throwable $exception): void { $this->exception = $exception; @@ -108,8 +121,8 @@ public function testConnectionErrorFromServer(): void $writer = new AsyncSmtpConnectionWriter($this->createConnectionMock(), $this->logger); $writer->write(new AsyncSingleResponseMessage('AUTH LOGIN', [334])) ->then( - function (): void { - $this->exception = false; + function (AsyncResult $result): void { + $this->exception = $result->isSuccess() ? false : $result->getError(); }, function (Throwable $exception): void { $this->exception = $exception; @@ -181,8 +194,8 @@ public function testSuccessfulWrites( { $writer = new AsyncSmtpConnectionWriter($this->createConnectionMock($message->getText()), $this->logger); $writer->write($message)->then( - function (): void { - $this->exception = false; + function (AsyncResult $result): void { + $this->exception = $result->isSuccess() ? false : $result->getError(); }, function (Throwable $exception): void { $this->exception = $exception; @@ -248,8 +261,8 @@ public function testFailedWrites( $writer = new AsyncSmtpConnectionWriter($this->createConnectionMock($message->getText()), $this->logger); $writer->write($message) ->then( - function (): void { - $this->exception = false; + function (AsyncResult $result): void { + $this->exception = $result->isSuccess() ? false : $result->getError(); }, function (Throwable $exception): void { $this->exception = $exception; diff --git a/tests/Smtp/AsyncSmtpConnectorTest.php b/tests/Smtp/AsyncSmtpConnectorTest.php index cd893c0..4b4d7d4 100644 --- a/tests/Smtp/AsyncSmtpConnectorTest.php +++ b/tests/Smtp/AsyncSmtpConnectorTest.php @@ -3,6 +3,7 @@ namespace AsyncConnection\Smtp; use AsyncConnection\AsyncMessage; +use AsyncConnection\AsyncResult; use AsyncConnection\AsyncTestTrait; use AsyncConnection\TestCase; use Exception; @@ -132,15 +133,15 @@ private function createConnector( $passwordIsInvalid ): PromiseInterface { if ($message->getText() === 'EHLO slevomat.cz') { - return reject(new AsyncSmtpConnectionException('')); + return resolve(AsyncResult::failure(new AsyncSmtpConnectionException(''))); } if ($message->getText() === 'HELO slevomat.cz') { $this->heloMessageWasSent = true; return $greetingShouldFail - ? reject(new AsyncSmtpConnectionException(self::INVALID_GREETING_MESSAGE)) - : resolve(null); + ? resolve(AsyncResult::failure(new AsyncSmtpConnectionException(self::INVALID_GREETING_MESSAGE))) + : resolve(AsyncResult::success()); } if ($message->getText() === 'AUTH LOGIN') { @@ -151,16 +152,16 @@ private function createConnector( $this->usernameWasSent = true; return $usernameIsInvalid - ? reject(new AsyncSmtpConnectionException(self::INVALID_USERNAME_MESSAGE)) - : resolve(null); + ? resolve(AsyncResult::failure(new AsyncSmtpConnectionException(self::INVALID_USERNAME_MESSAGE))) + : resolve(AsyncResult::success()); } return $passwordIsInvalid - ? reject(new AsyncSmtpConnectionException(self::INVALID_PASSWORD_MESSAGE)) - : resolve(null); + ? resolve(AsyncResult::failure(new AsyncSmtpConnectionException(self::INVALID_PASSWORD_MESSAGE))) + : resolve(AsyncResult::success()); } - return resolve(null); + return resolve(AsyncResult::success()); }); return new AsyncSmtpConnector( diff --git a/tests/Smtp/AsyncSmtpMessageSenderTest.php b/tests/Smtp/AsyncSmtpMessageSenderTest.php index d567588..acfcf72 100644 --- a/tests/Smtp/AsyncSmtpMessageSenderTest.php +++ b/tests/Smtp/AsyncSmtpMessageSenderTest.php @@ -3,6 +3,7 @@ namespace AsyncConnection\Smtp; use AsyncConnection\AsyncMessage; +use AsyncConnection\AsyncResult; use AsyncConnection\AsyncTestTrait; use AsyncConnection\TestCase; use Closure; @@ -13,7 +14,6 @@ use React\EventLoop\LoopInterface; use React\Promise\PromiseInterface; use Throwable; -use function React\Promise\reject; use function React\Promise\resolve; class AsyncSmtpMessageSenderTest extends TestCase @@ -41,7 +41,7 @@ protected function setUp(): void public function testSuccessfulSendingReturnsPromise(): void { $this->writerMock->method('write') - ->willReturn(resolve(null)); + ->willReturn(resolve(AsyncResult::success())); $this->runSuccessfulSendingTest($this->createMessage()); } @@ -65,8 +65,8 @@ public function testFailedSendingThrowsException(string $messageToFail): void { $this->writerMock->method('write') ->willReturnCallback(static fn (AsyncMessage $message) => Strings::startsWith($message->getText(), $messageToFail) - ? reject(new AsyncSmtpConnectionException('Sending failed')) - : resolve(null)); + ? resolve(AsyncResult::failure(new AsyncSmtpConnectionException('Sending failed'))) + : resolve(AsyncResult::success())); $assertOnFail = function (Throwable $exception): void { $this->assertInstanceOf(AsyncSmtpConnectionException::class, $exception); @@ -83,14 +83,14 @@ public function testFailedSendingThrowsException(string $messageToFail): void public function testMultipleRecipients(): void { $this->writerMock->method('write') - ->will($this->returnCallback(function (AsyncMessage $message): PromiseInterface { + ->willReturnCallback(function (AsyncMessage $message): PromiseInterface { $matches = Strings::match($message->getText(), '~RCPT TO:\s?\<(?[^>]+)\>~i'); if ($matches !== null) { $this->recipients[] = $matches['recipient']; } - return resolve(null); - })); + return resolve(AsyncResult::success()); + }); $assertOnSuccess = function (): void { $this->assertCount(6, $this->recipients);