From 52264c0883226d1a61c6796e789b535077e033ef Mon Sep 17 00:00:00 2001 From: Ruud Kamphuis Date: Thu, 31 Oct 2024 13:31:37 +0100 Subject: [PATCH 1/2] Refactor This bumps the minimum required PHP version to 8.2. PHPSpec is remover in favor of PHPUnit. Scrutinizer CI is removed because we no longer have coverage. --- .gitattributes | 6 +- .github/workflows/tests.yml | 77 +++---------------- .gitignore | 4 +- .scrutinizer.yml | 8 -- composer.json | 24 +++--- phpspec.yml.ci | 10 --- phpspec.yml.dist | 5 -- phpunit.xml | 26 +++++++ spec/LoggerPluginSpec.php | 149 ------------------------------------ src/LoggerPlugin.php | 28 +++---- tests/LoggerPluginTest.php | 83 ++++++++++++++++++++ tests/TestLogger.php | 55 +++++++++++++ 12 files changed, 202 insertions(+), 273 deletions(-) delete mode 100644 .scrutinizer.yml delete mode 100644 phpspec.yml.ci delete mode 100644 phpspec.yml.dist create mode 100644 phpunit.xml delete mode 100644 spec/LoggerPluginSpec.php create mode 100644 tests/LoggerPluginTest.php create mode 100644 tests/TestLogger.php diff --git a/.gitattributes b/.gitattributes index cd0ff7b..0c35ea4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,8 +3,6 @@ .github/ export-ignore .gitignore export-ignore .php_cs export-ignore -.scrutinizer.yml export-ignore .styleci.yml export-ignore -phpspec.yml.ci export-ignore -phpspec.yml.dist export-ignore -spec/ export-ignore +phpunit.xml export-ignore +tests export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e4d0050..f7250cc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,12 +5,15 @@ on: pull_request: jobs: - latest: - name: PHP ${{ matrix.php }} Latest + tests: + name: PHP ${{ matrix.php }} ${{ matrix.dependencies }} runs-on: ubuntu-latest strategy: matrix: - php: ['7.1', '7.2', '7.3', '7.4', '8.0'] + php: ['8.2', '8.3', '8.4'] + dependencies: + - "lowest" + - "highest" steps: - name: Checkout code @@ -20,72 +23,12 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} - tools: composer:v2 - coverage: none - - - name: Install PHP 7 dependencies - run: composer update --prefer-dist --no-interaction --no-progress - if: "matrix.php != '8.0'" - - - name: Install PHP 8 dependencies - run: | - composer require "phpdocumentor/reflection-docblock:^5.2@dev" --no-interaction --no-update - composer update --prefer-dist --prefer-stable --no-interaction --no-progress --ignore-platform-req=php - if: "matrix.php == '8.0'" - - - name: Execute tests - run: composer test - - lowest: - name: PHP ${{ matrix.php }} Lowest - runs-on: ubuntu-latest - strategy: - matrix: - php: ['7.1', '7.2', '7.3', '7.4'] - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: composer:v2 coverage: none - name: Install dependencies - run: | - composer require "sebastian/comparator:^3.0.2" --no-interaction --no-update - composer update --prefer-dist --prefer-stable --prefer-lowest --no-interaction --no-progress - - - name: Execute tests - run: composer test - - coverage: - name: Code Coverage - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 + uses: "ramsey/composer-install@v3" with: - php-version: 7.4 - tools: composer:v2 - coverage: xdebug + dependency-versions: "${{ matrix.dependencies }}" - - name: Install dependencies - run: | - composer require "friends-of-phpspec/phpspec-code-coverage:^4.3.2" --no-interaction --no-update - composer update --prefer-dist --no-interaction --no-progress - - - name: Execute tests - run: composer test-ci - - - name: Upload coverage - run: | - wget https://scrutinizer-ci.com/ocular.phar - php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml + - name: Run tests + run: composer test diff --git a/.gitignore b/.gitignore index 7608c4b..90f12a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -build/ vendor/ composer.lock -phpspec.yml -phpunit.xml +/.phpunit.cache/ diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 46a7560..0000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,8 +0,0 @@ -filter: - paths: [src/*] -checks: - php: - code_rating: true - duplication: true -tools: - external_code_coverage: true diff --git a/composer.json b/composer.json index d916b70..70e06ff 100644 --- a/composer.json +++ b/composer.json @@ -11,27 +11,31 @@ } ], "require": { - "php": "^7.0 || ^8.0", - "psr/log": "^1.0 || ^2 || ^3", - "php-http/client-common": "^1.9 || ^2.0", - "php-http/message": "^1.0", - "symfony/polyfill-php73": "^1.17" - }, - "require-dev": { - "phpspec/phpspec": "^5.1 || ^6.0" + "php": "^8.2", + "psr/log": "^2 || ^3", + "php-http/client-common": "^2.0", + "php-http/message": "^1.13" }, "autoload": { "psr-4": { "Http\\Client\\Common\\Plugin\\": "src/" } }, + "autoload-dev": { + "psr-4": { + "Http\\Client\\Common\\Plugin\\": "tests/" + } + }, "scripts": { - "test": "vendor/bin/phpspec run", - "test-ci": "vendor/bin/phpspec run -c phpspec.yml.ci" + "test": "vendor/bin/phpunit" }, "extra": { "branch-alias": { "dev-master": "1.2-dev" } + }, + "require-dev": { + "phpunit/phpunit": "^11", + "nyholm/psr7": "^1.8" } } diff --git a/phpspec.yml.ci b/phpspec.yml.ci deleted file mode 100644 index 087f808..0000000 --- a/phpspec.yml.ci +++ /dev/null @@ -1,10 +0,0 @@ -suites: - logger_plugin_suite: - namespace: Http\Client\Common\Plugin - psr4_prefix: Http\Client\Common\Plugin -formatter.name: pretty -extensions: - FriendsOfPhpSpec\PhpSpec\CodeCoverage\CodeCoverageExtension: ~ -code_coverage: - format: clover - output: build/coverage.xml diff --git a/phpspec.yml.dist b/phpspec.yml.dist deleted file mode 100644 index 93dd666..0000000 --- a/phpspec.yml.dist +++ /dev/null @@ -1,5 +0,0 @@ -suites: - logger_plugin_suite: - namespace: Http\Client\Common\Plugin - psr4_prefix: Http\Client\Common\Plugin -formatter.name: pretty diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..a825b0e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,26 @@ + + + + + tests + + + + + + src + + + diff --git a/spec/LoggerPluginSpec.php b/spec/LoggerPluginSpec.php deleted file mode 100644 index 91325a1..0000000 --- a/spec/LoggerPluginSpec.php +++ /dev/null @@ -1,149 +0,0 @@ -beConstructedWith($logger, $formatter); - } - - function it_is_initializable() - { - $this->shouldHaveType(LoggerPlugin::class); - } - - function it_is_a_plugin() - { - $this->shouldImplement(Plugin::class); - } - - function it_logs_request_and_response( - LoggerInterface $logger, - Formatter $formatter, - RequestInterface $request, - ResponseInterface $response - ) { - $formatter->formatRequest($request)->willReturn('GET / 1.1'); - if (method_exists(Formatter\SimpleFormatter::class, 'formatResponseForRequest')) { - $formatter->formatResponseForRequest($response, $request)->willReturn('200 OK 1.1'); - } else { - $formatter->formatResponse($response)->willReturn('200 OK 1.1'); - } - - $logger->info( - "Sending request:\nGET / 1.1", - Argument::that( - function(array $context) { - return is_string($context['uid']); - } - ) - )->shouldBeCalled(); - $logger->info( - "Received response:\n200 OK 1.1", - Argument::that( - function(array $context) { - return is_int($context['milliseconds']) - && is_string($context['uid']) - ; - } - ) - )->shouldBeCalled(); - - $next = function () use ($response) { - return new FulfilledPromise($response->getWrappedObject()); - }; - - $this->handleRequest($request, $next, function () {}); - } - - function it_logs_exception(LoggerInterface $logger, Formatter $formatter, RequestInterface $request) - { - $formatter->formatRequest($request)->willReturn('GET / 1.1'); - - $exception = new NetworkException('Cannot connect', $request->getWrappedObject()); - - $logger->info( - "Sending request:\nGET / 1.1", - Argument::that( - function(array $context) { - return is_string($context['uid']); - } - ) - )->shouldBeCalled(); - $logger->error( - "Error:\nCannot connect\nwhen sending request:\nGET / 1.1", - Argument::that( - function(array $context) use ($exception) { - return $context['exception'] === $exception - && is_int($context['milliseconds']) - && is_string($context['uid']) - ; - } - ) - )->shouldBeCalled(); - - $next = function () use ($exception) { - return new RejectedPromise($exception); - }; - - $this->handleRequest($request, $next, function () {}); - } - - function it_logs_response_within_exception( - LoggerInterface $logger, - Formatter $formatter, - RequestInterface $request, - ResponseInterface $response - ) { - $formatter->formatRequest($request)->willReturn('GET / 1.1'); - if (method_exists(Formatter\SimpleFormatter::class, 'formatResponseForRequest')) { - $formatter->formatResponseForRequest($response, $request)->willReturn('403 Forbidden 1.1'); - } else { - $formatter->formatResponse($response)->willReturn('403 Forbidden 1.1'); - } - - $exception = new HttpException('Forbidden', $request->getWrappedObject(), $response->getWrappedObject()); - - $logger->info( - "Sending request:\nGET / 1.1", - Argument::that( - function(array $context) { - return is_string($context['uid']); - } - ) - )->shouldBeCalled(); - $logger->error( - "Error:\nForbidden\nwith response:\n403 Forbidden 1.1", - Argument::that( - function(array $context) use ($exception) { - return $context['exception'] === $exception - && is_int($context['milliseconds']) - && is_string($context['uid']) - ; - } - ) - )->shouldBeCalled(); - - $next = function () use ($exception) { - return new RejectedPromise($exception); - }; - - $this->handleRequest($request, $next, function () {}); - } -} diff --git a/src/LoggerPlugin.php b/src/LoggerPlugin.php index e6126f7..87dc914 100644 --- a/src/LoggerPlugin.php +++ b/src/LoggerPlugin.php @@ -6,6 +6,7 @@ use Http\Client\Exception; use Http\Message\Formatter; use Http\Message\Formatter\SimpleFormatter; +use Http\Promise\Promise; use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; @@ -15,21 +16,18 @@ * * @author Joel Wurtz */ -final class LoggerPlugin implements Plugin +final readonly class LoggerPlugin implements Plugin { - use VersionBridgePlugin; + private Formatter $formatter; - private $logger; - - private $formatter; - - public function __construct(LoggerInterface $logger, Formatter $formatter = null) - { - $this->logger = $logger; - $this->formatter = $formatter ?: new SimpleFormatter(); + public function __construct( + private LoggerInterface $logger, + ?Formatter $formatter = null + ) { + $this->formatter = $formatter ?? new SimpleFormatter(); } - protected function doHandleRequest(RequestInterface $request, callable $next, callable $first) + public function handleRequest(RequestInterface $request, callable $next, callable $first): Promise { $start = hrtime(true) / 1E6; $uid = uniqid('', true); @@ -37,9 +35,7 @@ protected function doHandleRequest(RequestInterface $request, callable $next, ca return $next($request)->then(function (ResponseInterface $response) use ($start, $uid, $request) { $milliseconds = (int) round(hrtime(true) / 1E6 - $start); - $formattedResponse = method_exists($this->formatter, 'formatResponseForRequest') - ? $this->formatter->formatResponseForRequest($response, $request) - : $this->formatter->formatResponse($response); + $formattedResponse = $this->formatter->formatResponseForRequest($response, $request); $this->logger->info( sprintf("Received response:\n%s", $formattedResponse), [ @@ -52,9 +48,7 @@ protected function doHandleRequest(RequestInterface $request, callable $next, ca }, function (Exception $exception) use ($request, $start, $uid) { $milliseconds = (int) round(hrtime(true) / 1E6 - $start); if ($exception instanceof Exception\HttpException) { - $formattedResponse = method_exists($this->formatter, 'formatResponseForRequest') - ? $this->formatter->formatResponseForRequest($exception->getResponse(), $exception->getRequest()) - : $this->formatter->formatResponse($exception->getResponse()); + $formattedResponse = $this->formatter->formatResponseForRequest($exception->getResponse(), $exception->getRequest()); $this->logger->error( sprintf("Error:\n%s\nwith response:\n%s", $exception->getMessage(), $formattedResponse), [ diff --git a/tests/LoggerPluginTest.php b/tests/LoggerPluginTest.php new file mode 100644 index 0000000..78d56a6 --- /dev/null +++ b/tests/LoggerPluginTest.php @@ -0,0 +1,83 @@ +logger = new TestLogger(); + $this->plugin = new LoggerPlugin($this->logger, new SimpleFormatter()); + } + + public function testLogsRequestAndResponse() + { + $response = new Response(); + + $actualResponse = $this->plugin->handleRequest( + new Request('GET', 'http://example.com/'), + fn (RequestInterface $req) => new FulfilledPromise($response), + function () {} + )->wait(); + + self::assertSame($response, $actualResponse); + + self::assertCount(2, $this->logger->logMessages); + self::assertSame("Sending request:\nGET http://example.com/ 1.1", $this->logger->logMessages[0]['info']); + self::assertSame("Received response:\n200 OK 1.1", $this->logger->logMessages[1]['info']); + } + + public function testLogsRequestException() + { + $this->expectException(NetworkException::class); + + try { + $this->plugin->handleRequest( + new Request('GET', 'http://example.com/'), + fn (RequestInterface $req) => new RejectedPromise(new NetworkException('Network error', $req)), + function () {} + )->wait(); + } catch (NetworkException $exception) { + self::assertCount(2, $this->logger->logMessages); + self::assertSame("Sending request:\nGET http://example.com/ 1.1", $this->logger->logMessages[0]['info']); + self::assertSame("Error:\nNetwork error\nwhen sending request:\nGET http://example.com/ 1.1", $this->logger->logMessages[1]['error']); + + throw $exception; + } + } + + public function testLogsHttpException() + { + $this->expectException(HttpException::class); + + try { + $this->plugin->handleRequest( + new Request('GET', 'http://example.com/'), + fn (RequestInterface $req) => new RejectedPromise(new HttpException('Not Found', $req, new Response())), + function () {} + )->wait(); + } catch (HttpException $exception) { + // Expected + $this->assertCount(2, $this->logger->logMessages); + $this->assertSame("Sending request:\nGET http://example.com/ 1.1", $this->logger->logMessages[0]['info']); + // Ensure there's an error log for the exception + $this->assertStringContainsString("Error:\nNot Found", $this->logger->logMessages[1]['error']); + throw $exception; + } + } +} diff --git a/tests/TestLogger.php b/tests/TestLogger.php new file mode 100644 index 0000000..f6c69b9 --- /dev/null +++ b/tests/TestLogger.php @@ -0,0 +1,55 @@ +logMessages[] = ['emergency' => $message, 'context' => $context]; + } + + public function alert($message, array $context = []): void + { + $this->logMessages[] = ['alert' => $message, 'context' => $context]; + } + + public function critical($message, array $context = []): void + { + $this->logMessages[] = ['critical' => $message, 'context' => $context]; + } + + public function error($message, array $context = []): void + { + $this->logMessages[] = ['error' => $message, 'context' => $context]; + } + + public function warning($message, array $context = []): void + { + $this->logMessages[] = ['warning' => $message, 'context' => $context]; + } + + public function notice($message, array $context = []): void + { + $this->logMessages[] = ['notice' => $message, 'context' => $context]; + } + + public function info($message, array $context = []): void + { + $this->logMessages[] = ['info' => $message, 'context' => $context]; + } + + public function debug($message, array $context = []): void + { + $this->logMessages[] = ['debug' => $message, 'context' => $context]; + } + + public function log($level, $message, array $context = []): void + { + $this->logMessages[] = [$level => $message, 'context' => $context]; + } +} From 38bf6560bc12297e8c5aa35147217dcd8da2a55f Mon Sep 17 00:00:00 2001 From: David Buchmann Date: Thu, 31 Oct 2024 15:40:20 +0100 Subject: [PATCH 2/2] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 70e06ff..b2d4cd4 100644 --- a/composer.json +++ b/composer.json @@ -35,7 +35,7 @@ } }, "require-dev": { - "phpunit/phpunit": "^11", + "phpunit/phpunit": "^11.4.3", "nyholm/psr7": "^1.8" } }