From d64ec234cfc943ae6d67babcdb908bd7a8748a78 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:53:48 +0200 Subject: [PATCH 01/11] Created a logging HTTP client to intercept request/response in GitHub API calls --- src/HttpClient/LoggingHttpClient.php | 35 ++++++++++++ .../unit/HttpClient/LoggingHttpClientTest.php | 54 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/HttpClient/LoggingHttpClient.php create mode 100644 test/unit/HttpClient/LoggingHttpClientTest.php diff --git a/src/HttpClient/LoggingHttpClient.php b/src/HttpClient/LoggingHttpClient.php new file mode 100644 index 00000000..666835ba --- /dev/null +++ b/src/HttpClient/LoggingHttpClient.php @@ -0,0 +1,35 @@ +logger->debug('Sending request {request}', ['request' => $request]); + + $response = $this->next->sendRequest($request); + + $this->logger->debug( + 'Received response {response} to request {request}', + [ + 'request' => $request, + 'response' => $response, + ] + ); + + return $response; + } +} \ No newline at end of file diff --git a/test/unit/HttpClient/LoggingHttpClientTest.php b/test/unit/HttpClient/LoggingHttpClientTest.php new file mode 100644 index 00000000..3bc20e3f --- /dev/null +++ b/test/unit/HttpClient/LoggingHttpClientTest.php @@ -0,0 +1,54 @@ +createRequest('get', 'http://example.com/foo/bar'); + $response = Psr17FactoryDiscovery::findResponseFactory()->createResponse(204); + + $response->getBody() + ->write('hello world'); + + $logger = $this->createMock(LoggerInterface::class); + $next = $this->createMock(ClientInterface::class); + + $next->expects(self::once()) + ->method('sendRequest') + ->with($request) + ->willReturn($response); + + $logger->expects(self::exactly(2)) + ->method('debug') + ->withConsecutive( + ['Sending request {request}', ['request' => $request]], + [ + 'Received response {response} to request {request}', + [ + 'request' => $request, + 'response' => $response, + ], + ], + ); + + self::assertSame( + $response, + (new LoggingHttpClient($next, $logger)) + ->sendRequest($request) + ); + } +} From 6ab4bb0bb4dfc2e5e7ae13d0e45d9a3351f9dc4a Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:54:26 +0200 Subject: [PATCH 02/11] Log processor to convert `HttpRequestInterface` instance into loggable strings This also considers the privacy/security concerns of **NOT** logging sensitive parameters. --- ...nvertLogContextHttpRequestsIntoStrings.php | 37 ++++++++++++ ...tLogContextHttpRequestsIntoStringsTest.php | 56 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php create mode 100644 test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php diff --git a/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php b/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php new file mode 100644 index 00000000..e6109a1d --- /dev/null +++ b/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php @@ -0,0 +1,37 @@ +datetime, + $record->channel, + $record->level, + $record->message, + array_map(static function ($item): mixed { + if (! $item instanceof RequestInterface) { + return $item; + } + + return $item->getMethod() + . ' ' + . $item + ->getUri() + ->withUserInfo('') + ->__toString(); + }, $record->context), + $record->extra, + $record->formatted + ); + } +} \ No newline at end of file diff --git a/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php b/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php new file mode 100644 index 00000000..a55acc11 --- /dev/null +++ b/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php @@ -0,0 +1,56 @@ +createRequest('GET', 'http://example.com/foo'); + + $sensitiveRequest = $requestFactory->createRequest('POST', 'https://user:pass@example.com/foo?bar=baz') + ->withAddedHeader('Authentication', ['also secret']); + + $sensitiveRequest->getBody() + ->write('super: secret'); + + self::assertEquals( + new LogRecord( + $date, + 'a-channel', + Level::Critical, + 'a message', + [ + 'foo' => 'bar', + 'plain request' => 'GET http://example.com/foo', + 'sensitive request' => 'POST https://example.com/foo?bar=baz', + ] + ), + (new ConvertLogContextHttpRequestsIntoStrings())(new LogRecord( + $date, + 'a-channel', + Level::Critical, + 'a message', + [ + 'foo' => 'bar', + 'plain request' => $plainRequest, + 'sensitive request' => $sensitiveRequest, + ] + )) + ); + } +} From f749328c186b25391fa8f6b307f0ee5a17fd8333 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:54:39 +0200 Subject: [PATCH 03/11] Log processor to convert `HttpResponseInterface` instances into loggable strings This also considers the privacy/security concerns of **NOT** logging sensitive parameters. --- ...vertLogContextHttpResponsesIntoStrings.php | 37 +++++++++++ ...LogContextHttpResponsesIntoStringsTest.php | 62 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php create mode 100644 test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php diff --git a/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php b/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php new file mode 100644 index 00000000..0551a481 --- /dev/null +++ b/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php @@ -0,0 +1,37 @@ +datetime, + $record->channel, + $record->level, + $record->message, + array_map(static function ($item): mixed { + if (! $item instanceof ResponseInterface) { + return $item; + } + + return $item->getStatusCode() + . ' "' + . $item + ->getBody() + ->__toString() + . '"'; + }, $record->context), + $record->extra, + $record->formatted + ); + } +} \ No newline at end of file diff --git a/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php b/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php new file mode 100644 index 00000000..717cf7c8 --- /dev/null +++ b/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php @@ -0,0 +1,62 @@ +createResponse(401); + + $sensitiveResponse = $requestFactory->createResponse(403) + ->withAddedHeader('Super', 'secret'); + + $sensitiveResponse->getBody() + ->write('this should be printed'); + + self::assertEquals( + new LogRecord( + $date, + 'a-channel', + Level::Critical, + 'a message', + [ + 'foo' => 'bar', + 'plain response' => '401 ""', + 'sensitive response' => '403 "this should be printed"', + ] + ), + (new ConvertLogContextHttpResponsesIntoStrings())(new LogRecord( + $date, + 'a-channel', + Level::Critical, + 'a message', + [ + 'foo' => 'bar', + 'plain response' => $plainResponse, + 'sensitive response' => $sensitiveResponse, + ] + )) + ); + } +} From 19264d8c61913e73d11842b208adafd89afcf02f Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:54:55 +0200 Subject: [PATCH 04/11] Cementing logging infrastructure setup via a small integration test with Monolog --- .../Monolog/VerifyLoggingIntegrationTest.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/unit/Monolog/VerifyLoggingIntegrationTest.php diff --git a/test/unit/Monolog/VerifyLoggingIntegrationTest.php b/test/unit/Monolog/VerifyLoggingIntegrationTest.php new file mode 100644 index 00000000..0a91c342 --- /dev/null +++ b/test/unit/Monolog/VerifyLoggingIntegrationTest.php @@ -0,0 +1,53 @@ +createRequest('get', 'http://example.com/foo/bar'); + $response = Psr17FactoryDiscovery::findResponseFactory()->createResponse(204); + + $response->getBody() + ->write('hello world'); + + $stream = fopen('php://memory', 'rwb+'); + + $bufferHandler = new StreamHandler($stream); + + $logger = new Logger( + 'test-logger', + [$bufferHandler], + [ + new ConvertLogContextHttpRequestsIntoStrings(), + new ConvertLogContextHttpResponsesIntoStrings(), + ] + ); + + $logger->debug('message', ['request' => $request, 'response' => $response]); + + rewind($stream); + + self::assertStringContainsString( + ': message {"request":"GET http://example.com/foo/bar","response":"204 \"hello world\""} []', + stream_get_contents($stream), + 'Request and response contents have been serialized into the final log message' + ); + } +} From 1fdb077c6491a2149779953785b2047c2df83d14 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:56:51 +0200 Subject: [PATCH 05/11] Removed redundant PHPUnit test ignores --- psalm.xml.dist | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/psalm.xml.dist b/psalm.xml.dist index 6cbbbd44..419701dc 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -15,19 +15,6 @@ - - - - - - - - - - - - - From 068ae33fbf57841b7fda565a1ec664c49e4e2d72 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:57:30 +0200 Subject: [PATCH 06/11] Using the `LoggingHttpClient` to log all github API calls --- bin/console.php | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/bin/console.php b/bin/console.php index f1e2e6ba..db616ff1 100755 --- a/bin/console.php +++ b/bin/console.php @@ -37,6 +37,9 @@ use Laminas\AutomaticReleases\Github\JwageGenerateChangelog; use Laminas\AutomaticReleases\Github\MergeMultipleReleaseNotes; use Laminas\AutomaticReleases\Gpg\ImportGpgKeyFromStringViaTemporaryFile; +use Laminas\AutomaticReleases\HttpClient\LoggingHttpClient; +use Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpRequestsIntoStrings; +use Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpResponsesIntoStrings; use Lcobucci\Clock\SystemClock; use Monolog\Handler\StreamHandler; use Monolog\Logger; @@ -61,13 +64,19 @@ static function (int $errorCode, string $message = '', string $file = '', int $l ); $variables = EnvironmentVariables::fromEnvironment(new ImportGpgKeyFromStringViaTemporaryFile()); - $logger = new Logger('automatic-releases'); - $logger->pushHandler(new StreamHandler(STDERR, $variables->logLevel())); + $logger = new Logger( + 'automatic-releases', + [new StreamHandler(STDERR, $variables->logLevel())], + [ + new ConvertLogContextHttpRequestsIntoStrings(), + new ConvertLogContextHttpResponsesIntoStrings(), + ] + ); $loadEvent = new LoadCurrentGithubEventFromGithubActionPath($variables); $fetch = new FetchAndSetCurrentUserByReplacingCurrentOriginRemote($variables); $getCandidateBranches = new GetMergeTargetCandidateBranchesFromRemoteBranches(); $makeRequests = Psr17FactoryDiscovery::findRequestFactory(); - $httpClient = HttpClientDiscovery::find(); + $httpClient = new LoggingHttpClient(HttpClientDiscovery::find(), $logger); $githubToken = $variables->githubToken(); $getMilestone = new GetMilestoneFirst100IssuesAndPullRequests(new RunGraphQLQuery( $makeRequests, From 1f6df0e7a1702239eda2cbca5fea185af0147997 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 18:58:32 +0200 Subject: [PATCH 07/11] Applied automated CS fixes --- bin/console.php | 6 +++--- src/HttpClient/LoggingHttpClient.php | 4 ++-- .../ConvertLogContextHttpRequestsIntoStrings.php | 6 ++++-- .../ConvertLogContextHttpResponsesIntoStrings.php | 6 ++++-- test/unit/HttpClient/LoggingHttpClientTest.php | 5 +---- .../ConvertLogContextHttpRequestsIntoStringsTest.php | 6 +++--- ...ConvertLogContextHttpResponsesIntoStringsTest.php | 12 +++--------- test/unit/Monolog/VerifyLoggingIntegrationTest.php | 7 +++++-- 8 files changed, 25 insertions(+), 27 deletions(-) diff --git a/bin/console.php b/bin/console.php index db616ff1..25a6504c 100755 --- a/bin/console.php +++ b/bin/console.php @@ -63,14 +63,14 @@ static function (int $errorCode, string $message = '', string $file = '', int $l E_STRICT | E_NOTICE | E_WARNING, ); - $variables = EnvironmentVariables::fromEnvironment(new ImportGpgKeyFromStringViaTemporaryFile()); - $logger = new Logger( + $variables = EnvironmentVariables::fromEnvironment(new ImportGpgKeyFromStringViaTemporaryFile()); + $logger = new Logger( 'automatic-releases', [new StreamHandler(STDERR, $variables->logLevel())], [ new ConvertLogContextHttpRequestsIntoStrings(), new ConvertLogContextHttpResponsesIntoStrings(), - ] + ], ); $loadEvent = new LoadCurrentGithubEventFromGithubActionPath($variables); $fetch = new FetchAndSetCurrentUserByReplacingCurrentOriginRemote($variables); diff --git a/src/HttpClient/LoggingHttpClient.php b/src/HttpClient/LoggingHttpClient.php index 666835ba..017cdb1d 100644 --- a/src/HttpClient/LoggingHttpClient.php +++ b/src/HttpClient/LoggingHttpClient.php @@ -27,9 +27,9 @@ public function sendRequest(RequestInterface $request): ResponseInterface [ 'request' => $request, 'response' => $response, - ] + ], ); return $response; } -} \ No newline at end of file +} diff --git a/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php b/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php index e6109a1d..2848504d 100644 --- a/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php +++ b/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php @@ -8,6 +8,8 @@ use Monolog\Processor\ProcessorInterface; use Psr\Http\Message\RequestInterface; +use function array_map; + /** @internal */ final class ConvertLogContextHttpRequestsIntoStrings implements ProcessorInterface { @@ -31,7 +33,7 @@ public function __invoke(LogRecord $record): LogRecord ->__toString(); }, $record->context), $record->extra, - $record->formatted + $record->formatted, ); } -} \ No newline at end of file +} diff --git a/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php b/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php index 0551a481..77168be9 100644 --- a/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php +++ b/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php @@ -8,6 +8,8 @@ use Monolog\Processor\ProcessorInterface; use Psr\Http\Message\ResponseInterface; +use function array_map; + /** @internal */ final class ConvertLogContextHttpResponsesIntoStrings implements ProcessorInterface { @@ -31,7 +33,7 @@ public function __invoke(LogRecord $record): LogRecord . '"'; }, $record->context), $record->extra, - $record->formatted + $record->formatted, ); } -} \ No newline at end of file +} diff --git a/test/unit/HttpClient/LoggingHttpClientTest.php b/test/unit/HttpClient/LoggingHttpClientTest.php index 3bc20e3f..801152c7 100644 --- a/test/unit/HttpClient/LoggingHttpClientTest.php +++ b/test/unit/HttpClient/LoggingHttpClientTest.php @@ -6,12 +6,9 @@ use Http\Discovery\Psr17FactoryDiscovery; use Laminas\AutomaticReleases\HttpClient\LoggingHttpClient; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; use PHPUnit\Framework\TestCase; use Psr\Http\Client\ClientInterface; use Psr\Log\LoggerInterface; -use function fopen; /** @covers \Laminas\AutomaticReleases\HttpClient\LoggingHttpClient */ final class LoggingHttpClientTest extends TestCase @@ -48,7 +45,7 @@ public function testWillLogRequestAndResponse(): void self::assertSame( $response, (new LoggingHttpClient($next, $logger)) - ->sendRequest($request) + ->sendRequest($request), ); } } diff --git a/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php b/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php index a55acc11..039cdbd2 100644 --- a/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php +++ b/test/unit/Monolog/ConvertLogContextHttpRequestsIntoStringsTest.php @@ -38,7 +38,7 @@ public function testWillScrubSensitiveRequestInformation(): void 'foo' => 'bar', 'plain request' => 'GET http://example.com/foo', 'sensitive request' => 'POST https://example.com/foo?bar=baz', - ] + ], ), (new ConvertLogContextHttpRequestsIntoStrings())(new LogRecord( $date, @@ -49,8 +49,8 @@ public function testWillScrubSensitiveRequestInformation(): void 'foo' => 'bar', 'plain request' => $plainRequest, 'sensitive request' => $sensitiveRequest, - ] - )) + ], + )), ); } } diff --git a/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php b/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php index 717cf7c8..748e9611 100644 --- a/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php +++ b/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php @@ -6,16 +6,10 @@ use DateTimeImmutable; use Http\Discovery\Psr17FactoryDiscovery; -use Laminas\AutomaticReleases\HttpClient\LoggingHttpClient; -use Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpRequestsIntoStrings; use Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpResponsesIntoStrings; -use Monolog\Handler\StreamHandler; use Monolog\Level; -use Monolog\Logger; use Monolog\LogRecord; use PHPUnit\Framework\TestCase; -use Psr\Http\Client\ClientInterface; -use function fopen; /** @covers \Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpResponsesIntoStrings */ final class ConvertLogContextHttpResponsesIntoStringsTest extends TestCase @@ -44,7 +38,7 @@ public function testWillScrubSensitiveRequestInformation(): void 'foo' => 'bar', 'plain response' => '401 ""', 'sensitive response' => '403 "this should be printed"', - ] + ], ), (new ConvertLogContextHttpResponsesIntoStrings())(new LogRecord( $date, @@ -55,8 +49,8 @@ public function testWillScrubSensitiveRequestInformation(): void 'foo' => 'bar', 'plain response' => $plainResponse, 'sensitive response' => $sensitiveResponse, - ] - )) + ], + )), ); } } diff --git a/test/unit/Monolog/VerifyLoggingIntegrationTest.php b/test/unit/Monolog/VerifyLoggingIntegrationTest.php index 0a91c342..59afe262 100644 --- a/test/unit/Monolog/VerifyLoggingIntegrationTest.php +++ b/test/unit/Monolog/VerifyLoggingIntegrationTest.php @@ -10,7 +10,10 @@ use Monolog\Handler\StreamHandler; use Monolog\Logger; use PHPUnit\Framework\TestCase; + use function fopen; +use function rewind; +use function stream_get_contents; /** * Small integration test to ensure future compatibility with monolog in our setup. @@ -37,7 +40,7 @@ public function testLoggedRequestAndResponseBodyPartsDoNotContainSecretsAndPostD [ new ConvertLogContextHttpRequestsIntoStrings(), new ConvertLogContextHttpResponsesIntoStrings(), - ] + ], ); $logger->debug('message', ['request' => $request, 'response' => $response]); @@ -47,7 +50,7 @@ public function testLoggedRequestAndResponseBodyPartsDoNotContainSecretsAndPostD self::assertStringContainsString( ': message {"request":"GET http://example.com/foo/bar","response":"204 \"hello world\""} []', stream_get_contents($stream), - 'Request and response contents have been serialized into the final log message' + 'Request and response contents have been serialized into the final log message', ); } } From 7b784c8ad56d8ffe702629145e526165c9b6aed1 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 19:05:16 +0200 Subject: [PATCH 08/11] Adjusted test method names to match their intent --- .../Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php | 2 +- test/unit/Monolog/VerifyLoggingIntegrationTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php b/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php index 748e9611..157eaa84 100644 --- a/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php +++ b/test/unit/Monolog/ConvertLogContextHttpResponsesIntoStringsTest.php @@ -14,7 +14,7 @@ /** @covers \Laminas\AutomaticReleases\Monolog\ConvertLogContextHttpResponsesIntoStrings */ final class ConvertLogContextHttpResponsesIntoStringsTest extends TestCase { - public function testWillScrubSensitiveRequestInformation(): void + public function testWillScrubSensitiveResponseInformationAndRenderBody(): void { $date = new DateTimeImmutable(); diff --git a/test/unit/Monolog/VerifyLoggingIntegrationTest.php b/test/unit/Monolog/VerifyLoggingIntegrationTest.php index 59afe262..33b70425 100644 --- a/test/unit/Monolog/VerifyLoggingIntegrationTest.php +++ b/test/unit/Monolog/VerifyLoggingIntegrationTest.php @@ -22,7 +22,7 @@ */ final class VerifyLoggingIntegrationTest extends TestCase { - public function testLoggedRequestAndResponseBodyPartsDoNotContainSecretsAndPostData(): void + public function testMonologProducedMessageStructureMatchesLogProcessorExpectations(): void { $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('get', 'http://example.com/foo/bar'); $response = Psr17FactoryDiscovery::findResponseFactory()->createResponse(204); From 534fce41dfa164ef22f302211a39bb544cf74b87 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 19:07:56 +0200 Subject: [PATCH 09/11] Minor test adjustment: s/get/GET to have same case string comparison This failed with lowest PHPUnit versions, which was correct: the test was not precise enough. --- test/unit/Monolog/VerifyLoggingIntegrationTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/Monolog/VerifyLoggingIntegrationTest.php b/test/unit/Monolog/VerifyLoggingIntegrationTest.php index 33b70425..bd65f6d3 100644 --- a/test/unit/Monolog/VerifyLoggingIntegrationTest.php +++ b/test/unit/Monolog/VerifyLoggingIntegrationTest.php @@ -24,7 +24,7 @@ final class VerifyLoggingIntegrationTest extends TestCase { public function testMonologProducedMessageStructureMatchesLogProcessorExpectations(): void { - $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('get', 'http://example.com/foo/bar'); + $request = Psr17FactoryDiscovery::findRequestFactory()->createRequest('GET', 'http://example.com/foo/bar'); $response = Psr17FactoryDiscovery::findResponseFactory()->createResponse(204); $response->getBody() From 435cfad152aa830488d5cd401df0de324e909cf7 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 19:23:06 +0200 Subject: [PATCH 10/11] Adjusted minimum coverage As verified by a coverage report, phpdbg is not reporting correct coverage for `array_map()` here, and is skipping coverage for the closure inside both log processors. In turn, that leads to uncovered mutants being reported, but that is not the case. --- infection.json.dist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/infection.json.dist b/infection.json.dist index 8acadb61..80d45787 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -12,6 +12,6 @@ "mutators": { "@default": true }, - "minMsi": 97, + "minMsi": 95.4, "minCoveredMsi": 100 } From d4580753b78611ced925622c43fed4eb18b8d0f0 Mon Sep 17 00:00:00 2001 From: Marco Pivetta Date: Mon, 19 Sep 2022 20:05:16 +0200 Subject: [PATCH 11/11] Raising minimum coverage threshold again Squished some mutants in between: the reason why mutants were uncovered is that closures were not being covered at all. Replacing the closures with PHP 8.1 short closure declarations fixes this, working around the coverage issue. --- infection.json.dist | 2 +- src/Changelog/ChangelogReleaseNotes.php | 3 +-- .../Value/MergeTargetCandidateBranches.php | 10 ++++--- .../CreateReleaseTextViaKeepAChangelog.php | 19 +++++++------ ...nvertLogContextHttpRequestsIntoStrings.php | 27 ++++++++++--------- ...vertLogContextHttpResponsesIntoStrings.php | 27 ++++++++++--------- 6 files changed, 50 insertions(+), 38 deletions(-) diff --git a/infection.json.dist b/infection.json.dist index 80d45787..3bafc36e 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -12,6 +12,6 @@ "mutators": { "@default": true }, - "minMsi": 95.4, + "minMsi": 98, "minCoveredMsi": 100 } diff --git a/src/Changelog/ChangelogReleaseNotes.php b/src/Changelog/ChangelogReleaseNotes.php index 711df2c1..9c188d03 100644 --- a/src/Changelog/ChangelogReleaseNotes.php +++ b/src/Changelog/ChangelogReleaseNotes.php @@ -58,8 +58,7 @@ public function merge(self $next): self { if ($this->changelogEntry && $next->changelogEntry) { throw new RuntimeException( - 'Aborting: Both current release notes and next contain a ChangelogEntry;' - . ' only one CreateReleaseText implementation should resolve one.', + 'Aborting: Both current release notes and next contain a ChangelogEntry; only one CreateReleaseText implementation should resolve one.', ); } diff --git a/src/Git/Value/MergeTargetCandidateBranches.php b/src/Git/Value/MergeTargetCandidateBranches.php index 1df65e22..cec01cd0 100644 --- a/src/Git/Value/MergeTargetCandidateBranches.php +++ b/src/Git/Value/MergeTargetCandidateBranches.php @@ -26,9 +26,7 @@ public static function fromAllBranches(BranchName ...$branches): self return $branch->isReleaseBranch(); }); - $mergeTargetBranches = Vec\sort($mergeTargetBranches, static function (BranchName $a, BranchName $b): int { - return $a->majorAndMinor() <=> $b->majorAndMinor(); - }); + $mergeTargetBranches = Vec\sort($mergeTargetBranches, self::branchOrder(...)); return new self($mergeTargetBranches); } @@ -98,4 +96,10 @@ public function contains(BranchName $needle): bool static fn (BranchName $branch): bool => $needle->equals($branch) ); } + + /** @return -1|0|1 */ + private static function branchOrder(BranchName $a, BranchName $b): int + { + return $a->majorAndMinor() <=> $b->majorAndMinor(); + } } diff --git a/src/Github/CreateReleaseTextViaKeepAChangelog.php b/src/Github/CreateReleaseTextViaKeepAChangelog.php index d19ce5e9..a34380f8 100644 --- a/src/Github/CreateReleaseTextViaKeepAChangelog.php +++ b/src/Github/CreateReleaseTextViaKeepAChangelog.php @@ -130,17 +130,20 @@ private function updateReleaseDate(string $changelog, string $version): string */ private function removeDefaultContents(string $changelog): string { - $contents = Iter\reduce( + return Type\non_empty_string()->assert(Iter\reduce( self::DEFAULT_SECTIONS, - static fn (string $changelog, string $section): string => Regex\replace( - $changelog, - "/\n\#{3} " . $section . "\n\n- Nothing.\n/s", - '', - ), + self::removeEmptyDefaultChangelogSection(...), $changelog, - ); + )); + } - return Type\non_empty_string()->assert($contents); + private static function removeEmptyDefaultChangelogSection(string $changelog, string $section): string + { + return Regex\replace( + $changelog, + "/\n\#{3} " . $section . "\n\n- Nothing.\n/s", + '', + ); } /** diff --git a/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php b/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php index 2848504d..329d307f 100644 --- a/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php +++ b/src/Monolog/ConvertLogContextHttpRequestsIntoStrings.php @@ -20,20 +20,23 @@ public function __invoke(LogRecord $record): LogRecord $record->channel, $record->level, $record->message, - array_map(static function ($item): mixed { - if (! $item instanceof RequestInterface) { - return $item; - } - - return $item->getMethod() - . ' ' - . $item - ->getUri() - ->withUserInfo('') - ->__toString(); - }, $record->context), + array_map(self::contextItemToMessage(...), $record->context), $record->extra, $record->formatted, ); } + + private static function contextItemToMessage(mixed $item): mixed + { + if (! $item instanceof RequestInterface) { + return $item; + } + + return $item->getMethod() + . ' ' + . $item + ->getUri() + ->withUserInfo('') + ->__toString(); + } } diff --git a/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php b/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php index 77168be9..9987a16e 100644 --- a/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php +++ b/src/Monolog/ConvertLogContextHttpResponsesIntoStrings.php @@ -20,20 +20,23 @@ public function __invoke(LogRecord $record): LogRecord $record->channel, $record->level, $record->message, - array_map(static function ($item): mixed { - if (! $item instanceof ResponseInterface) { - return $item; - } - - return $item->getStatusCode() - . ' "' - . $item - ->getBody() - ->__toString() - . '"'; - }, $record->context), + array_map(self::contextItemToMessage(...), $record->context), $record->extra, $record->formatted, ); } + + private static function contextItemToMessage(mixed $item): mixed + { + if (! $item instanceof ResponseInterface) { + return $item; + } + + return $item->getStatusCode() + . ' "' + . $item + ->getBody() + ->__toString() + . '"'; + } }