From d79d66d501fb70296a638d75c3f7879529182172 Mon Sep 17 00:00:00 2001 From: Joel Wurtz Date: Wed, 10 Apr 2024 13:36:43 +0200 Subject: [PATCH] feat(http): add http timeout attribute --- composer.json | 2 +- src/Assert/AssertCaseTrait.php | 37 +---------------------- src/Assert/Assertion.php | 10 +++--- src/Attribute/HttpClientConfiguration.php | 13 ++++++++ src/Command/AsynitCommand.php | 6 ---- src/HttpClient/ApiResponse.php | 4 +-- src/HttpClient/HttpClientApiCaseTrait.php | 14 ++++----- src/HttpClient/HttpClientCaseTrait.php | 37 ++++++++++++++++++++--- src/Output/Chain.php | 12 -------- src/Output/Count.php | 12 -------- src/Output/OutputInterface.php | 2 -- src/Test.php | 5 ++- tests/HttpTimeoutTest.php | 27 +++++++++++++++++ tests/WebAndApiTests.php | 1 - 14 files changed, 89 insertions(+), 93 deletions(-) create mode 100644 src/Attribute/HttpClientConfiguration.php create mode 100644 tests/HttpTimeoutTest.php diff --git a/composer.json b/composer.json index fe7d46c..d0dd2bd 100644 --- a/composer.json +++ b/composer.json @@ -39,7 +39,7 @@ }, "require-dev": { "amphp/http-client": "^5.0.1", - "friendsofphp/php-cs-fixer": "^3.16.0", + "friendsofphp/php-cs-fixer": "^3.53.0", "phpstan/phpstan": "^1.10" }, "conflict": { diff --git a/src/Assert/AssertCaseTrait.php b/src/Assert/AssertCaseTrait.php index c42b1a2..eb865bd 100644 --- a/src/Assert/AssertCaseTrait.php +++ b/src/Assert/AssertCaseTrait.php @@ -61,7 +61,6 @@ public function setUpTest(Test $test): void /** * Asserts that an array has a specified key. * - * @param mixed $key * @param array|\ArrayAccess $array * @param string $message */ @@ -73,7 +72,6 @@ public function assertArrayHasKey($key, $array, $message = null) /** * Asserts that an array does not have a specified key. * - * @param mixed $key * @param array|\ArrayAccess $array * @param string $message */ @@ -89,8 +87,6 @@ public function assertArrayNotHasKey($key, $array, $message = null) * $checkForNonObjectIdentity to a non-default value will cause a fallback * to PHPUnit's constraint. * - * @param mixed $needle - * @param mixed $haystack * @param string $message */ public function assertContains($needle, $haystack, $message = null) @@ -105,8 +101,6 @@ public function assertContains($needle, $haystack, $message = null) * $checkForNonObjectIdentity to a non-default value will cause a fallback * to PHPUnit's constraint. * - * @param mixed $needle - * @param mixed $haystack * @param string $message */ public function assertNotContains($needle, $haystack, $message = null) @@ -178,7 +172,6 @@ public function assertNotContainsOnly($type, $haystack, $isNativeType = null, $m * Asserts the number of elements of an array, Countable or Traversable. * * @param int $expectedCount - * @param mixed $haystack * @param string $message */ public function assertCount($expectedCount, $haystack, $message = null) @@ -190,7 +183,6 @@ public function assertCount($expectedCount, $haystack, $message = null) * Asserts the number of elements of an array, Countable or Traversable. * * @param int $expectedCount - * @param mixed $haystack * @param string $message */ public function assertNotCount($expectedCount, $haystack, $message = null) @@ -204,8 +196,6 @@ public function assertNotCount($expectedCount, $haystack, $message = null) * Please note that setting $canonicalize or $ignoreCase to true will cause * a fallback to PHPUnit's constraint. * - * @param mixed $expected - * @param mixed $actual * @param string $message * @param float $delta */ @@ -220,8 +210,6 @@ public function assertEquals($expected, $actual, $message = null, $delta = 0.0) * Please note that setting $canonicalize or $ignoreCase to true will cause * a fallback to PHPUnit's constraint. * - * @param mixed $expected - * @param mixed $actual * @param string $message * @param float $delta */ @@ -233,7 +221,6 @@ public function assertNotEquals($expected, $actual, $message = null, $delta = 0. /** * Asserts that a variable is empty. * - * @param mixed $actual * @param string $message */ public function assertEmpty($actual, $message = null) @@ -244,7 +231,6 @@ public function assertEmpty($actual, $message = null) /** * Asserts that a variable is not empty. * - * @param mixed $actual * @param string $message */ public function assertNotEmpty($actual, $message = null) @@ -255,8 +241,6 @@ public function assertNotEmpty($actual, $message = null) /** * Asserts that a value is greater than another value. * - * @param mixed $expected - * @param mixed $actual * @param string $message */ public function assertGreaterThan($expected, $actual, $message = null) @@ -267,8 +251,6 @@ public function assertGreaterThan($expected, $actual, $message = null) /** * Asserts that a value is greater than or equal to another value. * - * @param mixed $expected - * @param mixed $actual * @param string $message */ public function assertGreaterThanOrEqual($expected, $actual, $message = null) @@ -279,8 +261,6 @@ public function assertGreaterThanOrEqual($expected, $actual, $message = null) /** * Asserts that a value is smaller than another value. * - * @param mixed $expected - * @param mixed $actual * @param string $message */ public function assertLessThan($expected, $actual, $message = null) @@ -291,8 +271,6 @@ public function assertLessThan($expected, $actual, $message = null) /** * Asserts that a value is smaller than or equal to another value. * - * @param mixed $expected - * @param mixed $actual * @param string $message */ public function assertLessThanOrEqual($expected, $actual, $message = null) @@ -347,7 +325,6 @@ public function assertFalse($condition, $message = null) /** * Asserts that a variable is not null. * - * @param mixed $actual * @param string $message */ public function assertNotNull($actual, $message = null) @@ -358,7 +335,6 @@ public function assertNotNull($actual, $message = null) /** * Asserts that a variable is null. * - * @param mixed $actual * @param string $message */ public function assertNull($actual, $message = null) @@ -369,7 +345,6 @@ public function assertNull($actual, $message = null) /** * Asserts that a variable is finite. * - * @param mixed $actual * @param string $message */ public function assertFinite($actual, $message = null) @@ -380,7 +355,6 @@ public function assertFinite($actual, $message = null) /** * Asserts that a variable is infinite. * - * @param mixed $actual * @param string $message */ public function assertInfinite($actual, $message = null) @@ -391,7 +365,6 @@ public function assertInfinite($actual, $message = null) /** * Asserts that a variable is nan. * - * @param mixed $actual * @param string $message */ public function assertNan($actual, $message = null) @@ -404,8 +377,6 @@ public function assertNan($actual, $message = null) * Used on objects, it asserts that two variables reference * the same object. * - * @param mixed $expected - * @param mixed $actual * @param string $message */ public function assertSame($expected, $actual, $message = null) @@ -418,8 +389,6 @@ public function assertSame($expected, $actual, $message = null) * Used on objects, it asserts that two variables do not reference * the same object. * - * @param mixed $expected - * @param mixed $actual * @param string $message */ public function assertNotSame($expected, $actual, $message = null) @@ -431,7 +400,6 @@ public function assertNotSame($expected, $actual, $message = null) * Asserts that a variable is of a given type. * * @param string $expected - * @param mixed $actual * @param string $message */ public function assertInstanceOf($expected, $actual, $message = null) @@ -443,7 +411,6 @@ public function assertInstanceOf($expected, $actual, $message = null) * Asserts that a variable is not of a given type. * * @param string $expected - * @param mixed $actual * @param string $message */ public function assertNotInstanceOf($expected, $actual, $message = null) @@ -455,7 +422,6 @@ public function assertNotInstanceOf($expected, $actual, $message = null) * Asserts that a variable is of a given type. * * @param string $expected - * @param mixed $actual * @param string $message */ public function assertInternalType($expected, $actual, $message = null) @@ -467,7 +433,6 @@ public function assertInternalType($expected, $actual, $message = null) * Asserts that a variable is not of a given type. * * @param string $expected - * @param mixed $actual * @param string $message */ public function assertNotInternalType($expected, $actual, $message = null) @@ -568,7 +533,7 @@ public function assertContainsSubset(array $other, array $subset, bool $strict = $this->assert($subset, containsSubset($other, $strict), $message); } - public function assert($value, callable $predicate, string $description = null): bool + public function assert($value, callable $predicate, ?string $description = null): bool { return (new Assertion($value, exporter(), $this->test)) ->evaluate(counting(Predicate::castFrom($predicate)), $description); diff --git a/src/Assert/Assertion.php b/src/Assert/Assertion.php index bb83fe4..8c9e2b1 100644 --- a/src/Assert/Assertion.php +++ b/src/Assert/Assertion.php @@ -20,8 +20,6 @@ class Assertion extends BaseAssertion /** * constructor. - * - * @param mixed $value */ public function __construct($value, Exporter $exporter, Test $test) { @@ -32,7 +30,7 @@ public function __construct($value, Exporter $exporter, Test $test) $this->test = $test; } - public function evaluate(Predicate $predicate, string $description = null): bool + public function evaluate(Predicate $predicate, ?string $description = null): bool { try { $result = parent::evaluate($predicate, $description); @@ -60,10 +58,10 @@ public function evaluate(Predicate $predicate, string $description = null): bool /** * creates failure description when value failed the test with given predicate. * - * @param \bovigo\assert\predicate\Predicate $predicate predicate that failed - * @param string $description additional description for failure message + * @param Predicate $predicate predicate that failed + * @param string $description additional description for failure message */ - private function describeSuccess(Predicate $predicate, string $description = null): string + private function describeSuccess(Predicate $predicate, ?string $description = null): string { if ($description) { return $description; diff --git a/src/Attribute/HttpClientConfiguration.php b/src/Attribute/HttpClientConfiguration.php new file mode 100644 index 0000000..154432f --- /dev/null +++ b/src/Attribute/HttpClientConfiguration.php @@ -0,0 +1,13 @@ +defaultBootstrapFilename = getcwd().'/vendor/autoload.php'; @@ -35,9 +32,6 @@ protected function configure(): void ; } - /** - * {@inheritdoc} - */ protected function execute(InputInterface $input, OutputInterface $output): int { /** @var string $bootstrapFilename */ diff --git a/src/HttpClient/ApiResponse.php b/src/HttpClient/ApiResponse.php index ab6578e..2d5b0e8 100644 --- a/src/HttpClient/ApiResponse.php +++ b/src/HttpClient/ApiResponse.php @@ -14,7 +14,7 @@ class ApiResponse extends HttpResponse implements \ArrayAccess /** * @var array|null */ - private array|null $data = null; + private ?array $data = null; public function __construct(private readonly Response $response) { @@ -77,7 +77,7 @@ public function hasHeader(string $name): bool return $this->response->hasHeader($name); } - public function getHeader(string $name): null|string + public function getHeader(string $name): ?string { return $this->response->getHeader($name); } diff --git a/src/HttpClient/HttpClientApiCaseTrait.php b/src/HttpClient/HttpClientApiCaseTrait.php index 51e4c79..f97c3a9 100644 --- a/src/HttpClient/HttpClientApiCaseTrait.php +++ b/src/HttpClient/HttpClientApiCaseTrait.php @@ -13,37 +13,37 @@ protected function getApiContentType(): string return 'application/json'; } - final protected function get(string $uri, array|null $json = null, array $headers = []): ApiResponse + final protected function get(string $uri, ?array $json = null, array $headers = []): ApiResponse { return new ApiResponse($this->sendRequest($this->createApiRequest('GET', $uri, $headers, $json))); } - final protected function post(string $uri, array|null $json = null, array $headers = []): ApiResponse + final protected function post(string $uri, ?array $json = null, array $headers = []): ApiResponse { return new ApiResponse($this->sendRequest($this->createApiRequest('POST', $uri, $headers, $json))); } - final protected function patch(string $uri, array|null $json = null, array $headers = []): ApiResponse + final protected function patch(string $uri, ?array $json = null, array $headers = []): ApiResponse { return new ApiResponse($this->sendRequest($this->createApiRequest('PATCH', $uri, $headers, $json))); } - final protected function put(string $uri, array|null $json = null, array $headers = []): ApiResponse + final protected function put(string $uri, ?array $json = null, array $headers = []): ApiResponse { return new ApiResponse($this->sendRequest($this->createApiRequest('PUT', $uri, $headers, $json))); } - final protected function delete(string $uri, array|null $json = null, array $headers = []): ApiResponse + final protected function delete(string $uri, ?array $json = null, array $headers = []): ApiResponse { return new ApiResponse($this->sendRequest($this->createApiRequest('DELETE', $uri, $headers, $json))); } - final protected function options(string $uri, array|null $json = null, array $headers = []): ApiResponse + final protected function options(string $uri, ?array $json = null, array $headers = []): ApiResponse { return new ApiResponse($this->sendRequest($this->createApiRequest('OPTIONS', $uri, $headers, $json))); } - private function createApiRequest(string $method, string $uri, array $headers = [], array|null $json = null): Request + private function createApiRequest(string $method, string $uri, array $headers = [], ?array $json = null): Request { $request = new Request($uri, $method); $request->addHeader('Content-Type', $this->getApiContentType()); diff --git a/src/HttpClient/HttpClientCaseTrait.php b/src/HttpClient/HttpClientCaseTrait.php index b6208b5..1cc4cf4 100644 --- a/src/HttpClient/HttpClientCaseTrait.php +++ b/src/HttpClient/HttpClientCaseTrait.php @@ -7,21 +7,25 @@ use Amp\Http\Client\HttpClient; use Amp\Http\Client\HttpClientBuilder; use Amp\Http\Client\Request; -use Amp\Http\HttpResponse; +use Amp\Http\Client\Response; use Amp\Socket\ClientTlsContext; use Amp\Socket\ConnectContext; use Asynit\Assert\AssertWebCaseTrait; +use Asynit\Attribute\HttpClientConfiguration; use Asynit\Attribute\OnCreate; +use Asynit\Test; trait HttpClientCaseTrait { use AssertWebCaseTrait; - private HttpClient|null $httpClient = null; + private ?HttpClient $httpClient = null; + + private ?\Closure $configureRequest = null; protected $allowSelfSignedCertificate = false; - protected function createHttpClient(bool $allowSelfSignedCertificate = false): HttpClient + protected function createHttpClient(bool $allowSelfSignedCertificate = false, HttpClientConfiguration $httpClientConfiguration = new HttpClientConfiguration()): HttpClient { $tlsContext = new ClientTlsContext(''); @@ -33,6 +37,7 @@ protected function createHttpClient(bool $allowSelfSignedCertificate = false): H $connectContext = $connectContext->withTlsContext($tlsContext); $builder = new HttpClientBuilder(); + $builder = $builder->retry($httpClientConfiguration->retry); $builder = $builder->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(null, $connectContext))); return $builder->build(); @@ -41,14 +46,36 @@ protected function createHttpClient(bool $allowSelfSignedCertificate = false): H #[OnCreate] final public function setUpHttpClient(): void { - $this->httpClient = $this->createHttpClient($this->allowSelfSignedCertificate); + $reflection = new \ReflectionClass($this); + + $httpClientConfiguration = $reflection->getAttributes(HttpClientConfiguration::class); + + if (!$httpClientConfiguration) { + $httpClientConfiguration = new HttpClientConfiguration(); + } else { + $httpClientConfiguration = $httpClientConfiguration[0]->newInstance(); + } + + $this->httpClient = $this->createHttpClient($this->allowSelfSignedCertificate, $httpClientConfiguration); + + $this->configureRequest = function (Request $request) use ($httpClientConfiguration) { + $request->setInactivityTimeout($httpClientConfiguration->timeout); + $request->setTcpConnectTimeout($httpClientConfiguration->timeout); + $request->setTlsHandshakeTimeout($httpClientConfiguration->timeout); + $request->setTransferTimeout($httpClientConfiguration->timeout); + }; } /** * Allow to test a rejection or a resolution of an async call. */ - final protected function sendRequest(Request $request): HttpResponse + final protected function sendRequest(Request $request): Response { + if (null !== $this->configureRequest) { + $configureRequest = $this->configureRequest; + $configureRequest($request); + } + return $this->httpClient->request($request); } } diff --git a/src/Output/Chain.php b/src/Output/Chain.php index e91fb89..05d4e84 100644 --- a/src/Output/Chain.php +++ b/src/Output/Chain.php @@ -19,9 +19,6 @@ public function addOutput(OutputInterface $output) $this->outputs[] = $output; } - /** - * {@inheritdoc} - */ public function outputStep(Test $test, $debugOutput) { foreach ($this->outputs as $output) { @@ -29,9 +26,6 @@ public function outputStep(Test $test, $debugOutput) } } - /** - * {@inheritdoc} - */ public function outputFailure(Test $test, $debugOutput, $failure) { foreach ($this->outputs as $output) { @@ -39,9 +33,6 @@ public function outputFailure(Test $test, $debugOutput, $failure) } } - /** - * {@inheritdoc} - */ public function outputSuccess(Test $test, $debugOutput) { foreach ($this->outputs as $output) { @@ -49,9 +40,6 @@ public function outputSuccess(Test $test, $debugOutput) } } - /** - * {@inheritdoc} - */ public function outputSkipped(Test $test, $debugOutput) { foreach ($this->outputs as $output) { diff --git a/src/Output/Count.php b/src/Output/Count.php index 313b2a3..74b39e4 100644 --- a/src/Output/Count.php +++ b/src/Output/Count.php @@ -12,32 +12,20 @@ class Count implements OutputInterface private $failed = 0; private $skipped = 0; - /** - * {@inheritdoc} - */ public function outputStep(Test $test, $debugOutput) { } - /** - * {@inheritdoc} - */ public function outputFailure(Test $test, $debugOutput, $failure) { ++$this->failed; } - /** - * {@inheritdoc} - */ public function outputSuccess(Test $test, $debugOutput) { ++$this->succeed; } - /** - * {@inheritdoc} - */ public function outputSkipped(Test $test, $debugOutput) { ++$this->skipped; diff --git a/src/Output/OutputInterface.php b/src/Output/OutputInterface.php index 9421a0b..589a569 100644 --- a/src/Output/OutputInterface.php +++ b/src/Output/OutputInterface.php @@ -14,8 +14,6 @@ public function outputStep(Test $test, $debugOutput); /** * @param string $debugOutput * @param \Throwable|\Exception $failure - * - * @return mixed */ public function outputFailure(Test $test, $debugOutput, $failure); diff --git a/src/Test.php b/src/Test.php index 697cd52..136a64b 100644 --- a/src/Test.php +++ b/src/Test.php @@ -30,10 +30,9 @@ class Test public function __construct( private readonly \ReflectionMethod $method, - string $identifier = null, + ?string $identifier = null, public readonly bool $isRealTest = true, - ) - { + ) { $this->identifier = $identifier ?: sprintf( '%s::%s', $this->method->getDeclaringClass()->getName(), diff --git a/tests/HttpTimeoutTest.php b/tests/HttpTimeoutTest.php new file mode 100644 index 0000000..688e369 --- /dev/null +++ b/tests/HttpTimeoutTest.php @@ -0,0 +1,27 @@ +get('http://127.0.0.1:8081/delay/1'); + $hasException = false; + } catch (\Exception $e) { + $this->assertInstanceOf(\Amp\Http\Client\HttpException::class, $e); + $hasException = true; + } + + $this->assertTrue($hasException); + } +} diff --git a/tests/WebAndApiTests.php b/tests/WebAndApiTests.php index 2cce93a..6e167a6 100644 --- a/tests/WebAndApiTests.php +++ b/tests/WebAndApiTests.php @@ -7,7 +7,6 @@ use Asynit\HttpClient\ApiResponse; use Asynit\HttpClient\HttpClientApiCaseTrait; use Asynit\HttpClient\HttpClientWebCaseTrait; -use Psr\Http\Message\ResponseInterface; #[TestCase] class WebAndApiTests