From 96d864063433b243ba88f62d659a1bb259b90750 Mon Sep 17 00:00:00 2001 From: m-yamagishi Date: Sun, 10 Sep 2023 17:13:39 +0900 Subject: [PATCH] feat: from Buzz to Amphp --- composer.json | 12 +- src/Console/Application.php | 2 - src/Console/Commands/GenerateStubCommand.php | 27 --- src/Console/Commands/RunCommand.php | 75 ++---- src/Console/Commands/WebCommand.php | 58 ----- src/Contracts/ExecutorInterface.php | 4 +- src/Contracts/HttpClientInterface.php | 4 +- src/Contracts/HttpProfilerInterface.php | 60 ----- src/Contracts/HttpResultInterface.php | 106 -------- src/Contracts/ReporterInterface.php | 4 +- src/Executor/ExecutorConfig.php | 39 --- src/Executor/ExecutorFactory.php | 11 +- .../Middlewares/ProfilingMiddleware.php | 43 ---- .../Middlewares/WaitSendRequestMiddleware.php | 38 --- src/Executor/SyncExecutor.php | 22 +- src/HttpClient/AmphpClient.php | 44 +++- src/HttpClient/AmphpEventListener.php | 126 ++++++++++ src/HttpClient/BuzzClient.php | 58 ----- src/HttpClient/ClientFactory.php | 27 ++- src/HttpClient/HttpProfiler.php | 68 ++++++ src/HttpClient/HttpResult.php | 179 ++++++++++++++ src/HttpClient/MiddlewareHandler.php | 12 +- src/HttpClient/MiddlewareQueue.php | 4 +- src/HttpClient/RequestException.php | 18 ++ src/Reporters/TableReporter.php | 151 ++++++------ src/Scenario/HttpProfiler.php | 68 ------ src/Scenario/HttpResult.php | 227 ------------------ src/Scenario/RequestException.php | 38 --- src/Web/Controllers/IndexController.php | 26 -- .../Commands/GenerateStubCommandTest.php | 28 --- tests/Integration/SimpleScenarioTest.php | 10 +- tests/Unit/Executor/ExecutorConfigTest.php | 76 ------ tests/Unit/Executor/ExecutorFactoryTest.php | 45 ---- .../WaitSendRequestMiddlewareTest.php | 41 ---- tests/Unit/Executor/SyncExecutorTest.php | 48 ---- .../Unit/HttpClient/MiddlewareHandlerTest.php | 53 ++++ tests/Unit/Scenario/ClientTest.php | 12 +- tests/Unit/Scenario/RequestExceptionTest.php | 32 --- 38 files changed, 643 insertions(+), 1253 deletions(-) delete mode 100644 src/Console/Commands/GenerateStubCommand.php delete mode 100644 src/Console/Commands/WebCommand.php delete mode 100644 src/Contracts/HttpProfilerInterface.php delete mode 100644 src/Contracts/HttpResultInterface.php delete mode 100644 src/Executor/ExecutorConfig.php delete mode 100644 src/Executor/Middlewares/ProfilingMiddleware.php delete mode 100644 src/Executor/Middlewares/WaitSendRequestMiddleware.php create mode 100644 src/HttpClient/AmphpEventListener.php delete mode 100644 src/HttpClient/BuzzClient.php create mode 100644 src/HttpClient/HttpProfiler.php create mode 100644 src/HttpClient/HttpResult.php create mode 100644 src/HttpClient/RequestException.php delete mode 100644 src/Scenario/HttpProfiler.php delete mode 100644 src/Scenario/HttpResult.php delete mode 100644 src/Scenario/RequestException.php delete mode 100644 src/Web/Controllers/IndexController.php delete mode 100644 tests/Integration/Console/Commands/GenerateStubCommandTest.php delete mode 100644 tests/Unit/Executor/ExecutorConfigTest.php delete mode 100644 tests/Unit/Executor/ExecutorFactoryTest.php delete mode 100644 tests/Unit/Executor/Middlewares/WaitSendRequestMiddlewareTest.php delete mode 100644 tests/Unit/Executor/SyncExecutorTest.php create mode 100644 tests/Unit/HttpClient/MiddlewareHandlerTest.php delete mode 100644 tests/Unit/Scenario/RequestExceptionTest.php diff --git a/composer.json b/composer.json index 6379cf4..86c5d8d 100644 --- a/composer.json +++ b/composer.json @@ -26,21 +26,19 @@ ], "require": { "php": ">=8.1", - "amphp/http-client": "v5.0.0-beta.15", - "kriswallsmith/buzz": "^1.2", + "amphp/http-client": "v5.0.0-beta.17", "laminas/laminas-diactoros": "^3.0", "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0|^2.0", - "slim/slim": "^4.11", - "symfony/console": "^6.3", - "symfony/process": "^6.3" + "psr/http-message": "^2.0", + "symfony/console": "^6.3" }, "require-dev": { "phpunit/phpunit": "^10.1" }, "suggest": { - "ext-mbstring": "For more accurate bodyLength" + "ext-mbstring": "For more accurate bodyLength", + "ext-uv": "For higher concurrency" }, "config": { "optimize-autoloader": true, diff --git a/src/Console/Application.php b/src/Console/Application.php index a27661d..4b86968 100644 --- a/src/Console/Application.php +++ b/src/Console/Application.php @@ -20,9 +20,7 @@ public function __construct() { parent::__construct(self::NAME, self::VERSION); $this->addCommands([ - new Commands\GenerateStubCommand(), new Commands\RunCommand(), - new Commands\WebCommand(), ]); } } diff --git a/src/Console/Commands/GenerateStubCommand.php b/src/Console/Commands/GenerateStubCommand.php deleted file mode 100644 index 4343a06..0000000 --- a/src/Console/Commands/GenerateStubCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -addOption( - 'config', - 'c', + 'output', + 'o', InputOption::VALUE_REQUIRED, - 'PHP filename for scenario config', - )->addOption( - 'timeout', - 't', - InputOption::VALUE_REQUIRED, - 'Request timeout seconds', - 0.0, - )->addOption( - 'verify-cert', - null, - InputOption::VALUE_NONE, - 'Enables SSL/TLS certificate verification', - )->addOption( - 'wait-after-scenario', - null, - InputOption::VALUE_REQUIRED, - 'Wait seconds after scenario', - 1.0, - )->addOption( - 'wait-after-request', - null, - InputOption::VALUE_REQUIRED, - 'Wait seconds after request', - 0.3, + 'Output format', + 'table', ); } @@ -88,10 +65,6 @@ protected function execute(InputInterface $input, OutputInterface $output) /** @var string $baseUri */ $baseUri = $input->getArgument('base-uri'); - $timeout = \floatval($input->getOption('timeout')); - $verifyCert = \boolval($input->getOption('verify-cert')); - $waitAfterScenarioSec = \floatval($input->getOption('wait-after-scenario')); - $waitAfterSendRequestSec = \floatval($input->getOption('wait-after-request')); /** @var string */ $scenarioFileName = $input->getArgument('scenario-php-file'); @@ -113,49 +86,33 @@ protected function execute(InputInterface $input, OutputInterface $output) if (\method_exists($scenarioFunction, 'isStatic') && !$scenarioFunction->isStatic()) { $io->warning('Scenario Closure should be static: `return static function(...`'); } - - $userAgentBase = \sprintf( - '%s/%s', - $this->getApplication()?->getName() ?? 'heavyrain', - $this->getApplication()?->getVersion() ?? 'dev', - ); - $config = new ExecutorConfig( - $baseUri, - $userAgentBase, - $waitAfterScenarioSec, - $waitAfterSendRequestSec, - $verifyCert, - $timeout, - ); - $profiler = new HttpProfiler(); $this->cancelToken = new CancellationToken(); $io->definitionList( ['Base URI' => $baseUri], ['Scenario' => $scenarioFilePath], - ['SSL/TLS verify' => $verifyCert ? 'yes' : 'no (default)'], ); $startMicrosec = \microtime(true); $io->writeln(\sprintf('Start execution at %s', \date('Y-m-d H:i:s'))); + $profiler = new HttpProfiler(); - $client = ClientFactory::create($baseUri); - - // TODO: Select executor - (new ExecutorFactory($config, $scenarioFunction->getClosure(), $profiler, $client)) - ->createSync() + (new SyncExecutor($scenarioFunction->getClosure(), new ClientFactory($profiler, $baseUri))) ->execute($this->cancelToken); $io->writeln( \sprintf( - 'End execution at %s (%f seconds)', + 'End execution at %s (%.3f seconds)', \date('Y-m-d H:i:s'), \microtime(true) - $startMicrosec, ), ); - // TODO: Select reporter - (new TableReporter($io))->report($profiler); + $reporter = match ($input->getOption('output')) { + 'table' => new TableReporter($io), + default => new TableReporter($io), + }; + $reporter->report($profiler->getResults()); return Command::SUCCESS; } diff --git a/src/Console/Commands/WebCommand.php b/src/Console/Commands/WebCommand.php deleted file mode 100644 index 7b89065..0000000 --- a/src/Console/Commands/WebCommand.php +++ /dev/null @@ -1,58 +0,0 @@ -addOption('host', null, InputOption::VALUE_OPTIONAL, 'serve hostname', 'localhost') - ->addOption('port', null, InputOption::VALUE_OPTIONAL, 'serve port', 8080); - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $host = $input->getOption('host'); - \assert(\is_string($host)); - $port = $input->getOption('port'); - \assert(\is_numeric($port)); - - $php = (new PhpExecutableFinder())->find(false); - - $process = new Process( - command: [$php, '-S', \sprintf('%s:%d', $host, $port), '-t', 'public'], - timeout: null, - ); - - $process->run(static function (string $type, string $buffer) use ($output) { - if ($type === Process::ERR) { - \assert($output instanceof ConsoleOutputInterface); - $output->getErrorOutput()->write($buffer); - } else { - $output->write($buffer); - } - }); - - return Command::SUCCESS; - } -} diff --git a/src/Contracts/ExecutorInterface.php b/src/Contracts/ExecutorInterface.php index 612c91f..960ad96 100644 --- a/src/Contracts/ExecutorInterface.php +++ b/src/Contracts/ExecutorInterface.php @@ -18,7 +18,7 @@ interface ExecutorInterface * It must not throw Exception * * @param CancellationTokenInterface $token - * @return void + * @return iterable */ - public function execute(CancellationTokenInterface $token): void; + public function execute(CancellationTokenInterface $token): iterable; } diff --git a/src/Contracts/HttpClientInterface.php b/src/Contracts/HttpClientInterface.php index 1853e67..5e9d37c 100644 --- a/src/Contracts/HttpClientInterface.php +++ b/src/Contracts/HttpClientInterface.php @@ -1,11 +1,11 @@ - * @phpstan-return array - * @phan-return array - */ - public function getResults(): array; - - /** - * Returns uncaught exceptions - * - * @return Throwable[] - */ - public function getExceptions(): array; - - /** - * Profiles HTTP result - * - * @param float $startMicrotime - * @param float $endMicrotime - * @param RequestInterface $request - * @param ResponseInterface $response - * @return HttpResultInterface - */ - public function profile( - float $startMicrotime, - float $endMicrotime, - RequestInterface $request, - ResponseInterface $response, - ): HttpResultInterface; - - /** - * Profiles uncaught exception - * - * @param Throwable $exception - * @return void - */ - public function profileException(Throwable $exception): void; -} diff --git a/src/Contracts/HttpResultInterface.php b/src/Contracts/HttpResultInterface.php deleted file mode 100644 index 25a3672..0000000 --- a/src/Contracts/HttpResultInterface.php +++ /dev/null @@ -1,106 +0,0 @@ - - * } - * @phpstan-return array{ - * protocolVersion: string, - * method: string, - * uri: string, - * path: string, - * headers: array - * } - * @phan-return array{ - * protocolVersion: string, - * method: string, - * uri: string, - * path: string, - * headers: array - * } - */ - public function getRequest(): array; - - /** - * Returns response information - * - * @return ?array - * @psalm-return null|array{ - * statusCode: int, - * reasonPhrase: string, - * bodyLength: int, - * headers: array - * } - * @phpstan-return null|array{ - * statusCode: int, - * reasonPhrase: string, - * bodyLength: int, - * headers: array - * } - * @phan-return null|array{ - * statusCode: int, - * reasonPhrase: string, - * bodyLength: int, - * headers: array - * } - */ - public function getResponse(): ?array; - - /** - * Returns exception information - * - * @return ?array - * @psalm-return null|array{ - * name: string, - * message: string, - * code: int|string, - * previousMessage: ?string - * } - * @phpstan-return null|array{ - * name: string, - * message: string, - * code: int|string, - * previousMessage: ?string - * } - * @phan-return null|array{ - * name: string, - * message: string, - * code: int|string, - * previousMessage: ?string - * } - */ - public function getException(): ?array; - - /** - * Returns curl result - * - * @return ?array - * @psalm-return null|array - * @phpstan-return null|array - * @phan-return null|array - */ - public function getCurlInfo(): ?array; -} diff --git a/src/Contracts/ReporterInterface.php b/src/Contracts/ReporterInterface.php index da049eb..10c1f75 100644 --- a/src/Contracts/ReporterInterface.php +++ b/src/Contracts/ReporterInterface.php @@ -16,8 +16,8 @@ interface ReporterInterface /** * Reports HTTP profiling results * - * @param HttpProfilerInterface $profiler + * @param array $results * @return void */ - public function report(HttpProfilerInterface $profiler): void; + public function report(array $results): void; } diff --git a/src/Executor/ExecutorConfig.php b/src/Executor/ExecutorConfig.php deleted file mode 100644 index 438b72f..0000000 --- a/src/Executor/ExecutorConfig.php +++ /dev/null @@ -1,39 +0,0 @@ -waitAfterScenarioSec) < 0.0) { - throw new LogicException('waitAfterScenarioSec must be positive-numeric'); - } elseif (\floatval($this->waitAfterSendRequestSec) < 0.0) { - throw new LogicException('waitAfterSendRequestSec must be positive-numeric'); - } elseif (\floatval($this->timeout) < 0.0) { - throw new LogicException('timeout must be positive-numeric'); - } - } -} diff --git a/src/Executor/ExecutorFactory.php b/src/Executor/ExecutorFactory.php index 6f7009d..97ae14f 100644 --- a/src/Executor/ExecutorFactory.php +++ b/src/Executor/ExecutorFactory.php @@ -9,27 +9,24 @@ namespace Heavyrain\Executor; use Closure; -use Heavyrain\Contracts\ClientInterface; use Heavyrain\Contracts\ExecutorInterface; -use Heavyrain\Scenario\HttpProfiler; +use Heavyrain\HttpClient\ClientFactory; +use Heavyrain\HttpClient\HttpProfiler; final class ExecutorFactory { public function __construct( - private readonly ExecutorConfig $config, private readonly Closure $scenarioFunction, private readonly HttpProfiler $profiler, - private readonly ClientInterface $client, + private readonly string $baseUri, ) { } public function createSync(): ExecutorInterface { return new SyncExecutor( - $this->config, $this->scenarioFunction, - $this->profiler, - $this->client, + new ClientFactory($this->profiler, $this->baseUri), ); } } diff --git a/src/Executor/Middlewares/ProfilingMiddleware.php b/src/Executor/Middlewares/ProfilingMiddleware.php deleted file mode 100644 index 5829ab5..0000000 --- a/src/Executor/Middlewares/ProfilingMiddleware.php +++ /dev/null @@ -1,43 +0,0 @@ -withHeader('Heavyrain-Start', \strval(\microtime(true)))); - } - - public function handleResponse(RequestInterface $request, ResponseInterface $response, callable $next): mixed - { - if (!$request->hasHeader('Heavyrain-Start')) { - throw new \RuntimeException('Undefined request header Heavyrain-Start'); - } - - $this->profiler->profile( - \floatval($request->getHeaderLine('Heavyrain-Start')), - \microtime(true), - $request, - $response, - ); - - return $next($request, $response); - } -} diff --git a/src/Executor/Middlewares/WaitSendRequestMiddleware.php b/src/Executor/Middlewares/WaitSendRequestMiddleware.php deleted file mode 100644 index 4a41168..0000000 --- a/src/Executor/Middlewares/WaitSendRequestMiddleware.php +++ /dev/null @@ -1,38 +0,0 @@ - */ - $usec = \intval(\round(\abs($this->waitSec) * 1_000_000)); - - \usleep($usec); - - return $result; - } -} diff --git a/src/Executor/SyncExecutor.php b/src/Executor/SyncExecutor.php index f9b8783..5c2a8d9 100644 --- a/src/Executor/SyncExecutor.php +++ b/src/Executor/SyncExecutor.php @@ -10,9 +10,9 @@ use Closure; use Heavyrain\Contracts\CancellationTokenInterface; -use Heavyrain\Contracts\ClientInterface; use Heavyrain\Contracts\ExecutorInterface; -use Heavyrain\Scenario\HttpProfiler; +use Heavyrain\HttpClient\ClientFactory; +use Heavyrain\HttpClient\RequestException; use Throwable; /** @@ -21,14 +21,12 @@ class SyncExecutor implements ExecutorInterface { public function __construct( - private readonly ExecutorConfig $config, private readonly Closure $scenarioFunction, - private readonly HttpProfiler $profiler, - private readonly ClientInterface $cl, + private readonly ClientFactory $factory, ) { } - public function execute(CancellationTokenInterface $token): void + public function execute(CancellationTokenInterface $token): iterable { while (true) { if ($token->isCancelled()) { @@ -36,16 +34,14 @@ public function execute(CancellationTokenInterface $token): void } try { - ($this->scenarioFunction)($this->cl); + ($this->scenarioFunction)($this->factory->create()); + } catch (RequestException $e) { + // do nothing because Heavyrain\HttpClient\RequestException was handled in AmphpClient } catch (Throwable $e) { - $this->profiler->profileException($e); + $this->factory->profiler->profileUncaughtException($e); } - /** @var int<0, max> */ - $usec = \intval(\round(\abs($this->config->waitAfterScenarioSec) * 1_000_000)); - \usleep($usec); - - return; + yield null; } } } diff --git a/src/HttpClient/AmphpClient.php b/src/HttpClient/AmphpClient.php index cf3e9c8..e0f657b 100644 --- a/src/HttpClient/AmphpClient.php +++ b/src/HttpClient/AmphpClient.php @@ -1,11 +1,11 @@ - $this->toPsrResponse($this->client->request($this->fromPsrRequest($request))); + // first class callable + $handler = $this->handle(...); $queue = new MiddlewareQueue($this->middlewares, $handler); return $queue->handle($request); } + /** + * Handle request + * + * @param RequestInterface $request + * @return ResponseInterface + */ + private function handle(RequestInterface $request): ResponseInterface + { + $ampRequest = $this->fromPsrRequest($request); + + // TODO: Set request parameters + if (!$ampRequest->hasHeader('User-Agent')) { + $ampRequest->setHeader('User-Agent', 'heavyrain/0.0.1'); + } + $ampRequest->setBodySizeLimit(1024 * 1024); + $ampRequest->setInactivityTimeout(10); + $ampRequest->setTcpConnectTimeout(10); + $ampRequest->setTlsHandshakeTimeout(10); + $ampRequest->setTransferTimeout(10); + + try { + $response = $this->client->request($ampRequest); + + // Profiles with HTTP events. + $this->profiler->profile($ampRequest, $response); + } catch (\Throwable $exception) { + // Profile exception during request + $this->profiler->profileException($ampRequest, $exception); + + throw new RequestException('failed to fetch response', previous: $exception); + } + + return $this->toPsrResponse($response); + } + /** * From Psr Request to Amp Request * diff --git a/src/HttpClient/AmphpEventListener.php b/src/HttpClient/AmphpEventListener.php new file mode 100644 index 0000000..12fde6c --- /dev/null +++ b/src/HttpClient/AmphpEventListener.php @@ -0,0 +1,126 @@ +hasAttribute(HttpResult::START_KEY)) { + // records the start time of the request + $request->setAttribute(HttpResult::START_KEY, \microtime(true)); + } + } + + public function requestFailed(Request $request, \Throwable $exception): void + { + // Do nothing. + } + + public function requestEnd(Request $request, Response $response): void + { + // Do nothing. + } + + public function requestRejected(Request $request): void + { + // Do nothing. + } + + public function applicationInterceptorStart(Request $request, ApplicationInterceptor $interceptor): void + { + // Do nothing. + } + + public function applicationInterceptorEnd(Request $request, ApplicationInterceptor $interceptor, Response $response): void + { + // Do nothing. + } + + public function networkInterceptorStart(Request $request, NetworkInterceptor $interceptor): void + { + // Do nothing. + } + + public function networkInterceptorEnd(Request $request, NetworkInterceptor $interceptor, Response $response): void + { + // Do nothing. + } + + public function connectionAcquired(Request $request, Connection $connection, int $streamCount): void + { + $request->setAttribute(HttpResult::CONNECT_KEY, $connection->getConnectDuration()); + } + + public function push(Request $request): void + { + // Do nothing. + } + + public function requestHeaderStart(Request $request, Stream $stream): void + { + $request->setAttribute(HttpResult::SEND_START_KEY, \microtime(true)); + } + + public function requestHeaderEnd(Request $request, Stream $stream): void + { + // Do nothing. + } + + public function requestBodyStart(Request $request, Stream $stream): void + { + // Do nothing. + } + + public function requestBodyProgress(Request $request, Stream $stream): void + { + // Do nothing. + } + + public function requestBodyEnd(Request $request, Stream $stream): void + { + $request->setAttribute(HttpResult::SEND_END_KEY, \microtime(true)); + } + + public function responseHeaderStart(Request $request, Stream $stream): void + { + $request->setAttribute(HttpResult::RECEIVE_START_KEY, \microtime(true)); + } + + public function responseHeaderEnd(Request $request, Stream $stream, Response $response): void + { + // Do nothing. + } + + public function responseBodyStart(Request $request, Stream $stream, Response $response): void + { + // Do nothing. + } + + public function responseBodyProgress(Request $request, Stream $stream, Response $response): void + { + // Do nothing. + } + + public function responseBodyEnd(Request $request, Stream $stream, Response $response): void + { + $request->setAttribute(HttpResult::RECEIVE_END_KEY, \microtime(true)); + } +} diff --git a/src/HttpClient/BuzzClient.php b/src/HttpClient/BuzzClient.php deleted file mode 100644 index ae30887..0000000 --- a/src/HttpClient/BuzzClient.php +++ /dev/null @@ -1,58 +0,0 @@ -buildClient($buzzClient, [ - // TODO: should be configurable - 'allow_redirects' => false, - // must be true for profiling - 'expose_curl_info' => true, - 'verify' => $this->config->sslVerify, - 'timeout' => $this->config->timeout, - ]); - $client->addMiddleware(new ProfilingMiddleware($this->profiler)); - $client->addMiddleware(new WaitSendRequestMiddleware($this->config->waitAfterSendRequestSec)); - - return new Client( - $client, - new RequestBuilder( - $builder->getUriFactory(), - $builder->getStreamFactory(), - $builder->getRequestFactory(), - $this->config->baseUri, - ), - ); - } -} diff --git a/src/HttpClient/ClientFactory.php b/src/HttpClient/ClientFactory.php index bbeee77..c5f54f4 100644 --- a/src/HttpClient/ClientFactory.php +++ b/src/HttpClient/ClientFactory.php @@ -1,11 +1,11 @@ allowDeprecatedUriUserInfo() - ->followRedirects(0) + ->followRedirects($this->followRedirects) ->skipAutomaticCompression() ->skipDefaultAcceptHeader() ->skipDefaultUserAgent() + ->listen(new AmphpEventListener()) ->build(); $httpClient = new AmphpClient( + $this->profiler, $ampHttpClient, new ResponseFactory(), ); @@ -47,7 +60,7 @@ public static function create(string $baseUri): ClientInterface new UriFactory(), new StreamFactory(), new RequestFactory(), - $baseUri, + $this->baseUri, ); return new Client($httpClient, $requestBuilder); diff --git a/src/HttpClient/HttpProfiler.php b/src/HttpClient/HttpProfiler.php new file mode 100644 index 0000000..0d0a213 --- /dev/null +++ b/src/HttpClient/HttpProfiler.php @@ -0,0 +1,68 @@ +results; + } + + /** + * Profiles successed response + * + * @param Request $request + * @param Response $response + * @return void + */ + public function profile(Request $request, Response $response): void + { + $this->results[] = new HttpResult($request, $response); + } + + /** + * Profiles response exception during sending request + * + * @param Request $request + * @param \Throwable $exception + * @return void + */ + public function profileException(Request $request, \Throwable $exception): void + { + $this->results[] = new HttpResult($request, null, $exception); + } + + /** + * Profiles uncaught exception doing scenario + * + * @param \Throwable $exception + * @return void + */ + public function profileUncaughtException(\Throwable $exception): void + { + $this->results[] = new HttpResult(null, null, null, $exception); + } +} diff --git a/src/HttpClient/HttpResult.php b/src/HttpClient/HttpResult.php new file mode 100644 index 0000000..4eee14c --- /dev/null +++ b/src/HttpClient/HttpResult.php @@ -0,0 +1,179 @@ + + */ +final class HttpResult implements ArrayAccess, JsonSerializable, Stringable +{ + public const START_KEY = 'start'; + public const CONNECT_KEY = 'connect'; + public const SEND_START_KEY = 'send_start'; + public const SEND_END_KEY = 'send_end'; + public const RECEIVE_START_KEY = 'receive_start'; + public const RECEIVE_END_KEY = 'receive_end'; + + public readonly array $result; + + public function __construct( + ?Request $request, + ?Response $response = null, + ?Throwable $requestException = null, + ?Throwable $uncaughtException = null, + ) { + $this->result = [ + 'request' => self::convertRequest($request), + 'response' => self::convertResponse($response), + 'requestException' => self::convertRequestException($request, $requestException), + 'uncaughtException' => self::convertUncaughtException($uncaughtException), + ]; + } + + public function offsetExists(mixed $offset): bool + { + return \in_array($offset, ['request', 'response', 'request_exception', 'uncaught_exception'], true); + } + + public function offsetGet(mixed $offset): mixed + { + if (!\array_key_exists($offset, $this->result)) { + throw new \RuntimeException('invalid offset. supported: request, response, request_exception, uncaught_exception'); + } + return $this->result[$offset]; + } + + public function offsetSet(mixed $offset, mixed $value): void + { + throw new \LogicException('cannot set result after created'); + } + + public function offsetUnset(mixed $offset): void + { + throw new \LogicException('cannot unset result after created'); + } + + public function jsonSerialize(): mixed + { + return $this->result; + } + + public function __toString(): string + { + return \json_encode($this->result, JSON_THROW_ON_ERROR); + } + + /** + * Conversion request to array + * + * @param Request|null $request + * @return array|null + */ + private static function convertRequest(?Request $request): array|null + { + if (\is_null($request)) { + return null; + } + + return [ + 'protocolVersion' => $request->getProtocolVersions()[0], + 'method' => $request->getMethod(), + 'uri' => (string) $request->getUri(), + 'pathTag' => $request->hasHeader('Path-Tag') ? $request->getHeader('Path-Tag') : $request->getUri()->getPath(), + 'size' => $request->getBody()->getContentLength(), + 'headers' => $request->getHeaders(), + ]; + } + + /** + * Conversion response to array + * + * @param Response|null $response + * @return array|null + */ + private static function convertResponse(?Response $response): array|null + { + if (\is_null($response)) { + return null; + } + + return [ + 'statusCode' => $response->getStatus(), + 'reasonPhrase' => $response->getReason(), + 'size' => -1, + 'timing' => [ + 'start' => $response->getRequest()->getAttribute(self::START_KEY), + 'connect' => $response->getRequest()->getAttribute(self::CONNECT_KEY), + 'sendStart' => $response->getRequest()->getAttribute(self::SEND_START_KEY), + 'sendEnd' => $response->getRequest()->getAttribute(self::SEND_END_KEY), + 'receiveStart' => $response->getRequest()->getAttribute(self::RECEIVE_START_KEY), + 'receiveEnd' => $response->getRequest()->getAttribute(self::RECEIVE_END_KEY), + ], + 'headers' => $response->getHeaders(), + ]; + } + + /** + * Conversion request exception to array + * + * @param Request|null $request + * @param Throwable|null $requestException + * @return array|null + */ + private static function convertRequestException(?Request $request, ?Throwable $requestException): array|null + { + if (\is_null($requestException)) { + return null; + } + + \assert(!\is_null($request), 'request must be not null if request exception is not null'); + + return self::convertException($requestException); + } + + /** + * Conversion uncaught exception to array + * + * @param Throwable|null $uncaughtException + * @return array|null + */ + private static function convertUncaughtException(?Throwable $uncaughtException): array|null + { + if (\is_null($uncaughtException)) { + return null; + } + + return self::convertException($uncaughtException); + } + + /** + * Conversion exception to array + * + * @param Throwable $exception + * @return array + */ + private static function convertException(Throwable $exception): array + { + return [ + 'class' => \get_class($exception), + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + 'previousMessage' => $exception->getPrevious() ? $exception->getPrevious()->getMessage() : null, + ]; + } +} diff --git a/src/HttpClient/MiddlewareHandler.php b/src/HttpClient/MiddlewareHandler.php index 5f52a6d..c55b8ad 100644 --- a/src/HttpClient/MiddlewareHandler.php +++ b/src/HttpClient/MiddlewareHandler.php @@ -1,11 +1,11 @@ middleware)($request, $handler); } + /** + * @param RequestInterface $request + * @return ResponseInterface + */ public function __invoke(RequestInterface $request): ResponseInterface { return $this->handle($request); diff --git a/src/HttpClient/MiddlewareQueue.php b/src/HttpClient/MiddlewareQueue.php index 4727d48..40e2de7 100644 --- a/src/HttpClient/MiddlewareQueue.php +++ b/src/HttpClient/MiddlewareQueue.php @@ -1,11 +1,11 @@ io->createTable(); - $rows = []; - foreach ($profiler->getResults() as $path => $results) { - $pathInfo = \explode('-', $path, 2); - \assert(\count($pathInfo) === 2); - $rows[] = [...$pathInfo, ...$this->getProfileRow($results)]; - } $table - ->setHeaders(['Method', 'Path', 'Count', 'Min', 'Max', 'Median', 'Average']) - ->addRows($rows) + ->addRows($results) ->render(); + return; - $table = $this->io->createTable(); - $rows = \array_map( - fn (Throwable $exception): array => $this->getExceptionRow($exception), - $profiler->getExceptions(), - ); - if (\count($rows) > 0) { - $this->io->error('Some requests has Error'); - $table - ->setHeaders(['Class', 'Path', 'Message', 'Body']) - ->addRows($rows) - ->render(); - } + // TODO: remove + // $rows = []; + // foreach ($profiler->getResults() as $path => $results) { + // $pathInfo = \explode('-', $path, 2); + // \assert(\count($pathInfo) === 2); + // $rows[] = [...$pathInfo, ...$this->getProfileRow($results)]; + // } + // $table + // ->setHeaders(['Method', 'Path', 'Count', 'Min', 'Max', 'Median', 'Average']) + // ->addRows($rows) + // ->render(); + + // $table = $this->io->createTable(); + // $rows = \array_map( + // fn (Throwable $exception): array => $this->getExceptionRow($exception), + // $profiler->getExceptions(), + // ); + // if (\count($rows) > 0) { + // $this->io->error('Some requests has Error'); + // $table + // ->setHeaders(['Class', 'Path', 'Message', 'Body']) + // ->addRows($rows) + // ->render(); + // } } - private function getProfileRow(array $results): array - { - $totalTimeList = array_map( - function (HttpResultInterface $result): float { - $curlInfo = $result->getCurlInfo(); - if (\is_null($curlInfo)) { - return 0.0; - } - return \round(\intval($curlInfo['total_time_us']) / 10) / 100; - }, - $results, - ); + // private function getProfileRow(array $results): array + // { + // $totalTimeList = array_map( + // function ($result): float { + // $curlInfo = $result->getCurlInfo(); + // if (\is_null($curlInfo)) { + // return 0.0; + // } + // return \round(\intval($curlInfo['total_time_us']) / 10) / 100; + // }, + // $results, + // ); - if (\count($totalTimeList) === 0) { - return [0, 0.0, 0.0, 0.0, 0.0]; - } + // if (\count($totalTimeList) === 0) { + // return [0, 0.0, 0.0, 0.0, 0.0]; + // } - \sort($totalTimeList, \SORT_NUMERIC | \SORT_ASC); + // \sort($totalTimeList, \SORT_NUMERIC | \SORT_ASC); - return [ - \count($totalTimeList), - $totalTimeList[0], - $totalTimeList[\count($totalTimeList) - 1], - $totalTimeList[\count($totalTimeList) / 2], - \array_sum($totalTimeList) / \count($totalTimeList), - ]; - } + // return [ + // \count($totalTimeList), + // $totalTimeList[0], + // $totalTimeList[\count($totalTimeList) - 1], + // $totalTimeList[\count($totalTimeList) / 2], + // \array_sum($totalTimeList) / \count($totalTimeList), + // ]; + // } - private function getExceptionRow(Throwable $exception): array - { - if ($exception instanceof RequestException) { - return [ - 'RequestException', - $exception->getRequest()->getUri()->__toString(), - $exception->getMessage(), - $exception->getResponse()?->getBody()->__toString(), - ]; - } - if ($exception instanceof ResponseAssertionException) { - return [ - 'ResponseAssertionException', - $exception->getRequest()->getUri()->__toString(), - $exception->getMessage(), - $exception->getResponse()->getBody()->__toString(), - ]; - } - return [ - \get_class($exception), - '', - $exception->getMessage(), - '', - ]; - } + // private function getExceptionRow(Throwable $exception): array + // { + // if ($exception instanceof RequestException) { + // return [ + // 'RequestException', + // $exception->getRequest()->getUri()->__toString(), + // $exception->getMessage(), + // $exception->getResponse()?->getBody()->__toString(), + // ]; + // } + // if ($exception instanceof ResponseAssertionException) { + // return [ + // 'ResponseAssertionException', + // $exception->getRequest()->getUri()->__toString(), + // $exception->getMessage(), + // $exception->getResponse()->getBody()->__toString(), + // ]; + // } + // return [ + // \get_class($exception), + // '', + // $exception->getMessage(), + // '', + // ]; + // } } diff --git a/src/Scenario/HttpProfiler.php b/src/Scenario/HttpProfiler.php deleted file mode 100644 index 2c34199..0000000 --- a/src/Scenario/HttpProfiler.php +++ /dev/null @@ -1,68 +0,0 @@ - */ - private array $results; - - /** @var Throwable[] */ - private array $exceptions; - - public function __construct() - { - $this->results = []; - $this->exceptions = []; - } - - public function getResults(): array - { - return [...$this->results]; - } - - public function getExceptions(): array - { - return [...$this->exceptions]; - } - - public function profileException(Throwable $exception): void - { - $this->exceptions[] = $exception; - } - - public function profile( - float $startMicrotime, - float $endMicrotime, - RequestInterface $request, - ResponseInterface $response, - ): HttpResult { - $result = new HttpResult( - $startMicrotime, - $endMicrotime, - $request, - $response, - ); - $method = $request->getMethod(); - $path = $method . '-' . ($request->hasHeader('Path-Tag') ? $request->getHeaderLine('Path-Tag') : $request->getUri()->getPath()); - if (!\array_key_exists($path, $this->results)) { - $this->results[$path] = []; - } - $this->results[$path][] = $result; - return $result; - } -} diff --git a/src/Scenario/HttpResult.php b/src/Scenario/HttpResult.php deleted file mode 100644 index 9eab6e3..0000000 --- a/src/Scenario/HttpResult.php +++ /dev/null @@ -1,227 +0,0 @@ - - * } - */ - public readonly array $request; - - /** - * Response information - * - * @var null|array{ - * statusCode: int, - * reasonPhrase: string, - * bodyLength: int, - * headers: array - * } - */ - public readonly ?array $response; - - /** - * Exception information - * - * @var null|array{ - * name: string, - * message: string, - * code: int|string, - * previousMessage: ?string - * } - */ - public readonly ?array $exception; - - /** - * Curl result information - * - * @var array|null - */ - public readonly ?array $curlInfo; - - public function __construct( - public readonly float $startMicrotime, - public readonly float $endMicrotime, - RequestInterface $request, - ?ResponseInterface $response = null, - ?Throwable $exception = null, - ) { - $this->request = self::createRequestToArray($request); - $this->response = self::createResponseToArray($response); - $this->exception = self::createExceptionToArray($exception); - $this->curlInfo = self::convertCurlInfo($response); - } - - public function getRequest(): array - { - return $this->request; - } - - public function getResponse(): ?array - { - return $this->response; - } - - public function getException(): ?array - { - return $this->exception; - } - - public function getCurlInfo(): ?array - { - return $this->curlInfo; - } - - public function jsonSerialize(): mixed - { - return [ - 'startMicrotime' => $this->startMicrotime, - 'endMicrotime' => $this->endMicrotime, - 'request' => $this->request, - 'response' => $this->response, - 'exception' => $this->exception, - 'curlInfo' => $this->curlInfo, - ]; - } - - public function __toString(): string - { - $prefix = \sprintf('%s %s', $this->request['method'], $this->request['path']); - - if (!\is_null($this->exception)) { - return \sprintf('%s: Exception: %s %s', $prefix, $this->exception['name'], $this->exception['message']); - } - \assert(!\is_null($this->response)); - return \sprintf( - '%s: Succeded: %s %s', - $prefix, - $this->response['statusCode'], - $this->response['reasonPhrase'], - ); - } - - /** - * Creates request informarion - * - * @param RequestInterface $request - * @return array{ - * protocolVersion: string, - * method: string, - * uri: string, - * path: string, - * headers: array - * } - */ - private static function createRequestToArray(RequestInterface $request): array - { - return [ - 'protocolVersion' => $request->getProtocolVersion(), - 'method' => $request->getMethod(), - 'uri' => $request->getUri()->__toString(), - 'path' => $request->getUri()->getPath(), - 'headers' => $request->getHeaders(), - ]; - } - - /** - * Creates response information - * - * @param ResponseInterface|null $response - * @return null|array{ - * statusCode: int, - * reasonPhrase: string, - * bodyLength: int, - * headers: array - * } - */ - private static function createResponseToArray(?ResponseInterface $response): ?array - { - if (\is_null($response)) { - return null; - } - - $body = $response->getBody()->__toString(); - - return [ - 'statusCode' => $response->getStatusCode(), - 'reasonPhrase' => $response->getReasonPhrase(), - 'bodyLength' => \function_exists('mb_strlen') ? \mb_strlen($body) : \strlen($body), - 'headers' => $response->getHeaders(), - ]; - } - - /** - * Creates exception information - * - * @param Throwable|null $exception - * @return null|array{ - * name: string, - * message: string, - * code: int|string, - * previousMessage: ?string - * } - */ - private static function createExceptionToArray(?Throwable $exception): ?array - { - if (\is_null($exception)) { - return null; - } - - return [ - 'name' => \get_class($exception), - 'message' => $exception->getMessage(), - 'code' => $exception->getCode(), - 'previousMessage' => $exception->getPrevious()?->getMessage(), - ]; - } - - /** - * Get Curl information from Response header - * - * @param ResponseInterface|null $response - * @return array|null - */ - private static function convertCurlInfo(?ResponseInterface $response): ?array - { - if (\is_null($response) || !$response->hasHeader(self::CURL_INFO_HEADER)) { - return null; - } - - $info = \json_decode( - $response->getHeaderLine(self::CURL_INFO_HEADER), - true, - 512, - \JSON_THROW_ON_ERROR | \JSON_UNESCAPED_UNICODE, - ); - - \assert(\is_array($info)); - - return $info; - } -} diff --git a/src/Scenario/RequestException.php b/src/Scenario/RequestException.php deleted file mode 100644 index fa020bf..0000000 --- a/src/Scenario/RequestException.php +++ /dev/null @@ -1,38 +0,0 @@ -request; - } - - public function getResponse(): ?ResponseInterface - { - return $this->response; - } -} diff --git a/src/Web/Controllers/IndexController.php b/src/Web/Controllers/IndexController.php deleted file mode 100644 index a6eac52..0000000 --- a/src/Web/Controllers/IndexController.php +++ /dev/null @@ -1,26 +0,0 @@ -getBody()->write($body); - return $response->withAddedHeader('Content-Type', 'application/json; charset=UTF-8') - ->withAddedHeader('Content-Length', \strval(\strlen($body))); - } -} diff --git a/tests/Integration/Console/Commands/GenerateStubCommandTest.php b/tests/Integration/Console/Commands/GenerateStubCommandTest.php deleted file mode 100644 index 982b407..0000000 --- a/tests/Integration/Console/Commands/GenerateStubCommandTest.php +++ /dev/null @@ -1,28 +0,0 @@ -expectException(\LogicException::class); - - $command = new GenerateStubCommand(); - $commandTester = new CommandTester($command); - - $commandTester->execute([], []); - } -} diff --git a/tests/Integration/SimpleScenarioTest.php b/tests/Integration/SimpleScenarioTest.php index 033d630..03fe5ec 100644 --- a/tests/Integration/SimpleScenarioTest.php +++ b/tests/Integration/SimpleScenarioTest.php @@ -9,6 +9,7 @@ namespace Heavyrain\Tests\Integration; use Heavyrain\Contracts\ClientInterface; +use Heavyrain\Contracts\HttpClientInterface; use Heavyrain\Scenario\AssertableResponse; use Heavyrain\Scenario\Client; use Heavyrain\Scenario\RequestBuilder; @@ -18,7 +19,6 @@ use Laminas\Diactoros\UriFactory; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; -use Psr\Http\Client\ClientInterface as PsrClientInterface; use Psr\Http\Message\ResponseInterface; #[CoversClass(AssertableResponse::class)] @@ -29,8 +29,8 @@ final class SimpleScenarioTest extends TestCase #[Test] public function run_simple_scenario(): void { - /** @var \PHPUnit\Framework\MockObject\MockObject&PsrClientInterface */ - $psrClient = $this->createMock(PsrClientInterface::class); + /** @var \PHPUnit\Framework\MockObject\MockObject&HttpClientInterface */ + $httpClient = $this->createMock(HttpClientInterface::class); $builder = new RequestBuilder( new UriFactory(), new StreamFactory(), @@ -39,12 +39,12 @@ public function run_simple_scenario(): void ); $response = $this->createMock(ResponseInterface::class); - $psrClient->expects($this->once()) + $httpClient->expects($this->once()) ->method('sendRequest') ->withAnyParameters() ->willReturn($response); - $cl = new Client($psrClient, $builder); + $cl = new Client($httpClient, $builder); $func = static function (ClientInterface $cl): void { $cl->get('/'); diff --git a/tests/Unit/Executor/ExecutorConfigTest.php b/tests/Unit/Executor/ExecutorConfigTest.php deleted file mode 100644 index 4d0b8f2..0000000 --- a/tests/Unit/Executor/ExecutorConfigTest.php +++ /dev/null @@ -1,76 +0,0 @@ -baseUri); - self::assertSame($scenarioConfig, $config->scenarioConfig); - self::assertSame($userAgentBase, $config->userAgentBase); - self::assertSame($waitAfterScenarioSec, $config->waitAfterScenarioSec); - self::assertSame($waitAfterSendRequestSec, $config->waitAfterSendRequestSec); - self::assertSame($sslVerify, $config->sslVerify); - self::assertSame($timeout, $config->timeout); - } - - #[Test] - public function testInvalidWaitAfterScenarioSec(): void - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('waitAfterScenarioSec must be positive-numeric'); - - new ExecutorConfig('', new DefaultScenarioConfig(), '', -1); - } - - #[Test] - public function testInvalidWaitAfterSendRequestSec(): void - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('waitAfterSendRequestSec must be positive-numeric'); - - new ExecutorConfig('', new DefaultScenarioConfig(), '', 1, -1); - } - - #[Test] - public function testInvalidTimeout(): void - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('timeout must be positive-numeric'); - - new ExecutorConfig('', new DefaultScenarioConfig(), '', 1, 1, false, -1); - } -} diff --git a/tests/Unit/Executor/ExecutorFactoryTest.php b/tests/Unit/Executor/ExecutorFactoryTest.php deleted file mode 100644 index a21656f..0000000 --- a/tests/Unit/Executor/ExecutorFactoryTest.php +++ /dev/null @@ -1,45 +0,0 @@ -createMock(BuzzClientInterface::class); - - $factory = new ExecutorFactory($config, static fn () => 1, $profiler); - - $actual = $factory->createSync($buzzClient); - - self::assertInstanceOf(SyncExecutor::class, $actual); - } -} diff --git a/tests/Unit/Executor/Middlewares/WaitSendRequestMiddlewareTest.php b/tests/Unit/Executor/Middlewares/WaitSendRequestMiddlewareTest.php deleted file mode 100644 index 47c4b20..0000000 --- a/tests/Unit/Executor/Middlewares/WaitSendRequestMiddlewareTest.php +++ /dev/null @@ -1,41 +0,0 @@ -handleRequest( - $this->createMock(RequestInterface::class), - static function (RequestInterface $request): mixed { return 'A'; }, - ); - - self::assertSame('A', $result); - - $result2 = $middleware->handleResponse( - $this->createMock(RequestInterface::class), - $this->createMock(ResponseInterface::class), - static function (RequestInterface $request, ResponseInterface $response): mixed { return 'B'; }, - ); - - self::assertSame('B', $result2); - } -} diff --git a/tests/Unit/Executor/SyncExecutorTest.php b/tests/Unit/Executor/SyncExecutorTest.php deleted file mode 100644 index a13080c..0000000 --- a/tests/Unit/Executor/SyncExecutorTest.php +++ /dev/null @@ -1,48 +0,0 @@ -createMock(ClientInterface::class); - $token = new CancellationToken(); - - $exception = new \RuntimeException('ERROR'); - $scenarioFunction = static function (ClientInterface $cl) use ($token, $exception): void { - self::assertInstanceOf(ClientInterface::class, $cl); - $token->cancel(); - throw $exception; - }; - - $executor = new SyncExecutor($config, $scenarioFunction, $profiler, $cl); - - $executor->execute($token); - - self::assertCount(1, $profiler->getExceptions()); - self::assertSame($exception, $profiler->getExceptions()[0]); - } -} diff --git a/tests/Unit/HttpClient/MiddlewareHandlerTest.php b/tests/Unit/HttpClient/MiddlewareHandlerTest.php new file mode 100644 index 0000000..a6132b4 --- /dev/null +++ b/tests/Unit/HttpClient/MiddlewareHandlerTest.php @@ -0,0 +1,53 @@ + + $this->createStub(ResponseInterface::class); + + $middlewareHandler = new MiddlewareHandler($handler); + + $actual = $middlewareHandler->handle($this->createStub(RequestInterface::class)); + + self::assertInstanceOf(ResponseInterface::class, $actual); + + $actual2 = $middlewareHandler($this->createStub(RequestInterface::class)); + + self::assertInstanceOf(ResponseInterface::class, $actual2); + } + + #[Test] + public function testSomeMiddlewares(): void + { + $handler = fn (RequestInterface $request): ResponseInterface => + $this->createStub(ResponseInterface::class); + $middleware = function (RequestInterface $request, callable $next): ResponseInterface { + self::assertTrue(true); + return $next($request); + }; + + $middlewareHandler = new MiddlewareHandler($handler, $middleware); + + $actual = $middlewareHandler->handle($this->createStub(RequestInterface::class)); + + self::assertInstanceOf(ResponseInterface::class, $actual); + } +} diff --git a/tests/Unit/Scenario/ClientTest.php b/tests/Unit/Scenario/ClientTest.php index 3b70714..36d0609 100644 --- a/tests/Unit/Scenario/ClientTest.php +++ b/tests/Unit/Scenario/ClientTest.php @@ -8,12 +8,12 @@ namespace Heavyrain\Scenario; +use Heavyrain\Contracts\HttpClientInterface; use Heavyrain\Contracts\RequestBuilderInterface; use Heavyrain\Tests\TestCase; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\Attributes\UsesClass; -use Psr\Http\Client\ClientInterface; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; @@ -25,7 +25,7 @@ final class ClientTest extends TestCase public function testWait(): void { $cl = new Client( - $this->createMock(ClientInterface::class), + $this->createMock(HttpClientInterface::class), $this->createMock(RequestBuilderInterface::class), ); @@ -129,13 +129,13 @@ public function testPatchJson(): void private function createClient(string $method, string $path, string $bodyMethod, string|array $bodyWith): Client { - /** @var \PHPUnit\Framework\MockObject\MockObject&ClientInterface */ - $psrCl = $this->createMock(ClientInterface::class); + /** @var \PHPUnit\Framework\MockObject\MockObject&HttpClientInterface */ + $cl = $this->createMock(HttpClientInterface::class); /** @var \PHPUnit\Framework\MockObject\MockObject&RequestBuilderInterface */ $builder = $this->createMock(RequestBuilderInterface::class); $request = $this->createMock(RequestInterface::class); - $psrCl->expects($this->once()) + $cl->expects($this->once()) ->method('sendRequest') ->with($request) ->willReturn($this->createMock(ResponseInterface::class)); @@ -157,6 +157,6 @@ private function createClient(string $method, string $path, string $bodyMethod, ->with() ->willReturn($request); - return new Client($psrCl, $builder); + return new Client($cl, $builder); } } diff --git a/tests/Unit/Scenario/RequestExceptionTest.php b/tests/Unit/Scenario/RequestExceptionTest.php deleted file mode 100644 index fa84c1d..0000000 --- a/tests/Unit/Scenario/RequestExceptionTest.php +++ /dev/null @@ -1,32 +0,0 @@ -getRequest()); - self::assertSame($response, $e->getResponse()); - self::assertSame($previous, $e->getPrevious()); - } -}