From 83eecb7a6434332f4b670dfb1c298aa925e94be0 Mon Sep 17 00:00:00 2001 From: Gustavo Freze Date: Sat, 24 Oct 2026 12:39:56 -0300 Subject: [PATCH 1/2] build: Update the dependencies to their latest versions. --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 3f1fcc0..04cad88 100644 --- a/composer.json +++ b/composer.json @@ -31,11 +31,11 @@ "ramsey/uuid": "^4.9" }, "require-dev": { - "ergebnis/composer-normalize": "^2.52", - "guzzlehttp/psr7": "^3.0", - "infection/infection": "^0.34", - "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^13.2", + "ergebnis/composer-normalize": "^2.54", + "guzzlehttp/psr7": "^3.2", + "infection/infection": "^0.35", + "phpstan/phpstan": "^2.3", + "phpunit/phpunit": "^13.4", "slevomat/coding-standard": "^8.31", "squizlabs/php_codesniffer": "^4.0" }, @@ -76,7 +76,7 @@ "test-file": "@php ./vendor/bin/phpunit --configuration phpunit.xml --no-coverage --filter", "tests": [ "@php -d memory_limit=2G ./vendor/bin/phpunit --configuration phpunit.xml tests", - "@php ./vendor/bin/infection --threads=max --logger-html=reports/coverage/mutation-report.html --coverage=reports/coverage" + "@php ./vendor/bin/infection --threads=max --logger-html=reports/coverage/mutation-report.html" ] } } From 83c30d43a937d213bcbe67b8d948a92d2963ac65 Mon Sep 17 00:00:00 2001 From: Gustavo Freze Date: Sat, 24 Oct 2026 13:00:06 -0300 Subject: [PATCH 2/2] refactor: Order members and parameters as the conformance hooks require. --- src/CorrelationIdMiddleware.php | 10 +- tests/Unit/CorrelatedLoggerTest.php | 60 ++++----- tests/Unit/CorrelationIdMiddlewareTest.php | 148 ++++++++++----------- 3 files changed, 109 insertions(+), 109 deletions(-) diff --git a/src/CorrelationIdMiddleware.php b/src/CorrelationIdMiddleware.php index d9b75ff..3c28494 100644 --- a/src/CorrelationIdMiddleware.php +++ b/src/CorrelationIdMiddleware.php @@ -19,17 +19,17 @@ */ final readonly class CorrelationIdMiddleware implements MiddlewareInterface { - /** - * Name of the request attribute carrying the resolved {@see CorrelationId}. - */ - public const string ATTRIBUTE_NAME = 'correlationId'; - /** * Name of the header carrying the correlation ID on requests and responses. Use it when * propagating the identifier to outbound requests, so the name never drifts across services. */ public const string HEADER_NAME = 'Correlation-Id'; + /** + * Name of the request attribute carrying the resolved {@see CorrelationId}. + */ + public const string ATTRIBUTE_NAME = 'correlationId'; + private function __construct( private CorrelationIdProvider $provider, private RequestScopedCorrelationId $correlationId diff --git a/tests/Unit/CorrelatedLoggerTest.php b/tests/Unit/CorrelatedLoggerTest.php index 3cc495e..7369af3 100644 --- a/tests/Unit/CorrelatedLoggerTest.php +++ b/tests/Unit/CorrelatedLoggerTest.php @@ -13,30 +13,32 @@ final class CorrelatedLoggerTest extends TestCase { - public function testAttachesCorrelationIdToLogContext(): void + public function testReadsTheCorrelationIdAtLogTime(): void { /** @Given an in-memory PSR-3 logger */ $logger = new CapturingLogger(); - /** @And a correlated logger reading a fixed correlation ID */ - $correlatedLogger = CorrelatedLogger::from( - logger: $logger, - correlationId: UuidCorrelationId::from(value: 'corr-123') + /** @And a middleware sharing its correlation ID with a correlated logger built before any request */ + $middleware = CorrelationIdMiddleware::build(); + $correlatedLogger = CorrelatedLogger::from(logger: $logger, correlationId: $middleware->correlationId()); + + /** @And a request carrying a Correlation-Id header handled after the logger was built */ + $middleware->process( + new ServerRequest('GET', '/', ['Correlation-Id' => 'corr-late-bound']), + new CapturingHandler() ); /** @When a log entry is emitted */ - $correlatedLogger->info('something happened'); + $correlatedLogger->info('charge routed'); - /** @Then the recorded entry carries the correlation ID in its context */ + /** @Then the recorded entry carries the correlation ID resolved after the logger was built */ $entries = $logger->loggedEntries(); self::assertCount(1, $entries); - self::assertSame(LogLevel::INFO, $entries[0]['level']); - self::assertSame('something happened', $entries[0]['message']); - self::assertSame('corr-123', $entries[0]['context']['correlation_id']); + self::assertSame('corr-late-bound', $entries[0]['context']['correlation_id']); } - public function testPreservesExistingContextKeysWhenAttachingCorrelationId(): void + public function testAttachesCorrelationIdToLogContext(): void { /** @Given an in-memory PSR-3 logger */ $logger = new CapturingLogger(); @@ -44,18 +46,19 @@ public function testPreservesExistingContextKeysWhenAttachingCorrelationId(): vo /** @And a correlated logger reading a fixed correlation ID */ $correlatedLogger = CorrelatedLogger::from( logger: $logger, - correlationId: UuidCorrelationId::from(value: 'corr-xyz') + correlationId: UuidCorrelationId::from(value: 'corr-123') ); - /** @When a log entry is emitted with additional context */ - $correlatedLogger->info('order placed', ['order_id' => 42]); + /** @When a log entry is emitted */ + $correlatedLogger->info('something happened'); - /** @Then both the original context and the correlation ID are recorded */ + /** @Then the recorded entry carries the correlation ID in its context */ $entries = $logger->loggedEntries(); self::assertCount(1, $entries); - self::assertSame(42, $entries[0]['context']['order_id']); - self::assertSame('corr-xyz', $entries[0]['context']['correlation_id']); + self::assertSame(LogLevel::INFO, $entries[0]['level']); + self::assertSame('something happened', $entries[0]['message']); + self::assertSame('corr-123', $entries[0]['context']['correlation_id']); } public function testOmitsCorrelationIdWhenTheValueIsEmpty(): void @@ -81,28 +84,25 @@ public function testOmitsCorrelationIdWhenTheValueIsEmpty(): void self::assertArrayNotHasKey('correlation_id', $entries[0]['context']); } - public function testReadsTheCorrelationIdAtLogTime(): void + public function testPreservesExistingContextKeysWhenAttachingCorrelationId(): void { /** @Given an in-memory PSR-3 logger */ $logger = new CapturingLogger(); - /** @And a middleware sharing its correlation ID with a correlated logger built before any request */ - $middleware = CorrelationIdMiddleware::build(); - $correlatedLogger = CorrelatedLogger::from(logger: $logger, correlationId: $middleware->correlationId()); - - /** @And a request carrying a Correlation-Id header handled after the logger was built */ - $middleware->process( - new ServerRequest('GET', '/', ['Correlation-Id' => 'corr-late-bound']), - new CapturingHandler() + /** @And a correlated logger reading a fixed correlation ID */ + $correlatedLogger = CorrelatedLogger::from( + logger: $logger, + correlationId: UuidCorrelationId::from(value: 'corr-xyz') ); - /** @When a log entry is emitted */ - $correlatedLogger->info('charge routed'); + /** @When a log entry is emitted with additional context */ + $correlatedLogger->info('order placed', ['order_id' => 42]); - /** @Then the recorded entry carries the correlation ID resolved after the logger was built */ + /** @Then both the original context and the correlation ID are recorded */ $entries = $logger->loggedEntries(); self::assertCount(1, $entries); - self::assertSame('corr-late-bound', $entries[0]['context']['correlation_id']); + self::assertSame(42, $entries[0]['context']['order_id']); + self::assertSame('corr-xyz', $entries[0]['context']['correlation_id']); } } diff --git a/tests/Unit/CorrelationIdMiddlewareTest.php b/tests/Unit/CorrelationIdMiddlewareTest.php index b408a54..722a1ea 100644 --- a/tests/Unit/CorrelationIdMiddlewareTest.php +++ b/tests/Unit/CorrelationIdMiddlewareTest.php @@ -56,80 +56,6 @@ public function generate(): CorrelationId self::assertSame($fixedValue, $response->getHeaderLine('Correlation-Id')); } - public function testCorrelationIdStartsEmptyBeforeAnyRequestIsHandled(): void - { - /** @Given a middleware no request has gone through yet */ - $middleware = CorrelationIdMiddleware::build(); - - /** @When the correlation ID of the request in flight is read */ - $value = $middleware->correlationId()->toString(); - - /** @Then the value should be empty */ - self::assertSame('', $value); - } - - public function testCorrelationIdExposesTheValueResolvedFromTheRequestHeader(): void - { - /** @Given an existing correlation ID carried in the request header */ - $existingCorrelationId = 'req-in-flight-42'; - - /** @And a middleware using the default provider */ - $middleware = CorrelationIdMiddleware::build(); - - /** @When the middleware processes a request with that Correlation-Id header */ - $middleware->process( - new ServerRequest('GET', '/', ['Correlation-Id' => $existingCorrelationId]), - new CapturingHandler() - ); - - /** @Then the correlation ID of the request in flight should expose the header value */ - self::assertSame($existingCorrelationId, $middleware->correlationId()->toString()); - } - - public function testCorrelationIdIsAssignedBeforeTheHandlerRuns(): void - { - /** @Given a middleware using the default provider */ - $middleware = CorrelationIdMiddleware::build(); - - /** @And a handler that records the correlation ID of the request in flight at handling time */ - $handler = new readonly class ($middleware->correlationId()) implements RequestHandlerInterface { - public function __construct(private CorrelationId $correlationId) - { - } - - public function handle(ServerRequestInterface $request): ResponseInterface - { - return new Response(status: 200, headers: ['Seen-By-Handler' => $this->correlationId->toString()]); - } - }; - - /** @When the middleware processes a request without the Correlation-Id header */ - $response = $middleware->process(new ServerRequest('GET', '/'), $handler); - - /** @Then the handler should have observed the correlation ID already assigned */ - self::assertNotSame('', $response->getHeaderLine('Seen-By-Handler')); - self::assertSame($response->getHeaderLine('Correlation-Id'), $response->getHeaderLine('Seen-By-Handler')); - } - - public function testCorrelationIdReflectsTheLatestHandledRequest(): void - { - /** @Given a middleware using the default provider */ - $middleware = CorrelationIdMiddleware::build(); - - /** @When the middleware processes two requests carrying different Correlation-Id headers */ - $middleware->process( - new ServerRequest('GET', '/first', ['Correlation-Id' => 'req-first']), - new CapturingHandler() - ); - $middleware->process( - new ServerRequest('GET', '/second', ['Correlation-Id' => 'req-second']), - new CapturingHandler() - ); - - /** @Then the correlation ID of the request in flight should expose the latest value */ - self::assertSame('req-second', $middleware->correlationId()->toString()); - } - public function testPreservesExistingResponseHeaders(): void { /** @Given a request without the Correlation-Id header */ @@ -287,6 +213,31 @@ public function testGeneratesNewCorrelationIdWhenHeaderIsEmpty(): void self::assertSame($correlationId->toString(), $response->getHeaderLine('Correlation-Id')); } + public function testCorrelationIdIsAssignedBeforeTheHandlerRuns(): void + { + /** @Given a middleware using the default provider */ + $middleware = CorrelationIdMiddleware::build(); + + /** @And a handler that records the correlation ID of the request in flight at handling time */ + $handler = new readonly class ($middleware->correlationId()) implements RequestHandlerInterface { + public function __construct(private CorrelationId $correlationId) + { + } + + public function handle(ServerRequestInterface $request): ResponseInterface + { + return new Response(status: 200, headers: ['Seen-By-Handler' => $this->correlationId->toString()]); + } + }; + + /** @When the middleware processes a request without the Correlation-Id header */ + $response = $middleware->process(new ServerRequest('GET', '/'), $handler); + + /** @Then the handler should have observed the correlation ID already assigned */ + self::assertNotSame('', $response->getHeaderLine('Seen-By-Handler')); + self::assertSame($response->getHeaderLine('Correlation-Id'), $response->getHeaderLine('Seen-By-Handler')); + } + public function testCorrelationIdAttributeIsAccessibleDownstream(): void { /** @Given a request without the Correlation-Id header */ @@ -308,6 +259,37 @@ public function testCorrelationIdAttributeIsAccessibleDownstream(): void self::assertSame($response->getHeaderLine('Correlation-Id'), $correlationId->toString()); } + public function testCorrelationIdReflectsTheLatestHandledRequest(): void + { + /** @Given a middleware using the default provider */ + $middleware = CorrelationIdMiddleware::build(); + + /** @When the middleware processes two requests carrying different Correlation-Id headers */ + $middleware->process( + new ServerRequest('GET', '/first', ['Correlation-Id' => 'req-first']), + new CapturingHandler() + ); + $middleware->process( + new ServerRequest('GET', '/second', ['Correlation-Id' => 'req-second']), + new CapturingHandler() + ); + + /** @Then the correlation ID of the request in flight should expose the latest value */ + self::assertSame('req-second', $middleware->correlationId()->toString()); + } + + public function testCorrelationIdStartsEmptyBeforeAnyRequestIsHandled(): void + { + /** @Given a middleware no request has gone through yet */ + $middleware = CorrelationIdMiddleware::build(); + + /** @When the correlation ID of the request in flight is read */ + $value = $middleware->correlationId()->toString(); + + /** @Then the value should be empty */ + self::assertSame('', $value); + } + public function testGeneratesNewCorrelationIdWhenHeaderIsWhitespaceOnly(): void { /** @Given the whitespace-only value that will be returned for the Correlation-Id header */ @@ -354,4 +336,22 @@ public function testGeneratesNewCorrelationIdWhenHeaderIsWhitespaceOnly(): void self::assertTrue($response->hasHeader('Correlation-Id')); self::assertSame($correlationId->toString(), $response->getHeaderLine('Correlation-Id')); } + + public function testCorrelationIdExposesTheValueResolvedFromTheRequestHeader(): void + { + /** @Given an existing correlation ID carried in the request header */ + $existingCorrelationId = 'req-in-flight-42'; + + /** @And a middleware using the default provider */ + $middleware = CorrelationIdMiddleware::build(); + + /** @When the middleware processes a request with that Correlation-Id header */ + $middleware->process( + new ServerRequest('GET', '/', ['Correlation-Id' => $existingCorrelationId]), + new CapturingHandler() + ); + + /** @Then the correlation ID of the request in flight should expose the header value */ + self::assertSame($existingCorrelationId, $middleware->correlationId()->toString()); + } }