From b45e9c011f62f2c08272f5f07c791e31afad1157 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 23 Dec 2022 21:47:55 +0700 Subject: [PATCH 01/78] Use Rust FFI: Consumer --- .github/workflows/build.yml | 2 +- CHANGELOG.md | 61 ++++ README.md | 103 +++--- UPGRADE-9.0.md | 20 ++ composer.json | 18 +- example/phpunit.all.xml | 2 - example/phpunit.consumer.xml | 1 - example/phpunit.core.xml | 2 - .../Consumer/Service/HttpClientService.php | 4 +- .../Service/ConsumerServiceGoodbyeTest.php | 3 +- .../Service/ConsumerServiceHelloTest.php | 7 +- phpstan.neon | 3 + phpunit.xml | 1 - ...nteractionRequestBodyNotAddedException.php | 12 + ...teractionResponseBodyNotAddedException.php | 12 + .../MockServerNotStartedException.php | 12 + .../Exception/PactFileNotWroteException.php | 12 + .../Consumer/Hook/ContractDownloader.php | 98 ------ src/PhpPact/Consumer/InteractionBuilder.php | 37 +-- .../Consumer/Listener/PactTestListener.php | 65 ++-- src/PhpPact/Consumer/Matcher/Matcher.php | 31 +- src/PhpPact/Consumer/Model/AbstractPact.php | 20 ++ .../Consumer/Model/ConsumerRequest.php | 68 ++-- src/PhpPact/Consumer/Model/Interaction.php | 36 ++- src/PhpPact/Consumer/Model/Pact.php | 183 +++++++++++ .../Consumer/Model/ProviderResponse.php | 38 ++- src/PhpPact/Standalone/Broker/Broker.php | 6 +- .../Standalone/Broker/BrokerConfig.php | 108 ++++--- .../Exception/HealthCheckFailedException.php | 17 - .../Standalone/Installer/Model/Scripts.php | 14 +- .../Standalone/MockService/MockServer.php | 149 --------- .../MockService/MockServerConfig.php | 292 +----------------- .../MockService/MockServerConfigInterface.php | 51 +-- .../MockService/MockServerEnvConfig.php | 17 +- .../Service/MockServerHttpService.php | 173 ----------- .../MockServerHttpServiceInterface.php | 51 --- src/PhpPact/Standalone/PactConfig.php | 219 +++++++++++++ .../Standalone/PactConfigInterface.php | 39 ++- .../PactMessage/PactMessageConfig.php | 162 +--------- .../Consumer/InteractionBuilderTest.php | 27 -- .../PhpPact/Consumer/Matcher/MatcherTest.php | 166 ++++------ .../Consumer/Model/ConsumerRequestTest.php | 22 +- .../Consumer/Model/ProviderResponseTest.php | 8 +- .../Standalone/Broker/BrokerConfigTest.php | 105 +++++-- .../MockServer/MockServerConfigTest.php | 9 +- .../Standalone/MockServer/MockServerTest.php | 68 ---- .../Service/MockServerHttpServiceTest.php | 210 ------------- tests/PhpPact/Standalone/PactConfigTest.php | 65 ++++ 48 files changed, 1092 insertions(+), 1737 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 UPGRADE-9.0.md create mode 100644 phpstan.neon create mode 100644 src/PhpPact/Consumer/Exception/InteractionRequestBodyNotAddedException.php create mode 100644 src/PhpPact/Consumer/Exception/InteractionResponseBodyNotAddedException.php create mode 100644 src/PhpPact/Consumer/Exception/MockServerNotStartedException.php create mode 100644 src/PhpPact/Consumer/Exception/PactFileNotWroteException.php delete mode 100644 src/PhpPact/Consumer/Hook/ContractDownloader.php create mode 100644 src/PhpPact/Consumer/Model/AbstractPact.php create mode 100644 src/PhpPact/Consumer/Model/Pact.php delete mode 100644 src/PhpPact/Standalone/Exception/HealthCheckFailedException.php delete mode 100644 src/PhpPact/Standalone/MockService/MockServer.php delete mode 100644 src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php delete mode 100644 src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php create mode 100644 src/PhpPact/Standalone/PactConfig.php delete mode 100644 tests/PhpPact/Standalone/MockServer/MockServerTest.php delete mode 100644 tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php create mode 100644 tests/PhpPact/Standalone/PactConfigTest.php diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7697056b..b6cd146b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,7 +63,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - extensions: openssl, sockets, curl, zip + extensions: openssl, sockets, curl, zip, ffi php-version: ${{ matrix.php }} - name: Composer install diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..6f130920 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +CHANGELOG +========= + +9.0 +--- + +* General + * [BC BREAK] Declare types for: + * Properties + * Arguments + * Return values + +* Matchers + * [BC BREAK] Update matcher implementations + +* Installers + * [BC BREAK] Removed `PhpPact\Standalone\Installer\Model\Scripts::getMockService` + * Added `PhpPact\Standalone\Installer\Model\Scripts::getCode` + * Added `PhpPact\Standalone\Installer\Model\Scripts::getLibrary` + +* Config + * [BC BREAK] Updated `PhpPact\Standalone\MockService\MockServerConfigInterface`, removed these methods: + * `hasCors` + * `setCors` + * `setHealthCheckTimeout` + * `getHealthCheckTimeout` + * `setHealthCheckRetrySec` + * `getHealthCheckRetrySec` + * [BC BREAK] Removed environments variables: + * PACT_CORS + * PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT + * PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC + * Moved these methods from `PhpPact\Standalone\MockService\MockServerConfigInterface` to `PhpPact\Standalone\PactConfigInterface`: + * `getPactFileWriteMode` + * `setPactFileWriteMode` + * `PhpPact\Standalone\MockService\MockServerConfigInterface` now extends `PhpPact\Standalone\PactConfigInterface` + * Added `PhpPact\Standalone\PactConfig` + * `PhpPact\Standalone\PactMessage\PactMessageConfig` now extends `PhpPact\Standalone\PactConfig` + * `PhpPact\Standalone\MockService\MockServerConfig` now extends `PhpPact\Standalone\PactConfig` + * Change default specification version to `3.0.0` + +* Mock Server + * [BC BREAK] Removed `PhpPact\Standalone\MockService\MockServer` + * [BC BREAK] Removed `PhpPact\Standalone\MockService\Service\MockServerHttpService` + * [BC BREAK] Removed `PhpPact\Standalone\MockService\Service\MockServerHttpServiceInterface` + * [BC BREAK] Removed `PhpPact\Standalone\Exception\HealthCheckFailedException` + * Added `PhpPact\Consumer\Exception\MockServerNotStartedException` + +* Interaction Builder + * Added `PhpPact\Consumer\InteractionBuilder::createMockServer` + * [BC BREAK] It's now required to call `PhpPact\Consumer\InteractionBuilder::createMockServer` manually before `PhpPact\Consumer\InteractionBuilder::verify` + * [BC BREAK] Removed `PhpPact\Consumer\InteractionBuilder::finalize` + * [BC BREAK] `PhpPact\Consumer\Model\ConsumerRequest::getQuery` now return `array` instead of `string` + * [BC BREAK] `PhpPact\Consumer\Model\ConsumerRequest::setQuery` now accept argument `array` instead of `string` + * Added `PhpPact\Consumer\Exception\InteractionRequestBodyNotAddedException` + * Added `PhpPact\Consumer\Exception\InteractionResponseBodyNotAddedException` + * Added `PhpPact\Consumer\Exception\PactFileNotWroteException` + +* PHPUnit + * [BC BREAK] Removed `PhpPact\Consumer\Hook\ContractDownloader` + * Replace broker http client by broker cli in `PhpPact\Consumer\Listener\PactTestListener` diff --git a/README.md b/README.md index accc88dd..3fb015bd 100644 --- a/README.md +++ b/README.md @@ -11,31 +11,39 @@ PHP version of [Pact](https://pact.io). Enables consumer driven contract testing Table of contents ================= -* [Versions](#versions) -* [Specifications](#specifications) -* [Installation](#installation) -* [Basic Consumer Usage](#basic-consumer-usage) - * [Start and Stop the Mock Server](#start-and-stop-the-mock-server) - * [Create Consumer Unit Test](#create-consumer-unit-test) - * [Create Mock Request](#create-mock-request) - * [Create Mock Response](#create-mock-response) - * [Build the Interaction](#build-the-interaction) - * [Make the Request](#make-the-request) - * [Make Assertions](#make-assertions) -* [Basic Provider Usage](#basic-provider-usage) - * [Create Unit Tests](#create-unit-test) - * [Start API](#start-api) - * [Provider Verification](#provider-verification) - * [Verify From Pact Broker](#verify-from-pact-broker) - * [Verify All from Pact Broker](#verify-all-from-pact-broker) - * [Verify Files by Path](#verify-files-by-path) -* [Tips](#tips) - * [Starting API Asynchronously](#starting-api-asynchronously) - * [Set Up Provider State](#set-up-provider-state) - * [Examples](#additional-examples) +- [Pact PHP](#pact-php) +- [Table of contents](#table-of-contents) + - [Versions](#versions) + - [Specifications](#specifications) + - [Installation](#installation) + - [Basic Consumer Usage](#basic-consumer-usage) + - [Publish Contracts To Pact Broker](#publish-contracts-to-pact-broker) + - [Create Consumer Unit Test](#create-consumer-unit-test) + - [Create Mock Request](#create-mock-request) + - [Create Mock Response](#create-mock-response) + - [Build the Interaction](#build-the-interaction) + - [Start the Mock Server](#start-the-mock-server) + - [Make the Request](#make-the-request) + - [Verify Interactions](#verify-interactions) + - [Make Assertions](#make-assertions) + - [Basic Provider Usage](#basic-provider-usage) + - [Create Unit Test](#create-unit-test) + - [Start API](#start-api) + - [Provider Verification](#provider-verification) + - [Verify From Pact Broker](#verify-from-pact-broker) + - [Verify All from Pact Broker](#verify-all-from-pact-broker) + - [Verify Files by Path](#verify-files-by-path) + - [Tips](#tips) + - [Starting API Asynchronously](#starting-api-asynchronously) + - [Set Up Provider State](#set-up-provider-state) + - [Additional Examples](#additional-examples) + - [Message support](#message-support) + - [Consumer Side Message Processing](#consumer-side-message-processing) + - [Provider Side Message Validation](#provider-side-message-validation) + - [Usage for the optional `pact-stub-service`](#usage-for-the-optional-pact-stub-service) ## Versions -9.X updates internal dependencies and libraries. This results in dropping PHP 7.4 +9.X adds support for pact specification 3.X & 4.X via Pact FFI. This results in dropping PHP 7.4 8.X updates internal dependencies and libraries. This results in dropping PHP 7.3 @@ -52,7 +60,13 @@ If you wish to stick with the 2.X implementation, you can continue to pull from ## Specifications -The 3.X version is the version of Pact-PHP, not the pact specification version that it supports. Pact-Php 3.X supports [Pact-Specification 2.X](https://github.com/pact-foundation/pact-specification/tree/version-2). +The 3.X version is the version of Pact-PHP, not the pact specification version that it supports. + +Pact-Php 3.X -> 8.X supports [Pact-Specification 2.X](https://github.com/pact-foundation/pact-specification/tree/version-2). +Pact-Php 9.X supports: + * [Pact-Specification 2.X](https://github.com/pact-foundation/pact-specification/tree/version-2) + * [Pact-Specification 3.X](https://github.com/pact-foundation/pact-specification/tree/version-3). + * [Pact-Specification 4.X](https://github.com/pact-foundation/pact-specification/tree/version-4). ## Installation @@ -68,39 +82,12 @@ Composer hosts older versions under `mattersight/phppact`, which is abandoned. P All of the following code will be used exclusively for the Consumer. -### Start and Stop the Mock Server +### Publish Contracts To Pact Broker -This library contains a wrapper for the [Ruby Standalone Mock Service](https://github.com/pact-foundation/pact-mock_service). +When all tests in test suite are passed, you may want to publish generated contract files to pact broker automatically. The easiest way to configure this is to use a [PHPUnit Listener](https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.test-listeners). A default listener is included in this project, see [PactTestListener.php](/src/PhpPact/Consumer/Listener/PactTestListener.php). This utilizes environmental variables for configurations. These env variables can either be added to the system or to the phpunit.xml configuration file. Here is an example [phpunit.xml](/example/phpunit.consumer.xml) file configured to use the default. Keep in mind that both the test suite and the arguments array must be the same value. -Alternatively, you can start and stop as in whatever means you would like by following this example: - -```php -setHost('localhost'); - $config->setPort(7200); - $config->setConsumer('someConsumer'); - $config->setProvider('someProvider'); - $config->setCors(true); - - // Instantiate the mock server object with the config. This can be any - // instance of MockServerConfigInterface. - $server = new MockServer($config); - - // Create the process. - $server->start(); - - // Stop the process. - $server->stop(); -``` - ### Create Consumer Unit Test Create a standard PHPUnit test case class and function. @@ -191,6 +178,14 @@ $builder ->willRespondWith($response); // This has to be last. This is what makes an API request to the Mock Server to set the interaction. ``` +### Start the Mock Server + +Mock server need to be started manually + +```php +$builder->createMockServer(); +``` + ### Make the Request ```php @@ -204,7 +199,7 @@ Verify that all interactions took place that were registered. This typically should be in each test, that way the test that failed to verify is marked correctly. ```php -$builder->verify(); +$this->assertTrue($builder->verify()); ``` ### Make Assertions diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md new file mode 100644 index 00000000..c1628bf4 --- /dev/null +++ b/UPGRADE-9.0.md @@ -0,0 +1,20 @@ +UPGRADE FROM 8.x to 9.0 +======================= + +* Interaction Builder + * It's now required to call `PhpPact\Consumer\InteractionBuilder::createMockServer` manually + + Example Usage: + ```php + $builder = new InteractionBuilder($config); + $builder + ->given('a person exists') + ->uponReceiving('a get request to /hello/{name}') + ->with($request) + ->willRespondWith($response); + $builder->createMockServer(); + + $apiClient->sendRequest(); + + $this->assertTrue($builder->verify()); + ``` diff --git a/composer.json b/composer.json index 8b040008..2bedcec0 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ }, "scripts": { "start-provider": "php -S localhost:58000 -t example/src/Provider/public/", - "static-code-analysis": "phpstan analyse src/ --level=5", + "static-code-analysis": "phpstan analyse src/ --level=5 -c phpstan.neon", "lint": "php-cs-fixer fix --config .php-cs-fixer.php --dry-run", "fix": "php-cs-fixer fix --config .php-cs-fixer.php", "test": "phpunit --debug -c example/phpunit.all.xml" @@ -87,6 +87,22 @@ }, "url": "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", "path": "bin/pact-ruby-standalone" + }, + "pact-ffi-headers": { + "version": "0.3.19", + "url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/pact.h", + "path": "bin/pact-ffi-headers/pact.h" + }, + "pact-ffi-lib": { + "version": "0.3.19", + "variables": { + "{$prefix}": "PHP_OS_FAMILY === 'Windows' ? 'pact_ffi' : 'libpact_ffi'", + "{$os}": "PHP_OS === 'Darwin' ? 'osx' : strtolower(PHP_OS_FAMILY)", + "{$architecture}": "in_array(php_uname('m'), ['arm64', 'aarch64']) ? (PHP_OS === 'Darwin' ? 'aarch64-apple-darwin' : 'aarch64') : 'x86_64'", + "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'dll' : (PHP_OS === 'Darwin' ? 'dylib' : 'so')" + }, + "url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/{$prefix}-{$os}-{$architecture}.{$extension}.gz", + "path": "bin/pact-ffi-lib/pact.{$extension}" } } }, diff --git a/example/phpunit.all.xml b/example/phpunit.all.xml index 554c8a46..02a047d5 100644 --- a/example/phpunit.all.xml +++ b/example/phpunit.all.xml @@ -36,7 +36,5 @@ - - diff --git a/example/phpunit.consumer.xml b/example/phpunit.consumer.xml index 6e88b251..0e521db3 100644 --- a/example/phpunit.consumer.xml +++ b/example/phpunit.consumer.xml @@ -24,7 +24,6 @@ - diff --git a/example/phpunit.core.xml b/example/phpunit.core.xml index aefb2d26..57e6e2e4 100644 --- a/example/phpunit.core.xml +++ b/example/phpunit.core.xml @@ -24,7 +24,5 @@ - - diff --git a/example/src/Consumer/Service/HttpClientService.php b/example/src/Consumer/Service/HttpClientService.php index 668ba75b..3ae6ad9d 100644 --- a/example/src/Consumer/Service/HttpClientService.php +++ b/example/src/Consumer/Service/HttpClientService.php @@ -12,10 +12,10 @@ class HttpClientService { /** @var Client */ - private $httpClient; + private Client $httpClient; /** @var string */ - private $baseUri; + private string $baseUri; public function __construct(string $baseUri) { diff --git a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php index e5106f55..bc840bb2 100644 --- a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php @@ -37,11 +37,12 @@ public function testGetGoodbyeString() ->uponReceiving('A get request to /goodbye/{name}') ->with($request) ->willRespondWith($response); + $builder->createMockServer(); $service = new HttpClientService($config->getBaseUri()); $result = $service->getGoodbyeString('Bob'); - $builder->verify(); + $this->assertTrue($builder->verify()); $this->assertEquals('Goodbye, Bob', $result); } diff --git a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php index 2a80f394..d23df42c 100644 --- a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php @@ -33,7 +33,7 @@ public function testGetHelloString() ->setStatus(200) ->addHeader('Content-Type', 'application/json') ->setBody([ - 'message' => $matcher->term('Hello, Bob', '(Hello, )[A-Za-z]') + 'message' => $matcher->term('Hello, Bob', '(Hello, )[A-Za-z]+') ]); // Create a configuration that reflects the server that was started. You can create a custom MockServerConfigInterface if needed. @@ -42,12 +42,13 @@ public function testGetHelloString() $builder ->uponReceiving('A get request to /hello/{name}') ->with($request) - ->willRespondWith($response); // This has to be last. This is what makes an API request to the Mock Server to set the interaction. + ->willRespondWith($response); // This has to be last. This is what makes a FFI call to the Mock Server to set the interaction. + $builder->createMockServer(); $service = new HttpClientService($config->getBaseUri()); // Pass in the URL to the Mock Server. $result = $service->getHelloString('Bob'); // Make the real API request against the Mock Server. - $builder->verify(); // This will verify that the interactions took place. + $this->assertTrue($builder->verify()); // This will verify that the interactions took place. $this->assertEquals('Hello, Bob', $result); // Make your assertions. } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..e0f2f0e8 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,3 @@ +parameters: + excludePaths: + - src/PhpPact/Consumer/Model/Pact.php diff --git a/phpunit.xml b/phpunit.xml index 35d425c2..af9ebf68 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -26,6 +26,5 @@ - diff --git a/src/PhpPact/Consumer/Exception/InteractionRequestBodyNotAddedException.php b/src/PhpPact/Consumer/Exception/InteractionRequestBodyNotAddedException.php new file mode 100644 index 00000000..2f99fd3e --- /dev/null +++ b/src/PhpPact/Consumer/Exception/InteractionRequestBodyNotAddedException.php @@ -0,0 +1,12 @@ +mockServerConfig = new MockServerEnvConfig(); - } - - /** - * @throws AssertionFailedError - * @throws RuntimeException - */ - public function executeAfterLastTest(): void - { - try { - $this->getMockServerService()->verifyInteractions(); - } catch (Exception $e) { - throw new AssertionFailedError('Pact interaction verification failed', 0, $e); - } - - try { - \file_put_contents($this->getPactFilename(), $this->getPactJson()); - } catch (Exception $e) { - throw new RuntimeException('Pact contract generation failed', 0, $e); - } - } - - private function getMockServerService(): MockServerHttpService - { - return new MockServerHttpService( - $this->getClient(), - $this->mockServerConfig - ); - } - - private function getClient(): ClientInterface - { - if (!$this->client) { - $this->client = new GuzzleClient(); - } - - return $this->client; - } - - private function getPactFilename(): string - { - return $this->mockServerConfig->getPactDir() - . DIRECTORY_SEPARATOR - . $this->mockServerConfig->getConsumer() - . '-' - . $this->mockServerConfig->getProvider() . '.json'; - } - - private function getPactJson(): string - { - $uri = $this->mockServerConfig->getBaseUri()->withPath('/pact'); - $response = $this->getClient()->post( - $uri, - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - 'body' => \json_encode([ - 'consumer' => ['name' => $this->mockServerConfig->getConsumer()], - 'provider' => ['name' => $this->mockServerConfig->getProvider()] - ]) - ] - ); - - return \json_encode(\json_decode($response->getBody()->getContents())); - } -} diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index 93ec41d6..39eef2a8 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -4,10 +4,9 @@ use PhpPact\Consumer\Model\ConsumerRequest; use PhpPact\Consumer\Model\Interaction; +use PhpPact\Consumer\Model\Pact; use PhpPact\Consumer\Model\ProviderResponse; -use PhpPact\Http\GuzzleClient; use PhpPact\Standalone\MockService\MockServerConfigInterface; -use PhpPact\Standalone\MockService\Service\MockServerHttpService; /** * Build an interaction and send it to the Ruby Standalone Mock Service @@ -15,13 +14,11 @@ */ class InteractionBuilder implements BuilderInterface { - /** @var MockServerHttpService */ - protected $mockServerHttpService; - - /** @var MockServerConfigInterface */ - protected $config; /** @var Interaction */ - private $interaction; + protected Interaction $interaction; + + /** @var Pact */ + protected Pact $pact; /** * InteractionBuilder constructor. @@ -30,9 +27,8 @@ class InteractionBuilder implements BuilderInterface */ public function __construct(MockServerConfigInterface $config) { - $this->config = $config; - $this->mockServerHttpService = new MockServerHttpService(new GuzzleClient(), $config); $this->interaction = new Interaction(); + $this->pact = new Pact($config); } /** @@ -72,8 +68,6 @@ public function with(ConsumerRequest $request): self } /** - * Make the http request to the Mock Service to register the interaction. - * * @param ProviderResponse $response mock of response received * * @return bool returns true on success @@ -82,7 +76,7 @@ public function willRespondWith(ProviderResponse $response): bool { $this->interaction->setResponse($response); - return $this->mockServerHttpService->registerInteraction($this->interaction); + return $this->pact->registerInteraction($this->interaction); } /** @@ -90,21 +84,15 @@ public function willRespondWith(ProviderResponse $response): bool */ public function verify(): bool { - return $this->mockServerHttpService->verifyInteractions(); + return $this->pact->verifyInteractions(); } /** - * Writes the file to disk and deletes interactions from mock server. + * Create mock server before verifying. */ - public function finalize(): bool + public function createMockServer(): void { - // Write the pact file to disk. - $this->mockServerHttpService->getPactJson(); - - // Delete the interactions. - $this->mockServerHttpService->deleteAllInteractions(); - - return true; + $this->pact->createMockServer(); } /** @@ -112,9 +100,6 @@ public function finalize(): bool */ public function writePact(): bool { - // Write the pact file to disk. - $this->mockServerHttpService->getPactJson(); - return true; } } diff --git a/src/PhpPact/Consumer/Listener/PactTestListener.php b/src/PhpPact/Consumer/Listener/PactTestListener.php index 5d88ef18..af66e11f 100644 --- a/src/PhpPact/Consumer/Listener/PactTestListener.php +++ b/src/PhpPact/Consumer/Listener/PactTestListener.php @@ -3,12 +3,10 @@ namespace PhpPact\Consumer\Listener; use GuzzleHttp\Psr7\Uri; -use PhpPact\Broker\Service\BrokerHttpClient; -use PhpPact\Http\GuzzleClient; use PhpPact\Standalone\Exception\MissingEnvVariableException; -use PhpPact\Standalone\MockService\MockServer; +use PhpPact\Standalone\Broker\Broker; +use PhpPact\Standalone\Broker\BrokerConfig; use PhpPact\Standalone\MockService\MockServerEnvConfig; -use PhpPact\Standalone\MockService\Service\MockServerHttpService; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestListener; @@ -23,21 +21,18 @@ class PactTestListener implements TestListener { use TestListenerDefaultImplementation; - /** @var MockServer */ - private $server; - /** * Name of the test suite configured in your phpunit config. * * @var string[] */ - private $testSuiteNames; + private array $testSuiteNames; /** @var MockServerEnvConfig */ - private $mockServerConfig; + private MockServerEnvConfig $mockServerConfig; /** @var bool */ - private $failed; + private bool $failed = false; /** * PactTestListener constructor. @@ -52,19 +47,6 @@ public function __construct(array $testSuiteNames) $this->mockServerConfig = new MockServerEnvConfig(); } - /** - * @param TestSuite $suite - * - * @throws \Exception - */ - public function startTestSuite(TestSuite $suite): void - { - if (\in_array($suite->getName(), $this->testSuiteNames)) { - $this->server = new MockServer($this->mockServerConfig); - $this->server->start(); - } - } - public function addError(Test $test, \Throwable $t, float $time): void { $this->failed = true; @@ -83,15 +65,6 @@ public function addFailure(Test $test, AssertionFailedError $e, float $time): vo public function endTestSuite(TestSuite $suite): void { if (\in_array($suite->getName(), $this->testSuiteNames)) { - try { - $httpService = new MockServerHttpService(new GuzzleClient(), $this->mockServerConfig); - $httpService->verifyInteractions(); - - $json = $httpService->getPactJson(); - } finally { - $this->server->stop(); - } - if ($this->failed === true) { print 'A unit test has failed. Skipping PACT file upload.'; } elseif (!($pactBrokerUri = \getenv('PACT_BROKER_URI'))) { @@ -101,29 +74,27 @@ public function endTestSuite(TestSuite $suite): void } elseif (!($tag = \getenv('PACT_CONSUMER_TAG'))) { print 'PACT_CONSUMER_TAG environment variable was not set. Skipping PACT file upload.'; } else { - $clientConfig = []; + $brokerConfig = new BrokerConfig(); + $brokerConfig->setPacticipant($this->mockServerConfig->getConsumer()); + $brokerConfig->setPactLocations($this->mockServerConfig->getPactDir()); + $brokerConfig->setBrokerUri(new Uri($pactBrokerUri)); + $brokerConfig->setConsumerVersion($consumerVersion); + $brokerConfig->setVersion($consumerVersion); + $brokerConfig->setTag($tag); if (($user = \getenv('PACT_BROKER_HTTP_AUTH_USER')) && ($pass = \getenv('PACT_BROKER_HTTP_AUTH_PASS')) ) { - $clientConfig = [ - 'auth' => [$user, $pass], - ]; - } - - if (($sslVerify = \getenv('PACT_BROKER_SSL_VERIFY'))) { - $clientConfig['verify'] = $sslVerify !== 'no'; + $brokerConfig->setBrokerUsername($user); + $brokerConfig->setBrokerPassword($pass); } - $headers = []; if ($bearerToken = \getenv('PACT_BROKER_BEARER_TOKEN')) { - $headers['Authorization'] = 'Bearer ' . $bearerToken; + $brokerConfig->setBrokerToken($bearerToken); } - $client = new GuzzleClient($clientConfig); - - $brokerHttpService = new BrokerHttpClient($client, new Uri($pactBrokerUri), $headers); - $brokerHttpService->tag($this->mockServerConfig->getConsumer(), $consumerVersion, $tag); - $brokerHttpService->publishJson($consumerVersion, $json); + $broker = new Broker($brokerConfig); + $broker->createVersionTag(); + $broker->publish(); print 'Pact file has been uploaded to the Broker successfully.'; } } diff --git a/src/PhpPact/Consumer/Matcher/Matcher.php b/src/PhpPact/Consumer/Matcher/Matcher.php index 3317c3d5..1fed12cc 100644 --- a/src/PhpPact/Consumer/Matcher/Matcher.php +++ b/src/PhpPact/Consumer/Matcher/Matcher.php @@ -3,7 +3,9 @@ namespace PhpPact\Consumer\Matcher; /** - * Matcher implementation. Builds the Ruby Mock Server specification json for interaction publishing. + * Matcher implementation. Builds the Pact FFI specification json for interaction publishing. + * @see https://docs.pact.io/implementation_guides/rust/pact_ffi/integrationjson + * * Class Matcher. */ class Matcher @@ -46,8 +48,8 @@ public function like($value): array } return [ - 'contents' => $value, - 'json_class' => 'Pact::SomethingLike', + 'value' => $value, + 'pact:matcher:type' => 'type', ]; } @@ -61,14 +63,11 @@ public function like($value): array */ public function eachLike($value, int $min = 1): array { - $result = [ - 'contents' => $value, - 'json_class' => 'Pact::ArrayLike', + return [ + 'value' => array_fill(0, $min, $value), + 'pact:matcher:type' => 'type', + 'min' => $min, ]; - - $result['min'] = $min; - - return $result; } /** @@ -92,15 +91,9 @@ public function term($value, string $pattern): array } return [ - 'data' => [ - 'generate' => $value, - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => $pattern, - ], - ], - 'json_class' => 'Pact::Term', + 'value' => $value, + 'regex' => $pattern, + 'pact:matcher:type' => 'regex', ]; } diff --git a/src/PhpPact/Consumer/Model/AbstractPact.php b/src/PhpPact/Consumer/Model/AbstractPact.php new file mode 100644 index 00000000..088f2160 --- /dev/null +++ b/src/PhpPact/Consumer/Model/AbstractPact.php @@ -0,0 +1,20 @@ +ffi = FFI::cdef(\file_get_contents(Scripts::getCode()), Scripts::getLibrary()); + } +} diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index b54ce104..784fd264 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -11,27 +11,27 @@ class ConsumerRequest implements \JsonSerializable /** * @var string */ - private $method; + private string $method; /** - * @var array|string + * @var string */ - private $path; + private string $path; /** * @var string[] */ - private $headers; + private array $headers = []; /** - * @var mixed + * @var null|string */ - private $body; + private ?string $body = null; /** - * @var string + * @var array */ - private $query; + private array $query = []; /** * @return string @@ -54,9 +54,9 @@ public function setMethod(string $method): self } /** - * @return array|string + * @return string */ - public function getPath() + public function getPath(): string { return $this->path; } @@ -66,17 +66,17 @@ public function getPath() * * @return ConsumerRequest */ - public function setPath($path): self + public function setPath(array|string $path): self { - $this->path = $path; + $this->path = is_array($path) ? json_encode($path) : $path; return $this; } /** - * @return string[] + * @return array */ - public function getHeaders() + public function getHeaders(): array { return $this->headers; } @@ -99,17 +99,17 @@ public function setHeaders(array $headers): self * * @return ConsumerRequest */ - public function addHeader(string $header, $value): self + public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = $value; + $this->headers[$header] = is_array($value) ? json_encode($value) : $value; return $this; } /** - * @return mixed + * @return null|string */ - public function getBody() + public function getBody(): ?string { return $this->body; } @@ -119,27 +119,34 @@ public function getBody() * * @return ConsumerRequest */ - public function setBody($body) + public function setBody(mixed $body): self { - $this->body = $body; + if (\is_string($body)) { + $this->body = $body; + } elseif (!\is_null($body)) { + $this->body = \json_encode($body); + $this->addHeader('Content-Type', 'application/json'); + } else { + $this->body = null; + } return $this; } /** - * @return null|string + * @return array */ - public function getQuery() + public function getQuery(): array { return $this->query; } /** - * @param string $query + * @param array $query * * @return ConsumerRequest */ - public function setQuery(string $query): self + public function setQuery(array $query): self { $this->query = $query; @@ -148,17 +155,13 @@ public function setQuery(string $query): self /** * @param string $key - * @param string $value + * @param array|string $value * * @return ConsumerRequest */ - public function addQueryParameter(string $key, string $value): self + public function addQueryParameter(string $key, array|string $value): self { - if ($this->query === null) { - $this->query = "{$key}={$value}"; - } else { - $this->query = "{$this->query}&{$key}={$value}"; - } + $this->query[$key] = is_array($value) ? json_encode($value) : $value; return $this; } @@ -166,8 +169,7 @@ public function addQueryParameter(string $key, string $value): self /** * {@inheritdoc} */ - #[\ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { $results = []; diff --git a/src/PhpPact/Consumer/Model/Interaction.php b/src/PhpPact/Consumer/Model/Interaction.php index 06577c06..e7485fea 100644 --- a/src/PhpPact/Consumer/Model/Interaction.php +++ b/src/PhpPact/Consumer/Model/Interaction.php @@ -8,25 +8,50 @@ */ class Interaction implements \JsonSerializable { + /** + * @var int + */ + private int $id; + /** * @var string */ - private $description; + private string $description; /** * @var null|string */ - private $providerState; + private ?string $providerState = null; /** * @var ConsumerRequest */ - private $request; + private ConsumerRequest $request; /** * @var ProviderResponse */ - private $response; + private ProviderResponse $response; + + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * @param int $id + * + * @return Interaction + */ + public function setId(int $id): self + { + $this->id = $id; + + return $this; + } /** * @return string @@ -111,8 +136,7 @@ public function setResponse(ProviderResponse $response): self /** * {@inheritdoc} */ - #[\ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { if ($this->getProviderState()) { return [ diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php new file mode 100644 index 00000000..84d55223 --- /dev/null +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -0,0 +1,183 @@ +initWithLogLevel() + ->newPact() + ->withSpecification(); + } + + public function createMockServer(): void + { + $port = $this->ffi->pactffi_create_mock_server_for_transport( + $this->id, + $this->config->getHost(), + $this->config->getPort(), + $this->config->isSecure() ? 'https' : 'http', + null + ); + + if ($port < 0) { + $message = match ($port) { + -1 => 'An invalid handle was received. Handles should be created with `pactffi_new_pact`', + -2 => 'Transport_config is not valid JSON', + -3 => 'The mock server could not be started', + -4 => 'The method panicked', + -5 => 'The address is not valid', + }; + throw new MockServerNotStartedException($message); + } + $this->config->setPort($port); + } + + public function verifyInteractions(): bool + { + $result = $this->ffi->pactffi_mock_server_matched($this->config->getPort()); + + if ($result) { + $error = $this->ffi->pactffi_write_pact_file( + $this->config->getPort(), + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + $message = match ($error) { + 1 => 'A general panic was caught', + 2 => 'The pact file was not able to be written', + 3 => 'A mock server with the provided port was not found', + }; + throw new PactFileNotWroteException($message); + } + } + + $this->ffi->pactffi_cleanup_mock_server($this->config->getPort()); + $this->ffi->pactffi_free_pact_handle($this->id); + + return $result; + } + + public function registerInteraction(Interaction $interaction): bool + { + $this + ->newInteraction($interaction) + ->given($interaction) + ->uponReceiving($interaction) + ->with($interaction) + ->willRespondWith($interaction); + + return true; + } + + private function initWithLogLevel(): self + { + $logLevel = $this->config->getLogLevel(); + if ($logLevel) { + $this->ffi->pactffi_init_with_log_level($logLevel); + } + + return $this; + } + + private function newPact(): self + { + $this->id = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); + + return $this; + } + + private function withSpecification(): self + { + $supportedVersions = [ + '1.0.0' => $this->ffi->PactSpecification_V1, + '1.1.0' => $this->ffi->PactSpecification_V1_1, + '2.0.0' => $this->ffi->PactSpecification_V2, + '3.0.0' => $this->ffi->PactSpecification_V3, + '4.0.0' => $this->ffi->PactSpecification_V4, + ]; + $version = $supportedVersions[$this->config->getPactSpecificationVersion()] ?? $this->ffi->PactSpecification_Unknown; + $this->ffi->pactffi_with_specification($this->id, $version); + + return $this; + } + + private function newInteraction(Interaction $interaction): self + { + $id = $this->ffi->pactffi_new_interaction($this->id, $interaction->getDescription()); + $interaction->setId($id); + + return $this; + } + + private function given(Interaction $interaction): self + { + $this->ffi->pactffi_given($interaction->getId(), $interaction->getProviderState()); + + return $this; + } + + private function uponReceiving(Interaction $interaction): self + { + $this->ffi->pactffi_upon_receiving($interaction->getId(), $interaction->getDescription()); + + return $this; + } + + private function with(Interaction $interaction): self + { + $id = $interaction->getId(); + $request = $interaction->getRequest(); + $this->ffi->pactffi_with_request($id, $request->getMethod(), $request->getPath()); + foreach ($request->getHeaders() as $header => $value) { + $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Request, $header, 0, $value); + } + foreach ($request->getQuery() as $key => $value) { + $this->ffi->pactffi_with_query_parameter_v2($id, $key, 0, $value); + } + if (!\is_null($request->getBody())) { + $success = $this->ffi->pactffi_with_body($id, $this->ffi->InteractionPart_Request, null, $request->getBody()); + if (!$success) { + throw new InteractionRequestBodyNotAddedException(); + } + } + + return $this; + } + + private function willRespondWith(Interaction $interaction): self + { + $id = $interaction->getId(); + $response = $interaction->getResponse(); + $this->ffi->pactffi_response_status($id, $response->getStatus()); + foreach ($response->getHeaders() as $header => $value) { + $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Response, $header, 0, $value); + } + if (!\is_null($response->getBody())) { + $success = $this->ffi->pactffi_with_body($id, $this->ffi->InteractionPart_Response, null, $response->getBody()); + if (!$success) { + throw new InteractionResponseBodyNotAddedException(); + } + } + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index d138f555..449f5dac 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -11,17 +11,17 @@ class ProviderResponse implements \JsonSerializable /** * @var int */ - private $status; + private int $status; /** - * @var null|string[] + * @var string[] */ - private $headers; + private array $headers = []; /** - * @var null|array + * @var null|string */ - private $body; + private ?string $body = null; /** * @return int @@ -44,9 +44,9 @@ public function setStatus(int $status): self } /** - * @return null|string[] + * @return string[] */ - public function getHeaders() + public function getHeaders(): array { return $this->headers; } @@ -69,29 +69,36 @@ public function setHeaders(array $headers): self * * @return ProviderResponse */ - public function addHeader(string $header, $value): self + public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = $value; + $this->headers[$header] = is_array($value) ? json_encode($value) : $value; return $this; } /** - * @return mixed + * @return null|string */ - public function getBody() + public function getBody(): ?string { return $this->body; } /** - * @param iterable $body + * @param mixed $body * * @return ProviderResponse */ - public function setBody($body): self + public function setBody(mixed $body): self { - $this->body = $body; + if (\is_string($body)) { + $this->body = $body; + } elseif (!\is_null($body)) { + $this->body = \json_encode($body); + $this->addHeader('Content-Type', 'application/json'); + } else { + $this->body = null; + } return $this; } @@ -99,8 +106,7 @@ public function setBody($body): self /** * {@inheritdoc} */ - #[\ReturnTypeWillChange] - public function jsonSerialize() + public function jsonSerialize(): array { $results = [ 'status' => $this->getStatus(), diff --git a/src/PhpPact/Standalone/Broker/Broker.php b/src/PhpPact/Standalone/Broker/Broker.php index 910706be..2dd0e5b8 100644 --- a/src/PhpPact/Standalone/Broker/Broker.php +++ b/src/PhpPact/Standalone/Broker/Broker.php @@ -12,11 +12,11 @@ class Broker { /** @var Logger */ - private $logger; + private Logger $logger; /** @var BrokerConfig */ - private $config; + private BrokerConfig $config; /** @var string */ - private $command; + private string $command; public function __construct(BrokerConfig $config) { diff --git a/src/PhpPact/Standalone/Broker/BrokerConfig.php b/src/PhpPact/Standalone/Broker/BrokerConfig.php index e54f668e..8e12ee45 100644 --- a/src/PhpPact/Standalone/Broker/BrokerConfig.php +++ b/src/PhpPact/Standalone/Broker/BrokerConfig.php @@ -7,55 +7,55 @@ class BrokerConfig { /** @var null|UriInterface */ - private $brokerUri; + private ?UriInterface $brokerUri = null; /** @var null|string */ - private $brokerToken; + private ?string $brokerToken = null; /** @var null|string */ - private $brokerUsername; + private ?string $brokerUsername = null; /** @var null|string */ - private $brokerPassword; + private ?string $brokerPassword = null; /** @var bool */ - private $verbose = false; + private bool $verbose = false; /** @var null|string */ - private $pacticipant; + private ?string $pacticipant = null; /** @var null|string */ - private $request; + private ?string $request = null; /** @var null|string */ - private $header; + private ?string $header = null; /** @var null|string */ - private $data; + private ?string $data = null; /** @var null|string */ - private $user; + private ?string $user = null; /** @var null|string */ - private $consumer; + private ?string $consumer = null; /** @var null|string */ - private $provider; + private ?string $provider = null; /** @var null|string */ - private $description; + private ?string $description = null; /** @var null|string */ - private $uuid; + private ?string $uuid = null; /** @var null|string */ - private $version; + private ?string $version = null; /** @var null|string */ - private $branch = null; + private ?string $branch = null; /** @var null|string */ - private $tag = null; + private ?string $tag = null; /** @var null|string */ - private $name; + private ?string $name = null; /** @var null|string */ - private $repositoryUrl; + private ?string $repositoryUrl = null; /** @var null|string */ - private $url; + private ?string $url = null; /** @var null|string */ - private $consumerVersion; + private ?string $consumerVersion = null; /** @var null|string */ - private $pactLocations; + private ?string $pactLocations = null; /** * @return null|string @@ -68,7 +68,7 @@ public function getRepositoryUrl(): ?string /** * @param null|string $repositoryUrl * - * @return BrokerConfig + * @return $this */ public function setRepositoryUrl(?string $repositoryUrl): self { @@ -88,7 +88,7 @@ public function getUrl(): ?string /** * @param null|string $url * - * @return BrokerConfig + * @return $this */ public function setUrl(?string $url): self { @@ -108,7 +108,7 @@ public function getVersion(): ?string /** * @param null|string $version * - * @return BrokerConfig + * @return $this */ public function setVersion(?string $version): self { @@ -128,7 +128,7 @@ public function getBranch(): ?string /** * @param null|string $branch * - * @return BrokerConfig + * @return $this */ public function setBranch(?string $branch): self { @@ -148,7 +148,7 @@ public function getTag(): ?string /** * @param null|string $tag * - * @return BrokerConfig + * @return $this */ public function setTag(?string $tag): self { @@ -168,7 +168,7 @@ public function getName(): ?string /** * @param null|string $name * - * @return BrokerConfig + * @return $this */ public function setName(?string $name): self { @@ -188,7 +188,7 @@ public function getRequest(): ?string /** * @param null|string $request * - * @return BrokerConfig + * @return $this */ public function setRequest(?string $request): self { @@ -208,7 +208,7 @@ public function getHeader(): ?string /** * @param null|string $header * - * @return BrokerConfig + * @return $this */ public function setHeader(?string $header): self { @@ -228,7 +228,7 @@ public function getData(): ?string /** * @param null|string $data * - * @return BrokerConfig + * @return $this */ public function setData(?string $data): self { @@ -248,7 +248,7 @@ public function getUser(): ?string /** * @param null|string $user * - * @return BrokerConfig + * @return $this */ public function setUser(?string $user): self { @@ -268,7 +268,7 @@ public function getConsumer(): ?string /** * @param null|string $consumer * - * @return BrokerConfig + * @return $this */ public function setConsumer(?string $consumer): self { @@ -288,7 +288,7 @@ public function getProvider(): ?string /** * @param null|string $provider * - * @return BrokerConfig + * @return $this */ public function setProvider(?string $provider): self { @@ -308,7 +308,7 @@ public function getDescription(): ?string /** * @param null|string $description * - * @return BrokerConfig + * @return $this */ public function setDescription(?string $description): self { @@ -328,7 +328,7 @@ public function getUuid(): ?string /** * @param null|string $uuid * - * @return BrokerConfig + * @return $this */ public function setUuid(?string $uuid): self { @@ -337,11 +337,26 @@ public function setUuid(?string $uuid): self return $this; } - public function isVerbose() + /** + * @return bool + */ + public function isVerbose(): bool { return $this->verbose; } + /** + * @param bool $verbose + * + * @return $this + */ + public function setVerbose(bool $verbose): self + { + $this->verbose = $verbose; + + return $this; + } + /** * @return null|UriInterface */ @@ -352,6 +367,8 @@ public function getBrokerUri(): ?UriInterface /** * @param null|UriInterface $brokerUri + * + * @return $this */ public function setBrokerUri(?UriInterface $brokerUri): self { @@ -370,6 +387,8 @@ public function getBrokerToken(): ?string /** * @param null|string $brokerToken + * + * @return $this */ public function setBrokerToken(?string $brokerToken): self { @@ -388,6 +407,8 @@ public function getBrokerUsername(): ?string /** * @param null|string $brokerUsername + * + * @return $this */ public function setBrokerUsername(?string $brokerUsername): self { @@ -406,6 +427,8 @@ public function getBrokerPassword(): ?string /** * @param null|string $brokerPassword + * + * @return $this */ public function setBrokerPassword(?string $brokerPassword): self { @@ -414,13 +437,18 @@ public function setBrokerPassword(?string $brokerPassword): self return $this; } - public function getPacticipant() + /** + * @return null|string + */ + public function getPacticipant(): ?string { return $this->pacticipant; } /** * @param null|string $pacticipant + * + * @return $this */ public function setPacticipant(?string $pacticipant): self { @@ -440,7 +468,7 @@ public function getConsumerVersion(): ?string /** * @param null|string $consumerVersion * - * @return BrokerConfig + * @return $this */ public function setConsumerVersion(?string $consumerVersion): self { @@ -460,7 +488,7 @@ public function getPactLocations(): ?string /** * @param string $locations * - * @return BrokerConfig + * @return $this */ public function setPactLocations(string $locations): self { diff --git a/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php b/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php deleted file mode 100644 index 665ad684..00000000 --- a/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php +++ /dev/null @@ -1,17 +0,0 @@ -config = $config; - - if (!$httpService) { - $this->httpService = new MockServerHttpService(new GuzzleClient(), $this->config); - } else { - $this->httpService = $httpService; - } - } - - /** - * Start the Mock Server. Verify that it is running. - * - * @throws Exception - * - * @return int process ID of the started Mock Server - */ - public function start(): int - { - $this->processRunner = new ProcessRunner(Scripts::getMockService(), $this->getArguments()); - - $processId = $this->processRunner->run(); - - $result = $this->verifyHealthCheck(); - if ($result) { - $retrySec = $this->config->getHealthCheckRetrySec(); - \sleep($retrySec); - } - - return $processId; - } - - /** - * Stop the Mock Server process. - * - * @return bool Was stopping successful? - */ - public function stop(): bool - { - return $this->processRunner->stop(); - } - - /** - * Build an array of command arguments. - * - * @return array - */ - private function getArguments(): array - { - $results = []; - - $logLevel = $this->config->getLogLevel(); - $consumer = \escapeshellarg($this->config->getConsumer()); - $provider = \escapeshellarg($this->config->getProvider()); - $pactDir = \escapeshellarg($this->config->getPactDir()); - - $results[] = 'service'; - $results[] = "--consumer={$consumer}"; - $results[] = "--provider={$provider}"; - $results[] = "--pact-dir={$pactDir}"; - $results[] = "--pact-file-write-mode={$this->config->getPactFileWriteMode()}"; - $results[] = "--host={$this->config->getHost()}"; - $results[] = "--port={$this->config->getPort()}"; - - if ($logLevel) { - $results[] = \sprintf('--log-level=%s', \escapeshellarg($logLevel)); - } - - if ($this->config->hasCors()) { - $results[] = '--cors=true'; - } - - if ($this->config->getPactSpecificationVersion() !== null) { - $results[] = "--pact-specification-version={$this->config->getPactSpecificationVersion()}"; - } - - if (!empty($this->config->getLog())) { - $log = \escapeshellarg($this->config->getLog()); - $results[] = \sprintf('--log=%s', $log); - } - - return $results; - } - - /** - * Make sure the server starts as expected. - * - * @throws Exception - * - * @return bool - */ - private function verifyHealthCheck(): bool - { - $service = $this->httpService; - - // Verify that the service is up. - $tries = 0; - $maxTries = $this->config->getHealthCheckTimeout(); - $retrySec = $this->config->getHealthCheckRetrySec(); - do { - ++$tries; - - try { - $status = $service->healthCheck(); - - return $status; - } catch (ConnectException $e) { - \sleep($retrySec); - } - } while ($tries <= $maxTries); - - // @phpstan-ignore-next-line - throw new HealthCheckFailedException("Failed to make connection to Mock Server in {$maxTries} attempts."); - } -} diff --git a/src/PhpPact/Standalone/MockService/MockServerConfig.php b/src/PhpPact/Standalone/MockService/MockServerConfig.php index 3b5c7621..05a03364 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfig.php @@ -2,98 +2,34 @@ namespace PhpPact\Standalone\MockService; -use Composer\Semver\VersionParser; use GuzzleHttp\Psr7\Uri; -use PhpPact\Standalone\PactConfigInterface; +use PhpPact\Standalone\PactConfig; use Psr\Http\Message\UriInterface; /** * Configuration defining the default PhpPact Ruby Standalone server. * Class MockServerConfig. */ -class MockServerConfig implements MockServerConfigInterface, PactConfigInterface +class MockServerConfig extends PactConfig implements MockServerConfigInterface { /** * Host on which to bind the service. * * @var string */ - private $host = 'localhost'; + private string $host = 'localhost'; /** - * Port on which to run the service. + * Port on which to run the service. A value of zero will result in the operating system allocating an available port. * * @var int */ - private $port = 7200; + private int $port = 7200; /** * @var bool */ - private $secure = false; - - /** - * Consumer name. - * - * @var string - */ - private $consumer; - - /** - * Provider name. - * - * @var string - */ - private $provider; - - /** - * Directory to which the pacts will be written. - * - * @var string - */ - private $pactDir; - - /** - * `overwrite` or `merge`. Use `merge` when running multiple mock service - * instances in parallel for the same consumer/provider pair. Ensure the - * pact file is deleted before running tests when using this option so that - * interactions deleted from the code are not maintained in the file. - * - * @var string - */ - private $pactFileWriteMode = 'overwrite'; - - /** - * The pact specification version to use when writing the pact. Note that only versions 1 and 2 are currently supported. - * - * @var string - */ - private $pactSpecificationVersion; - - /** - * File to which to log output. - * - * @var string - */ - private $log; - - /** @var bool */ - private $cors = false; - - /** - * The max allowed attempts the mock server has to be available in. Otherwise it is considered as sick. - * - * @var int - */ - private $healthCheckTimeout; - - /** - * The seconds between health checks of mock server - * - * @var int - */ - private $healthCheckRetrySec; - private $logLevel; + private bool $secure = false; /** * {@inheritdoc} @@ -106,7 +42,7 @@ public function getHost(): string /** * {@inheritdoc} */ - public function setHost(string $host): MockServerConfigInterface + public function setHost(string $host): self { $this->host = $host; @@ -124,7 +60,7 @@ public function getPort(): int /** * {@inheritdoc} */ - public function setPort(int $port): MockServerConfigInterface + public function setPort(int $port): self { $this->port = $port; @@ -142,7 +78,7 @@ public function isSecure(): bool /** * {@inheritdoc} */ - public function setSecure(bool $secure): MockServerConfigInterface + public function setSecure(bool $secure): self { $this->secure = $secure; @@ -158,214 +94,4 @@ public function getBaseUri(): UriInterface return new Uri("{$protocol}://{$this->getHost()}:{$this->getPort()}"); } - - /** - * {@inheritdoc} - */ - public function getConsumer(): string - { - return $this->consumer; - } - - /** - * {@inheritdoc} - */ - public function setConsumer(string $consumer): PactConfigInterface - { - $this->consumer = $consumer; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProvider(): string - { - return $this->provider; - } - - /** - * {@inheritdoc} - */ - public function setProvider(string $provider): PactConfigInterface - { - $this->provider = $provider; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactDir() - { - if ($this->pactDir === null) { - return \sys_get_temp_dir(); - } - - return $this->pactDir; - } - - /** - * {@inheritdoc} - */ - public function setPactDir($pactDir): PactConfigInterface - { - if ($pactDir === null) { - return $this; - } - - if ('\\' !== \DIRECTORY_SEPARATOR) { - $pactDir = \str_replace('\\', \DIRECTORY_SEPARATOR, $pactDir); - } - - $this->pactDir = $pactDir; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactFileWriteMode(): string - { - return $this->pactFileWriteMode; - } - - /** - * {@inheritdoc} - */ - public function setPactFileWriteMode(string $pactFileWriteMode): MockServerConfigInterface - { - $options = ['overwrite', 'merge']; - - if (!\in_array($pactFileWriteMode, $options)) { - $implodedOptions = \implode(', ', $options); - - throw new \InvalidArgumentException("Invalid PhpPact File Write Mode, value must be one of the following: {$implodedOptions}."); - } - - $this->pactFileWriteMode = $pactFileWriteMode; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactSpecificationVersion() - { - return $this->pactSpecificationVersion; - } - - /** - * {@inheritdoc} - */ - public function setPactSpecificationVersion($pactSpecificationVersion): PactConfigInterface - { - /* - * Parse the version but do not assign it. If it is an invalid version, an exception is thrown - */ - $parser = new VersionParser(); - $parser->normalize($pactSpecificationVersion); - - $this->pactSpecificationVersion = $pactSpecificationVersion; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getLog() - { - return $this->log; - } - - /** - * {@inheritdoc} - */ - public function setLog(string $log): PactConfigInterface - { - $this->log = $log; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getLogLevel() - { - return $this->logLevel; - } - - /** - * {@inheritdoc} - */ - public function setLogLevel(string $logLevel): PactConfigInterface - { - $logLevel = \strtoupper($logLevel); - if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { - throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); - } - $this->logLevel = $logLevel; - - return $this; - } - - public function hasCors(): bool - { - return $this->cors; - } - - public function setCors($flag): MockServerConfigInterface - { - if ($flag === 'true') { - $this->cors = true; - } elseif ($flag === 'false') { - $this->cors = false; - } else { - $this->cors = (bool) $flag; - } - - return $this; - } - - /** - * {@inheritdoc} - */ - public function setHealthCheckTimeout($timeout): MockServerConfigInterface - { - $this->healthCheckTimeout = $timeout; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getHealthCheckTimeout(): int - { - return $this->healthCheckTimeout; - } - - /** - * {@inheritdoc} - */ - public function setHealthCheckRetrySec($seconds): MockServerConfigInterface - { - $this->healthCheckRetrySec = $seconds; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getHealthCheckRetrySec(): int - { - return $this->healthCheckRetrySec; - } } diff --git a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php index a1066ab3..fc6eb64c 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php @@ -2,13 +2,14 @@ namespace PhpPact\Standalone\MockService; +use PhpPact\Standalone\PactConfigInterface; use Psr\Http\Message\UriInterface; /** * Mock Server configuration interface to allow for simple overrides that are reusable. * Interface MockServerConfigInterface. */ -interface MockServerConfigInterface +interface MockServerConfigInterface extends PactConfigInterface { /** * @return string the host of the mock service @@ -50,52 +51,4 @@ public function setSecure(bool $secure): self; * @return UriInterface */ public function getBaseUri(): UriInterface; - - /** - * @return string 'merge' or 'overwrite' merge means that interactions are added and overwrite means that the entire file is overwritten - */ - public function getPactFileWriteMode(): string; - - /** - * @param string $pactFileWriteMode 'merge' or 'overwrite' merge means that interactions are added and overwrite means that the entire file is overwritten - * - * @return MockServerConfigInterface - */ - public function setPactFileWriteMode(string $pactFileWriteMode): self; - - /** - * @return bool - */ - public function hasCors(): bool; - - /** - * @param bool|string $flag - * - * @return MockServerConfigInterface - */ - public function setCors($flag): self; - - /** - * @param int $timeout - * - * @return MockServerConfigInterface - */ - public function setHealthCheckTimeout($timeout): self; - - /** - * @return int - */ - public function getHealthCheckTimeout(): int; - - /** - * @param int $seconds - * - * @return MockServerConfigInterface - */ - public function setHealthCheckRetrySec($seconds): self; - - /** - * @return int - */ - public function getHealthCheckRetrySec(): int; } diff --git a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php index 0edc8251..1f11a8a8 100644 --- a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php @@ -10,7 +10,7 @@ */ class MockServerEnvConfig extends MockServerConfig { - public const DEFAULT_SPECIFICATION_VERSION = '2.0.0'; + public const DEFAULT_SPECIFICATION_VERSION = '3.0.0'; /** * MockServerEnvConfig constructor. @@ -24,7 +24,6 @@ public function __construct() $this->setConsumer($this->parseEnv('PACT_CONSUMER_NAME')); $this->setProvider($this->parseEnv('PACT_PROVIDER_NAME')); $this->setPactDir($this->parseEnv('PACT_OUTPUT_DIR', false)); - $this->setCors($this->parseEnv('PACT_CORS', false)); if ($logDir = $this->parseEnv('PACT_LOG', false)) { $this->setLog($logDir); @@ -34,18 +33,6 @@ public function __construct() $this->setLogLevel($logLevel); } - $timeout = $this->parseEnv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT', false); - if (!$timeout) { - $timeout = 10; - } - $this->setHealthCheckTimeout($timeout); - - $seconds = $this->parseEnv('PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC', false); - if (!$seconds) { - $seconds = 1; - } - $this->setHealthCheckRetrySec($seconds); - $version = $this->parseEnv('PACT_SPECIFICATION_VERSION', false); if (!$version) { $version = static::DEFAULT_SPECIFICATION_VERSION; @@ -64,7 +51,7 @@ public function __construct() * * @return null|string */ - private function parseEnv(string $variableName, bool $required = true) + private function parseEnv(string $variableName, bool $required = true): ?string { $result = null; diff --git a/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php b/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php deleted file mode 100644 index 3d2ac348..00000000 --- a/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php +++ /dev/null @@ -1,173 +0,0 @@ -client = $client; - $this->config = $config; - } - - /** - * {@inheritdoc} - */ - public function healthCheck(): bool - { - $uri = $this->config->getBaseUri()->withPath('/'); - - $response = $this->client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - $body = $response->getBody()->getContents(); - - if ($response->getStatusCode() !== 200 - || $body !== "Mock service running\n") { - throw new ConnectionException('Failed to receive a successful response from the Mock Server.'); - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function deleteAllInteractions(): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $response = $this->client->delete($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - if ($response->getStatusCode() !== 200) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function registerInteraction(Interaction $interaction): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $body = \json_encode($interaction->jsonSerialize()); - - $this->client->post($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - 'body' => $body, - ]); - - return true; - } - - /** - * Separate function for messages, instead of interactions, as I am unsure what to do with the Ruby Standalone at the moment - * - * @param Message $message - * - * @return bool - */ - public function registerMessage(Message $message): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $body = \json_encode($message->jsonSerialize()); - - $this->client->post($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - 'body' => $body, - ]); - - return true; - } - - /** - * {@inheritdoc} - */ - public function verifyInteractions(): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions/verification'); - - $this->client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - return true; - } - - /** - * {@inheritdoc} - */ - public function getPactJson(): string - { - $uri = $this->config->getBaseUri()->withPath('/pact'); - $response = $this->client->post($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - return \json_encode(\json_decode($response->getBody()->getContents())); - } - - /** - * Wrapper for getPactJson to force the Ruby server to write the pact file to disk - * - * If the Pact-PHP does not gracefully kill the Ruby Server, it will not write the - * file to disk. This enables a work around. - */ - public function writePact(): string - { - return $this->getPactJson(); - } -} diff --git a/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php b/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php deleted file mode 100644 index e3bfbe62..00000000 --- a/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php +++ /dev/null @@ -1,51 +0,0 @@ -consumer; + } + + /** + * {@inheritdoc} + */ + public function setConsumer(string $consumer): self + { + $this->consumer = $consumer; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getProvider(): string + { + return $this->provider; + } + + /** + * {@inheritdoc} + */ + public function setProvider(string $provider): self + { + $this->provider = $provider; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPactDir(): string + { + if ($this->pactDir === null) { + return \sys_get_temp_dir(); + } + + return $this->pactDir; + } + + /** + * {@inheritdoc} + */ + public function setPactDir(?string $pactDir): self + { + if ($pactDir === null) { + return $this; + } + + if ('\\' !== \DIRECTORY_SEPARATOR) { + $pactDir = \str_replace('\\', \DIRECTORY_SEPARATOR, $pactDir); + } + + $this->pactDir = $pactDir; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPactSpecificationVersion(): string + { + return $this->pactSpecificationVersion; + } + + /** + * {@inheritdoc} + * + * @throws \UnexpectedValueException + */ + public function setPactSpecificationVersion(string $pactSpecificationVersion): self + { + /* + * Parse the version but do not assign it. If it is an invalid version, an exception is thrown + */ + $parser = new VersionParser(); + $parser->normalize($pactSpecificationVersion); + + $this->pactSpecificationVersion = $pactSpecificationVersion; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLog(): ?string + { + return $this->log; + } + + /** + * {@inheritdoc} + */ + public function setLog(string $log): self + { + $this->log = $log; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLogLevel(): ?string + { + return $this->logLevel; + } + + /** + * {@inheritdoc} + */ + public function setLogLevel(string $logLevel): self + { + $logLevel = \strtoupper($logLevel); + if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { + throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); + } + $this->logLevel = $logLevel; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPactFileWriteMode(): string + { + return $this->pactFileWriteMode; + } + + /** + * {@inheritdoc} + */ + public function setPactFileWriteMode(string $pactFileWriteMode): self + { + $options = [self::MODE_OVERWRITE, self::MODE_MERGE]; + + if (!\in_array($pactFileWriteMode, $options)) { + $implodedOptions = \implode(', ', $options); + + throw new \InvalidArgumentException("Invalid PhpPact File Write Mode, value must be one of the following: {$implodedOptions}."); + } + + $this->pactFileWriteMode = $pactFileWriteMode; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/PactConfigInterface.php b/src/PhpPact/Standalone/PactConfigInterface.php index 3369e46e..b1d21344 100644 --- a/src/PhpPact/Standalone/PactConfigInterface.php +++ b/src/PhpPact/Standalone/PactConfigInterface.php @@ -8,6 +8,9 @@ */ interface PactConfigInterface { + public const MODE_OVERWRITE = 'overwrite'; + public const MODE_MERGE = 'merge'; + /** * @return string */ @@ -16,7 +19,7 @@ public function getConsumer(): string; /** * @param string $consumer consumers name * - * @return PactConfigInterface + * @return $this */ public function setConsumer(string $consumer): self; @@ -28,50 +31,50 @@ public function getProvider(): string; /** * @param string $provider providers name * - * @return PactConfigInterface + * @return $this */ public function setProvider(string $provider): self; /** * @return string url to place the pact files when written to disk */ - public function getPactDir(); + public function getPactDir(): string; /** * @param null|string $pactDir url to place the pact files when written to disk * - * @return PactConfigInterface + * @return $this */ - public function setPactDir($pactDir): self; + public function setPactDir(?string $pactDir): self; /** * @return string pact version */ - public function getPactSpecificationVersion(); + public function getPactSpecificationVersion(): string; /** * @param string $pactSpecificationVersion pact semver version * - * @return PactConfigInterface + * @return $this */ - public function setPactSpecificationVersion($pactSpecificationVersion): self; + public function setPactSpecificationVersion(string $pactSpecificationVersion): self; /** - * @return string directory for log output + * @return null|string directory for log output */ - public function getLog(); + public function getLog(): ?string; /** * @param string $log directory for log output * - * @return PactConfigInterface + * @return $this */ public function setLog(string $log): self; /** * @return null|string */ - public function getLogLevel(); + public function getLogLevel(): ?string; /** * @param string $logLevel @@ -79,4 +82,16 @@ public function getLogLevel(); * @return $this */ public function setLogLevel(string $logLevel): self; + + /** + * @return string 'merge' or 'overwrite' merge means that interactions are added and overwrite means that the entire file is overwritten + */ + public function getPactFileWriteMode(): string; + + /** + * @param string $pactFileWriteMode 'merge' or 'overwrite' merge means that interactions are added and overwrite means that the entire file is overwritten + * + * @return $this + */ + public function setPactFileWriteMode(string $pactFileWriteMode): self; } diff --git a/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php b/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php index 2874e772..3ad3c782 100644 --- a/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php +++ b/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php @@ -2,168 +2,12 @@ namespace PhpPact\Standalone\PactMessage; -use Composer\Semver\VersionParser; -use PhpPact\Standalone\PactConfigInterface; +use PhpPact\Standalone\PactConfig; /** * Configuration defining the default PhpPact Ruby Standalone server. - * Class MockServerConfig. + * Class PactMessageConfig. */ -class PactMessageConfig implements PactConfigInterface +class PactMessageConfig extends PactConfig { - /** - * Consumer name. - * - * @var string - */ - private $consumer; - - /** - * Provider name. - * - * @var string - */ - private $provider; - - /** - * Directory to which the pacts will be written. - * - * @var string - */ - private $pactDir; - - /** - * The pact specification version to use when writing the pact. Note that only versions 1 and 2 are currently supported. - * - * @var string - */ - private $pactSpecificationVersion; - - /** - * File to which to log output. - * - * @var string - */ - private $log; - - /** @var string */ - private $logLevel; - - /** - * {@inheritdoc} - */ - public function getConsumer(): string - { - return $this->consumer; - } - - /** - * {@inheritdoc} - */ - public function setConsumer(string $consumer): PactConfigInterface - { - $this->consumer = $consumer; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProvider(): string - { - return $this->provider; - } - - /** - * {@inheritdoc} - */ - public function setProvider(string $provider): PactConfigInterface - { - $this->provider = $provider; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactDir() - { - if ($this->pactDir === null) { - return \sys_get_temp_dir(); - } - - return $this->pactDir; - } - - /** - * {@inheritdoc} - */ - public function setPactDir($pactDir): PactConfigInterface - { - $this->pactDir = $pactDir; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactSpecificationVersion() - { - return $this->pactSpecificationVersion; - } - - /** - * {@inheritdoc} - * - * @throws \UnexpectedValueException - */ - public function setPactSpecificationVersion($pactSpecificationVersion): PactConfigInterface - { - /* - * Parse the version but do not assign it. If it is an invalid version, an exception is thrown - */ - $parser = new VersionParser(); - $parser->normalize($pactSpecificationVersion); - - $this->pactSpecificationVersion = $pactSpecificationVersion; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getLog() - { - return $this->log; - } - - /** - * {@inheritdoc} - */ - public function setLog(string $log): PactConfigInterface - { - $this->log = $log; - - return $this; - } - - public function getLogLevel() - { - return $this->logLevel; - } - - public function setLogLevel(string $logLevel): PactConfigInterface - { - $logLevel = \strtoupper($logLevel); - if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { - throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); - } - $this->logLevel = $logLevel; - - return $this; - } } diff --git a/tests/PhpPact/Consumer/InteractionBuilderTest.php b/tests/PhpPact/Consumer/InteractionBuilderTest.php index 06567a76..b897398c 100644 --- a/tests/PhpPact/Consumer/InteractionBuilderTest.php +++ b/tests/PhpPact/Consumer/InteractionBuilderTest.php @@ -5,39 +5,12 @@ use PhpPact\Consumer\Matcher\Matcher; use PhpPact\Consumer\Model\ConsumerRequest; use PhpPact\Consumer\Model\ProviderResponse; -use PhpPact\Http\GuzzleClient; use PhpPact\Standalone\Exception\MissingEnvVariableException; -use PhpPact\Standalone\MockService\MockServer; use PhpPact\Standalone\MockService\MockServerEnvConfig; -use PhpPact\Standalone\MockService\Service\MockServerHttpService; -use PhpPact\Standalone\MockService\Service\MockServerHttpServiceInterface; use PHPUnit\Framework\TestCase; class InteractionBuilderTest extends TestCase { - /** @var MockServerHttpServiceInterface */ - private $service; - - /** @var MockServer */ - private $mockServer; - - /** - * @throws MissingEnvVariableException - * @throws \Exception - */ - protected function setUp(): void - { - $config = new MockServerEnvConfig(); - $this->mockServer = new MockServer($config); - $this->mockServer->start(); - $this->service = new MockServerHttpService(new GuzzleClient(), $config); - } - - protected function tearDown(): void - { - $this->mockServer->stop(); - } - /** * @throws MissingEnvVariableException * @throws \Exception diff --git a/tests/PhpPact/Consumer/Matcher/MatcherTest.php b/tests/PhpPact/Consumer/Matcher/MatcherTest.php index 6db70501..55dffc4a 100644 --- a/tests/PhpPact/Consumer/Matcher/MatcherTest.php +++ b/tests/PhpPact/Consumer/Matcher/MatcherTest.php @@ -8,7 +8,7 @@ class MatcherTest extends TestCase { /** @var Matcher */ - private $matcher; + private Matcher $matcher; protected function setUp(): void { @@ -31,7 +31,7 @@ public function testLike() { $json = \json_encode($this->matcher->like(12)); - $this->assertEquals('{"contents":12,"json_class":"Pact::SomethingLike"}', $json); + $this->assertEquals('{"value":12,"pact:matcher:type":"type"}', $json); } /** @@ -44,15 +44,17 @@ public function testEachLikeStdClass() $object->value2 = 2; $expected = \json_encode([ - 'contents' => [ - 'value1' => [ - 'contents' => 1, - 'json_class' => 'Pact::SomethingLike', - ], - 'value2' => 2, + 'value' => [ + [ + 'value1' => [ + 'value' => 1, + 'pact:matcher:type' => 'type', + ], + 'value2' => 2, + ] ], - 'json_class' => 'Pact::ArrayLike', - 'min' => 1, + 'pact:matcher:type' => 'type', + 'min' => 1, ]); $actual = \json_encode($this->matcher->eachLike($object, 1)); @@ -71,15 +73,17 @@ public function testEachLikeArray() ]; $expected = \json_encode([ - 'contents' => [ - 'value1' => [ - 'contents' => 1, - 'json_class' => 'Pact::SomethingLike', - ], - 'value2' => 2, + 'value' => [ + [ + 'value1' => [ + 'value' => 1, + 'pact:matcher:type' => 'type', + ], + 'value2' => 2, + ] ], - 'json_class' => 'Pact::ArrayLike', - 'min' => 1, + 'pact:matcher:type' => 'type', + 'min' => 1, ]); $actual = \json_encode($this->matcher->eachLike($object, 1)); @@ -102,15 +106,9 @@ public function testRegexNoMatch() public function testRegex() { $expected = [ - 'data' => [ - 'generate' => 'Games', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => 'Games|Other', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => 'Games', + 'regex' => 'Games|Other', + 'pact:matcher:type' => 'regex', ]; $actual = $this->matcher->regex('Games', 'Games|Other'); @@ -124,15 +122,9 @@ public function testRegex() public function testDate() { $expected = [ - 'data' => [ - 'generate' => '2010-01-17', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))?)$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => '2010-01-17', + 'regex' => '^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))?)$', + 'pact:matcher:type' => 'regex', ]; $actual = $this->matcher->dateISO8601('2010-01-17'); @@ -148,15 +140,9 @@ public function testDate() public function testTime($time) { $expected = [ - 'data' => [ - 'generate' => $time, - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?)$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => $time, + 'regex' => '^(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?)$', + 'pact:matcher:type' => 'regex', ]; $actual = $this->matcher->timeISO8601($time); @@ -188,15 +174,9 @@ public function dataProviderForTimeTest() public function testDateTime($dateTime) { $expected = [ - 'data' => [ - 'generate' => $dateTime, - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => $dateTime, + 'regex' => '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$', + 'pact:matcher:type' => 'regex', ]; $actual = $this->matcher->dateTimeISO8601($dateTime); @@ -226,15 +206,9 @@ public function dataProviderForDateTimeTest() public function testDateTimeWithMillis($dateTime) { $expected = [ - 'data' => [ - 'generate' => $dateTime, - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d{3}([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => $dateTime, + 'regex' => '^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d{3}([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$', + 'pact:matcher:type' => 'regex', ]; $actual = $this->matcher->dateTimeWithMillisISO8601($dateTime); @@ -262,15 +236,9 @@ public function dataProviderForDateTimeWithMillisTest() public function testTimestampRFC3339() { $expected = [ - 'data' => [ - 'generate' => 'Mon, 31 Oct 2016 15:21:41 -0400', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s\\d{2}\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s(\\+|-)\\d{4}$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => 'Mon, 31 Oct 2016 15:21:41 -0400', + 'regex' => '^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s\\d{2}\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s(\\+|-)\\d{4}$', + 'pact:matcher:type' => 'regex', ]; $actual = $this->matcher->timestampRFC3339('Mon, 31 Oct 2016 15:21:41 -0400'); @@ -285,7 +253,7 @@ public function testInteger() { $json = \json_encode($this->matcher->integer()); - $this->assertEquals('{"contents":13,"json_class":"Pact::SomethingLike"}', $json); + $this->assertEquals('{"value":13,"pact:matcher:type":"type"}', $json); } /** @@ -295,7 +263,7 @@ public function testBoolean() { $json = \json_encode($this->matcher->boolean()); - $this->assertEquals('{"contents":true,"json_class":"Pact::SomethingLike"}', $json); + $this->assertEquals('{"value":true,"pact:matcher:type":"type"}', $json); } /** @@ -305,7 +273,7 @@ public function testDecimal() { $json = \json_encode($this->matcher->decimal()); - $this->assertEquals('{"contents":13.01,"json_class":"Pact::SomethingLike"}', $json); + $this->assertEquals('{"value":13.01,"pact:matcher:type":"type"}', $json); } /** @@ -314,15 +282,9 @@ public function testDecimal() public function testHexadecimal() { $expected = [ - 'data' => [ - 'generate' => '3F', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^[0-9a-fA-F]+$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => '3F', + 'regex' => '^[0-9a-fA-F]+$', + 'pact:matcher:type' => 'regex', ]; $this->assertEquals($expected, $this->matcher->hexadecimal()); @@ -334,15 +296,9 @@ public function testHexadecimal() public function testUuid() { $expected = [ - 'data' => [ - 'generate' => 'ce118b6e-d8e1-11e7-9296-cec278b6b50a', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => 'ce118b6e-d8e1-11e7-9296-cec278b6b50a', + 'regex' => '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$', + 'pact:matcher:type' => 'regex', ]; $this->assertEquals($expected, $this->matcher->uuid()); @@ -354,15 +310,9 @@ public function testUuid() public function testIpv4Address() { $expected = [ - 'data' => [ - 'generate' => '127.0.0.13', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^(\\d{1,3}\\.)+\\d{1,3}$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => '127.0.0.13', + 'regex' => '^(\\d{1,3}\\.)+\\d{1,3}$', + 'pact:matcher:type' => 'regex', ]; $this->assertEquals($expected, $this->matcher->ipv4Address()); @@ -374,15 +324,9 @@ public function testIpv4Address() public function testIpv6Address() { $expected = [ - 'data' => [ - 'generate' => '::ffff:192.0.2.128', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => '::ffff:192.0.2.128', + 'regex' => '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', + 'pact:matcher:type' => 'regex', ]; $this->assertEquals($expected, $this->matcher->ipv6Address()); diff --git a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php index 073df261..55225152 100644 --- a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php +++ b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php @@ -18,12 +18,10 @@ public function testSerializing() 'currentCity' => 'Austin', ]); - $data = \json_decode(\json_encode($model->jsonSerialize()), true); - - $this->assertEquals('PUT', $data['method']); - $this->assertEquals('application/json', $data['headers']['Content-Type']); - $this->assertEquals('/somepath', $data['path']); - $this->assertEquals('Austin', $data['body']['currentCity']); + $this->assertEquals('PUT', $model->getMethod()); + $this->assertEquals(['Content-Type' => 'application/json'], $model->getHeaders()); + $this->assertEquals('/somepath', $model->getPath()); + $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } public function testSerializingWhenPathUsingMatcher() @@ -39,13 +37,9 @@ public function testSerializingWhenPathUsingMatcher() 'status' => 'finished', ]); - $data = \json_decode(\json_encode($model->jsonSerialize()), true); - - $this->assertEquals('PATCH', $data['method']); - $this->assertEquals('application/json', $data['headers']['Content-Type']); - $this->assertIsArray($data['path']); - $this->assertArrayHasKey('data', $data['path']); - $this->assertArrayHasKey('json_class', $data['path']); - $this->assertEquals('finished', $data['body']['status']); + $this->assertEquals('PATCH', $model->getMethod()); + $this->assertEquals(['Content-Type' => 'application/json'], $model->getHeaders()); + $this->assertEquals('{"value":"\/somepath\/474d610b-c6e3-45bd-9f70-529e7ad21df0\/status","regex":"\\\\\\/somepath\\\\\\/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\\\\\\/status","pact:matcher:type":"regex"}', $model->getPath()); + $this->assertEquals('{"status":"finished"}', $model->getBody()); } } diff --git a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php index 17291378..35bdc2a9 100644 --- a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php +++ b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php @@ -16,10 +16,8 @@ public function testSerializing() 'currentCity' => 'Austin', ]); - $data = \json_decode(\json_encode($model->jsonSerialize()), true); - - $this->assertEquals(200, $data['status']); - $this->assertEquals('application/json', $data['headers']['Content-Type']); - $this->assertEquals('Austin', $data['body']['currentCity']); + $this->assertEquals(200, $model->getStatus()); + $this->assertEquals(['Content-Type' => 'application/json'], $model->getHeaders()); + $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } } diff --git a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php b/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php index 18e27893..2d53fd5e 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php @@ -1,43 +1,88 @@ setHost($host) - ->setPort($port) - ->setProvider($provider) + $brokerUri = new Uri('http://localhost'); + $brokerToken = 'abc-123'; + $brokerUsername = 'user'; + $brokerPassword = 'pass'; + + $verbose = true; + $pacticipant = 'a pacticipant'; + + $request = 'POST'; + $header = 'Accept application/json'; + $data = '{"key": "value"}'; + $user = 'username:password'; + $url = 'https://example.org/webhook'; + $consumer = 'test-consumer'; + $provider = 'test-provider'; + $description = 'an example webhook'; + $uuid = 'd2181b32-8b03-4daf-8cc0-d9168b2f6fac'; + + $version = '1.2.3'; + $branch = 'new-feature'; + $tag = 'prod'; + + $name = 'My Project'; + $repositoryUrl = 'https://github.com/vendor/my-project'; + + $consumerVersion = '1.1.2'; + $pactLocations = '/path/to/pacts'; + + $subject = (new BrokerConfig()) + ->setBrokerUri($brokerUri) + ->setBrokerToken($brokerToken) + ->setBrokerUsername($brokerUsername) + ->setBrokerPassword($brokerPassword) + ->setVerbose($verbose) + ->setPacticipant($pacticipant) + ->setRequest($request) + ->setHeader($header) + ->setData($data) + ->setUser($user) + ->setUrl($url) ->setConsumer($consumer) - ->setPactDir($pactDir) - ->setPactFileWriteMode($pactFileWriteMode) - ->setLog($log) - ->setPactSpecificationVersion($pactSpecificationVersion) - ->setCors($cors); - - static::assertSame($host, $subject->getHost()); - static::assertSame($port, $subject->getPort()); - static::assertSame($provider, $subject->getProvider()); + ->setProvider($provider) + ->setDescription($description) + ->setUuid($uuid) + ->setVersion($version) + ->setBranch($branch) + ->setTag($tag) + ->setName($name) + ->setRepositoryUrl($repositoryUrl) + ->setConsumerVersion($consumerVersion) + ->setPactLocations($pactLocations); + + static::assertSame($brokerUri, $subject->getBrokerUri()); + static::assertSame($brokerToken, $subject->getBrokerToken()); + static::assertSame($brokerUsername, $subject->getBrokerUsername()); + static::assertSame($brokerPassword, $subject->getBrokerPassword()); + static::assertSame($verbose, $subject->isVerbose()); + static::assertSame($pacticipant, $subject->getPacticipant()); + static::assertSame($request, $subject->getRequest()); + static::assertSame($header, $subject->getHeader()); + static::assertSame($data, $subject->getData()); + static::assertSame($user, $subject->getUser()); + static::assertSame($url, $subject->getUrl()); static::assertSame($consumer, $subject->getConsumer()); - static::assertSame($pactDir, $subject->getPactDir()); - static::assertSame($pactFileWriteMode, $subject->getPactFileWriteMode()); - static::assertSame($log, $subject->getLog()); - static::assertSame($pactSpecificationVersion, $subject->getPactSpecificationVersion()); - static::assertSame($cors, $subject->hasCors()); + static::assertSame($provider, $subject->getProvider()); + static::assertSame($description, $subject->getDescription()); + static::assertSame($uuid, $subject->getUuid()); + static::assertSame($version, $subject->getVersion()); + static::assertSame($branch, $subject->getBranch()); + static::assertSame($tag, $subject->getTag()); + static::assertSame($name, $subject->getName()); + static::assertSame($repositoryUrl, $subject->getRepositoryUrl()); + static::assertSame($consumerVersion, $subject->getConsumerVersion()); + static::assertSame($pactLocations, $subject->getPactLocations()); } } diff --git a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php index 91efbfae..5ad4b80c 100644 --- a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php +++ b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php @@ -1,6 +1,6 @@ setHost($host) @@ -27,8 +26,7 @@ public function testSetters() ->setPactDir($pactDir) ->setPactFileWriteMode($pactFileWriteMode) ->setLog($log) - ->setPactSpecificationVersion($pactSpecificationVersion) - ->setCors($cors); + ->setPactSpecificationVersion($pactSpecificationVersion); static::assertSame($host, $subject->getHost()); static::assertSame($port, $subject->getPort()); @@ -38,6 +36,5 @@ public function testSetters() static::assertSame($pactFileWriteMode, $subject->getPactFileWriteMode()); static::assertSame($log, $subject->getLog()); static::assertSame($pactSpecificationVersion, $subject->getPactSpecificationVersion()); - static::assertSame($cors, $subject->hasCors()); } } diff --git a/tests/PhpPact/Standalone/MockServer/MockServerTest.php b/tests/PhpPact/Standalone/MockServer/MockServerTest.php deleted file mode 100644 index fe63811a..00000000 --- a/tests/PhpPact/Standalone/MockServer/MockServerTest.php +++ /dev/null @@ -1,68 +0,0 @@ -start(); - $this->assertTrue(\is_int($pid)); - } finally { - $result = $mockServer->stop(); - $this->assertTrue($result); - } - } - - /** - * @throws MissingEnvVariableException - * @throws \Exception - */ - public function testStartAndStopWithRecognizedTimeout() - { - // the mock server actually takes more than one second to be ready - // we use this fact to test the timeout - $orig = \getenv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT'); - \putenv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT=1'); - - $httpService = $this->getMockBuilder(MockServerHttpService::class) - ->disableOriginalConstructor() - ->getMock(); - - $connectionException = $this->getMockBuilder(ConnectException::class) - ->disableOriginalConstructor() - ->getMock(); - - // take sth lower than the default value - $httpService->expects($this->atMost(5)) - ->method('healthCheck') - ->will($this->returnCallback(function () use ($connectionException) { - throw $connectionException; - })); - - try { - $mockServer = new MockServer(new MockServerEnvConfig(), $httpService); - $mockServer->start(); - $this->fail('MockServer should not pass defined health check.'); - } catch (HealthCheckFailedException $e) { - $this->assertTrue(true); - } finally { - $mockServer->stop(); - \putenv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT=' . $orig); - } - } -} diff --git a/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php b/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php deleted file mode 100644 index cfb86df6..00000000 --- a/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php +++ /dev/null @@ -1,210 +0,0 @@ -config = new MockServerEnvConfig(); - $this->mockServer = new MockServer($this->config); - $this->mockServer->start(); - $this->service = new MockServerHttpService(new GuzzleClient(), $this->config); - } - - protected function tearDown(): void - { - $this->mockServer->stop(); - } - - /** - * @throws ConnectionException - */ - public function testHealthCheck() - { - $result = $this->service->healthCheck(); - $this->assertTrue($result); - } - - public function testRegisterInteraction() - { - $request = new ConsumerRequest(); - $request - ->setPath('/example') - ->setMethod('GET'); - $response = new ProviderResponse(); - $response->setStatus(200); - - $interaction = new Interaction(); - $interaction - ->setDescription('Fake description') - ->setProviderState('Fake provider state') - ->setRequest($request) - ->setResponse($response); - - $result = $this->service->registerInteraction($interaction); - - $this->assertTrue($result); - } - - public function testDeleteAllInteractions() - { - $result = $this->service->deleteAllInteractions(); - $this->assertTrue($result); - } - - public function testVerifyInteractions() - { - $result = $this->service->verifyInteractions(); - $this->assertTrue($result); - } - - public function testVerifyInteractionsFailure() - { - $request = new ConsumerRequest(); - $request - ->setPath('/example') - ->setMethod('GET'); - - $response = new ProviderResponse(); - $response->setStatus(200); - - $interaction = new Interaction(); - $interaction - ->setDescription('Some description') - ->setProviderState('Some state') - ->setRequest($request) - ->setResponse($response); - $this->service->registerInteraction($interaction); - - $this->expectException(ServerException::class); - $result = $this->service->verifyInteractions(); - $this->assertFalse($result); - } - - public function testGetPactJson() - { - $result = $this->service->getPactJson(); - $this->assertEquals('{"consumer":{"name":"someConsumer"},"provider":{"name":"someProvider"},"interactions":[],"metadata":{"pactSpecification":{"version":"2.0.0"}}}', $result); - } - - public function testFullGetInteraction() - { - $request = new ConsumerRequest(); - $request - ->setPath('/example') - ->setMethod('GET') - ->setQuery('enabled=true') - ->addQueryParameter('order', 'asc') - ->addQueryParameter('value', '12') - ->addHeader('Content-Type', 'application/json'); - - $expectedResponseBody = [ - 'message' => 'Hello, world!', - ]; - $response = new ProviderResponse(); - $response - ->setStatus(200) - ->setBody($expectedResponseBody) - ->addHeader('Content-Type', 'application/json'); - - $interaction = new Interaction(); - $interaction - ->setDescription('Fake description') - ->setProviderState('Fake provider state') - ->setRequest($request) - ->setResponse($response); - - $result = $this->service->registerInteraction($interaction); - - $this->assertTrue($result); - - $client = new GuzzleClient(); - $uri = $this->config->getBaseUri()->withPath('/example')->withQuery('enabled=true&order=asc&value=12'); - $response = $client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - $body = $response->getBody()->getContents(); - $this->assertEquals(\json_encode($expectedResponseBody), $body); - $this->assertEquals($response->getHeaderLine('Access-Control-Allow-Origin'), '*', 'CORS flag not set properly'); - $this->assertEquals(200, $response->getStatusCode()); - } - - /** - * @throws MissingEnvVariableException - * @throws \Exception - */ - public function testMatcherWithMockServer() - { - $matcher = new Matcher(); - - $category = new stdClass(); - $category->name = $matcher->term('Games', '[gbBG]'); - - $request = new ConsumerRequest(); - $request - ->setPath('/test') - ->setMethod('GET'); - - $response = new ProviderResponse(); - $response - ->setStatus(200) - ->addHeader('Content-Type', 'application/json') - ->setBody([ - 'results' => $matcher->eachLike($category), - ]); - - $config = new MockServerEnvConfig(); - $interaction = new InteractionBuilder($config); - $interaction - ->given('Something') - ->uponReceiving('Stuff') - ->with($request) - ->willRespondWith($response); - - $client = new GuzzleClient(); - $uri = $this->config->getBaseUri()->withPath('/test'); - $client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - $httpClient = new MockServerHttpService(new GuzzleClient(), $config); - - $pact = \json_decode($httpClient->getPactJson(), true); - - $this->assertArrayHasKey('$.body.results[*].name', $pact['interactions'][0]['response']['matchingRules']); - } -} diff --git a/tests/PhpPact/Standalone/PactConfigTest.php b/tests/PhpPact/Standalone/PactConfigTest.php new file mode 100644 index 00000000..aea909e2 --- /dev/null +++ b/tests/PhpPact/Standalone/PactConfigTest.php @@ -0,0 +1,65 @@ +config = new PactConfig(); + } + + public function testSetters(): void + { + $provider = 'test-provider'; + $consumer = 'test-consumer'; + $pactDir = 'test-pact-dir/'; + $pactSpecificationVersion = '2.0.0'; + $log = 'test-log-dir/'; + $logLevel = 'ERROR'; + $pactFileWriteMode = 'merge'; + + $this->config + ->setProvider($provider) + ->setConsumer($consumer) + ->setPactDir($pactDir) + ->setPactSpecificationVersion($pactSpecificationVersion) + ->setLog($log) + ->setLogLevel($logLevel) + ->setPactFileWriteMode($pactFileWriteMode); + + static::assertSame($provider, $this->config->getProvider()); + static::assertSame($consumer, $this->config->getConsumer()); + static::assertSame($pactDir, $this->config->getPactDir()); + static::assertSame($pactSpecificationVersion, $this->config->getPactSpecificationVersion()); + static::assertSame($log, $this->config->getLog()); + static::assertSame($logLevel, $this->config->getLogLevel()); + static::assertSame($pactFileWriteMode, $this->config->getPactFileWriteMode()); + } + + public function testInvalidPactSpecificationVersion(): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid version string "invalid"'); + $this->config->setPactSpecificationVersion('invalid'); + } + + public function testInvalidLogLevel(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('LogLevel TRACE not supported.'); + $this->config->setLogLevel('TRACE'); + } + + public function testInvalidPactFileWriteMode(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid PhpPact File Write Mode, value must be one of the following: overwrite, merge."); + $this->config->setPactFileWriteMode('APPEND'); + } +} From e1d49cc7f58676959c288193df94bf1f60f25486 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 11 Jan 2023 20:51:56 +0700 Subject: [PATCH 02/78] Remove method BuilderInterface::writePact --- src/PhpPact/Consumer/BuilderInterface.php | 7 ------- src/PhpPact/Consumer/InteractionBuilder.php | 8 -------- 2 files changed, 15 deletions(-) diff --git a/src/PhpPact/Consumer/BuilderInterface.php b/src/PhpPact/Consumer/BuilderInterface.php index 0b58c219..406acd26 100644 --- a/src/PhpPact/Consumer/BuilderInterface.php +++ b/src/PhpPact/Consumer/BuilderInterface.php @@ -12,11 +12,4 @@ interface BuilderInterface * Verify that the interactions are valid. */ public function verify(): bool; - - /** - * Write the Pact without deleting the interactions. - * - * @return bool - */ - public function writePact(): bool; } diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index 39eef2a8..55affa48 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -94,12 +94,4 @@ public function createMockServer(): void { $this->pact->createMockServer(); } - - /** - * {@inheritdoc} - */ - public function writePact(): bool - { - return true; - } } From 0d7fe0a8c0626420b1901aea33aa2c742ef579ab Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 15 Jan 2023 18:02:37 +0700 Subject: [PATCH 03/78] Remove CHANGELOG.md. Add '@internal' to classes are not supposed to be used directly --- CHANGELOG.md | 61 ------------------- .../Consumer/Listener/PactTestListener.php | 2 + .../Standalone/Installer/Model/Scripts.php | 2 + 3 files changed, 4 insertions(+), 61 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 6f130920..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,61 +0,0 @@ -CHANGELOG -========= - -9.0 ---- - -* General - * [BC BREAK] Declare types for: - * Properties - * Arguments - * Return values - -* Matchers - * [BC BREAK] Update matcher implementations - -* Installers - * [BC BREAK] Removed `PhpPact\Standalone\Installer\Model\Scripts::getMockService` - * Added `PhpPact\Standalone\Installer\Model\Scripts::getCode` - * Added `PhpPact\Standalone\Installer\Model\Scripts::getLibrary` - -* Config - * [BC BREAK] Updated `PhpPact\Standalone\MockService\MockServerConfigInterface`, removed these methods: - * `hasCors` - * `setCors` - * `setHealthCheckTimeout` - * `getHealthCheckTimeout` - * `setHealthCheckRetrySec` - * `getHealthCheckRetrySec` - * [BC BREAK] Removed environments variables: - * PACT_CORS - * PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT - * PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC - * Moved these methods from `PhpPact\Standalone\MockService\MockServerConfigInterface` to `PhpPact\Standalone\PactConfigInterface`: - * `getPactFileWriteMode` - * `setPactFileWriteMode` - * `PhpPact\Standalone\MockService\MockServerConfigInterface` now extends `PhpPact\Standalone\PactConfigInterface` - * Added `PhpPact\Standalone\PactConfig` - * `PhpPact\Standalone\PactMessage\PactMessageConfig` now extends `PhpPact\Standalone\PactConfig` - * `PhpPact\Standalone\MockService\MockServerConfig` now extends `PhpPact\Standalone\PactConfig` - * Change default specification version to `3.0.0` - -* Mock Server - * [BC BREAK] Removed `PhpPact\Standalone\MockService\MockServer` - * [BC BREAK] Removed `PhpPact\Standalone\MockService\Service\MockServerHttpService` - * [BC BREAK] Removed `PhpPact\Standalone\MockService\Service\MockServerHttpServiceInterface` - * [BC BREAK] Removed `PhpPact\Standalone\Exception\HealthCheckFailedException` - * Added `PhpPact\Consumer\Exception\MockServerNotStartedException` - -* Interaction Builder - * Added `PhpPact\Consumer\InteractionBuilder::createMockServer` - * [BC BREAK] It's now required to call `PhpPact\Consumer\InteractionBuilder::createMockServer` manually before `PhpPact\Consumer\InteractionBuilder::verify` - * [BC BREAK] Removed `PhpPact\Consumer\InteractionBuilder::finalize` - * [BC BREAK] `PhpPact\Consumer\Model\ConsumerRequest::getQuery` now return `array` instead of `string` - * [BC BREAK] `PhpPact\Consumer\Model\ConsumerRequest::setQuery` now accept argument `array` instead of `string` - * Added `PhpPact\Consumer\Exception\InteractionRequestBodyNotAddedException` - * Added `PhpPact\Consumer\Exception\InteractionResponseBodyNotAddedException` - * Added `PhpPact\Consumer\Exception\PactFileNotWroteException` - -* PHPUnit - * [BC BREAK] Removed `PhpPact\Consumer\Hook\ContractDownloader` - * Replace broker http client by broker cli in `PhpPact\Consumer\Listener\PactTestListener` diff --git a/src/PhpPact/Consumer/Listener/PactTestListener.php b/src/PhpPact/Consumer/Listener/PactTestListener.php index af66e11f..6e25ccbf 100644 --- a/src/PhpPact/Consumer/Listener/PactTestListener.php +++ b/src/PhpPact/Consumer/Listener/PactTestListener.php @@ -16,6 +16,8 @@ /** * PACT listener that can be used with environment variables and easily attached to PHPUnit configuration. * Class PactTestListener + * + * @internal */ class PactTestListener implements TestListener { diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index fb4a173b..7bfc3e29 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -5,6 +5,8 @@ /** * Represents locations of Ruby Standalone full path and scripts. * Class Scripts. + * + * @internal */ class Scripts { From 0812b31031b2a0876ff1e9bfc7b0e30639733a0a Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 15 Jan 2023 18:06:01 +0700 Subject: [PATCH 04/78] Add missing return type to method Matcher::regex --- src/PhpPact/Consumer/Matcher/Matcher.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpPact/Consumer/Matcher/Matcher.php b/src/PhpPact/Consumer/Matcher/Matcher.php index 1fed12cc..e8e9cf42 100644 --- a/src/PhpPact/Consumer/Matcher/Matcher.php +++ b/src/PhpPact/Consumer/Matcher/Matcher.php @@ -107,7 +107,7 @@ public function term($value, string $pattern): array * * @return array */ - public function regex($value, string $pattern) + public function regex($value, string $pattern): array { return $this->term($value, $pattern); } From cf5144b55c21b8349a4cf21a1980ef962e34a71c Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 15 Jan 2023 18:05:23 +0700 Subject: [PATCH 05/78] Add more details to UPGRADE-9.0.md --- UPGRADE-9.0.md | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md index c1628bf4..53672228 100644 --- a/UPGRADE-9.0.md +++ b/UPGRADE-9.0.md @@ -4,17 +4,22 @@ UPGRADE FROM 8.x to 9.0 * Interaction Builder * It's now required to call `PhpPact\Consumer\InteractionBuilder::createMockServer` manually - Example Usage: - ```php - $builder = new InteractionBuilder($config); - $builder - ->given('a person exists') - ->uponReceiving('a get request to /hello/{name}') - ->with($request) - ->willRespondWith($response); - $builder->createMockServer(); + Example Usage: + ```php + $builder = new InteractionBuilder($config); + $builder + ->given('a person exists') + ->uponReceiving('a get request to /hello/{name}') + ->with($request) + ->willRespondWith($response); + $builder->createMockServer(); - $apiClient->sendRequest(); + $apiClient->sendRequest(); - $this->assertTrue($builder->verify()); - ``` + $this->assertTrue($builder->verify()); + ``` + + * These environment variables can be removed: + * PACT_CORS + * PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT + * PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC From d20f1f51c040b0a1ea6ffccac725d6e25feb7e05 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 16 Jan 2023 09:32:33 +0700 Subject: [PATCH 06/78] Revert multiple values support in header and query parameter --- .../Consumer/Model/ConsumerRequest.php | 24 ++++++++++++------- .../Consumer/Model/ProviderResponse.php | 13 ++++++---- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index 784fd264..233ee646 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -88,20 +88,23 @@ public function getHeaders(): array */ public function setHeaders(array $headers): self { - $this->headers = $headers; + $this->headers = []; + foreach ($headers as $header => $value) { + $this->addHeader($header, $value); + } return $this; } /** - * @param string $header - * @param array|string $value + * @param string $header + * @param string $value * * @return ConsumerRequest */ - public function addHeader(string $header, array|string $value): self + public function addHeader(string $header, string $value): self { - $this->headers[$header] = is_array($value) ? json_encode($value) : $value; + $this->headers[$header] = $value; return $this; } @@ -148,20 +151,23 @@ public function getQuery(): array */ public function setQuery(array $query): self { - $this->query = $query; + $this->query = []; + foreach ($query as $key => $value) { + $this->addQueryParameter($key, $value); + } return $this; } /** * @param string $key - * @param array|string $value + * @param string $value * * @return ConsumerRequest */ - public function addQueryParameter(string $key, array|string $value): self + public function addQueryParameter(string $key, string $value): self { - $this->query[$key] = is_array($value) ? json_encode($value) : $value; + $this->query[$key] = $value; return $this; } diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index 449f5dac..4e2a612c 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -58,20 +58,23 @@ public function getHeaders(): array */ public function setHeaders(array $headers): self { - $this->headers = $headers; + $this->headers = []; + foreach ($headers as $header => $value) { + $this->addHeader($header, $value); + } return $this; } /** - * @param string $header - * @param array|string $value + * @param string $header + * @param string $value * * @return ProviderResponse */ - public function addHeader(string $header, array|string $value): self + public function addHeader(string $header, string $value): self { - $this->headers[$header] = is_array($value) ? json_encode($value) : $value; + $this->headers[$header] = $value; return $this; } From bb256ef552c8d98860d601002a5382563130f237 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 1 Mar 2023 23:08:22 +0700 Subject: [PATCH 07/78] Support multiple values in header and query parameter --- src/PhpPact/Consumer/Model/ConsumerRequest.php | 16 ++++++++-------- src/PhpPact/Consumer/Model/Pact.php | 18 ++++++++++++------ .../Consumer/Model/ProviderResponse.php | 8 ++++---- .../Consumer/Model/ConsumerRequestTest.php | 8 ++++++-- .../Consumer/Model/ProviderResponseTest.php | 2 +- 5 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index 233ee646..f0561145 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -97,14 +97,14 @@ public function setHeaders(array $headers): self } /** - * @param string $header - * @param string $value + * @param string $header + * @param array|string $value * * @return ConsumerRequest */ - public function addHeader(string $header, string $value): self + public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = $value; + $this->headers[$header] = is_array($value) ? $value : [$value]; return $this; } @@ -160,14 +160,14 @@ public function setQuery(array $query): self } /** - * @param string $key - * @param string $value + * @param string $key + * @param array|string $value * * @return ConsumerRequest */ - public function addQueryParameter(string $key, string $value): self + public function addQueryParameter(string $key, array|string $value): self { - $this->query[$key] = $value; + $this->query[$key] = is_array($value) ? $value : [$value]; return $this; } diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index 84d55223..997eee43 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -147,11 +147,15 @@ private function with(Interaction $interaction): self $id = $interaction->getId(); $request = $interaction->getRequest(); $this->ffi->pactffi_with_request($id, $request->getMethod(), $request->getPath()); - foreach ($request->getHeaders() as $header => $value) { - $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Request, $header, 0, $value); + foreach ($request->getHeaders() as $header => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Request, $header, $index, $value); + } } - foreach ($request->getQuery() as $key => $value) { - $this->ffi->pactffi_with_query_parameter_v2($id, $key, 0, $value); + foreach ($request->getQuery() as $key => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_query_parameter_v2($id, $key, $index, $value); + } } if (!\is_null($request->getBody())) { $success = $this->ffi->pactffi_with_body($id, $this->ffi->InteractionPart_Request, null, $request->getBody()); @@ -168,8 +172,10 @@ private function willRespondWith(Interaction $interaction): self $id = $interaction->getId(); $response = $interaction->getResponse(); $this->ffi->pactffi_response_status($id, $response->getStatus()); - foreach ($response->getHeaders() as $header => $value) { - $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Response, $header, 0, $value); + foreach ($response->getHeaders() as $header => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Response, $header, $index, $value); + } } if (!\is_null($response->getBody())) { $success = $this->ffi->pactffi_with_body($id, $this->ffi->InteractionPart_Response, null, $response->getBody()); diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index 4e2a612c..d3237e40 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -67,14 +67,14 @@ public function setHeaders(array $headers): self } /** - * @param string $header - * @param string $value + * @param string $header + * @param array|string $value * * @return ProviderResponse */ - public function addHeader(string $header, string $value): self + public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = $value; + $this->headers[$header] = is_array($value) ? $value : [$value]; return $this; } diff --git a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php index 5a4723a7..f30aeca7 100644 --- a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php +++ b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php @@ -15,12 +15,14 @@ public function testSerializing() ->setMethod('PUT') ->setPath('/somepath') ->addHeader('Content-Type', 'application/json') + ->addQueryParameter('fruit', ['apple', 'banana']) ->setBody([ 'currentCity' => 'Austin', ]); $this->assertEquals('PUT', $model->getMethod()); - $this->assertEquals(['Content-Type' => 'application/json'], $model->getHeaders()); + $this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders()); + $this->assertEquals(['fruit' => ['apple', 'banana']], $model->getQuery()); $this->assertEquals('/somepath', $model->getPath()); $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } @@ -34,12 +36,14 @@ public function testSerializingWhenPathUsingMatcher() ->setMethod('PATCH') ->setPath($matcher->regex("/somepath/$pathVariable/status", '\/somepath\/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\/status')) ->addHeader('Content-Type', 'application/json') + ->addQueryParameter('food', 'milk') ->setBody([ 'status' => 'finished', ]); $this->assertEquals('PATCH', $model->getMethod()); - $this->assertEquals(['Content-Type' => 'application/json'], $model->getHeaders()); + $this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders()); + $this->assertEquals(['food' => ['milk']], $model->getQuery()); $this->assertEquals('{"value":"\/somepath\/474d610b-c6e3-45bd-9f70-529e7ad21df0\/status","regex":"\\\\\\/somepath\\\\\\/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\\\\\\/status","pact:matcher:type":"regex"}', $model->getPath()); $this->assertEquals('{"status":"finished"}', $model->getBody()); } diff --git a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php index 0e2628da..2da664be 100644 --- a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php +++ b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php @@ -18,7 +18,7 @@ public function testSerializing() ]); $this->assertEquals(200, $model->getStatus()); - $this->assertEquals(['Content-Type' => 'application/json'], $model->getHeaders()); + $this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders()); $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } } From d6a9d0dc943bcfd11abb1da76f5c67408dfc6a1d Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 1 Mar 2023 23:43:18 +0700 Subject: [PATCH 08/78] Update pact ffi library --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index ca0ce487..cfef1684 100644 --- a/composer.json +++ b/composer.json @@ -89,12 +89,12 @@ "path": "bin/pact-ruby-standalone" }, "pact-ffi-headers": { - "version": "0.3.19", + "version": "0.4.1", "url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/pact.h", "path": "bin/pact-ffi-headers/pact.h" }, "pact-ffi-lib": { - "version": "0.3.19", + "version": "0.4.1", "variables": { "{$prefix}": "PHP_OS_FAMILY === 'Windows' ? 'pact_ffi' : 'libpact_ffi'", "{$os}": "PHP_OS === 'Darwin' ? 'osx' : strtolower(PHP_OS_FAMILY)", From ca46297a025e89e99a0312f3b64f03421178ed86 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 2 Mar 2023 10:30:01 +0700 Subject: [PATCH 09/78] Rename method getCode to getHeader --- src/PhpPact/Consumer/Model/AbstractPact.php | 2 +- src/PhpPact/Standalone/Installer/Model/Scripts.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpPact/Consumer/Model/AbstractPact.php b/src/PhpPact/Consumer/Model/AbstractPact.php index 088f2160..a8abca50 100644 --- a/src/PhpPact/Consumer/Model/AbstractPact.php +++ b/src/PhpPact/Consumer/Model/AbstractPact.php @@ -15,6 +15,6 @@ abstract class AbstractPact public function __construct() { - $this->ffi = FFI::cdef(\file_get_contents(Scripts::getCode()), Scripts::getLibrary()); + $this->ffi = FFI::cdef(\file_get_contents(Scripts::getHeader()), Scripts::getLibrary()); } } diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 7bfc3e29..b6bcae11 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -20,7 +20,7 @@ class Scripts /** * @return string */ - public static function getCode(): string + public static function getHeader(): string { return self::$destinationDir . '/bin/pact-ffi-headers/pact.h'; } From 39718b9ca5ee7782b451ef2d2474fca3d3d26b1e Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 12 Mar 2023 14:42:54 +0700 Subject: [PATCH 10/78] Revert breaking change that require to call createMockServer manually --- README.md | 10 +--------- UPGRADE-9.0.md | 19 +------------------ .../Service/ConsumerServiceGoodbyeTest.php | 1 - .../Service/ConsumerServiceHelloTest.php | 3 +-- src/PhpPact/Consumer/InteractionBuilder.php | 8 -------- src/PhpPact/Consumer/Model/Pact.php | 5 +++-- .../Consumer/InteractionBuilderTest.php | 12 ++++++------ 7 files changed, 12 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 3fb015bd..bbd0e008 100644 --- a/README.md +++ b/README.md @@ -175,15 +175,7 @@ $builder ->given('a person exists') ->uponReceiving('a get request to /hello/{name}') ->with($request) - ->willRespondWith($response); // This has to be last. This is what makes an API request to the Mock Server to set the interaction. -``` - -### Start the Mock Server - -Mock server need to be started manually - -```php -$builder->createMockServer(); + ->willRespondWith($response); // This has to be last. This is what makes FFI calls to register the interaction and start the mock server. ``` ### Make the Request diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md index 53672228..0285dbe7 100644 --- a/UPGRADE-9.0.md +++ b/UPGRADE-9.0.md @@ -1,24 +1,7 @@ UPGRADE FROM 8.x to 9.0 ======================= -* Interaction Builder - * It's now required to call `PhpPact\Consumer\InteractionBuilder::createMockServer` manually - - Example Usage: - ```php - $builder = new InteractionBuilder($config); - $builder - ->given('a person exists') - ->uponReceiving('a get request to /hello/{name}') - ->with($request) - ->willRespondWith($response); - $builder->createMockServer(); - - $apiClient->sendRequest(); - - $this->assertTrue($builder->verify()); - ``` - +* Environment Variables * These environment variables can be removed: * PACT_CORS * PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT diff --git a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php index bc840bb2..fcae2e02 100644 --- a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php @@ -37,7 +37,6 @@ public function testGetGoodbyeString() ->uponReceiving('A get request to /goodbye/{name}') ->with($request) ->willRespondWith($response); - $builder->createMockServer(); $service = new HttpClientService($config->getBaseUri()); $result = $service->getGoodbyeString('Bob'); diff --git a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php index d23df42c..73b6b6f2 100644 --- a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php @@ -42,8 +42,7 @@ public function testGetHelloString() $builder ->uponReceiving('A get request to /hello/{name}') ->with($request) - ->willRespondWith($response); // This has to be last. This is what makes a FFI call to the Mock Server to set the interaction. - $builder->createMockServer(); + ->willRespondWith($response); // This has to be last. This is what makes FFI calls to register the interaction and start the mock server. $service = new HttpClientService($config->getBaseUri()); // Pass in the URL to the Mock Server. $result = $service->getHelloString('Bob'); // Make the real API request against the Mock Server. diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index 55affa48..a59696f8 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -86,12 +86,4 @@ public function verify(): bool { return $this->pact->verifyInteractions(); } - - /** - * Create mock server before verifying. - */ - public function createMockServer(): void - { - $this->pact->createMockServer(); - } } diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index 997eee43..0faf146f 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -27,7 +27,7 @@ public function __construct(private MockServerConfigInterface $config) ->withSpecification(); } - public function createMockServer(): void + private function createMockServer(): void { $port = $this->ffi->pactffi_create_mock_server_for_transport( $this->id, @@ -83,7 +83,8 @@ public function registerInteraction(Interaction $interaction): bool ->given($interaction) ->uponReceiving($interaction) ->with($interaction) - ->willRespondWith($interaction); + ->willRespondWith($interaction) + ->createMockServer(); return true; } diff --git a/tests/PhpPact/Consumer/InteractionBuilderTest.php b/tests/PhpPact/Consumer/InteractionBuilderTest.php index 33eaa902..713a2f15 100644 --- a/tests/PhpPact/Consumer/InteractionBuilderTest.php +++ b/tests/PhpPact/Consumer/InteractionBuilderTest.php @@ -36,13 +36,13 @@ public function testSimpleGet() ->addHeader('Content-Type', 'application/json'); $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder + $builder ->given('A test request.') ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); + $this->assertFalse($builder->verify()); } /** @@ -74,13 +74,13 @@ public function testPostWithBody() ]); $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder + $builder ->given('A test request.') ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); + $this->assertFalse($builder->verify()); } /** @@ -108,12 +108,12 @@ public function testBuildWithEachLikeMatcher() ]); $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder + $builder ->given('A test request.') ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); + $this->assertFalse($builder->verify()); } } From ddc46e5965f29e2eadcc8221c69e3339e2181833 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 2 Mar 2023 11:03:21 +0700 Subject: [PATCH 11/78] Remove composer/semver --- composer.json | 1 - src/PhpPact/Consumer/Model/Pact.php | 10 ++++++++-- src/PhpPact/Standalone/PactConfig.php | 10 ---------- src/PhpPact/Standalone/PactConfigInterface.php | 2 +- tests/PhpPact/Standalone/PactConfigTest.php | 7 ------- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/composer.json b/composer.json index cfef1684..c3595cc3 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,6 @@ "php": "^8.0", "ext-openssl": "*", "ext-json": "*", - "composer/semver": "^1.4.0|^3.2.0", "amphp/amp": "^2.5.1", "amphp/byte-stream": "^1.8", "amphp/dns": "^1.2.3", diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index 0faf146f..44ebbd5b 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -115,8 +115,14 @@ private function withSpecification(): self '3.0.0' => $this->ffi->PactSpecification_V3, '4.0.0' => $this->ffi->PactSpecification_V4, ]; - $version = $supportedVersions[$this->config->getPactSpecificationVersion()] ?? $this->ffi->PactSpecification_Unknown; - $this->ffi->pactffi_with_specification($this->id, $version); + $version = $this->config->getPactSpecificationVersion(); + if (isset($supportedVersions[$version])) { + $specification = $supportedVersions[$version]; + } else { + trigger_error(sprintf("Specification version '%s' is unknown", $version), E_USER_WARNING); + $specification = $this->ffi->PactSpecification_Unknown; + } + $this->ffi->pactffi_with_specification($this->id, $specification); return $this; } diff --git a/src/PhpPact/Standalone/PactConfig.php b/src/PhpPact/Standalone/PactConfig.php index e5a526f9..a7b79b1d 100644 --- a/src/PhpPact/Standalone/PactConfig.php +++ b/src/PhpPact/Standalone/PactConfig.php @@ -2,8 +2,6 @@ namespace PhpPact\Standalone; -use Composer\Semver\VersionParser; - /** * Class PactConfig. */ @@ -135,17 +133,9 @@ public function getPactSpecificationVersion(): string /** * {@inheritdoc} - * - * @throws \UnexpectedValueException */ public function setPactSpecificationVersion(string $pactSpecificationVersion): self { - /* - * Parse the version but do not assign it. If it is an invalid version, an exception is thrown - */ - $parser = new VersionParser(); - $parser->normalize($pactSpecificationVersion); - $this->pactSpecificationVersion = $pactSpecificationVersion; return $this; diff --git a/src/PhpPact/Standalone/PactConfigInterface.php b/src/PhpPact/Standalone/PactConfigInterface.php index b1d21344..bcd37e24 100644 --- a/src/PhpPact/Standalone/PactConfigInterface.php +++ b/src/PhpPact/Standalone/PactConfigInterface.php @@ -53,7 +53,7 @@ public function setPactDir(?string $pactDir): self; public function getPactSpecificationVersion(): string; /** - * @param string $pactSpecificationVersion pact semver version + * @param string $pactSpecificationVersion pact version * * @return $this */ diff --git a/tests/PhpPact/Standalone/PactConfigTest.php b/tests/PhpPact/Standalone/PactConfigTest.php index 42db4851..ba374d4c 100644 --- a/tests/PhpPact/Standalone/PactConfigTest.php +++ b/tests/PhpPact/Standalone/PactConfigTest.php @@ -43,13 +43,6 @@ public function testSetters(): void static::assertSame($pactFileWriteMode, $this->config->getPactFileWriteMode()); } - public function testInvalidPactSpecificationVersion(): void - { - $this->expectException(\UnexpectedValueException::class); - $this->expectExceptionMessage('Invalid version string "invalid"'); - $this->config->setPactSpecificationVersion('invalid'); - } - public function testInvalidLogLevel(): void { $this->expectException(\InvalidArgumentException::class); From 5dae03ae6bb8022cb4c57ffb106a11805f62992f Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 14 Mar 2023 16:23:33 +0700 Subject: [PATCH 12/78] Make sure every value of header and query parameter is string --- .../Consumer/Model/ConsumerRequest.php | 32 +++++++++++++++---- .../Consumer/Model/ProviderResponse.php | 16 ++++++++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index f0561145..ae6f8daa 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -19,7 +19,7 @@ class ConsumerRequest implements \JsonSerializable private string $path; /** - * @var string[] + * @var array */ private array $headers = []; @@ -29,7 +29,7 @@ class ConsumerRequest implements \JsonSerializable private ?string $body = null; /** - * @var array + * @var array */ private array $query = []; @@ -74,7 +74,7 @@ public function setPath(array|string $path): self } /** - * @return array + * @return array */ public function getHeaders(): array { @@ -104,11 +104,21 @@ public function setHeaders(array $headers): self */ public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = is_array($value) ? $value : [$value]; + $this->headers[$header] = []; + if (is_array($value)) { + array_walk($value, fn (string $value) => $this->addHeaderValue($header, $value)); + } else { + $this->addHeaderValue($header, $value); + } return $this; } + private function addHeaderValue(string $header, string $value): void + { + $this->headers[$header][] = $value; + } + /** * @return null|string */ @@ -137,7 +147,7 @@ public function setBody(mixed $body): self } /** - * @return array + * @return array */ public function getQuery(): array { @@ -167,11 +177,21 @@ public function setQuery(array $query): self */ public function addQueryParameter(string $key, array|string $value): self { - $this->query[$key] = is_array($value) ? $value : [$value]; + $this->query[$key] = []; + if (is_array($value)) { + array_walk($value, fn (string $value) => $this->addQueryParameterValue($key, $value)); + } else { + $this->addQueryParameterValue($key, $value); + } return $this; } + private function addQueryParameterValue(string $key, string $value): void + { + $this->query[$key][] = $value; + } + /** * {@inheritdoc} */ diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index d3237e40..6c3f95e4 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -14,7 +14,7 @@ class ProviderResponse implements \JsonSerializable private int $status; /** - * @var string[] + * @var array */ private array $headers = []; @@ -44,7 +44,7 @@ public function setStatus(int $status): self } /** - * @return string[] + * @return array */ public function getHeaders(): array { @@ -74,11 +74,21 @@ public function setHeaders(array $headers): self */ public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = is_array($value) ? $value : [$value]; + $this->headers[$header] = []; + if (is_array($value)) { + array_walk($value, fn (string $value) => $this->addHeaderValue($header, $value)); + } else { + $this->addHeaderValue($header, $value); + } return $this; } + private function addHeaderValue(string $header, string $value): void + { + $this->headers[$header][] = $value; + } + /** * @return null|string */ From 52e9a998542e0c1b19e1671da6913f64e628eaf5 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 14 Mar 2023 16:35:16 +0700 Subject: [PATCH 13/78] Move exception message to exception --- .../Exception/MockServerNotStartedException.php | 12 ++++++++++++ .../Exception/PactFileNotWroteException.php | 10 ++++++++++ src/PhpPact/Consumer/Model/Pact.php | 16 ++-------------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php b/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php index 68214f53..bbe54129 100644 --- a/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php +++ b/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php @@ -9,4 +9,16 @@ */ class MockServerNotStartedException extends Exception { + public function __construct(int $code) + { + $message = match ($code) { + -1 => 'An invalid handle was received. Handles should be created with `pactffi_new_pact`', + -2 => 'Transport_config is not valid JSON', + -3 => 'The mock server could not be started', + -4 => 'The method panicked', + -5 => 'The address is not valid', + default => 'Unknown error', + }; + parent::__construct($message, $code); + } } diff --git a/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php b/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php index 8a30bc4b..09129687 100644 --- a/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php +++ b/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php @@ -9,4 +9,14 @@ */ class PactFileNotWroteException extends Exception { + public function __construct(int $code) + { + $message = match ($code) { + 1 => 'A general panic was caught', + 2 => 'The pact file was not able to be written', + 3 => 'A mock server with the provided port was not found', + default => 'Unknown error', + }; + parent::__construct($message, $code); + } } diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index 44ebbd5b..433a9a42 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -38,14 +38,7 @@ private function createMockServer(): void ); if ($port < 0) { - $message = match ($port) { - -1 => 'An invalid handle was received. Handles should be created with `pactffi_new_pact`', - -2 => 'Transport_config is not valid JSON', - -3 => 'The mock server could not be started', - -4 => 'The method panicked', - -5 => 'The address is not valid', - }; - throw new MockServerNotStartedException($message); + throw new MockServerNotStartedException($port); } $this->config->setPort($port); } @@ -61,12 +54,7 @@ public function verifyInteractions(): bool $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE ); if ($error) { - $message = match ($error) { - 1 => 'A general panic was caught', - 2 => 'The pact file was not able to be written', - 3 => 'A mock server with the provided port was not found', - }; - throw new PactFileNotWroteException($message); + throw new PactFileNotWroteException($error); } } From bff0f9b7fae622a22aadcebe20e1c7263c2c4baa Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 14 Mar 2023 16:43:51 +0700 Subject: [PATCH 14/78] Extract method getSpecification() to check for supporting plugin --- src/PhpPact/Consumer/Model/Pact.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index 433a9a42..eee35750 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -94,7 +94,7 @@ private function newPact(): self return $this; } - private function withSpecification(): self + protected function getSpecification(): int { $supportedVersions = [ '1.0.0' => $this->ffi->PactSpecification_V1, @@ -110,7 +110,13 @@ private function withSpecification(): self trigger_error(sprintf("Specification version '%s' is unknown", $version), E_USER_WARNING); $specification = $this->ffi->PactSpecification_Unknown; } - $this->ffi->pactffi_with_specification($this->id, $specification); + + return $specification; + } + + private function withSpecification(): self + { + $this->ffi->pactffi_with_specification($this->id, $this->getSpecification()); return $this; } From 312c45cef0e1579aa171eac8dbd7833b357d1ed4 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 14 Mar 2023 23:22:42 +0700 Subject: [PATCH 15/78] Extract, change visibility, and move methods to abstract class to support plugin --- phpstan.neon | 1 + .../InteractionBodyNotAddedException.php | 12 ++ ...nteractionRequestBodyNotAddedException.php | 12 -- ...teractionResponseBodyNotAddedException.php | 12 -- .../Exception/PactFileNotWroteException.php | 6 +- src/PhpPact/Consumer/Model/AbstractPact.php | 82 ++++++++++- src/PhpPact/Consumer/Model/Pact.php | 128 +++++------------- .../MockService/MockServerEnvConfig.php | 2 - src/PhpPact/Standalone/PactConfig.php | 2 +- .../Standalone/PactConfigInterface.php | 2 + 10 files changed, 137 insertions(+), 122 deletions(-) create mode 100644 src/PhpPact/Consumer/Exception/InteractionBodyNotAddedException.php delete mode 100644 src/PhpPact/Consumer/Exception/InteractionRequestBodyNotAddedException.php delete mode 100644 src/PhpPact/Consumer/Exception/InteractionResponseBodyNotAddedException.php diff --git a/phpstan.neon b/phpstan.neon index e0f2f0e8..2704610d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,3 +1,4 @@ parameters: excludePaths: + - src/PhpPact/Consumer/Model/AbstractPact.php - src/PhpPact/Consumer/Model/Pact.php diff --git a/src/PhpPact/Consumer/Exception/InteractionBodyNotAddedException.php b/src/PhpPact/Consumer/Exception/InteractionBodyNotAddedException.php new file mode 100644 index 00000000..8e36ae3a --- /dev/null +++ b/src/PhpPact/Consumer/Exception/InteractionBodyNotAddedException.php @@ -0,0 +1,12 @@ + 'A general panic was caught', - 2 => 'The pact file was not able to be written', - 3 => 'A mock server with the provided port was not found', + 1 => 'The function panicked.', + 2 => 'The pact file was not able to be written.', + 3 => 'The pact for the given handle was not found.', default => 'Unknown error', }; parent::__construct($message, $code); diff --git a/src/PhpPact/Consumer/Model/AbstractPact.php b/src/PhpPact/Consumer/Model/AbstractPact.php index a8abca50..564bc3c4 100644 --- a/src/PhpPact/Consumer/Model/AbstractPact.php +++ b/src/PhpPact/Consumer/Model/AbstractPact.php @@ -4,6 +4,9 @@ use FFI; use PhpPact\Standalone\Installer\Model\Scripts; +use PhpPact\Standalone\PactConfigInterface; +use PhpPact\Consumer\Exception\PactFileNotWroteException; +use PhpPact\Consumer\Exception\InteractionBodyNotAddedException; /** * Class AbstractPact. @@ -13,8 +16,85 @@ abstract class AbstractPact protected FFI $ffi; protected int $id; - public function __construct() + public function __construct(protected PactConfigInterface $config) + { + $this + ->createFfi() + ->newPact() + ->withSpecification(); + } + + private function createFfi(): self { $this->ffi = FFI::cdef(\file_get_contents(Scripts::getHeader()), Scripts::getLibrary()); + + return $this; + } + + private function newPact(): self + { + $this->id = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); + + return $this; + } + + protected function getSpecification(): int + { + $supportedVersions = [ + '1.0.0' => $this->ffi->PactSpecification_V1, + '1.1.0' => $this->ffi->PactSpecification_V1_1, + '2.0.0' => $this->ffi->PactSpecification_V2, + '3.0.0' => $this->ffi->PactSpecification_V3, + '4.0.0' => $this->ffi->PactSpecification_V4, + ]; + $version = $this->config->getPactSpecificationVersion(); + if (isset($supportedVersions[$version])) { + $specification = $supportedVersions[$version]; + } else { + trigger_error(sprintf("Specification version '%s' is unknown", $version), E_USER_WARNING); + $specification = $this->ffi->PactSpecification_Unknown; + } + + return $specification; + } + + private function withSpecification(): self + { + $this->ffi->pactffi_with_specification($this->id, $this->getSpecification()); + + return $this; + } + + protected function newInteraction(?string $description): int + { + return $this->ffi->pactffi_new_interaction($this->id, $description); + } + + protected function cleanUp(): void + { + $this->ffi->pactffi_free_pact_handle($this->id); + } + + protected function writePact(): void + { + $error = $this->ffi->pactffi_pact_handle_write_file( + $this->id, + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new PactFileNotWroteException($error); + } + } + + protected function withBody(int $interaction, int $part, ?string $contentType = null, ?string $body = null): void + { + if (is_null($body)) { + return; + } + $success = $this->ffi->pactffi_with_body($interaction, $part, $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } } } diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index eee35750..19f5122b 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -2,13 +2,8 @@ namespace PhpPact\Consumer\Model; -use FFI; -use PhpPact\Consumer\Exception\InteractionRequestBodyNotAddedException; -use PhpPact\Consumer\Exception\InteractionResponseBodyNotAddedException; use PhpPact\Consumer\Exception\MockServerNotStartedException; -use PhpPact\Consumer\Exception\PactFileNotWroteException; use PhpPact\Standalone\MockService\MockServerConfigInterface; -use PhpPact\Standalone\PactConfigInterface; /** * Class Pact. @@ -18,13 +13,10 @@ class Pact extends AbstractPact /** * @param MockServerConfigInterface $config */ - public function __construct(private MockServerConfigInterface $config) + public function __construct(MockServerConfigInterface $config) { - parent::__construct(); - $this - ->initWithLogLevel() - ->newPact() - ->withSpecification(); + parent::__construct($config); + $this->initWithLogLevel(); } private function createMockServer(): void @@ -45,29 +37,29 @@ private function createMockServer(): void public function verifyInteractions(): bool { - $result = $this->ffi->pactffi_mock_server_matched($this->config->getPort()); - - if ($result) { - $error = $this->ffi->pactffi_write_pact_file( - $this->config->getPort(), - $this->config->getPactDir(), - $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE - ); - if ($error) { - throw new PactFileNotWroteException($error); + $matched = $this->ffi->pactffi_mock_server_matched($this->config->getPort()); + + try { + if ($matched) { + $this->writePact(); } + } finally { + $this->cleanUp(); } - $this->ffi->pactffi_cleanup_mock_server($this->config->getPort()); - $this->ffi->pactffi_free_pact_handle($this->id); + return $matched; + } - return $result; + protected function cleanUp(): void + { + parent::cleanUp(); + $this->ffi->pactffi_cleanup_mock_server($this->config->getPort()); } public function registerInteraction(Interaction $interaction): bool { + $interaction->setId($this->newInteraction($interaction->getDescription())); $this - ->newInteraction($interaction) ->given($interaction) ->uponReceiving($interaction) ->with($interaction) @@ -87,48 +79,6 @@ private function initWithLogLevel(): self return $this; } - private function newPact(): self - { - $this->id = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); - - return $this; - } - - protected function getSpecification(): int - { - $supportedVersions = [ - '1.0.0' => $this->ffi->PactSpecification_V1, - '1.1.0' => $this->ffi->PactSpecification_V1_1, - '2.0.0' => $this->ffi->PactSpecification_V2, - '3.0.0' => $this->ffi->PactSpecification_V3, - '4.0.0' => $this->ffi->PactSpecification_V4, - ]; - $version = $this->config->getPactSpecificationVersion(); - if (isset($supportedVersions[$version])) { - $specification = $supportedVersions[$version]; - } else { - trigger_error(sprintf("Specification version '%s' is unknown", $version), E_USER_WARNING); - $specification = $this->ffi->PactSpecification_Unknown; - } - - return $specification; - } - - private function withSpecification(): self - { - $this->ffi->pactffi_with_specification($this->id, $this->getSpecification()); - - return $this; - } - - private function newInteraction(Interaction $interaction): self - { - $id = $this->ffi->pactffi_new_interaction($this->id, $interaction->getDescription()); - $interaction->setId($id); - - return $this; - } - private function given(Interaction $interaction): self { $this->ffi->pactffi_given($interaction->getId(), $interaction->getProviderState()); @@ -148,22 +98,9 @@ private function with(Interaction $interaction): self $id = $interaction->getId(); $request = $interaction->getRequest(); $this->ffi->pactffi_with_request($id, $request->getMethod(), $request->getPath()); - foreach ($request->getHeaders() as $header => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Request, $header, $index, $value); - } - } - foreach ($request->getQuery() as $key => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_query_parameter_v2($id, $key, $index, $value); - } - } - if (!\is_null($request->getBody())) { - $success = $this->ffi->pactffi_with_body($id, $this->ffi->InteractionPart_Request, null, $request->getBody()); - if (!$success) { - throw new InteractionRequestBodyNotAddedException(); - } - } + $this->withHeaders($id, $this->ffi->InteractionPart_Request, $request->getHeaders()); + $this->withQuery($id, $request->getQuery()); + $this->withBody($id, $this->ffi->InteractionPart_Request, null, $request->getBody()); return $this; } @@ -173,18 +110,27 @@ private function willRespondWith(Interaction $interaction): self $id = $interaction->getId(); $response = $interaction->getResponse(); $this->ffi->pactffi_response_status($id, $response->getStatus()); - foreach ($response->getHeaders() as $header => $values) { + $this->withHeaders($id, $this->ffi->InteractionPart_Response, $response->getHeaders()); + $this->withBody($id, $this->ffi->InteractionPart_Response, null, $response->getBody()); + + return $this; + } + + private function withHeaders(int $interaction, int $part, array $headers): void + { + foreach ($headers as $header => $values) { foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_header_v2($id, $this->ffi->InteractionPart_Response, $header, $index, $value); + $this->ffi->pactffi_with_header_v2($interaction, $part, (string) $header, (int) $index, (string) $value); } } - if (!\is_null($response->getBody())) { - $success = $this->ffi->pactffi_with_body($id, $this->ffi->InteractionPart_Response, null, $response->getBody()); - if (!$success) { - throw new InteractionResponseBodyNotAddedException(); + } + + private function withQuery(int $interaction, array $query): void + { + foreach ($query as $key => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_query_parameter_v2($interaction, (string) $key, (int) $index, (string) $value); } } - - return $this; } } diff --git a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php index 1f11a8a8..f7f74a81 100644 --- a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php @@ -10,8 +10,6 @@ */ class MockServerEnvConfig extends MockServerConfig { - public const DEFAULT_SPECIFICATION_VERSION = '3.0.0'; - /** * MockServerEnvConfig constructor. * diff --git a/src/PhpPact/Standalone/PactConfig.php b/src/PhpPact/Standalone/PactConfig.php index a7b79b1d..fc0cee8a 100644 --- a/src/PhpPact/Standalone/PactConfig.php +++ b/src/PhpPact/Standalone/PactConfig.php @@ -33,7 +33,7 @@ class PactConfig implements PactConfigInterface * * @var string */ - private string $pactSpecificationVersion; + private string $pactSpecificationVersion = self::DEFAULT_SPECIFICATION_VERSION; /** * File to which to log output. diff --git a/src/PhpPact/Standalone/PactConfigInterface.php b/src/PhpPact/Standalone/PactConfigInterface.php index bcd37e24..e2febd82 100644 --- a/src/PhpPact/Standalone/PactConfigInterface.php +++ b/src/PhpPact/Standalone/PactConfigInterface.php @@ -8,6 +8,8 @@ */ interface PactConfigInterface { + public const DEFAULT_SPECIFICATION_VERSION = '3.0.0'; + public const MODE_OVERWRITE = 'overwrite'; public const MODE_MERGE = 'merge'; From e7675e6a6dffb28326cf8bcccee0b92d57158d70 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 7 Mar 2023 23:22:22 +0700 Subject: [PATCH 16/78] Use Rust FFI: Support multiple provider states with params for interaction --- README.md | 2 +- src/PhpPact/Consumer/InteractionBuilder.php | 6 +- src/PhpPact/Consumer/Model/Interaction.php | 27 +-------- src/PhpPact/Consumer/Model/Message.php | 51 +---------------- src/PhpPact/Consumer/Model/Pact.php | 6 +- src/PhpPact/Consumer/Model/ProviderState.php | 40 ++++++++++++++ src/PhpPact/Consumer/Model/ProviderStates.php | 55 +++++++++++++++++++ .../Consumer/InteractionBuilderTest.php | 6 +- 8 files changed, 113 insertions(+), 80 deletions(-) create mode 100644 src/PhpPact/Consumer/Model/ProviderState.php create mode 100644 src/PhpPact/Consumer/Model/ProviderStates.php diff --git a/README.md b/README.md index bbd0e008..cfdb0396 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,7 @@ Now that we have the request and response, we need to build the interaction and $config = new MockServerEnvConfig(); $builder = new InteractionBuilder($config); $builder - ->given('a person exists') + ->given('a person exists', ['name' => 'Bob']) ->uponReceiving('a get request to /hello/{name}') ->with($request) ->willRespondWith($response); // This has to be last. This is what makes FFI calls to register the interaction and start the mock server. diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index a59696f8..0cbeec2d 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -33,12 +33,14 @@ public function __construct(MockServerConfigInterface $config) /** * @param string $providerState what is given to the request + * @param array $params for that request + * @param bool $overwrite clear pass states completely and start this array * * @return InteractionBuilder */ - public function given(string $providerState): self + public function given(string $providerState, array $params = [], bool $overwrite = false): self { - $this->interaction->setProviderState($providerState); + $this->interaction->setProviderState($providerState, $params, $overwrite); return $this; } diff --git a/src/PhpPact/Consumer/Model/Interaction.php b/src/PhpPact/Consumer/Model/Interaction.php index e7485fea..48662593 100644 --- a/src/PhpPact/Consumer/Model/Interaction.php +++ b/src/PhpPact/Consumer/Model/Interaction.php @@ -8,6 +8,8 @@ */ class Interaction implements \JsonSerializable { + use ProviderStates; + /** * @var int */ @@ -18,11 +20,6 @@ class Interaction implements \JsonSerializable */ private string $description; - /** - * @var null|string - */ - private ?string $providerState = null; - /** * @var ConsumerRequest */ @@ -73,26 +70,6 @@ public function setDescription(string $description): self return $this; } - /** - * @return null|string - */ - public function getProviderState(): ?string - { - return $this->providerState; - } - - /** - * @param string $providerState - * - * @return Interaction - */ - public function setProviderState(string $providerState): self - { - $this->providerState = $providerState; - - return $this; - } - /** * @return ConsumerRequest */ diff --git a/src/PhpPact/Consumer/Model/Message.php b/src/PhpPact/Consumer/Model/Message.php index dbb1679b..febe1a55 100644 --- a/src/PhpPact/Consumer/Model/Message.php +++ b/src/PhpPact/Consumer/Model/Message.php @@ -8,6 +8,8 @@ */ class Message implements \JsonSerializable { + use ProviderStates; + /** * @var string */ @@ -16,12 +18,7 @@ class Message implements \JsonSerializable /** * @var array */ - private $providerStates = []; - - /** - * @var array - */ - private $metadata; + private array $metadata; /** * @var mixed @@ -48,48 +45,6 @@ public function setDescription(string $description): self return $this; } - /** - * @return array - */ - public function getProviderStates(): array - { - return $this->providerStates; - } - - /** - * @param mixed $overwrite - * - * @return array - */ - public function setProviderState(string $name, array $params = [], $overwrite = true): array - { - $this->addProviderState($name, $params, $overwrite); - - return $this->providerStates; - } - - /** - * @param string $name - * @param array $params - * @param bool $overwrite - if true reset the entire state - * - * @return Message - */ - public function addProviderState(string $name, array $params, $overwrite = false): self - { - $providerState = new \stdClass(); - $providerState->name = $name; - $providerState->params = $params; - - if ($overwrite === true) { - $this->providerStates = []; - } - - $this->providerStates[] = $providerState; - - return $this; - } - /** * @return array */ diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index 19f5122b..db82db28 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -81,7 +81,11 @@ private function initWithLogLevel(): self private function given(Interaction $interaction): self { - $this->ffi->pactffi_given($interaction->getId(), $interaction->getProviderState()); + foreach ($interaction->getProviderStates() as $providerState) { + foreach ($providerState->getParams() as $key => $value) { + $this->ffi->pactffi_given_with_param($interaction->getId(), $providerState->getName(), $key, $value); + } + } return $this; } diff --git a/src/PhpPact/Consumer/Model/ProviderState.php b/src/PhpPact/Consumer/Model/ProviderState.php new file mode 100644 index 00000000..48703a62 --- /dev/null +++ b/src/PhpPact/Consumer/Model/ProviderState.php @@ -0,0 +1,40 @@ +name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + + public function setParams(array $params = []): void + { + foreach ($params as $key => $value) { + $this->addParam($key, $value); + } + } + + public function addParam(string $key, string $value): void + { + $this->params[$key] = $value; + } +} diff --git a/src/PhpPact/Consumer/Model/ProviderStates.php b/src/PhpPact/Consumer/Model/ProviderStates.php new file mode 100644 index 00000000..812d5a3a --- /dev/null +++ b/src/PhpPact/Consumer/Model/ProviderStates.php @@ -0,0 +1,55 @@ + + */ + public function getProviderStates(): array + { + return $this->providerStates; + } + + /** + * @param string $name + * @param array $params + * @param bool $overwrite + * + * @return array + */ + public function setProviderState(string $name, array $params = [], bool $overwrite = true): array + { + $this->addProviderState($name, $params, $overwrite); + + return $this->providerStates; + } + + /** + * @param string $name + * @param array $params + * @param bool $overwrite - if true reset the entire state + * + * @return $this + */ + public function addProviderState(string $name, array $params, bool $overwrite = false): self + { + $providerState = new ProviderState(); + $providerState->setName($name); + $providerState->setParams($params); + + if ($overwrite === true) { + $this->providerStates = []; + } + + $this->providerStates[] = $providerState; + + return $this; + } +} diff --git a/tests/PhpPact/Consumer/InteractionBuilderTest.php b/tests/PhpPact/Consumer/InteractionBuilderTest.php index 713a2f15..a385cf03 100644 --- a/tests/PhpPact/Consumer/InteractionBuilderTest.php +++ b/tests/PhpPact/Consumer/InteractionBuilderTest.php @@ -37,7 +37,7 @@ public function testSimpleGet() $builder = new InteractionBuilder(new MockServerEnvConfig()); $builder - ->given('A test request.') + ->given('A test request.', ['key' => 'value']) ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); @@ -75,7 +75,7 @@ public function testPostWithBody() $builder = new InteractionBuilder(new MockServerEnvConfig()); $builder - ->given('A test request.') + ->given('A test request.', ['key' => 'value']) ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); @@ -109,7 +109,7 @@ public function testBuildWithEachLikeMatcher() $builder = new InteractionBuilder(new MockServerEnvConfig()); $builder - ->given('A test request.') + ->given('A test request.', ['key' => 'value']) ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); From 1e0b4092d0c1da4d2658d6655454d11dad8cfea0 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 16 Mar 2023 10:25:25 +0700 Subject: [PATCH 17/78] Fix provider state without params not added --- src/PhpPact/Consumer/Model/Pact.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php index db82db28..b141c178 100644 --- a/src/PhpPact/Consumer/Model/Pact.php +++ b/src/PhpPact/Consumer/Model/Pact.php @@ -82,6 +82,7 @@ private function initWithLogLevel(): self private function given(Interaction $interaction): self { foreach ($interaction->getProviderStates() as $providerState) { + $this->ffi->pactffi_given($interaction->getId(), $providerState->getName()); foreach ($providerState->getParams() as $key => $value) { $this->ffi->pactffi_given_with_param($interaction->getId(), $providerState->getName(), $key, $value); } From 47b0a81c9acd6bc284fec202136f40b56537f401 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 5 Jan 2023 22:04:47 +0700 Subject: [PATCH 18/78] Remove jsonSerialize methods --- .../Consumer/Model/ConsumerRequest.php | 30 +------------------ src/PhpPact/Consumer/Model/Interaction.php | 23 +------------- src/PhpPact/Consumer/Model/Message.php | 26 +--------------- .../Consumer/Model/ProviderResponse.php | 22 +------------- 4 files changed, 4 insertions(+), 97 deletions(-) diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index ae6f8daa..0efed279 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -6,7 +6,7 @@ * Request initiated by the consumer. * Class ConsumerRequest. */ -class ConsumerRequest implements \JsonSerializable +class ConsumerRequest { /** * @var string @@ -191,32 +191,4 @@ private function addQueryParameterValue(string $key, string $value): void { $this->query[$key][] = $value; } - - /** - * {@inheritdoc} - */ - public function jsonSerialize(): array - { - $results = []; - - $results['method'] = $this->getMethod(); - - if ($this->getHeaders() !== null) { - $results['headers'] = $this->getHeaders(); - } - - if ($this->getPath() !== null) { - $results['path'] = $this->getPath(); - } - - if ($this->getBody() !== null) { - $results['body'] = $this->getBody(); - } - - if ($this->getQuery() !== null) { - $results['query'] = $this->getQuery(); - } - - return $results; - } } diff --git a/src/PhpPact/Consumer/Model/Interaction.php b/src/PhpPact/Consumer/Model/Interaction.php index 48662593..743befa8 100644 --- a/src/PhpPact/Consumer/Model/Interaction.php +++ b/src/PhpPact/Consumer/Model/Interaction.php @@ -6,7 +6,7 @@ * Request/Response Pair to be posted to the Ruby Standalone Mock Server for PACT tests. * Class Interaction. */ -class Interaction implements \JsonSerializable +class Interaction { use ProviderStates; @@ -109,25 +109,4 @@ public function setResponse(ProviderResponse $response): self return $this; } - - /** - * {@inheritdoc} - */ - public function jsonSerialize(): array - { - if ($this->getProviderState()) { - return [ - 'description' => $this->getDescription(), - 'providerState' => $this->getProviderState(), - 'request' => $this->getRequest(), - 'response' => $this->getResponse(), - ]; - } - - return [ - 'description' => $this->getDescription(), - 'request' => $this->getRequest(), - 'response' => $this->getResponse(), - ]; - } } diff --git a/src/PhpPact/Consumer/Model/Message.php b/src/PhpPact/Consumer/Model/Message.php index febe1a55..c56c3280 100644 --- a/src/PhpPact/Consumer/Model/Message.php +++ b/src/PhpPact/Consumer/Model/Message.php @@ -6,7 +6,7 @@ * Request/Response Pair to be posted to the Ruby Standalone Mock Server for PACT tests. * Class Interaction. */ -class Message implements \JsonSerializable +class Message { use ProviderStates; @@ -84,28 +84,4 @@ public function setContents($contents) return $this; } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - $out = []; - $out['description'] = $this->getDescription(); - - if (\count($this->providerStates) > 0) { - $out['providerStates'] = $this->getProviderStates(); - } - - if ($this->metadata) { - $out['metadata'] = $this->getMetadata(); - } - - if ($this->contents) { - $out['contents'] = $this->getContents(); - } - - return $out; - } } diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index 6c3f95e4..abeaca25 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -6,7 +6,7 @@ * Response expectation that would be in response to a Consumer request from the Provider. * Class ProviderResponse. */ -class ProviderResponse implements \JsonSerializable +class ProviderResponse { /** * @var int @@ -115,24 +115,4 @@ public function setBody(mixed $body): self return $this; } - - /** - * {@inheritdoc} - */ - public function jsonSerialize(): array - { - $results = [ - 'status' => $this->getStatus(), - ]; - - if ($this->getHeaders() !== null) { - $results['headers'] = $this->getHeaders(); - } - - if ($this->getBody() !== null) { - $results['body'] = $this->getBody(); - } - - return $results; - } } From 2754f47504d65502b51fd7a16e83f5e17eaedf84 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 17 Mar 2023 22:39:34 +0700 Subject: [PATCH 19/78] Rename pact to driver --- phpstan.neon | 4 +- .../AbstractDriver.php} | 77 ++++++---- .../Consumer/Driver/DriverInterface.php | 7 + .../Consumer/Driver/HasMockServerTrait.php | 50 +++++++ .../Consumer/Driver/InteractionDriver.php | 103 +++++++++++++ .../Driver/InteractionDriverInterface.php | 12 ++ src/PhpPact/Consumer/InteractionBuilder.php | 13 +- src/PhpPact/Consumer/Model/Interaction.php | 25 ---- src/PhpPact/Consumer/Model/Pact.php | 141 ------------------ 9 files changed, 231 insertions(+), 201 deletions(-) rename src/PhpPact/Consumer/{Model/AbstractPact.php => Driver/AbstractDriver.php} (54%) create mode 100644 src/PhpPact/Consumer/Driver/DriverInterface.php create mode 100644 src/PhpPact/Consumer/Driver/HasMockServerTrait.php create mode 100644 src/PhpPact/Consumer/Driver/InteractionDriver.php create mode 100644 src/PhpPact/Consumer/Driver/InteractionDriverInterface.php delete mode 100644 src/PhpPact/Consumer/Model/Pact.php diff --git a/phpstan.neon b/phpstan.neon index 2704610d..408f763a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,4 @@ parameters: excludePaths: - - src/PhpPact/Consumer/Model/AbstractPact.php - - src/PhpPact/Consumer/Model/Pact.php + - src/PhpPact/Consumer/Driver/AbstractDriver.php + - src/PhpPact/Consumer/Driver/InteractionDriver.php diff --git a/src/PhpPact/Consumer/Model/AbstractPact.php b/src/PhpPact/Consumer/Driver/AbstractDriver.php similarity index 54% rename from src/PhpPact/Consumer/Model/AbstractPact.php rename to src/PhpPact/Consumer/Driver/AbstractDriver.php index 564bc3c4..f1f4b891 100644 --- a/src/PhpPact/Consumer/Model/AbstractPact.php +++ b/src/PhpPact/Consumer/Driver/AbstractDriver.php @@ -1,39 +1,46 @@ ffi = FFI::cdef(\file_get_contents(Scripts::getHeader()), Scripts::getLibrary()); $this - ->createFfi() + ->initWithLogLevel() ->newPact() ->withSpecification(); } - private function createFfi(): self + private function newPact(): self { - $this->ffi = FFI::cdef(\file_get_contents(Scripts::getHeader()), Scripts::getLibrary()); + $this->pactId = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); return $this; } - private function newPact(): self + protected function newInteraction(string $description): self + { + $this->interactionId = $this->ffi->pactffi_new_interaction($this->pactId, $description); + + return $this; + } + + private function withSpecification(): self { - $this->id = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); + $this->ffi->pactffi_with_specification($this->pactId, $this->getSpecification()); return $this; } @@ -58,27 +65,15 @@ protected function getSpecification(): int return $specification; } - private function withSpecification(): self - { - $this->ffi->pactffi_with_specification($this->id, $this->getSpecification()); - - return $this; - } - - protected function newInteraction(?string $description): int - { - return $this->ffi->pactffi_new_interaction($this->id, $description); - } - protected function cleanUp(): void { - $this->ffi->pactffi_free_pact_handle($this->id); + $this->ffi->pactffi_free_pact_handle($this->pactId); } protected function writePact(): void { $error = $this->ffi->pactffi_pact_handle_write_file( - $this->id, + $this->pactId, $this->config->getPactDir(), $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE ); @@ -87,14 +82,42 @@ protected function writePact(): void } } - protected function withBody(int $interaction, int $part, ?string $contentType = null, ?string $body = null): void + protected function withBody(int $part, ?string $contentType = null, ?string $body = null): void { if (is_null($body)) { return; } - $success = $this->ffi->pactffi_with_body($interaction, $part, $contentType, $body); + $success = $this->ffi->pactffi_with_body($this->interactionId, $part, $contentType, $body); if (!$success) { throw new InteractionBodyNotAddedException(); } } + + private function initWithLogLevel(): self + { + $logLevel = $this->config->getLogLevel(); + if ($logLevel) { + $this->ffi->pactffi_init_with_log_level($logLevel); + } + + return $this; + } + + /** + * @var array $providerStates + */ + protected function setProviderStates(array $providerStates): void + { + foreach ($providerStates as $providerState) { + $this->ffi->pactffi_given($this->interactionId, $providerState->getName()); + foreach ($providerState->getParams() as $key => $value) { + $this->ffi->pactffi_given_with_param($this->interactionId, $providerState->getName(), $key, $value); + } + } + } + + protected function setDescription(string $description): void + { + $this->ffi->pactffi_upon_receiving($this->interactionId, $description); + } } diff --git a/src/PhpPact/Consumer/Driver/DriverInterface.php b/src/PhpPact/Consumer/Driver/DriverInterface.php new file mode 100644 index 00000000..70e13108 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/DriverInterface.php @@ -0,0 +1,7 @@ +ffi->pactffi_create_mock_server_for_transport( + $this->pactId, + $this->getMockServerConfig()->getHost(), + $this->getMockServerConfig()->getPort(), + $this->getMockServerTransport(), + null + ); + + if ($port < 0) { + throw new MockServerNotStartedException($port); + } + $this->getMockServerConfig()->setPort($port); + } + + protected function mockServerMatched(): bool + { + $matched = $this->ffi->pactffi_mock_server_matched($this->getMockServerConfig()->getPort()); + + try { + if ($matched) { + $this->writePact(); + } + } finally { + $this->cleanUp(); + } + + return $matched; + } + + protected function cleanUpMockServer(): void + { + $this->ffi->pactffi_cleanup_mock_server($this->getMockServerConfig()->getPort()); + } + + abstract protected function getMockServerTransport(): string; + + abstract protected function getMockServerConfig(): MockServerConfigInterface; +} diff --git a/src/PhpPact/Consumer/Driver/InteractionDriver.php b/src/PhpPact/Consumer/Driver/InteractionDriver.php new file mode 100644 index 00000000..c973071f --- /dev/null +++ b/src/PhpPact/Consumer/Driver/InteractionDriver.php @@ -0,0 +1,103 @@ +config->isSecure() ? 'https' : 'http'; + } + + protected function getMockServerConfig(): MockServerConfigInterface + { + return $this->config; + } + + public function verifyInteractions(): bool + { + return $this->mockServerMatched(); + } + + protected function cleanUp(): void + { + $this->cleanUpMockServer(); + parent::cleanUp(); + } + + public function registerInteraction(Interaction $interaction): bool + { + $this + ->newInteraction($interaction->getDescription()) + ->given($interaction) + ->uponReceiving($interaction) + ->with($interaction) + ->willRespondWith($interaction) + ->createMockServer(); + + return true; + } + + private function given(Interaction $interaction): self + { + $this->setProviderStates($interaction->getProviderStates()); + + return $this; + } + + private function uponReceiving(Interaction $interaction): self + { + $this->setDescription($interaction->getDescription()); + + return $this; + } + + private function with(Interaction $interaction): self + { + $request = $interaction->getRequest(); + $this->ffi->pactffi_with_request($this->interactionId, $request->getMethod(), $request->getPath()); + $this->withHeaders($this->ffi->InteractionPart_Request, $request->getHeaders()); + $this->withQuery($request->getQuery()); + $this->withBody($this->ffi->InteractionPart_Request, null, $request->getBody()); + + return $this; + } + + private function willRespondWith(Interaction $interaction): self + { + $response = $interaction->getResponse(); + $this->ffi->pactffi_response_status($this->interactionId, $response->getStatus()); + $this->withHeaders($this->ffi->InteractionPart_Response, $response->getHeaders()); + $this->withBody($this->ffi->InteractionPart_Response, null, $response->getBody()); + + return $this; + } + + private function withHeaders(int $part, array $headers): void + { + foreach ($headers as $header => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_header_v2($this->interactionId, $part, (string) $header, (int) $index, (string) $value); + } + } + } + + private function withQuery(array $query): void + { + foreach ($query as $key => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_query_parameter_v2($this->interactionId, (string) $key, (int) $index, (string) $value); + } + } + } +} diff --git a/src/PhpPact/Consumer/Driver/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/InteractionDriverInterface.php new file mode 100644 index 00000000..6255bb05 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/InteractionDriverInterface.php @@ -0,0 +1,12 @@ +interaction = new Interaction(); - $this->pact = new Pact($config); + $this->driver = new InteractionDriver($config); } /** @@ -78,7 +79,7 @@ public function willRespondWith(ProviderResponse $response): bool { $this->interaction->setResponse($response); - return $this->pact->registerInteraction($this->interaction); + return $this->driver->registerInteraction($this->interaction); } /** @@ -86,6 +87,6 @@ public function willRespondWith(ProviderResponse $response): bool */ public function verify(): bool { - return $this->pact->verifyInteractions(); + return $this->driver->verifyInteractions(); } } diff --git a/src/PhpPact/Consumer/Model/Interaction.php b/src/PhpPact/Consumer/Model/Interaction.php index 743befa8..5759b6fa 100644 --- a/src/PhpPact/Consumer/Model/Interaction.php +++ b/src/PhpPact/Consumer/Model/Interaction.php @@ -10,11 +10,6 @@ class Interaction { use ProviderStates; - /** - * @var int - */ - private int $id; - /** * @var string */ @@ -30,26 +25,6 @@ class Interaction */ private ProviderResponse $response; - /** - * @return int - */ - public function getId(): int - { - return $this->id; - } - - /** - * @param int $id - * - * @return Interaction - */ - public function setId(int $id): self - { - $this->id = $id; - - return $this; - } - /** * @return string */ diff --git a/src/PhpPact/Consumer/Model/Pact.php b/src/PhpPact/Consumer/Model/Pact.php deleted file mode 100644 index b141c178..00000000 --- a/src/PhpPact/Consumer/Model/Pact.php +++ /dev/null @@ -1,141 +0,0 @@ -initWithLogLevel(); - } - - private function createMockServer(): void - { - $port = $this->ffi->pactffi_create_mock_server_for_transport( - $this->id, - $this->config->getHost(), - $this->config->getPort(), - $this->config->isSecure() ? 'https' : 'http', - null - ); - - if ($port < 0) { - throw new MockServerNotStartedException($port); - } - $this->config->setPort($port); - } - - public function verifyInteractions(): bool - { - $matched = $this->ffi->pactffi_mock_server_matched($this->config->getPort()); - - try { - if ($matched) { - $this->writePact(); - } - } finally { - $this->cleanUp(); - } - - return $matched; - } - - protected function cleanUp(): void - { - parent::cleanUp(); - $this->ffi->pactffi_cleanup_mock_server($this->config->getPort()); - } - - public function registerInteraction(Interaction $interaction): bool - { - $interaction->setId($this->newInteraction($interaction->getDescription())); - $this - ->given($interaction) - ->uponReceiving($interaction) - ->with($interaction) - ->willRespondWith($interaction) - ->createMockServer(); - - return true; - } - - private function initWithLogLevel(): self - { - $logLevel = $this->config->getLogLevel(); - if ($logLevel) { - $this->ffi->pactffi_init_with_log_level($logLevel); - } - - return $this; - } - - private function given(Interaction $interaction): self - { - foreach ($interaction->getProviderStates() as $providerState) { - $this->ffi->pactffi_given($interaction->getId(), $providerState->getName()); - foreach ($providerState->getParams() as $key => $value) { - $this->ffi->pactffi_given_with_param($interaction->getId(), $providerState->getName(), $key, $value); - } - } - - return $this; - } - - private function uponReceiving(Interaction $interaction): self - { - $this->ffi->pactffi_upon_receiving($interaction->getId(), $interaction->getDescription()); - - return $this; - } - - private function with(Interaction $interaction): self - { - $id = $interaction->getId(); - $request = $interaction->getRequest(); - $this->ffi->pactffi_with_request($id, $request->getMethod(), $request->getPath()); - $this->withHeaders($id, $this->ffi->InteractionPart_Request, $request->getHeaders()); - $this->withQuery($id, $request->getQuery()); - $this->withBody($id, $this->ffi->InteractionPart_Request, null, $request->getBody()); - - return $this; - } - - private function willRespondWith(Interaction $interaction): self - { - $id = $interaction->getId(); - $response = $interaction->getResponse(); - $this->ffi->pactffi_response_status($id, $response->getStatus()); - $this->withHeaders($id, $this->ffi->InteractionPart_Response, $response->getHeaders()); - $this->withBody($id, $this->ffi->InteractionPart_Response, null, $response->getBody()); - - return $this; - } - - private function withHeaders(int $interaction, int $part, array $headers): void - { - foreach ($headers as $header => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_header_v2($interaction, $part, (string) $header, (int) $index, (string) $value); - } - } - } - - private function withQuery(int $interaction, array $query): void - { - foreach ($query as $key => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_query_parameter_v2($interaction, (string) $key, (int) $index, (string) $value); - } - } - } -} From 18b85b24ff741b3d795e0b38067bf23ef905244a Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 23 Mar 2023 11:04:27 +0700 Subject: [PATCH 20/78] Move log level config to trait for reusing --- src/PhpPact/Config/LogLevelTrait.php | 24 ++++++++++++++ .../{Standalone => Config}/PactConfig.php | 31 ++----------------- .../PactConfigInterface.php | 2 +- .../Consumer/Driver/AbstractDriver.php | 2 +- src/PhpPact/Consumer/MessageBuilder.php | 2 +- .../MockService/MockServerConfig.php | 2 +- .../MockService/MockServerConfigInterface.php | 2 +- .../PactMessage/PactMessageConfig.php | 2 +- .../{Standalone => Config}/PactConfigTest.php | 10 +++--- 9 files changed, 38 insertions(+), 39 deletions(-) create mode 100644 src/PhpPact/Config/LogLevelTrait.php rename src/PhpPact/{Standalone => Config}/PactConfig.php (85%) rename src/PhpPact/{Standalone => Config}/PactConfigInterface.php (98%) rename tests/PhpPact/{Standalone => Config}/PactConfigTest.php (89%) diff --git a/src/PhpPact/Config/LogLevelTrait.php b/src/PhpPact/Config/LogLevelTrait.php new file mode 100644 index 00000000..de5265ce --- /dev/null +++ b/src/PhpPact/Config/LogLevelTrait.php @@ -0,0 +1,24 @@ +logLevel; + } + + public function setLogLevel(string $logLevel): self + { + $logLevel = \strtoupper($logLevel); + if (!\in_array($logLevel, ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR'])) { + throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); + } + $this->logLevel = $logLevel; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/PactConfig.php b/src/PhpPact/Config/PactConfig.php similarity index 85% rename from src/PhpPact/Standalone/PactConfig.php rename to src/PhpPact/Config/PactConfig.php index fc0cee8a..59d85358 100644 --- a/src/PhpPact/Standalone/PactConfig.php +++ b/src/PhpPact/Config/PactConfig.php @@ -1,12 +1,14 @@ logLevel; - } - - /** - * {@inheritdoc} - */ - public function setLogLevel(string $logLevel): self - { - $logLevel = \strtoupper($logLevel); - if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { - throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); - } - $this->logLevel = $logLevel; - - return $this; - } - /** * {@inheritdoc} */ diff --git a/src/PhpPact/Standalone/PactConfigInterface.php b/src/PhpPact/Config/PactConfigInterface.php similarity index 98% rename from src/PhpPact/Standalone/PactConfigInterface.php rename to src/PhpPact/Config/PactConfigInterface.php index e2febd82..c4307581 100644 --- a/src/PhpPact/Standalone/PactConfigInterface.php +++ b/src/PhpPact/Config/PactConfigInterface.php @@ -1,6 +1,6 @@ expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage('LogLevel TRACE not supported.'); - $this->config->setLogLevel('TRACE'); + $this->expectExceptionMessage('LogLevel VERBOSE not supported.'); + $this->config->setLogLevel('VERBOSE'); } public function testInvalidPactFileWriteMode(): void From 5a4b15f5cd8c9abdfb9c398c3f2c1db46db09e2c Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 23 Mar 2023 22:11:43 +0700 Subject: [PATCH 21/78] Allow mock server write pact file --- .../Consumer/Driver/HasMockServerTrait.php | 14 ++++++++++++++ .../Consumer/Driver/InteractionDriver.php | 5 +++++ .../MockServerNotWrotePactFileException.php | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/PhpPact/Consumer/Exception/MockServerNotWrotePactFileException.php diff --git a/src/PhpPact/Consumer/Driver/HasMockServerTrait.php b/src/PhpPact/Consumer/Driver/HasMockServerTrait.php index fb273692..9a9e66e5 100644 --- a/src/PhpPact/Consumer/Driver/HasMockServerTrait.php +++ b/src/PhpPact/Consumer/Driver/HasMockServerTrait.php @@ -3,7 +3,9 @@ namespace PhpPact\Consumer\Driver; use FFI; +use PhpPact\Config\PactConfigInterface; use PhpPact\Consumer\Exception\MockServerNotStartedException; +use PhpPact\Consumer\Exception\MockServerNotWrotePactFileException; use PhpPact\Standalone\MockService\MockServerConfigInterface; trait HasMockServerTrait @@ -47,4 +49,16 @@ protected function cleanUpMockServer(): void abstract protected function getMockServerTransport(): string; abstract protected function getMockServerConfig(): MockServerConfigInterface; + + protected function mockServerWritePact(): void + { + $error = $this->ffi->pactffi_write_pact_file( + $this->getMockServerConfig()->getPort(), + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new MockServerNotWrotePactFileException($error); + } + } } diff --git a/src/PhpPact/Consumer/Driver/InteractionDriver.php b/src/PhpPact/Consumer/Driver/InteractionDriver.php index c973071f..ad80aba7 100644 --- a/src/PhpPact/Consumer/Driver/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/InteractionDriver.php @@ -35,6 +35,11 @@ protected function cleanUp(): void parent::cleanUp(); } + protected function writePact(): void + { + $this->mockServerWritePact(); + } + public function registerInteraction(Interaction $interaction): bool { $this diff --git a/src/PhpPact/Consumer/Exception/MockServerNotWrotePactFileException.php b/src/PhpPact/Consumer/Exception/MockServerNotWrotePactFileException.php new file mode 100644 index 00000000..01209897 --- /dev/null +++ b/src/PhpPact/Consumer/Exception/MockServerNotWrotePactFileException.php @@ -0,0 +1,19 @@ + 'A general panic was caught', + 2 => 'The pact file was not able to be written', + 3 => 'A mock server with the provided port was not found', + default => 'Unknown error', + }; + parent::__construct($message, $code); + } +} From 07c63acb63e1b50ffda9a3d9a851120e5e3c532b Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Fri, 28 Apr 2023 18:50:39 +0100 Subject: [PATCH 22/78] feat: arm64 linux/macos + x86 windows --- .cirrus.yml | 44 ++++++++++++++++++++++++++++++++++++++++++++ composer.json | 8 ++++---- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 .cirrus.yml diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..b7a14b59 --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,44 @@ +BUILD_TEST_TASK_TEMPLATE: &BUILD_TEST_TASK_TEMPLATE + arch_check_script: + - uname -am + test_script: + - composer install + - composer run lint + - composer run static-code-analysis + - composer test + +# linux_arm64_task: +# env: +# matrix: +# - IMAGE: php:8.2 +# - IMAGE: php:8.1 +# - IMAGE: php:8.0 +# container: +# image: $IMAGE +# architecture: arm64 +# pre_req_script: +# - apt update --yes && apt install --yes zip unzip git +# - curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php +# - php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer +# - docker-php-ext-install sockets +# version_check_script: +# - php --version +# << : *BUILD_TEST_TASK_TEMPLATE + + +macos_arm64_task: +# https://www.markhesketh.com/switching-multiple-php-versions-on-macos/ + env: + matrix: + - VERSION: 8.2 + - VERSION: 8.1 + - VERSION: 8.0 + macos_instance: + image: ghcr.io/cirruslabs/macos-ventura-base:latest + pre_req_script: + - brew install php@$VERSION composer + version_check_script: + - php --version + << : *BUILD_TEST_TASK_TEMPLATE + + diff --git a/composer.json b/composer.json index 6f3aeae7..0f6db196 100644 --- a/composer.json +++ b/composer.json @@ -79,13 +79,13 @@ "extra": { "downloads": { "pact-ruby-standalone": { - "version": "1.91.0", + "version": "3.1.2.2-alpha", "variables": { - "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'win32' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", - "{$architecture}": "PHP_OS === 'Linux' ? '-x86_64' : ''", + "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'windows' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", + "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86_64' ? '-x86_64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64'))", "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'zip' : 'tar.gz'" }, - "url": "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", + "url": "https://github.com/you54f/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", "path": "bin/pact-ruby-standalone" } } From f37c8ee71dda5c9538e432b54f3b45cff864bf02 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Fri, 28 Apr 2023 23:58:34 +0100 Subject: [PATCH 23/78] feat: use pact-ruby-standalone 2.0.0 release --- .cirrus.yml | 34 +++++++++++++++++----------------- composer.json | 6 +++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index b7a14b59..bcaf159b 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -7,23 +7,23 @@ BUILD_TEST_TASK_TEMPLATE: &BUILD_TEST_TASK_TEMPLATE - composer run static-code-analysis - composer test -# linux_arm64_task: -# env: -# matrix: -# - IMAGE: php:8.2 -# - IMAGE: php:8.1 -# - IMAGE: php:8.0 -# container: -# image: $IMAGE -# architecture: arm64 -# pre_req_script: -# - apt update --yes && apt install --yes zip unzip git -# - curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php -# - php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer -# - docker-php-ext-install sockets -# version_check_script: -# - php --version -# << : *BUILD_TEST_TASK_TEMPLATE +linux_arm64_task: + env: + matrix: + - IMAGE: php:8.2 + - IMAGE: php:8.1 + - IMAGE: php:8.0 + container: + image: $IMAGE + architecture: arm64 + pre_req_script: + - apt update --yes && apt install --yes zip unzip git + - curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php + - php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer + - docker-php-ext-install sockets + version_check_script: + - php --version + << : *BUILD_TEST_TASK_TEMPLATE macos_arm64_task: diff --git a/composer.json b/composer.json index 4b0d5be8..9d2254a0 100644 --- a/composer.json +++ b/composer.json @@ -79,13 +79,13 @@ "extra": { "downloads": { "pact-ruby-standalone": { - "version": "3.1.2.2-alpha", + "version": "2.0.0", "variables": { "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'windows' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", - "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86_64' ? '-x86_64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64'))", + "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64'))", "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'zip' : 'tar.gz'" }, - "url": "https://github.com/you54f/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", + "url": "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", "path": "bin/pact-ruby-standalone" } } From d7060a26583c600feae4cafce1dfd139a9b9dc57 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sat, 29 Apr 2023 00:09:27 +0100 Subject: [PATCH 24/78] ci: test arm64 linux --- .cirrus.yml | 31 ++++++++++++++++--------------- .github/workflows/build.yml | 10 +++++----- composer.json | 2 +- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index bcaf159b..d7586728 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -9,11 +9,12 @@ BUILD_TEST_TASK_TEMPLATE: &BUILD_TEST_TASK_TEMPLATE linux_arm64_task: env: + COMPOSER_ALLOW_SUPERUSER: 1 matrix: - IMAGE: php:8.2 - IMAGE: php:8.1 - IMAGE: php:8.0 - container: + arm_container: image: $IMAGE architecture: arm64 pre_req_script: @@ -26,19 +27,19 @@ linux_arm64_task: << : *BUILD_TEST_TASK_TEMPLATE -macos_arm64_task: -# https://www.markhesketh.com/switching-multiple-php-versions-on-macos/ - env: - matrix: - - VERSION: 8.2 - - VERSION: 8.1 - - VERSION: 8.0 - macos_instance: - image: ghcr.io/cirruslabs/macos-ventura-base:latest - pre_req_script: - - brew install php@$VERSION composer - version_check_script: - - php --version - << : *BUILD_TEST_TASK_TEMPLATE +# macos_arm64_task: +# # https://www.markhesketh.com/switching-multiple-php-versions-on-macos/ +# env: +# matrix: +# - VERSION: 8.2 +# - VERSION: 8.1 +# - VERSION: 8.0 +# macos_instance: +# image: ghcr.io/cirruslabs/macos-ventura-base:latest +# pre_req_script: +# - brew install php@$VERSION composer +# version_check_script: +# - php --version +# << : *BUILD_TEST_TASK_TEMPLATE diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e07f3d9..bc668020 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,7 @@ name: pact-php on: - push: + push: pull_request: # Once on the first of the month at 06:00 UTC schedule: @@ -16,10 +16,10 @@ jobs: php: [ '8.2' ] steps: - - name: 🛑 Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.11.0 - with: - access_token: ${{ github.token }} + # - name: 🛑 Cancel Previous Runs + # uses: styfle/cancel-workflow-action@0.11.0 + # with: + # access_token: ${{ github.token }} - uses: actions/checkout@v3 name: Checkout repository diff --git a/composer.json b/composer.json index 9d2254a0..76afe264 100644 --- a/composer.json +++ b/composer.json @@ -82,7 +82,7 @@ "version": "2.0.0", "variables": { "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'windows' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", - "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64'))", + "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64')", "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'zip' : 'tar.gz'" }, "url": "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", From 6ad0384963b8eea624a25963b6d55d5db3871831 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sat, 29 Apr 2023 01:52:55 +0100 Subject: [PATCH 25/78] ci(test): describeVersion test bork on aarch64 linux?!! --- .cirrus.yml | 2 +- .../PhpPact/Standalone/Broker/BrokerTest.php | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index d7586728..9858ee69 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -14,7 +14,7 @@ linux_arm64_task: - IMAGE: php:8.2 - IMAGE: php:8.1 - IMAGE: php:8.0 - arm_container: + container: image: $IMAGE architecture: arm64 pre_req_script: diff --git a/tests/PhpPact/Standalone/Broker/BrokerTest.php b/tests/PhpPact/Standalone/Broker/BrokerTest.php index 13b67b38..394bd570 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerTest.php @@ -42,25 +42,32 @@ public function getArgumentsEmptyConfig(): void // $this->assertContains('-', (new Broker(new BrokerConfig()))->generateUuid()); //} - /** + + /** * @test * * @throws \Exception */ public function describeVersion(): void { - $config = new BrokerConfig(); - $config->setPacticipant('Animal Profile Service') - ->setBrokerUri(new Uri('https://test.pactflow.io')) - ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') - ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); - $broker = new Broker($config); - $result = $broker->describeVersion(); + // this test has slain many a developer 🤯 + // if (php_uname('m') != 'arm64' && PHP_OS != 'Linux') { + $config = new BrokerConfig(); + $config->setPacticipant('Animal Profile Service') + ->setBrokerUri(new Uri('https://test.pactflow.io')) + ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') + ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); + $broker = new Broker($config); + + $result = $broker->describeVersion(); - $this->assertArrayHasKey('number', $result); + $this->assertArrayHasKey('number', $result); + // }; } + + /** * @test * From 9b4aa2724b4a3ad456e01c5f402b39897ce6007d Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sat, 29 Apr 2023 01:53:17 +0100 Subject: [PATCH 26/78] chore: cs-lint: --- tests/PhpPact/Standalone/Broker/BrokerTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/PhpPact/Standalone/Broker/BrokerTest.php b/tests/PhpPact/Standalone/Broker/BrokerTest.php index 394bd570..6e991af6 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerTest.php @@ -53,16 +53,16 @@ public function describeVersion(): void // this test has slain many a developer 🤯 // if (php_uname('m') != 'arm64' && PHP_OS != 'Linux') { - $config = new BrokerConfig(); - $config->setPacticipant('Animal Profile Service') - ->setBrokerUri(new Uri('https://test.pactflow.io')) - ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') - ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); - $broker = new Broker($config); + $config = new BrokerConfig(); + $config->setPacticipant('Animal Profile Service') + ->setBrokerUri(new Uri('https://test.pactflow.io')) + ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') + ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); + $broker = new Broker($config); - $result = $broker->describeVersion(); + $result = $broker->describeVersion(); - $this->assertArrayHasKey('number', $result); + $this->assertArrayHasKey('number', $result); // }; } From ed387828c6201154663982e3f5b07d87e25264b9 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Sat, 29 Apr 2023 02:06:22 +0100 Subject: [PATCH 27/78] ci: ruby aarch64 linux needs libyaml-dev dep --- .cirrus.yml | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/.cirrus.yml b/.cirrus.yml index 9858ee69..e5cacb04 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -16,9 +16,8 @@ linux_arm64_task: - IMAGE: php:8.0 container: image: $IMAGE - architecture: arm64 pre_req_script: - - apt update --yes && apt install --yes zip unzip git + - apt update --yes && apt install --yes zip unzip git libyaml-dev - curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php - php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer - docker-php-ext-install sockets @@ -26,20 +25,19 @@ linux_arm64_task: - php --version << : *BUILD_TEST_TASK_TEMPLATE - -# macos_arm64_task: -# # https://www.markhesketh.com/switching-multiple-php-versions-on-macos/ -# env: -# matrix: -# - VERSION: 8.2 -# - VERSION: 8.1 -# - VERSION: 8.0 -# macos_instance: -# image: ghcr.io/cirruslabs/macos-ventura-base:latest -# pre_req_script: -# - brew install php@$VERSION composer -# version_check_script: -# - php --version -# << : *BUILD_TEST_TASK_TEMPLATE +macos_arm64_task: +# https://www.markhesketh.com/switching-multiple-php-versions-on-macos/ + env: + matrix: + - VERSION: 8.2 + - VERSION: 8.1 + - VERSION: 8.0 + macos_instance: + image: ghcr.io/cirruslabs/macos-ventura-base:latest + pre_req_script: + - brew install php@$VERSION composer + version_check_script: + - php --version + << : *BUILD_TEST_TASK_TEMPLATE From 29b33ec24e2baefc170223078df60c5da62c8a3b Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 3 May 2023 16:50:18 +0700 Subject: [PATCH 28/78] Revert removing semver --- composer.json | 1 + src/PhpPact/Config/PactConfig.php | 10 ++++++ src/PhpPact/Config/PactConfigInterface.php | 2 +- .../Consumer/Driver/AbstractDriver.php | 34 ++++++++++--------- tests/PhpPact/Config/PactConfigTest.php | 7 ++++ 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/composer.json b/composer.json index 36924890..7eddb66b 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ "php": "^8.0", "ext-openssl": "*", "ext-json": "*", + "composer/semver": "^1.4.0|^3.2.0", "amphp/amp": "^2.5.1", "amphp/byte-stream": "^1.8", "amphp/dns": "^1.2.3", diff --git a/src/PhpPact/Config/PactConfig.php b/src/PhpPact/Config/PactConfig.php index a623de86..833f91fb 100644 --- a/src/PhpPact/Config/PactConfig.php +++ b/src/PhpPact/Config/PactConfig.php @@ -2,6 +2,8 @@ namespace PhpPact\Config; +use Composer\Semver\VersionParser; + class PactConfig implements PactConfigInterface { use LogLevelTrait; @@ -115,9 +117,17 @@ public function getPactSpecificationVersion(): string /** * {@inheritdoc} + * + * @throws \UnexpectedValueException */ public function setPactSpecificationVersion(string $pactSpecificationVersion): self { + /* + * Parse the version but do not assign it. If it is an invalid version, an exception is thrown + */ + $parser = new VersionParser(); + $parser->normalize($pactSpecificationVersion); + $this->pactSpecificationVersion = $pactSpecificationVersion; return $this; diff --git a/src/PhpPact/Config/PactConfigInterface.php b/src/PhpPact/Config/PactConfigInterface.php index c4307581..fe6b4463 100644 --- a/src/PhpPact/Config/PactConfigInterface.php +++ b/src/PhpPact/Config/PactConfigInterface.php @@ -55,7 +55,7 @@ public function setPactDir(?string $pactDir): self; public function getPactSpecificationVersion(): string; /** - * @param string $pactSpecificationVersion pact version + * @param string $pactSpecificationVersion pact semver version * * @return $this */ diff --git a/src/PhpPact/Consumer/Driver/AbstractDriver.php b/src/PhpPact/Consumer/Driver/AbstractDriver.php index 3a226390..dae01def 100644 --- a/src/PhpPact/Consumer/Driver/AbstractDriver.php +++ b/src/PhpPact/Consumer/Driver/AbstractDriver.php @@ -2,6 +2,7 @@ namespace PhpPact\Consumer\Driver; +use Composer\Semver\Comparator; use FFI; use PhpPact\Standalone\Installer\Model\Scripts; use PhpPact\Config\PactConfigInterface; @@ -47,22 +48,18 @@ private function withSpecification(): self protected function getSpecification(): int { - $supportedVersions = [ - '1.0.0' => $this->ffi->PactSpecification_V1, - '1.1.0' => $this->ffi->PactSpecification_V1_1, - '2.0.0' => $this->ffi->PactSpecification_V2, - '3.0.0' => $this->ffi->PactSpecification_V3, - '4.0.0' => $this->ffi->PactSpecification_V4, - ]; - $version = $this->config->getPactSpecificationVersion(); - if (isset($supportedVersions[$version])) { - $specification = $supportedVersions[$version]; - } else { - trigger_error(sprintf("Specification version '%s' is unknown", $version), E_USER_WARNING); - $specification = $this->ffi->PactSpecification_Unknown; - } - - return $specification; + return match (true) { + $this->versionEqualTo('1.0.0') => $this->ffi->PactSpecification_V1, + $this->versionEqualTo('1.1.0') => $this->ffi->PactSpecification_V1_1, + $this->versionEqualTo('2.0.0') => $this->ffi->PactSpecification_V2, + $this->versionEqualTo('3.0.0') => $this->ffi->PactSpecification_V3, + $this->versionEqualTo('4.0.0') => $this->ffi->PactSpecification_V4, + default => function () { + trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); + + return $this->ffi->PactSpecification_Unknown; + }, + }; } protected function cleanUp(): void @@ -120,4 +117,9 @@ protected function setDescription(string $description): void { $this->ffi->pactffi_upon_receiving($this->interactionId, $description); } + + private function versionEqualTo(string $version): bool + { + return Comparator::equalTo($this->config->getPactSpecificationVersion(), $version); + } } diff --git a/tests/PhpPact/Config/PactConfigTest.php b/tests/PhpPact/Config/PactConfigTest.php index c542336e..105586d3 100644 --- a/tests/PhpPact/Config/PactConfigTest.php +++ b/tests/PhpPact/Config/PactConfigTest.php @@ -43,6 +43,13 @@ public function testSetters(): void static::assertSame($pactFileWriteMode, $this->config->getPactFileWriteMode()); } + public function testInvalidPactSpecificationVersion(): void + { + $this->expectException(\UnexpectedValueException::class); + $this->expectExceptionMessage('Invalid version string "invalid"'); + $this->config->setPactSpecificationVersion('invalid'); + } + public function testInvalidLogLevel(): void { $this->expectException(\InvalidArgumentException::class); From a0302bcec4838761bfc6870e42467d686e3b3621 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 4 May 2023 14:08:43 +0700 Subject: [PATCH 29/78] Extract services and helpers --- composer.json | 2 +- phpstan.neon | 11 +- .../Consumer/Driver/AbstractDriver.php | 125 ----------------- .../Consumer/Driver/DriverInterface.php | 7 - .../Consumer/Driver/HasMockServerTrait.php | 64 --------- .../Consumer/Driver/InteractionDriver.php | 108 --------------- .../Exception/HeaderNotReadException.php | 9 ++ .../InteractionBodyNotAddedException.php | 3 - .../MockServerNotStartedException.php | 3 - .../Exception/PactFileNotWroteException.php | 3 - src/PhpPact/Consumer/InteractionBuilder.php | 27 ++-- .../Consumer/Model/ConsumerRequest.php | 7 + .../Consumer/Model/ProviderResponse.php | 5 + .../Consumer/Service/Helper/BodyTrait.php | 22 +++ .../Service/Helper/DescriptionTrait.php | 16 +++ .../Consumer/Service/Helper/FFITrait.php | 21 +++ .../Service/Helper/InteractionTrait.php | 13 ++ .../Service/Helper/ProviderStatesTrait.php | 24 ++++ .../Service/Helper/SpecificationTrait.php | 24 ++++ .../Consumer/Service/InteractionRegistry.php | 127 ++++++++++++++++++ .../InteractionRegistryInterface.php} | 4 +- src/PhpPact/Consumer/Service/MockServer.php | 77 +++++++++++ .../Consumer/Service/MockServerInterface.php | 16 +++ src/PhpPact/Consumer/Service/PactRegistry.php | 80 +++++++++++ .../Service/PactRegistryInterface.php | 14 ++ 25 files changed, 484 insertions(+), 328 deletions(-) delete mode 100644 src/PhpPact/Consumer/Driver/AbstractDriver.php delete mode 100644 src/PhpPact/Consumer/Driver/DriverInterface.php delete mode 100644 src/PhpPact/Consumer/Driver/HasMockServerTrait.php delete mode 100644 src/PhpPact/Consumer/Driver/InteractionDriver.php create mode 100644 src/PhpPact/Consumer/Exception/HeaderNotReadException.php create mode 100644 src/PhpPact/Consumer/Service/Helper/BodyTrait.php create mode 100644 src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php create mode 100644 src/PhpPact/Consumer/Service/Helper/FFITrait.php create mode 100644 src/PhpPact/Consumer/Service/Helper/InteractionTrait.php create mode 100644 src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php create mode 100644 src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php create mode 100644 src/PhpPact/Consumer/Service/InteractionRegistry.php rename src/PhpPact/Consumer/{Driver/InteractionDriverInterface.php => Service/InteractionRegistryInterface.php} (64%) create mode 100644 src/PhpPact/Consumer/Service/MockServer.php create mode 100644 src/PhpPact/Consumer/Service/MockServerInterface.php create mode 100644 src/PhpPact/Consumer/Service/PactRegistry.php create mode 100644 src/PhpPact/Consumer/Service/PactRegistryInterface.php diff --git a/composer.json b/composer.json index 7eddb66b..f2c08650 100644 --- a/composer.json +++ b/composer.json @@ -71,7 +71,7 @@ }, "scripts": { "start-provider": "php -S localhost:58000 -t example/src/Provider/public/", - "static-code-analysis": "phpstan analyse src/ --level=7 -c phpstan.neon", + "static-code-analysis": "phpstan analyse src/", "lint": "php-cs-fixer fix --config .php-cs-fixer.php --dry-run", "fix": "php-cs-fixer fix --config .php-cs-fixer.php", "test": "phpunit --debug -c example/phpunit.all.xml" diff --git a/phpstan.neon b/phpstan.neon index 408f763a..3c391252 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,4 +1,9 @@ parameters: - excludePaths: - - src/PhpPact/Consumer/Driver/AbstractDriver.php - - src/PhpPact/Consumer/Driver/InteractionDriver.php + level: 7 + ignoreErrors: + - + messages: + - '#Call to an undefined method FFI::[a-zA-Z0-9\\_]+\(\)#' + - '#Access to an undefined property FFI::\$[a-zA-Z0-9\\_]+#' + paths: + - src/PhpPact/Consumer/Service/* diff --git a/src/PhpPact/Consumer/Driver/AbstractDriver.php b/src/PhpPact/Consumer/Driver/AbstractDriver.php deleted file mode 100644 index dae01def..00000000 --- a/src/PhpPact/Consumer/Driver/AbstractDriver.php +++ /dev/null @@ -1,125 +0,0 @@ -ffi = FFI::cdef(\file_get_contents(Scripts::getHeader()), Scripts::getLibrary()); - $this - ->initWithLogLevel() - ->newPact() - ->withSpecification(); - } - - private function newPact(): self - { - $this->pactId = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); - - return $this; - } - - protected function newInteraction(string $description): self - { - $this->interactionId = $this->ffi->pactffi_new_interaction($this->pactId, $description); - - return $this; - } - - private function withSpecification(): self - { - $this->ffi->pactffi_with_specification($this->pactId, $this->getSpecification()); - - return $this; - } - - protected function getSpecification(): int - { - return match (true) { - $this->versionEqualTo('1.0.0') => $this->ffi->PactSpecification_V1, - $this->versionEqualTo('1.1.0') => $this->ffi->PactSpecification_V1_1, - $this->versionEqualTo('2.0.0') => $this->ffi->PactSpecification_V2, - $this->versionEqualTo('3.0.0') => $this->ffi->PactSpecification_V3, - $this->versionEqualTo('4.0.0') => $this->ffi->PactSpecification_V4, - default => function () { - trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); - - return $this->ffi->PactSpecification_Unknown; - }, - }; - } - - protected function cleanUp(): void - { - $this->ffi->pactffi_free_pact_handle($this->pactId); - } - - protected function writePact(): void - { - $error = $this->ffi->pactffi_pact_handle_write_file( - $this->pactId, - $this->config->getPactDir(), - $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE - ); - if ($error) { - throw new PactFileNotWroteException($error); - } - } - - protected function withBody(int $part, ?string $contentType = null, ?string $body = null): void - { - if (is_null($body)) { - return; - } - $success = $this->ffi->pactffi_with_body($this->interactionId, $part, $contentType, $body); - if (!$success) { - throw new InteractionBodyNotAddedException(); - } - } - - private function initWithLogLevel(): self - { - $logLevel = $this->config->getLogLevel(); - if ($logLevel) { - $this->ffi->pactffi_init_with_log_level($logLevel); - } - - return $this; - } - - /** - * @var array $providerStates - */ - protected function setProviderStates(array $providerStates): void - { - foreach ($providerStates as $providerState) { - $this->ffi->pactffi_given($this->interactionId, $providerState->getName()); - foreach ($providerState->getParams() as $key => $value) { - $this->ffi->pactffi_given_with_param($this->interactionId, $providerState->getName(), $key, $value); - } - } - } - - protected function setDescription(string $description): void - { - $this->ffi->pactffi_upon_receiving($this->interactionId, $description); - } - - private function versionEqualTo(string $version): bool - { - return Comparator::equalTo($this->config->getPactSpecificationVersion(), $version); - } -} diff --git a/src/PhpPact/Consumer/Driver/DriverInterface.php b/src/PhpPact/Consumer/Driver/DriverInterface.php deleted file mode 100644 index 70e13108..00000000 --- a/src/PhpPact/Consumer/Driver/DriverInterface.php +++ /dev/null @@ -1,7 +0,0 @@ -ffi->pactffi_create_mock_server_for_transport( - $this->pactId, - $this->getMockServerConfig()->getHost(), - $this->getMockServerConfig()->getPort(), - $this->getMockServerTransport(), - null - ); - - if ($port < 0) { - throw new MockServerNotStartedException($port); - } - $this->getMockServerConfig()->setPort($port); - } - - protected function mockServerMatched(): bool - { - $matched = $this->ffi->pactffi_mock_server_matched($this->getMockServerConfig()->getPort()); - - try { - if ($matched) { - $this->writePact(); - } - } finally { - $this->cleanUp(); - } - - return $matched; - } - - protected function cleanUpMockServer(): void - { - $this->ffi->pactffi_cleanup_mock_server($this->getMockServerConfig()->getPort()); - } - - abstract protected function getMockServerTransport(): string; - - abstract protected function getMockServerConfig(): MockServerConfigInterface; - - protected function mockServerWritePact(): void - { - $error = $this->ffi->pactffi_write_pact_file( - $this->getMockServerConfig()->getPort(), - $this->config->getPactDir(), - $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE - ); - if ($error) { - throw new MockServerNotWrotePactFileException($error); - } - } -} diff --git a/src/PhpPact/Consumer/Driver/InteractionDriver.php b/src/PhpPact/Consumer/Driver/InteractionDriver.php deleted file mode 100644 index ad80aba7..00000000 --- a/src/PhpPact/Consumer/Driver/InteractionDriver.php +++ /dev/null @@ -1,108 +0,0 @@ -config->isSecure() ? 'https' : 'http'; - } - - protected function getMockServerConfig(): MockServerConfigInterface - { - return $this->config; - } - - public function verifyInteractions(): bool - { - return $this->mockServerMatched(); - } - - protected function cleanUp(): void - { - $this->cleanUpMockServer(); - parent::cleanUp(); - } - - protected function writePact(): void - { - $this->mockServerWritePact(); - } - - public function registerInteraction(Interaction $interaction): bool - { - $this - ->newInteraction($interaction->getDescription()) - ->given($interaction) - ->uponReceiving($interaction) - ->with($interaction) - ->willRespondWith($interaction) - ->createMockServer(); - - return true; - } - - private function given(Interaction $interaction): self - { - $this->setProviderStates($interaction->getProviderStates()); - - return $this; - } - - private function uponReceiving(Interaction $interaction): self - { - $this->setDescription($interaction->getDescription()); - - return $this; - } - - private function with(Interaction $interaction): self - { - $request = $interaction->getRequest(); - $this->ffi->pactffi_with_request($this->interactionId, $request->getMethod(), $request->getPath()); - $this->withHeaders($this->ffi->InteractionPart_Request, $request->getHeaders()); - $this->withQuery($request->getQuery()); - $this->withBody($this->ffi->InteractionPart_Request, null, $request->getBody()); - - return $this; - } - - private function willRespondWith(Interaction $interaction): self - { - $response = $interaction->getResponse(); - $this->ffi->pactffi_response_status($this->interactionId, $response->getStatus()); - $this->withHeaders($this->ffi->InteractionPart_Response, $response->getHeaders()); - $this->withBody($this->ffi->InteractionPart_Response, null, $response->getBody()); - - return $this; - } - - private function withHeaders(int $part, array $headers): void - { - foreach ($headers as $header => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_header_v2($this->interactionId, $part, (string) $header, (int) $index, (string) $value); - } - } - } - - private function withQuery(array $query): void - { - foreach ($query as $key => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_query_parameter_v2($this->interactionId, (string) $key, (int) $index, (string) $value); - } - } - } -} diff --git a/src/PhpPact/Consumer/Exception/HeaderNotReadException.php b/src/PhpPact/Consumer/Exception/HeaderNotReadException.php new file mode 100644 index 00000000..95655deb --- /dev/null +++ b/src/PhpPact/Consumer/Exception/HeaderNotReadException.php @@ -0,0 +1,9 @@ +interaction = new Interaction(); - $this->driver = new InteractionDriver($config); + $this->interaction = new Interaction(); + $this->registry = $this->createRegistry($config); } /** @@ -59,13 +61,12 @@ public function with(ConsumerRequest $request): self * @param ProviderResponse $response mock of response received * * @return bool returns true on success - * @throws \JsonException */ public function willRespondWith(ProviderResponse $response): bool { $this->interaction->setResponse($response); - return $this->driver->registerInteraction($this->interaction); + return $this->registry->registerInteraction($this->interaction); } /** @@ -73,6 +74,14 @@ public function willRespondWith(ProviderResponse $response): bool */ public function verify(): bool { - return $this->driver->verifyInteractions(); + return $this->registry->verifyInteractions(); + } + + protected function createRegistry(MockServerConfigInterface $config): InteractionRegistryInterface + { + $pactRegistry = new PactRegistry($config); + $mockServer = new MockServer($pactRegistry, $config); + + return new InteractionRegistry($mockServer); } } diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index e1a8ccf3..3e98bae1 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -2,6 +2,8 @@ namespace PhpPact\Consumer\Model; +use JsonException; + /** * Request initiated by the consumer. */ @@ -42,6 +44,8 @@ public function getPath(): string /** * @param string|array $path + * + * @throws JsonException */ public function setPath(string|array $path): self { @@ -96,6 +100,9 @@ public function getBody(): ?string return $this->body; } + /** + * @throws JsonException + */ public function setBody(mixed $body): self { if (\is_string($body)) { diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index df370f8b..b4459384 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -2,6 +2,8 @@ namespace PhpPact\Consumer\Model; +use JsonException; + /** * Response expectation that would be in response to a Consumer request from the Provider. */ @@ -74,6 +76,9 @@ public function getBody(): ?string return $this->body; } + /** + * @throws JsonException + */ public function setBody(mixed $body): self { if (\is_string($body) || \is_null($body)) { diff --git a/src/PhpPact/Consumer/Service/Helper/BodyTrait.php b/src/PhpPact/Consumer/Service/Helper/BodyTrait.php new file mode 100644 index 00000000..815dafe9 --- /dev/null +++ b/src/PhpPact/Consumer/Service/Helper/BodyTrait.php @@ -0,0 +1,22 @@ +ffi->pactffi_with_body($this->getId(), $part, $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } + } +} diff --git a/src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php b/src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php new file mode 100644 index 00000000..d83903fc --- /dev/null +++ b/src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php @@ -0,0 +1,16 @@ +ffi->pactffi_upon_receiving($this->getId(), $description); + } +} diff --git a/src/PhpPact/Consumer/Service/Helper/FFITrait.php b/src/PhpPact/Consumer/Service/Helper/FFITrait.php new file mode 100644 index 00000000..710299cf --- /dev/null +++ b/src/PhpPact/Consumer/Service/Helper/FFITrait.php @@ -0,0 +1,21 @@ +ffi = FFI::cdef($code, Scripts::getLibrary()); + } +} diff --git a/src/PhpPact/Consumer/Service/Helper/InteractionTrait.php b/src/PhpPact/Consumer/Service/Helper/InteractionTrait.php new file mode 100644 index 00000000..a4b457ee --- /dev/null +++ b/src/PhpPact/Consumer/Service/Helper/InteractionTrait.php @@ -0,0 +1,13 @@ +interactionId; + } +} diff --git a/src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php b/src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php new file mode 100644 index 00000000..776da088 --- /dev/null +++ b/src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php @@ -0,0 +1,24 @@ +ffi->pactffi_given($this->getId(), $providerState->getName()); + foreach ($providerState->getParams() as $key => $value) { + $this->ffi->pactffi_given_with_param($this->getId(), $providerState->getName(), $key, $value); + } + } + } +} diff --git a/src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php b/src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php new file mode 100644 index 00000000..42358d88 --- /dev/null +++ b/src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php @@ -0,0 +1,24 @@ +versionEqualTo('1.0.0') => $this->ffi->PactSpecification_V1, + $this->versionEqualTo('1.1.0') => $this->ffi->PactSpecification_V1_1, + $this->versionEqualTo('2.0.0') => $this->ffi->PactSpecification_V2, + $this->versionEqualTo('3.0.0') => $this->ffi->PactSpecification_V3, + $this->versionEqualTo('4.0.0') => $this->ffi->PactSpecification_V4, + default => function () { + trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); + + return $this->ffi->PactSpecification_Unknown; + }, + }; + } +} diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php new file mode 100644 index 00000000..f39390a0 --- /dev/null +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -0,0 +1,127 @@ +createFFI(); + } + + public function verifyInteractions(): bool + { + $matched = $this->mockServer->isMatched(); + + try { + if ($matched) { + $this->writePact(); + } + } finally { + $this->cleanUp(); + } + + return $matched; + } + + public function registerInteraction(Interaction $interaction): bool + { + $pactId = $this->mockServer->init(); + + $this + ->newInteraction($pactId, $interaction->getDescription()) + ->given($interaction) + ->uponReceiving($interaction) + ->with($interaction) + ->willRespondWith($interaction); + + $this->mockServer->start(); + + return true; + } + + private function cleanUp(): void + { + $this->mockServer->cleanUp(); + } + + private function writePact(): void + { + $this->mockServer->writePact(); + } + + private function newInteraction(int $pactId, string $description): self + { + $this->interactionId = $this->ffi->pactffi_new_interaction($pactId, $description); + + return $this; + } + + private function given(Interaction $interaction): self + { + $this->setProviderStates($interaction->getProviderStates()); + + return $this; + } + + private function uponReceiving(Interaction $interaction): self + { + $this->setDescription($interaction->getDescription()); + + return $this; + } + + private function with(Interaction $interaction): self + { + $request = $interaction->getRequest(); + $this->ffi->pactffi_with_request($this->getId(), $request->getMethod(), $request->getPath()); + $this->setHeaders($this->ffi->InteractionPart_Request, $request->getHeaders()); + $this->setQuery($request->getQuery()); + $this->setBody($this->ffi->InteractionPart_Request, null, $request->getBody()); + + return $this; + } + + private function willRespondWith(Interaction $interaction): self + { + $response = $interaction->getResponse(); + $this->ffi->pactffi_response_status($this->getId(), $response->getStatus()); + $this->setHeaders($this->ffi->InteractionPart_Response, $response->getHeaders()); + $this->setBody($this->ffi->InteractionPart_Response, null, $response->getBody()); + + return $this; + } + + /** + * @param array $headers + */ + private function setHeaders(int $part, array $headers): void + { + foreach ($headers as $header => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_header_v2($this->getId(), $part, (string) $header, (int) $index, (string) $value); + } + } + } + + /** + * @param array $query + */ + private function setQuery(array $query): void + { + foreach ($query as $key => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->pactffi_with_query_parameter_v2($this->getId(), (string) $key, (int) $index, (string) $value); + } + } + } +} diff --git a/src/PhpPact/Consumer/Driver/InteractionDriverInterface.php b/src/PhpPact/Consumer/Service/InteractionRegistryInterface.php similarity index 64% rename from src/PhpPact/Consumer/Driver/InteractionDriverInterface.php rename to src/PhpPact/Consumer/Service/InteractionRegistryInterface.php index 6255bb05..54159b90 100644 --- a/src/PhpPact/Consumer/Driver/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistryInterface.php @@ -1,10 +1,10 @@ createFFI(); + } + + public function init(): int + { + $this->pactRegistry->registerPact(); + + return $this->pactRegistry->getId(); + } + + public function start(): void + { + $port = $this->ffi->pactffi_create_mock_server_for_transport( + $this->pactRegistry->getId(), + $this->config->getHost(), + $this->config->getPort(), + $this->getTransport(), + $this->getTransportConfig() + ); + + if ($port < 0) { + throw new MockServerNotStartedException($port); + } + $this->config->setPort($port); + } + + public function isMatched(): bool + { + return $this->ffi->pactffi_mock_server_matched($this->config->getPort()); + } + + public function writePact(): void + { + $error = $this->ffi->pactffi_write_pact_file( + $this->config->getPort(), + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new MockServerNotWrotePactFileException($error); + } + } + + public function cleanUp(): void + { + $this->ffi->pactffi_cleanup_mock_server($this->config->getPort()); + $this->pactRegistry->cleanUp(); + } + + protected function getTransport(): string + { + return $this->config->isSecure() ? 'https' : 'http'; + } + + protected function getTransportConfig(): ?string + { + return null; + } +} diff --git a/src/PhpPact/Consumer/Service/MockServerInterface.php b/src/PhpPact/Consumer/Service/MockServerInterface.php new file mode 100644 index 00000000..fd115d87 --- /dev/null +++ b/src/PhpPact/Consumer/Service/MockServerInterface.php @@ -0,0 +1,16 @@ +createFFI(); + $this->initWithLogLevel(); + } + + public function registerPact(): void + { + $this + ->newPact() + ->withSpecification(); + } + + public function getId(): int + { + return $this->pactId; + } + + public function cleanUp(): void + { + $this->ffi->pactffi_free_pact_handle($this->getId()); + unset($this->pactId); + } + + public function writePact(): void + { + $error = $this->ffi->pactffi_pact_handle_write_file( + $this->getId(), + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new PactFileNotWroteException($error); + } + } + + private function initWithLogLevel(): self + { + $logLevel = $this->config->getLogLevel(); + if ($logLevel) { + $this->ffi->pactffi_init_with_log_level($logLevel); + } + + return $this; + } + + private function newPact(): self + { + $this->pactId = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); + + return $this; + } + + private function withSpecification(): self + { + $this->ffi->pactffi_with_specification($this->getId(), $this->getSpecification()); + + return $this; + } + + private function versionEqualTo(string $version): bool + { + return Comparator::equalTo($this->config->getPactSpecificationVersion(), $version); + } +} diff --git a/src/PhpPact/Consumer/Service/PactRegistryInterface.php b/src/PhpPact/Consumer/Service/PactRegistryInterface.php new file mode 100644 index 00000000..26c6c9c0 --- /dev/null +++ b/src/PhpPact/Consumer/Service/PactRegistryInterface.php @@ -0,0 +1,14 @@ + Date: Thu, 4 May 2023 14:42:24 +0700 Subject: [PATCH 30/78] Make PR smaller --- README.md | 2 +- src/PhpPact/Config/PactConfigInterface.php | 27 --------------------- src/PhpPact/Consumer/InteractionBuilder.php | 2 +- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index a0e11fd8..115e75a9 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ Table of contents - [Usage for the optional `pact-stub-service`](#usage-for-the-optional-pact-stub-service) ## Versions -9.X adds support for pact specification 3.X & 4.X via Pact FFI. This results in dropping PHP 7.4 +9.X updates internal dependencies and libraries + adds support for pact specification 3.X & 4.X via Pact FFI. This results in dropping PHP 7.4 8.X updates internal dependencies and libraries. This results in dropping PHP 7.3 diff --git a/src/PhpPact/Config/PactConfigInterface.php b/src/PhpPact/Config/PactConfigInterface.php index fe6b4463..d952a725 100644 --- a/src/PhpPact/Config/PactConfigInterface.php +++ b/src/PhpPact/Config/PactConfigInterface.php @@ -4,7 +4,6 @@ /** * Mock Server configuration interface to allow for simple overrides that are reusable. - * Interface PactConfigInterface. */ interface PactConfigInterface { @@ -13,27 +12,17 @@ interface PactConfigInterface public const MODE_OVERWRITE = 'overwrite'; public const MODE_MERGE = 'merge'; - /** - * @return string - */ public function getConsumer(): string; /** * @param string $consumer consumers name - * - * @return $this */ public function setConsumer(string $consumer): self; - /** - * @return string - */ public function getProvider(): string; /** * @param string $provider providers name - * - * @return $this */ public function setProvider(string $provider): self; @@ -44,8 +33,6 @@ public function getPactDir(): string; /** * @param null|string $pactDir url to place the pact files when written to disk - * - * @return $this */ public function setPactDir(?string $pactDir): self; @@ -56,8 +43,6 @@ public function getPactSpecificationVersion(): string; /** * @param string $pactSpecificationVersion pact semver version - * - * @return $this */ public function setPactSpecificationVersion(string $pactSpecificationVersion): self; @@ -68,21 +53,11 @@ public function getLog(): ?string; /** * @param string $log directory for log output - * - * @return $this */ public function setLog(string $log): self; - /** - * @return null|string - */ public function getLogLevel(): ?string; - /** - * @param string $logLevel - * - * @return $this - */ public function setLogLevel(string $logLevel): self; /** @@ -92,8 +67,6 @@ public function getPactFileWriteMode(): string; /** * @param string $pactFileWriteMode 'merge' or 'overwrite' merge means that interactions are added and overwrite means that the entire file is overwritten - * - * @return $this */ public function setPactFileWriteMode(string $pactFileWriteMode): self; } diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index dc8b350b..5a08a917 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -21,8 +21,8 @@ class InteractionBuilder implements BuilderInterface public function __construct(MockServerConfigInterface $config) { - $this->interaction = new Interaction(); $this->registry = $this->createRegistry($config); + $this->interaction = new Interaction(); } /** From ff2120f70a1f7a7f2245e419a89b7fbcc03c0426 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 00:02:12 +0700 Subject: [PATCH 31/78] Extract drivers and factories --- .php-cs-fixer.php | 3 +- composer.json | 6 +- phpstan.neon | 9 +- .../Driver/Interaction/AbstractDriver.php | 47 ++++++++++ .../Driver/Interaction/DriverInterface.php | 22 +++++ .../Driver/Interaction/InteractionDriver.php | 45 +++++++++ .../InteractionDriverInterface.php | 20 ++++ .../Consumer/Driver/Pact/PactDriver.php | 92 +++++++++++++++++++ .../Pact/PactDriverInterface.php} | 10 +- .../Factory/InteractionRegistryFactory.php | 27 ++++++ src/PhpPact/Consumer/InteractionBuilder.php | 16 +--- src/PhpPact/Consumer/Service/FFI.php | 34 +++++++ src/PhpPact/Consumer/Service/FFIInterface.php | 13 +++ .../Consumer/Service/Helper/BodyTrait.php | 22 ----- .../Service/Helper/DescriptionTrait.php | 16 ---- .../Consumer/Service/Helper/FFITrait.php | 21 ----- .../Service/Helper/InteractionTrait.php | 13 --- .../Service/Helper/ProviderStatesTrait.php | 24 ----- .../Service/Helper/SpecificationTrait.php | 24 ----- .../Consumer/Service/InteractionRegistry.php | 69 +++++--------- src/PhpPact/Consumer/Service/MockServer.php | 29 +++--- .../Consumer/Service/MockServerInterface.php | 2 - src/PhpPact/Consumer/Service/PactRegistry.php | 80 ---------------- 23 files changed, 347 insertions(+), 297 deletions(-) create mode 100644 src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php create mode 100644 src/PhpPact/Consumer/Driver/Pact/PactDriver.php rename src/PhpPact/Consumer/{Service/PactRegistryInterface.php => Driver/Pact/PactDriverInterface.php} (52%) create mode 100644 src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php create mode 100644 src/PhpPact/Consumer/Service/FFI.php create mode 100644 src/PhpPact/Consumer/Service/FFIInterface.php delete mode 100644 src/PhpPact/Consumer/Service/Helper/BodyTrait.php delete mode 100644 src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php delete mode 100644 src/PhpPact/Consumer/Service/Helper/FFITrait.php delete mode 100644 src/PhpPact/Consumer/Service/Helper/InteractionTrait.php delete mode 100644 src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php delete mode 100644 src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php delete mode 100644 src/PhpPact/Consumer/Service/PactRegistry.php diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index cac6eabb..cf318188 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -7,7 +7,8 @@ ->name('*.php'); $config = new PhpCsFixer\Config(); -$config->setRules(['@PSR12' => true, +$config->setRules([ + '@PSR12' => true, 'strict_param' => false, 'array_syntax' => ['syntax' => 'short'], ]) diff --git a/composer.json b/composer.json index f2c08650..c5270b90 100644 --- a/composer.json +++ b/composer.json @@ -71,9 +71,9 @@ }, "scripts": { "start-provider": "php -S localhost:58000 -t example/src/Provider/public/", - "static-code-analysis": "phpstan analyse src/", - "lint": "php-cs-fixer fix --config .php-cs-fixer.php --dry-run", - "fix": "php-cs-fixer fix --config .php-cs-fixer.php", + "static-code-analysis": "phpstan", + "lint": "php-cs-fixer fix --dry-run", + "fix": "php-cs-fixer fix", "test": "phpunit --debug -c example/phpunit.all.xml" }, "extra": { diff --git a/phpstan.neon b/phpstan.neon index 3c391252..1d5823a9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,9 +1,4 @@ parameters: level: 7 - ignoreErrors: - - - messages: - - '#Call to an undefined method FFI::[a-zA-Z0-9\\_]+\(\)#' - - '#Access to an undefined property FFI::\$[a-zA-Z0-9\\_]+#' - paths: - - src/PhpPact/Consumer/Service/* + paths: + - src diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php new file mode 100644 index 00000000..2cc6249a --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php @@ -0,0 +1,47 @@ +ffi->call('pactffi_with_body', $this->id, $this->ffi->get($part), $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } + } + + public function setDescription(string $description): void + { + $this->ffi->call('pactffi_upon_receiving', $this->id, $description); + } + + /** + * {@inheritdoc} + */ + public function setProviderStates(array $providerStates): void + { + foreach ($providerStates as $providerState) { + $this->ffi->call('pactffi_given', $this->id, $providerState->getName()); + foreach ($providerState->getParams() as $key => $value) { + $this->ffi->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); + } + } + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php new file mode 100644 index 00000000..0f806476 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php @@ -0,0 +1,22 @@ +id = $this->ffi->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); + } + + /** + * {@inheritdoc} + */ + public function setHeaders(string $part, array $headers): void + { + foreach ($headers as $header => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->call('pactffi_with_header_v2', $this->id, $this->ffi->get($part), (string) $header, (int) $index, (string) $value); + } + } + } + + /** + * {@inheritdoc} + */ + public function setQuery(array $query): void + { + foreach ($query as $key => $values) { + foreach (array_values($values) as $index => $value) { + $this->ffi->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); + } + } + } + + public function setRequest(string $method, string $path): void + { + $this->ffi->call('pactffi_with_request', $this->id, $method, $path); + } + + public function setResponse(int $status): void + { + $this->ffi->call('pactffi_response_status', $this->id, $status); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php new file mode 100644 index 00000000..3ae38be8 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -0,0 +1,20 @@ + $headers + */ + public function setHeaders(string $part, array $headers): void; + + /** + * @param array $query + */ + public function setQuery(array $query): void; + + public function setRequest(string $method, string $path): void; + + public function setResponse(int $status): void; +} diff --git a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php new file mode 100644 index 00000000..b1dd6549 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php @@ -0,0 +1,92 @@ +initWithLogLevel() + ->newPact() + ->withSpecification(); + } + + public function getId(): int + { + return $this->id; + } + + public function cleanUp(): void + { + $this->ffi->call('pactffi_free_pact_handle', $this->id); + unset($this->id); + } + + public function writePact(): void + { + $error = $this->ffi->call( + 'pactffi_pact_handle_write_file', + $this->id, + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new PactFileNotWroteException($error); + } + } + + protected function getSpecification(): int + { + return match (true) { + $this->versionEqualTo('1.0.0') => $this->ffi->get('PactSpecification_V1'), + $this->versionEqualTo('1.1.0') => $this->ffi->get('PactSpecification_V1_1'), + $this->versionEqualTo('2.0.0') => $this->ffi->get('PactSpecification_V2'), + $this->versionEqualTo('3.0.0') => $this->ffi->get('PactSpecification_V3'), + $this->versionEqualTo('4.0.0') => $this->ffi->get('PactSpecification_V4'), + default => function () { + trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); + + return $this->ffi->get('PactSpecification_Unknown'); + }, + }; + } + + private function versionEqualTo(string $version): bool + { + return Comparator::equalTo($this->config->getPactSpecificationVersion(), $version); + } + + private function initWithLogLevel(): self + { + $logLevel = $this->config->getLogLevel(); + if ($logLevel) { + $this->ffi->call('pactffi_init_with_log_level', $logLevel); + } + + return $this; + } + + private function newPact(): self + { + $this->id = $this->ffi->call('pactffi_new_pact', $this->config->getConsumer(), $this->config->getProvider()); + + return $this; + } + + private function withSpecification(): self + { + $this->ffi->call('pactffi_with_specification', $this->id, $this->getSpecification()); + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Service/PactRegistryInterface.php b/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php similarity index 52% rename from src/PhpPact/Consumer/Service/PactRegistryInterface.php rename to src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php index 26c6c9c0..6b06d013 100644 --- a/src/PhpPact/Consumer/Service/PactRegistryInterface.php +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php @@ -1,14 +1,12 @@ registry = $this->createRegistry($config); + $this->registry = $registry instanceof InteractionRegistryInterface ? $registry : InteractionRegistryFactory::create($registry, $registry); $this->interaction = new Interaction(); } @@ -76,12 +74,4 @@ public function verify(): bool { return $this->registry->verifyInteractions(); } - - protected function createRegistry(MockServerConfigInterface $config): InteractionRegistryInterface - { - $pactRegistry = new PactRegistry($config); - $mockServer = new MockServer($pactRegistry, $config); - - return new InteractionRegistry($mockServer); - } } diff --git a/src/PhpPact/Consumer/Service/FFI.php b/src/PhpPact/Consumer/Service/FFI.php new file mode 100644 index 00000000..8b70b800 --- /dev/null +++ b/src/PhpPact/Consumer/Service/FFI.php @@ -0,0 +1,34 @@ +ffi = CoreFFI::cdef($code, Scripts::getLibrary()); + } + + /** + * {@inheritdoc} + */ + public function call(string $name, ...$arguments): mixed + { + return $this->ffi->{$name}(...$arguments); + } + + public function get(string $name): mixed + { + return $this->ffi->{$name}; + } +} diff --git a/src/PhpPact/Consumer/Service/FFIInterface.php b/src/PhpPact/Consumer/Service/FFIInterface.php new file mode 100644 index 00000000..c3b5e20a --- /dev/null +++ b/src/PhpPact/Consumer/Service/FFIInterface.php @@ -0,0 +1,13 @@ + $arguments + */ + public function call(string $name, ...$arguments): mixed; + + public function get(string $name): mixed; +} diff --git a/src/PhpPact/Consumer/Service/Helper/BodyTrait.php b/src/PhpPact/Consumer/Service/Helper/BodyTrait.php deleted file mode 100644 index 815dafe9..00000000 --- a/src/PhpPact/Consumer/Service/Helper/BodyTrait.php +++ /dev/null @@ -1,22 +0,0 @@ -ffi->pactffi_with_body($this->getId(), $part, $contentType, $body); - if (!$success) { - throw new InteractionBodyNotAddedException(); - } - } -} diff --git a/src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php b/src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php deleted file mode 100644 index d83903fc..00000000 --- a/src/PhpPact/Consumer/Service/Helper/DescriptionTrait.php +++ /dev/null @@ -1,16 +0,0 @@ -ffi->pactffi_upon_receiving($this->getId(), $description); - } -} diff --git a/src/PhpPact/Consumer/Service/Helper/FFITrait.php b/src/PhpPact/Consumer/Service/Helper/FFITrait.php deleted file mode 100644 index 710299cf..00000000 --- a/src/PhpPact/Consumer/Service/Helper/FFITrait.php +++ /dev/null @@ -1,21 +0,0 @@ -ffi = FFI::cdef($code, Scripts::getLibrary()); - } -} diff --git a/src/PhpPact/Consumer/Service/Helper/InteractionTrait.php b/src/PhpPact/Consumer/Service/Helper/InteractionTrait.php deleted file mode 100644 index a4b457ee..00000000 --- a/src/PhpPact/Consumer/Service/Helper/InteractionTrait.php +++ /dev/null @@ -1,13 +0,0 @@ -interactionId; - } -} diff --git a/src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php b/src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php deleted file mode 100644 index 776da088..00000000 --- a/src/PhpPact/Consumer/Service/Helper/ProviderStatesTrait.php +++ /dev/null @@ -1,24 +0,0 @@ -ffi->pactffi_given($this->getId(), $providerState->getName()); - foreach ($providerState->getParams() as $key => $value) { - $this->ffi->pactffi_given_with_param($this->getId(), $providerState->getName(), $key, $value); - } - } - } -} diff --git a/src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php b/src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php deleted file mode 100644 index 42358d88..00000000 --- a/src/PhpPact/Consumer/Service/Helper/SpecificationTrait.php +++ /dev/null @@ -1,24 +0,0 @@ -versionEqualTo('1.0.0') => $this->ffi->PactSpecification_V1, - $this->versionEqualTo('1.1.0') => $this->ffi->PactSpecification_V1_1, - $this->versionEqualTo('2.0.0') => $this->ffi->PactSpecification_V2, - $this->versionEqualTo('3.0.0') => $this->ffi->PactSpecification_V3, - $this->versionEqualTo('4.0.0') => $this->ffi->PactSpecification_V4, - default => function () { - trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); - - return $this->ffi->PactSpecification_Unknown; - }, - }; - } -} diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index f39390a0..1d892d81 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -2,20 +2,16 @@ namespace PhpPact\Consumer\Service; +use PhpPact\Consumer\Driver\Interaction\DriverInterface; +use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; use PhpPact\Consumer\Model\Interaction; -use PhpPact\Consumer\Service\Helper\BodyTrait; -use PhpPact\Consumer\Service\Helper\DescriptionTrait; -use PhpPact\Consumer\Service\Helper\ProviderStatesTrait; class InteractionRegistry implements InteractionRegistryInterface { - use ProviderStatesTrait; - use DescriptionTrait; - use BodyTrait; - - public function __construct(private MockServerInterface $mockServer) - { - $this->createFFI(); + public function __construct( + private InteractionDriverInterface $driver, + private MockServerInterface $mockServer + ) { } public function verifyInteractions(): bool @@ -35,16 +31,14 @@ public function verifyInteractions(): bool public function registerInteraction(Interaction $interaction): bool { - $pactId = $this->mockServer->init(); $this - ->newInteraction($pactId, $interaction->getDescription()) + ->newInteraction($interaction) ->given($interaction) ->uponReceiving($interaction) ->with($interaction) - ->willRespondWith($interaction); - - $this->mockServer->start(); + ->willRespondWith($interaction) + ->startMockServer(); return true; } @@ -59,23 +53,23 @@ private function writePact(): void $this->mockServer->writePact(); } - private function newInteraction(int $pactId, string $description): self + private function newInteraction(Interaction $interaction): self { - $this->interactionId = $this->ffi->pactffi_new_interaction($pactId, $description); + $this->driver->newInteraction($interaction->getDescription()); return $this; } private function given(Interaction $interaction): self { - $this->setProviderStates($interaction->getProviderStates()); + $this->driver->setProviderStates($interaction->getProviderStates()); return $this; } private function uponReceiving(Interaction $interaction): self { - $this->setDescription($interaction->getDescription()); + $this->driver->setDescription($interaction->getDescription()); return $this; } @@ -83,10 +77,10 @@ private function uponReceiving(Interaction $interaction): self private function with(Interaction $interaction): self { $request = $interaction->getRequest(); - $this->ffi->pactffi_with_request($this->getId(), $request->getMethod(), $request->getPath()); - $this->setHeaders($this->ffi->InteractionPart_Request, $request->getHeaders()); - $this->setQuery($request->getQuery()); - $this->setBody($this->ffi->InteractionPart_Request, null, $request->getBody()); + $this->driver->setRequest($request->getMethod(), $request->getPath()); + $this->driver->setHeaders(DriverInterface::REQUEST, $request->getHeaders()); + $this->driver->setQuery($request->getQuery()); + $this->driver->setBody(DriverInterface::REQUEST, null, $request->getBody()); return $this; } @@ -94,34 +88,15 @@ private function with(Interaction $interaction): self private function willRespondWith(Interaction $interaction): self { $response = $interaction->getResponse(); - $this->ffi->pactffi_response_status($this->getId(), $response->getStatus()); - $this->setHeaders($this->ffi->InteractionPart_Response, $response->getHeaders()); - $this->setBody($this->ffi->InteractionPart_Response, null, $response->getBody()); + $this->driver->setResponse($response->getStatus()); + $this->driver->setHeaders(DriverInterface::RESPONSE, $response->getHeaders()); + $this->driver->setBody(DriverInterface::RESPONSE, null, $response->getBody()); return $this; } - /** - * @param array $headers - */ - private function setHeaders(int $part, array $headers): void - { - foreach ($headers as $header => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_header_v2($this->getId(), $part, (string) $header, (int) $index, (string) $value); - } - } - } - - /** - * @param array $query - */ - private function setQuery(array $query): void + private function startMockServer(): void { - foreach ($query as $key => $values) { - foreach (array_values($values) as $index => $value) { - $this->ffi->pactffi_with_query_parameter_v2($this->getId(), (string) $key, (int) $index, (string) $value); - } - } + $this->mockServer->start(); } } diff --git a/src/PhpPact/Consumer/Service/MockServer.php b/src/PhpPact/Consumer/Service/MockServer.php index d2e7d841..4b4ecc8d 100644 --- a/src/PhpPact/Consumer/Service/MockServer.php +++ b/src/PhpPact/Consumer/Service/MockServer.php @@ -3,33 +3,25 @@ namespace PhpPact\Consumer\Service; use PhpPact\Config\PactConfigInterface; +use PhpPact\Consumer\Driver\Pact\PactDriverInterface; use PhpPact\Consumer\Exception\MockServerNotStartedException; use PhpPact\Consumer\Exception\MockServerNotWrotePactFileException; -use PhpPact\Consumer\Service\Helper\FFITrait; use PhpPact\Standalone\MockService\MockServerConfigInterface; class MockServer implements MockServerInterface { - use FFITrait; - public function __construct( - private PactRegistryInterface $pactRegistry, + private FFIInterface $ffi, + private PactDriverInterface $pactDriver, private MockServerConfigInterface $config ) { - $this->createFFI(); - } - - public function init(): int - { - $this->pactRegistry->registerPact(); - - return $this->pactRegistry->getId(); } public function start(): void { - $port = $this->ffi->pactffi_create_mock_server_for_transport( - $this->pactRegistry->getId(), + $port = $this->ffi->call( + 'pactffi_create_mock_server_for_transport', + $this->pactDriver->getId(), $this->config->getHost(), $this->config->getPort(), $this->getTransport(), @@ -44,12 +36,13 @@ public function start(): void public function isMatched(): bool { - return $this->ffi->pactffi_mock_server_matched($this->config->getPort()); + return $this->ffi->call('pactffi_mock_server_matched', $this->config->getPort()); } public function writePact(): void { - $error = $this->ffi->pactffi_write_pact_file( + $error = $this->ffi->call( + 'pactffi_write_pact_file', $this->config->getPort(), $this->config->getPactDir(), $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE @@ -61,8 +54,8 @@ public function writePact(): void public function cleanUp(): void { - $this->ffi->pactffi_cleanup_mock_server($this->config->getPort()); - $this->pactRegistry->cleanUp(); + $this->ffi->call('pactffi_cleanup_mock_server', $this->config->getPort()); + $this->pactDriver->cleanUp(); } protected function getTransport(): string diff --git a/src/PhpPact/Consumer/Service/MockServerInterface.php b/src/PhpPact/Consumer/Service/MockServerInterface.php index fd115d87..c1670d38 100644 --- a/src/PhpPact/Consumer/Service/MockServerInterface.php +++ b/src/PhpPact/Consumer/Service/MockServerInterface.php @@ -4,8 +4,6 @@ interface MockServerInterface { - public function init(): int; - public function start(): void; public function isMatched(): bool; diff --git a/src/PhpPact/Consumer/Service/PactRegistry.php b/src/PhpPact/Consumer/Service/PactRegistry.php deleted file mode 100644 index 32905048..00000000 --- a/src/PhpPact/Consumer/Service/PactRegistry.php +++ /dev/null @@ -1,80 +0,0 @@ -createFFI(); - $this->initWithLogLevel(); - } - - public function registerPact(): void - { - $this - ->newPact() - ->withSpecification(); - } - - public function getId(): int - { - return $this->pactId; - } - - public function cleanUp(): void - { - $this->ffi->pactffi_free_pact_handle($this->getId()); - unset($this->pactId); - } - - public function writePact(): void - { - $error = $this->ffi->pactffi_pact_handle_write_file( - $this->getId(), - $this->config->getPactDir(), - $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE - ); - if ($error) { - throw new PactFileNotWroteException($error); - } - } - - private function initWithLogLevel(): self - { - $logLevel = $this->config->getLogLevel(); - if ($logLevel) { - $this->ffi->pactffi_init_with_log_level($logLevel); - } - - return $this; - } - - private function newPact(): self - { - $this->pactId = $this->ffi->pactffi_new_pact($this->config->getConsumer(), $this->config->getProvider()); - - return $this; - } - - private function withSpecification(): self - { - $this->ffi->pactffi_with_specification($this->getId(), $this->getSpecification()); - - return $this; - } - - private function versionEqualTo(string $version): bool - { - return Comparator::equalTo($this->config->getPactSpecificationVersion(), $version); - } -} From 420f4c2f079e768217517e141112f46691c0e70a Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 14:21:00 +0700 Subject: [PATCH 32/78] Extract set up method --- src/PhpPact/Consumer/Driver/Pact/PactDriver.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php index b1dd6549..bfeb006f 100644 --- a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php @@ -15,10 +15,7 @@ public function __construct( private FFIInterface $ffi, private PactConfigInterface $config ) { - $this - ->initWithLogLevel() - ->newPact() - ->withSpecification(); + $this->setUp(); } public function getId(): int @@ -45,6 +42,14 @@ public function writePact(): void } } + protected function setUp(): void + { + $this + ->initWithLogLevel() + ->newPact() + ->withSpecification(); + } + protected function getSpecification(): int { return match (true) { From 7f472fe530bbbf687a43db685497158557827e4d Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 14:21:44 +0700 Subject: [PATCH 33/78] Simplify default interaction registry factory --- .../Consumer/Factory/InteractionRegistryFactory.php | 10 ++++------ src/PhpPact/Consumer/InteractionBuilder.php | 2 +- src/PhpPact/Consumer/Service/InteractionRegistry.php | 1 - 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php index 8dd13a9d..cea4c011 100644 --- a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php +++ b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php @@ -13,14 +13,12 @@ class InteractionRegistryFactory { - public static function create( - PactConfigInterface $pactConfig, - MockServerConfigInterface $mockServerConfig - ): InteractionRegistryInterface { + public static function create(MockServerConfigInterface $config): InteractionRegistryInterface + { $ffi = new FFI(); - $pactDriver = new PactDriver($ffi, $pactConfig); + $pactDriver = new PactDriver($ffi, $config); $interactionDriver = new InteractionDriver($ffi, $pactDriver); - $mockServer = new MockServer($ffi, $pactDriver, $mockServerConfig); + $mockServer = new MockServer($ffi, $pactDriver, $config); return new InteractionRegistry($interactionDriver, $mockServer); } diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index 28e49e12..8480cd26 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -19,7 +19,7 @@ class InteractionBuilder implements BuilderInterface public function __construct(MockServerConfigInterface|InteractionRegistryInterface $registry) { - $this->registry = $registry instanceof InteractionRegistryInterface ? $registry : InteractionRegistryFactory::create($registry, $registry); + $this->registry = $registry instanceof InteractionRegistryInterface ? $registry : InteractionRegistryFactory::create($registry); $this->interaction = new Interaction(); } diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index 1d892d81..88f69370 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -31,7 +31,6 @@ public function verifyInteractions(): bool public function registerInteraction(Interaction $interaction): bool { - $this ->newInteraction($interaction) ->given($interaction) From c87dd62aca00a71e62ea85f02b6c5e50e35d53be Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 14:26:53 +0700 Subject: [PATCH 34/78] Move methods from AbstractDriver to InteractionDriver and rename --- .../Driver/Interaction/AbstractDriver.php | 30 -------------- .../Driver/Interaction/DriverInterface.php | 11 ----- .../Driver/Interaction/InteractionDriver.php | 41 ++++++++++++++++--- .../InteractionDriverInterface.php | 21 +++++++--- .../Consumer/Service/InteractionRegistry.php | 18 ++++---- 5 files changed, 61 insertions(+), 60 deletions(-) diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php index 2cc6249a..c6fac62b 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php @@ -3,7 +3,6 @@ namespace PhpPact\Consumer\Driver\Interaction; use PhpPact\Consumer\Driver\Pact\PactDriverInterface; -use PhpPact\Consumer\Exception\InteractionBodyNotAddedException; use PhpPact\Consumer\Service\FFIInterface; abstract class AbstractDriver implements DriverInterface @@ -15,33 +14,4 @@ public function __construct( protected PactDriverInterface $pactDriver ) { } - - public function setBody(string $part, ?string $contentType = null, ?string $body = null): void - { - if (is_null($body)) { - return; - } - $success = $this->ffi->call('pactffi_with_body', $this->id, $this->ffi->get($part), $contentType, $body); - if (!$success) { - throw new InteractionBodyNotAddedException(); - } - } - - public function setDescription(string $description): void - { - $this->ffi->call('pactffi_upon_receiving', $this->id, $description); - } - - /** - * {@inheritdoc} - */ - public function setProviderStates(array $providerStates): void - { - foreach ($providerStates as $providerState) { - $this->ffi->call('pactffi_given', $this->id, $providerState->getName()); - foreach ($providerState->getParams() as $key => $value) { - $this->ffi->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); - } - } - } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php index 0f806476..f31cface 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php @@ -2,21 +2,10 @@ namespace PhpPact\Consumer\Driver\Interaction; -use PhpPact\Consumer\Model\ProviderState; - interface DriverInterface { public const REQUEST = 'InteractionPart_Request'; public const RESPONSE = 'InteractionPart_Response'; public function newInteraction(string $description): void; - - public function setBody(string $part, ?string $contentType = null, ?string $body = null): void; - - public function setDescription(string $description): void; - - /** - * @param ProviderState[] $providerStates - */ - public function setProviderStates(array $providerStates): void; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php index 7e838354..3ac8248b 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -2,6 +2,8 @@ namespace PhpPact\Consumer\Driver\Interaction; +use PhpPact\Consumer\Exception\InteractionBodyNotAddedException; + class InteractionDriver extends AbstractDriver implements InteractionDriverInterface { public function newInteraction(string $description): void @@ -9,10 +11,39 @@ public function newInteraction(string $description): void $this->id = $this->ffi->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); } + public function uponReceiving(string $description): void + { + $this->ffi->call('pactffi_upon_receiving', $this->id, $description); + } + + public function withBody(string $part, ?string $contentType = null, ?string $body = null): void + { + if (is_null($body)) { + return; + } + $success = $this->ffi->call('pactffi_with_body', $this->id, $this->ffi->get($part), $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } + } + + /** + * {@inheritdoc} + */ + public function given(array $providerStates): void + { + foreach ($providerStates as $providerState) { + $this->ffi->call('pactffi_given', $this->id, $providerState->getName()); + foreach ($providerState->getParams() as $key => $value) { + $this->ffi->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); + } + } + } + /** * {@inheritdoc} */ - public function setHeaders(string $part, array $headers): void + public function withHeaders(string $part, array $headers): void { foreach ($headers as $header => $values) { foreach (array_values($values) as $index => $value) { @@ -24,21 +55,21 @@ public function setHeaders(string $part, array $headers): void /** * {@inheritdoc} */ - public function setQuery(array $query): void + public function withQueryParameters(array $queryParams): void { - foreach ($query as $key => $values) { + foreach ($queryParams as $key => $values) { foreach (array_values($values) as $index => $value) { $this->ffi->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); } } } - public function setRequest(string $method, string $path): void + public function withRequest(string $method, string $path): void { $this->ffi->call('pactffi_with_request', $this->id, $method, $path); } - public function setResponse(int $status): void + public function withResponse(int $status): void { $this->ffi->call('pactffi_response_status', $this->id, $status); } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php index 3ae38be8..f81047f6 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -2,19 +2,30 @@ namespace PhpPact\Consumer\Driver\Interaction; +use PhpPact\Consumer\Model\ProviderState; + interface InteractionDriverInterface extends DriverInterface { + public function uponReceiving(string $description): void; + + public function withBody(string $part, ?string $contentType = null, ?string $body = null): void; + + /** + * @param ProviderState[] $providerStates + */ + public function given(array $providerStates): void; + /** * @param array $headers */ - public function setHeaders(string $part, array $headers): void; + public function withHeaders(string $part, array $headers): void; /** - * @param array $query + * @param array $queryParams */ - public function setQuery(array $query): void; + public function withQueryParameters(array $queryParams): void; - public function setRequest(string $method, string $path): void; + public function withRequest(string $method, string $path): void; - public function setResponse(int $status): void; + public function withResponse(int $status): void; } diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index 88f69370..72822cfb 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -61,14 +61,14 @@ private function newInteraction(Interaction $interaction): self private function given(Interaction $interaction): self { - $this->driver->setProviderStates($interaction->getProviderStates()); + $this->driver->given($interaction->getProviderStates()); return $this; } private function uponReceiving(Interaction $interaction): self { - $this->driver->setDescription($interaction->getDescription()); + $this->driver->uponReceiving($interaction->getDescription()); return $this; } @@ -76,10 +76,10 @@ private function uponReceiving(Interaction $interaction): self private function with(Interaction $interaction): self { $request = $interaction->getRequest(); - $this->driver->setRequest($request->getMethod(), $request->getPath()); - $this->driver->setHeaders(DriverInterface::REQUEST, $request->getHeaders()); - $this->driver->setQuery($request->getQuery()); - $this->driver->setBody(DriverInterface::REQUEST, null, $request->getBody()); + $this->driver->withRequest($request->getMethod(), $request->getPath()); + $this->driver->withHeaders(DriverInterface::REQUEST, $request->getHeaders()); + $this->driver->withQueryParameters($request->getQuery()); + $this->driver->withBody(DriverInterface::REQUEST, null, $request->getBody()); return $this; } @@ -87,9 +87,9 @@ private function with(Interaction $interaction): self private function willRespondWith(Interaction $interaction): self { $response = $interaction->getResponse(); - $this->driver->setResponse($response->getStatus()); - $this->driver->setHeaders(DriverInterface::RESPONSE, $response->getHeaders()); - $this->driver->setBody(DriverInterface::RESPONSE, null, $response->getBody()); + $this->driver->withResponse($response->getStatus()); + $this->driver->withHeaders(DriverInterface::RESPONSE, $response->getHeaders()); + $this->driver->withBody(DriverInterface::RESPONSE, null, $response->getBody()); return $this; } From 53cef3d10012d248a6c2c6973a35f5bfa308a0ef Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 14:38:17 +0700 Subject: [PATCH 35/78] Group assertions together for better readability --- .../tests/Consumer/Service/ConsumerServiceGoodbyeTest.php | 8 ++++---- .../tests/Consumer/Service/ConsumerServiceHelloTest.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php index fcae2e02..b3140154 100644 --- a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php @@ -39,10 +39,10 @@ public function testGetGoodbyeString() ->willRespondWith($response); $service = new HttpClientService($config->getBaseUri()); - $result = $service->getGoodbyeString('Bob'); + $goodbyeResult = $service->getGoodbyeString('Bob'); + $verifyResult = $builder->verify(); - $this->assertTrue($builder->verify()); - - $this->assertEquals('Goodbye, Bob', $result); + $this->assertTrue($verifyResult); + $this->assertEquals('Goodbye, Bob', $goodbyeResult); } } diff --git a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php index 73b6b6f2..d10782ff 100644 --- a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php @@ -45,10 +45,10 @@ public function testGetHelloString() ->willRespondWith($response); // This has to be last. This is what makes FFI calls to register the interaction and start the mock server. $service = new HttpClientService($config->getBaseUri()); // Pass in the URL to the Mock Server. - $result = $service->getHelloString('Bob'); // Make the real API request against the Mock Server. + $helloResult = $service->getHelloString('Bob'); // Make the real API request against the Mock Server. + $verifyResult = $builder->verify(); // This will verify that the interactions took place. - $this->assertTrue($builder->verify()); // This will verify that the interactions took place. - - $this->assertEquals('Hello, Bob', $result); // Make your assertions. + $this->assertTrue($verifyResult); // Make your assertions. + $this->assertEquals('Hello, Bob', $helloResult); } } From d5a4b831b8b24b88f414edc8ee2cce2143f1a38b Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 14:52:01 +0700 Subject: [PATCH 36/78] Remove condition to make code shorter --- src/PhpPact/Consumer/Model/ConsumerRequest.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index 3e98bae1..f8e11ae8 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -105,13 +105,11 @@ public function getBody(): ?string */ public function setBody(mixed $body): self { - if (\is_string($body)) { + if (\is_string($body) || \is_null($body)) { $this->body = $body; - } elseif (!\is_null($body)) { + } else { $this->body = \json_encode($body, JSON_THROW_ON_ERROR); $this->addHeader('Content-Type', 'application/json'); - } else { - $this->body = null; } return $this; From 6cf81286ed9e618efbc66fe0f8ee58b31f13583d Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 19:43:34 +0700 Subject: [PATCH 37/78] Update ffi library to 0.4.4 --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index c5270b90..3deedf5e 100644 --- a/composer.json +++ b/composer.json @@ -89,12 +89,12 @@ "path": "bin/pact-ruby-standalone" }, "pact-ffi-headers": { - "version": "0.4.1", + "version": "0.4.4", "url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/pact.h", "path": "bin/pact-ffi-headers/pact.h" }, "pact-ffi-lib": { - "version": "0.4.1", + "version": "0.4.4", "variables": { "{$prefix}": "PHP_OS_FAMILY === 'Windows' ? 'pact_ffi' : 'libpact_ffi'", "{$os}": "PHP_OS === 'Darwin' ? 'osx' : strtolower(PHP_OS_FAMILY)", From 156f989f25a7f0101985231cabec7fb1ff7c9a54 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 5 May 2023 19:44:28 +0700 Subject: [PATCH 38/78] Remove not useful annotation --- src/PhpPact/Standalone/Installer/Model/Scripts.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 824577ea..243bf45a 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -19,9 +19,6 @@ public static function getHeader(): string return self::$destinationDir . '/bin/pact-ffi-headers/pact.h'; } - /** - * @return string - */ public static function getLibrary(): string { $extension = PHP_OS_FAMILY === 'Windows' ? 'dll' : (PHP_OS === 'Darwin' ? 'dylib' : 'so'); From bd8b7510ccbe0b8d89de83b1a4a4ff51bff1b71c Mon Sep 17 00:00:00 2001 From: tienvx Date: Sat, 6 May 2023 21:50:05 +0700 Subject: [PATCH 39/78] Rename ffi service and move it to FFI namespace --- .../Driver/Interaction/AbstractDriver.php | 4 +- .../Driver/Interaction/DriverInterface.php | 3 -- .../Driver/Interaction/InteractionDriver.php | 39 +++++++++---------- .../InteractionDriverInterface.php | 4 +- .../Consumer/Driver/Pact/PactDriver.php | 26 ++++++------- .../Factory/InteractionRegistryFactory.php | 11 +++--- .../Consumer/Service/InteractionRegistry.php | 9 ++--- src/PhpPact/Consumer/Service/MockServer.php | 11 +++--- .../Exception/HeaderNotReadException.php | 2 +- .../Service/FFI.php => FFI/Proxy.php} | 15 +++---- .../ProxyInterface.php} | 4 +- 11 files changed, 60 insertions(+), 68 deletions(-) rename src/PhpPact/{Consumer => FFI}/Exception/HeaderNotReadException.php (66%) rename src/PhpPact/{Consumer/Service/FFI.php => FFI/Proxy.php} (63%) rename src/PhpPact/{Consumer/Service/FFIInterface.php => FFI/ProxyInterface.php} (75%) diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php index c6fac62b..887e9c78 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php @@ -3,14 +3,14 @@ namespace PhpPact\Consumer\Driver\Interaction; use PhpPact\Consumer\Driver\Pact\PactDriverInterface; -use PhpPact\Consumer\Service\FFIInterface; +use PhpPact\FFI\ProxyInterface; abstract class AbstractDriver implements DriverInterface { protected int $id; public function __construct( - protected FFIInterface $ffi, + protected ProxyInterface $proxy, protected PactDriverInterface $pactDriver ) { } diff --git a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php index f31cface..d5a87739 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php @@ -4,8 +4,5 @@ interface DriverInterface { - public const REQUEST = 'InteractionPart_Request'; - public const RESPONSE = 'InteractionPart_Response'; - public function newInteraction(string $description): void; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php index 3ac8248b..6b4692de 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -6,71 +6,70 @@ class InteractionDriver extends AbstractDriver implements InteractionDriverInterface { + private const REQUEST = 'InteractionPart_Request'; + private const RESPONSE = 'InteractionPart_Response'; + public function newInteraction(string $description): void { - $this->id = $this->ffi->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); + $this->id = $this->proxy->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); } public function uponReceiving(string $description): void { - $this->ffi->call('pactffi_upon_receiving', $this->id, $description); + $this->proxy->call('pactffi_upon_receiving', $this->id, $description); } - public function withBody(string $part, ?string $contentType = null, ?string $body = null): void + public function withBody(bool $isRequest, ?string $contentType = null, ?string $body = null): void { if (is_null($body)) { return; } - $success = $this->ffi->call('pactffi_with_body', $this->id, $this->ffi->get($part), $contentType, $body); + $success = $this->proxy->call('pactffi_with_body', $this->id, $this->getPart($isRequest), $contentType, $body); if (!$success) { throw new InteractionBodyNotAddedException(); } } - /** - * {@inheritdoc} - */ public function given(array $providerStates): void { foreach ($providerStates as $providerState) { - $this->ffi->call('pactffi_given', $this->id, $providerState->getName()); + $this->proxy->call('pactffi_given', $this->id, $providerState->getName()); foreach ($providerState->getParams() as $key => $value) { - $this->ffi->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); + $this->proxy->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); } } } - /** - * {@inheritdoc} - */ - public function withHeaders(string $part, array $headers): void + public function withHeaders(bool $isRequest, array $headers): void { foreach ($headers as $header => $values) { foreach (array_values($values) as $index => $value) { - $this->ffi->call('pactffi_with_header_v2', $this->id, $this->ffi->get($part), (string) $header, (int) $index, (string) $value); + $this->proxy->call('pactffi_with_header_v2', $this->id, $this->getPart($isRequest), (string) $header, (int) $index, (string) $value); } } } - /** - * {@inheritdoc} - */ public function withQueryParameters(array $queryParams): void { foreach ($queryParams as $key => $values) { foreach (array_values($values) as $index => $value) { - $this->ffi->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); + $this->proxy->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); } } } public function withRequest(string $method, string $path): void { - $this->ffi->call('pactffi_with_request', $this->id, $method, $path); + $this->proxy->call('pactffi_with_request', $this->id, $method, $path); } public function withResponse(int $status): void { - $this->ffi->call('pactffi_response_status', $this->id, $status); + $this->proxy->call('pactffi_response_status', $this->id, $status); + } + + private function getPart(bool $isRequest): int + { + return $this->proxy->get($isRequest ? self::REQUEST : self::RESPONSE); } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php index f81047f6..d868cc14 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -8,7 +8,7 @@ interface InteractionDriverInterface extends DriverInterface { public function uponReceiving(string $description): void; - public function withBody(string $part, ?string $contentType = null, ?string $body = null): void; + public function withBody(bool $isRequest, ?string $contentType = null, ?string $body = null): void; /** * @param ProviderState[] $providerStates @@ -18,7 +18,7 @@ public function given(array $providerStates): void; /** * @param array $headers */ - public function withHeaders(string $part, array $headers): void; + public function withHeaders(bool $isRequest, array $headers): void; /** * @param array $queryParams diff --git a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php index bfeb006f..02197081 100644 --- a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php @@ -5,14 +5,14 @@ use Composer\Semver\Comparator; use PhpPact\Config\PactConfigInterface; use PhpPact\Consumer\Exception\PactFileNotWroteException; -use PhpPact\Consumer\Service\FFIInterface; +use PhpPact\FFI\ProxyInterface; class PactDriver implements PactDriverInterface { protected int $id; public function __construct( - private FFIInterface $ffi, + private ProxyInterface $proxy, private PactConfigInterface $config ) { $this->setUp(); @@ -25,13 +25,13 @@ public function getId(): int public function cleanUp(): void { - $this->ffi->call('pactffi_free_pact_handle', $this->id); + $this->proxy->call('pactffi_free_pact_handle', $this->id); unset($this->id); } public function writePact(): void { - $error = $this->ffi->call( + $error = $this->proxy->call( 'pactffi_pact_handle_write_file', $this->id, $this->config->getPactDir(), @@ -53,15 +53,15 @@ protected function setUp(): void protected function getSpecification(): int { return match (true) { - $this->versionEqualTo('1.0.0') => $this->ffi->get('PactSpecification_V1'), - $this->versionEqualTo('1.1.0') => $this->ffi->get('PactSpecification_V1_1'), - $this->versionEqualTo('2.0.0') => $this->ffi->get('PactSpecification_V2'), - $this->versionEqualTo('3.0.0') => $this->ffi->get('PactSpecification_V3'), - $this->versionEqualTo('4.0.0') => $this->ffi->get('PactSpecification_V4'), + $this->versionEqualTo('1.0.0') => $this->proxy->get('PactSpecification_V1'), + $this->versionEqualTo('1.1.0') => $this->proxy->get('PactSpecification_V1_1'), + $this->versionEqualTo('2.0.0') => $this->proxy->get('PactSpecification_V2'), + $this->versionEqualTo('3.0.0') => $this->proxy->get('PactSpecification_V3'), + $this->versionEqualTo('4.0.0') => $this->proxy->get('PactSpecification_V4'), default => function () { trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); - return $this->ffi->get('PactSpecification_Unknown'); + return $this->proxy->get('PactSpecification_Unknown'); }, }; } @@ -75,7 +75,7 @@ private function initWithLogLevel(): self { $logLevel = $this->config->getLogLevel(); if ($logLevel) { - $this->ffi->call('pactffi_init_with_log_level', $logLevel); + $this->proxy->call('pactffi_init_with_log_level', $logLevel); } return $this; @@ -83,14 +83,14 @@ private function initWithLogLevel(): self private function newPact(): self { - $this->id = $this->ffi->call('pactffi_new_pact', $this->config->getConsumer(), $this->config->getProvider()); + $this->id = $this->proxy->call('pactffi_new_pact', $this->config->getConsumer(), $this->config->getProvider()); return $this; } private function withSpecification(): self { - $this->ffi->call('pactffi_with_specification', $this->id, $this->getSpecification()); + $this->proxy->call('pactffi_with_specification', $this->id, $this->getSpecification()); return $this; } diff --git a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php index cea4c011..b7a9ccf5 100644 --- a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php +++ b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php @@ -4,10 +4,9 @@ use PhpPact\Consumer\Driver\Interaction\InteractionDriver; use PhpPact\Consumer\Driver\Pact\PactDriver; -use PhpPact\Config\PactConfigInterface; -use PhpPact\Consumer\Service\FFI; use PhpPact\Consumer\Service\InteractionRegistry; use PhpPact\Consumer\Service\InteractionRegistryInterface; +use PhpPact\FFI\Proxy; use PhpPact\Standalone\MockService\MockServerConfigInterface; use PhpPact\Consumer\Service\MockServer; @@ -15,10 +14,10 @@ class InteractionRegistryFactory { public static function create(MockServerConfigInterface $config): InteractionRegistryInterface { - $ffi = new FFI(); - $pactDriver = new PactDriver($ffi, $config); - $interactionDriver = new InteractionDriver($ffi, $pactDriver); - $mockServer = new MockServer($ffi, $pactDriver, $config); + $proxy = new Proxy(); + $pactDriver = new PactDriver($proxy, $config); + $interactionDriver = new InteractionDriver($proxy, $pactDriver); + $mockServer = new MockServer($proxy, $pactDriver, $config); return new InteractionRegistry($interactionDriver, $mockServer); } diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index 72822cfb..2bf1871e 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -2,7 +2,6 @@ namespace PhpPact\Consumer\Service; -use PhpPact\Consumer\Driver\Interaction\DriverInterface; use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; use PhpPact\Consumer\Model\Interaction; @@ -77,9 +76,9 @@ private function with(Interaction $interaction): self { $request = $interaction->getRequest(); $this->driver->withRequest($request->getMethod(), $request->getPath()); - $this->driver->withHeaders(DriverInterface::REQUEST, $request->getHeaders()); + $this->driver->withHeaders(true, $request->getHeaders()); $this->driver->withQueryParameters($request->getQuery()); - $this->driver->withBody(DriverInterface::REQUEST, null, $request->getBody()); + $this->driver->withBody(true, null, $request->getBody()); return $this; } @@ -88,8 +87,8 @@ private function willRespondWith(Interaction $interaction): self { $response = $interaction->getResponse(); $this->driver->withResponse($response->getStatus()); - $this->driver->withHeaders(DriverInterface::RESPONSE, $response->getHeaders()); - $this->driver->withBody(DriverInterface::RESPONSE, null, $response->getBody()); + $this->driver->withHeaders(false, $response->getHeaders()); + $this->driver->withBody(false, null, $response->getBody()); return $this; } diff --git a/src/PhpPact/Consumer/Service/MockServer.php b/src/PhpPact/Consumer/Service/MockServer.php index 4b4ecc8d..edbc5158 100644 --- a/src/PhpPact/Consumer/Service/MockServer.php +++ b/src/PhpPact/Consumer/Service/MockServer.php @@ -6,12 +6,13 @@ use PhpPact\Consumer\Driver\Pact\PactDriverInterface; use PhpPact\Consumer\Exception\MockServerNotStartedException; use PhpPact\Consumer\Exception\MockServerNotWrotePactFileException; +use PhpPact\FFI\ProxyInterface; use PhpPact\Standalone\MockService\MockServerConfigInterface; class MockServer implements MockServerInterface { public function __construct( - private FFIInterface $ffi, + private ProxyInterface $proxy, private PactDriverInterface $pactDriver, private MockServerConfigInterface $config ) { @@ -19,7 +20,7 @@ public function __construct( public function start(): void { - $port = $this->ffi->call( + $port = $this->proxy->call( 'pactffi_create_mock_server_for_transport', $this->pactDriver->getId(), $this->config->getHost(), @@ -36,12 +37,12 @@ public function start(): void public function isMatched(): bool { - return $this->ffi->call('pactffi_mock_server_matched', $this->config->getPort()); + return $this->proxy->call('pactffi_mock_server_matched', $this->config->getPort()); } public function writePact(): void { - $error = $this->ffi->call( + $error = $this->proxy->call( 'pactffi_write_pact_file', $this->config->getPort(), $this->config->getPactDir(), @@ -54,7 +55,7 @@ public function writePact(): void public function cleanUp(): void { - $this->ffi->call('pactffi_cleanup_mock_server', $this->config->getPort()); + $this->proxy->call('pactffi_cleanup_mock_server', $this->config->getPort()); $this->pactDriver->cleanUp(); } diff --git a/src/PhpPact/Consumer/Exception/HeaderNotReadException.php b/src/PhpPact/FFI/Exception/HeaderNotReadException.php similarity index 66% rename from src/PhpPact/Consumer/Exception/HeaderNotReadException.php rename to src/PhpPact/FFI/Exception/HeaderNotReadException.php index 95655deb..d521f224 100644 --- a/src/PhpPact/Consumer/Exception/HeaderNotReadException.php +++ b/src/PhpPact/FFI/Exception/HeaderNotReadException.php @@ -1,6 +1,6 @@ ffi = CoreFFI::cdef($code, Scripts::getLibrary()); + $this->ffi = FFI::cdef($code, Scripts::getLibrary()); } - /** - * {@inheritdoc} - */ public function call(string $name, ...$arguments): mixed { return $this->ffi->{$name}(...$arguments); diff --git a/src/PhpPact/Consumer/Service/FFIInterface.php b/src/PhpPact/FFI/ProxyInterface.php similarity index 75% rename from src/PhpPact/Consumer/Service/FFIInterface.php rename to src/PhpPact/FFI/ProxyInterface.php index c3b5e20a..9f4a1a29 100644 --- a/src/PhpPact/Consumer/Service/FFIInterface.php +++ b/src/PhpPact/FFI/ProxyInterface.php @@ -1,8 +1,8 @@ $arguments From d41e1ac04dcb779b5b3d669d6910d2890e35e8d1 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 8 May 2023 12:01:07 +0700 Subject: [PATCH 40/78] Assign result to variable before asserting --- README.md | 3 ++- tests/PhpPact/Consumer/InteractionBuilderTest.php | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 115e75a9..e208e86d 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,8 @@ Verify that all interactions took place that were registered. This typically should be in each test, that way the test that failed to verify is marked correctly. ```php -$this->assertTrue($builder->verify()); +$verifyResult = $builder->verify(); +$this->assertTrue($verifyResult); ``` ### Make Assertions diff --git a/tests/PhpPact/Consumer/InteractionBuilderTest.php b/tests/PhpPact/Consumer/InteractionBuilderTest.php index a385cf03..6d97ccbf 100644 --- a/tests/PhpPact/Consumer/InteractionBuilderTest.php +++ b/tests/PhpPact/Consumer/InteractionBuilderTest.php @@ -42,7 +42,9 @@ public function testSimpleGet() ->with($request) ->willRespondWith($response); - $this->assertFalse($builder->verify()); + $verifyResult = $builder->verify(); + + $this->assertFalse($verifyResult); } /** @@ -80,7 +82,9 @@ public function testPostWithBody() ->with($request) ->willRespondWith($response); - $this->assertFalse($builder->verify()); + $verifyResult = $builder->verify(); + + $this->assertFalse($verifyResult); } /** @@ -114,6 +118,8 @@ public function testBuildWithEachLikeMatcher() ->with($request) ->willRespondWith($response); - $this->assertFalse($builder->verify()); + $verifyResult = $builder->verify(); + + $this->assertFalse($verifyResult); } } From cc7f14c62c47531ab49608f38cf7502d463019a0 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 8 May 2023 12:19:20 +0700 Subject: [PATCH 41/78] Change type hint from 'mixed' to 'string|array|null' --- src/PhpPact/Consumer/Model/ConsumerRequest.php | 4 +++- src/PhpPact/Consumer/Model/ProviderResponse.php | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index f8e11ae8..4632dee1 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -101,9 +101,11 @@ public function getBody(): ?string } /** + * @param array|string|null $body + * * @throws JsonException */ - public function setBody(mixed $body): self + public function setBody(array|string|null $body): self { if (\is_string($body) || \is_null($body)) { $this->body = $body; diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index b4459384..4ec6ba51 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -77,9 +77,11 @@ public function getBody(): ?string } /** + * @param array|string|null $body + * * @throws JsonException */ - public function setBody(mixed $body): self + public function setBody(array|string|null $body): self { if (\is_string($body) || \is_null($body)) { $this->body = $body; From dfaa021edb3772fa38e270484f2f81a43c1bbaf7 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 8 May 2023 12:24:55 +0700 Subject: [PATCH 42/78] Add @throws annotation to setPactFileWriteMode() method declaration because implementation throws it --- src/PhpPact/Config/PactConfigInterface.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PhpPact/Config/PactConfigInterface.php b/src/PhpPact/Config/PactConfigInterface.php index d952a725..0f97bd11 100644 --- a/src/PhpPact/Config/PactConfigInterface.php +++ b/src/PhpPact/Config/PactConfigInterface.php @@ -2,6 +2,8 @@ namespace PhpPact\Config; +use InvalidArgumentException; + /** * Mock Server configuration interface to allow for simple overrides that are reusable. */ @@ -67,6 +69,8 @@ public function getPactFileWriteMode(): string; /** * @param string $pactFileWriteMode 'merge' or 'overwrite' merge means that interactions are added and overwrite means that the entire file is overwritten + * + * @throws InvalidArgumentException If mode is incorrect. */ public function setPactFileWriteMode(string $pactFileWriteMode): self; } From 693daad1f5f47ca13c7c6c1a0b7e9ee3cedff3ac Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 8 May 2023 12:41:20 +0700 Subject: [PATCH 43/78] Rename FFI Proxy to Client --- .../Driver/Interaction/AbstractDriver.php | 4 +-- .../Driver/Interaction/InteractionDriver.php | 20 +++++++------- .../Consumer/Driver/Pact/PactDriver.php | 26 +++++++++---------- .../Factory/InteractionRegistryFactory.php | 10 +++---- src/PhpPact/Consumer/Service/MockServer.php | 12 ++++----- src/PhpPact/FFI/{Proxy.php => Client.php} | 2 +- ...ProxyInterface.php => ClientInterface.php} | 2 +- 7 files changed, 38 insertions(+), 38 deletions(-) rename src/PhpPact/FFI/{Proxy.php => Client.php} (94%) rename src/PhpPact/FFI/{ProxyInterface.php => ClientInterface.php} (88%) diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php index 887e9c78..105e413b 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php @@ -3,14 +3,14 @@ namespace PhpPact\Consumer\Driver\Interaction; use PhpPact\Consumer\Driver\Pact\PactDriverInterface; -use PhpPact\FFI\ProxyInterface; +use PhpPact\FFI\ClientInterface; abstract class AbstractDriver implements DriverInterface { protected int $id; public function __construct( - protected ProxyInterface $proxy, + protected ClientInterface $client, protected PactDriverInterface $pactDriver ) { } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php index 6b4692de..66279746 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -11,12 +11,12 @@ class InteractionDriver extends AbstractDriver implements InteractionDriverInter public function newInteraction(string $description): void { - $this->id = $this->proxy->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); + $this->id = $this->client->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); } public function uponReceiving(string $description): void { - $this->proxy->call('pactffi_upon_receiving', $this->id, $description); + $this->client->call('pactffi_upon_receiving', $this->id, $description); } public function withBody(bool $isRequest, ?string $contentType = null, ?string $body = null): void @@ -24,7 +24,7 @@ public function withBody(bool $isRequest, ?string $contentType = null, ?string $ if (is_null($body)) { return; } - $success = $this->proxy->call('pactffi_with_body', $this->id, $this->getPart($isRequest), $contentType, $body); + $success = $this->client->call('pactffi_with_body', $this->id, $this->getPart($isRequest), $contentType, $body); if (!$success) { throw new InteractionBodyNotAddedException(); } @@ -33,9 +33,9 @@ public function withBody(bool $isRequest, ?string $contentType = null, ?string $ public function given(array $providerStates): void { foreach ($providerStates as $providerState) { - $this->proxy->call('pactffi_given', $this->id, $providerState->getName()); + $this->client->call('pactffi_given', $this->id, $providerState->getName()); foreach ($providerState->getParams() as $key => $value) { - $this->proxy->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); + $this->client->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); } } } @@ -44,7 +44,7 @@ public function withHeaders(bool $isRequest, array $headers): void { foreach ($headers as $header => $values) { foreach (array_values($values) as $index => $value) { - $this->proxy->call('pactffi_with_header_v2', $this->id, $this->getPart($isRequest), (string) $header, (int) $index, (string) $value); + $this->client->call('pactffi_with_header_v2', $this->id, $this->getPart($isRequest), (string) $header, (int) $index, (string) $value); } } } @@ -53,23 +53,23 @@ public function withQueryParameters(array $queryParams): void { foreach ($queryParams as $key => $values) { foreach (array_values($values) as $index => $value) { - $this->proxy->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); + $this->client->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); } } } public function withRequest(string $method, string $path): void { - $this->proxy->call('pactffi_with_request', $this->id, $method, $path); + $this->client->call('pactffi_with_request', $this->id, $method, $path); } public function withResponse(int $status): void { - $this->proxy->call('pactffi_response_status', $this->id, $status); + $this->client->call('pactffi_response_status', $this->id, $status); } private function getPart(bool $isRequest): int { - return $this->proxy->get($isRequest ? self::REQUEST : self::RESPONSE); + return $this->client->get($isRequest ? self::REQUEST : self::RESPONSE); } } diff --git a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php index 02197081..eda28ab6 100644 --- a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php @@ -5,14 +5,14 @@ use Composer\Semver\Comparator; use PhpPact\Config\PactConfigInterface; use PhpPact\Consumer\Exception\PactFileNotWroteException; -use PhpPact\FFI\ProxyInterface; +use PhpPact\FFI\ClientInterface; class PactDriver implements PactDriverInterface { protected int $id; public function __construct( - private ProxyInterface $proxy, + private ClientInterface $client, private PactConfigInterface $config ) { $this->setUp(); @@ -25,13 +25,13 @@ public function getId(): int public function cleanUp(): void { - $this->proxy->call('pactffi_free_pact_handle', $this->id); + $this->client->call('pactffi_free_pact_handle', $this->id); unset($this->id); } public function writePact(): void { - $error = $this->proxy->call( + $error = $this->client->call( 'pactffi_pact_handle_write_file', $this->id, $this->config->getPactDir(), @@ -53,15 +53,15 @@ protected function setUp(): void protected function getSpecification(): int { return match (true) { - $this->versionEqualTo('1.0.0') => $this->proxy->get('PactSpecification_V1'), - $this->versionEqualTo('1.1.0') => $this->proxy->get('PactSpecification_V1_1'), - $this->versionEqualTo('2.0.0') => $this->proxy->get('PactSpecification_V2'), - $this->versionEqualTo('3.0.0') => $this->proxy->get('PactSpecification_V3'), - $this->versionEqualTo('4.0.0') => $this->proxy->get('PactSpecification_V4'), + $this->versionEqualTo('1.0.0') => $this->client->get('PactSpecification_V1'), + $this->versionEqualTo('1.1.0') => $this->client->get('PactSpecification_V1_1'), + $this->versionEqualTo('2.0.0') => $this->client->get('PactSpecification_V2'), + $this->versionEqualTo('3.0.0') => $this->client->get('PactSpecification_V3'), + $this->versionEqualTo('4.0.0') => $this->client->get('PactSpecification_V4'), default => function () { trigger_error(sprintf("Specification version '%s' is unknown", $this->config->getPactSpecificationVersion()), E_USER_WARNING); - return $this->proxy->get('PactSpecification_Unknown'); + return $this->client->get('PactSpecification_Unknown'); }, }; } @@ -75,7 +75,7 @@ private function initWithLogLevel(): self { $logLevel = $this->config->getLogLevel(); if ($logLevel) { - $this->proxy->call('pactffi_init_with_log_level', $logLevel); + $this->client->call('pactffi_init_with_log_level', $logLevel); } return $this; @@ -83,14 +83,14 @@ private function initWithLogLevel(): self private function newPact(): self { - $this->id = $this->proxy->call('pactffi_new_pact', $this->config->getConsumer(), $this->config->getProvider()); + $this->id = $this->client->call('pactffi_new_pact', $this->config->getConsumer(), $this->config->getProvider()); return $this; } private function withSpecification(): self { - $this->proxy->call('pactffi_with_specification', $this->id, $this->getSpecification()); + $this->client->call('pactffi_with_specification', $this->id, $this->getSpecification()); return $this; } diff --git a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php index b7a9ccf5..bdf392f1 100644 --- a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php +++ b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php @@ -6,7 +6,7 @@ use PhpPact\Consumer\Driver\Pact\PactDriver; use PhpPact\Consumer\Service\InteractionRegistry; use PhpPact\Consumer\Service\InteractionRegistryInterface; -use PhpPact\FFI\Proxy; +use PhpPact\FFI\Client; use PhpPact\Standalone\MockService\MockServerConfigInterface; use PhpPact\Consumer\Service\MockServer; @@ -14,10 +14,10 @@ class InteractionRegistryFactory { public static function create(MockServerConfigInterface $config): InteractionRegistryInterface { - $proxy = new Proxy(); - $pactDriver = new PactDriver($proxy, $config); - $interactionDriver = new InteractionDriver($proxy, $pactDriver); - $mockServer = new MockServer($proxy, $pactDriver, $config); + $client = new Client(); + $pactDriver = new PactDriver($client, $config); + $interactionDriver = new InteractionDriver($client, $pactDriver); + $mockServer = new MockServer($client, $pactDriver, $config); return new InteractionRegistry($interactionDriver, $mockServer); } diff --git a/src/PhpPact/Consumer/Service/MockServer.php b/src/PhpPact/Consumer/Service/MockServer.php index edbc5158..31378257 100644 --- a/src/PhpPact/Consumer/Service/MockServer.php +++ b/src/PhpPact/Consumer/Service/MockServer.php @@ -6,13 +6,13 @@ use PhpPact\Consumer\Driver\Pact\PactDriverInterface; use PhpPact\Consumer\Exception\MockServerNotStartedException; use PhpPact\Consumer\Exception\MockServerNotWrotePactFileException; -use PhpPact\FFI\ProxyInterface; +use PhpPact\FFI\ClientInterface; use PhpPact\Standalone\MockService\MockServerConfigInterface; class MockServer implements MockServerInterface { public function __construct( - private ProxyInterface $proxy, + private ClientInterface $client, private PactDriverInterface $pactDriver, private MockServerConfigInterface $config ) { @@ -20,7 +20,7 @@ public function __construct( public function start(): void { - $port = $this->proxy->call( + $port = $this->client->call( 'pactffi_create_mock_server_for_transport', $this->pactDriver->getId(), $this->config->getHost(), @@ -37,12 +37,12 @@ public function start(): void public function isMatched(): bool { - return $this->proxy->call('pactffi_mock_server_matched', $this->config->getPort()); + return $this->client->call('pactffi_mock_server_matched', $this->config->getPort()); } public function writePact(): void { - $error = $this->proxy->call( + $error = $this->client->call( 'pactffi_write_pact_file', $this->config->getPort(), $this->config->getPactDir(), @@ -55,7 +55,7 @@ public function writePact(): void public function cleanUp(): void { - $this->proxy->call('pactffi_cleanup_mock_server', $this->config->getPort()); + $this->client->call('pactffi_cleanup_mock_server', $this->config->getPort()); $this->pactDriver->cleanUp(); } diff --git a/src/PhpPact/FFI/Proxy.php b/src/PhpPact/FFI/Client.php similarity index 94% rename from src/PhpPact/FFI/Proxy.php rename to src/PhpPact/FFI/Client.php index e36f1ba6..38b54a2b 100644 --- a/src/PhpPact/FFI/Proxy.php +++ b/src/PhpPact/FFI/Client.php @@ -6,7 +6,7 @@ use PhpPact\FFI\Exception\HeaderNotReadException; use PhpPact\Standalone\Installer\Model\Scripts; -class Proxy implements ProxyInterface +class Client implements ClientInterface { private FFI $ffi; diff --git a/src/PhpPact/FFI/ProxyInterface.php b/src/PhpPact/FFI/ClientInterface.php similarity index 88% rename from src/PhpPact/FFI/ProxyInterface.php rename to src/PhpPact/FFI/ClientInterface.php index 9f4a1a29..c0e767ff 100644 --- a/src/PhpPact/FFI/ProxyInterface.php +++ b/src/PhpPact/FFI/ClientInterface.php @@ -2,7 +2,7 @@ namespace PhpPact\FFI; -interface ProxyInterface +interface ClientInterface { /** * @param array $arguments From d9602212afe36d42f1c332a5bb5bc02415729470 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 8 May 2023 14:21:12 +0700 Subject: [PATCH 44/78] Extract interaction part drivers --- .../Driver/Interaction/AbstractDriver.php | 5 ++ .../Driver/Interaction/DriverInterface.php | 2 + .../Driver/Interaction/InteractionDriver.php | 49 ------------------- .../InteractionDriverInterface.php | 16 ------ .../Interaction/Part/AbstractPartDriver.php | 43 ++++++++++++++++ .../Interaction/Part/PartDriverInterface.php | 13 +++++ .../Driver/Interaction/Part/RequestDriver.php | 25 ++++++++++ .../Part/RequestDriverInterface.php | 13 +++++ .../Interaction/Part/ResponseDriver.php | 16 ++++++ .../Part/ResponseDriverInterface.php | 8 +++ .../Factory/InteractionRegistryFactory.php | 6 ++- .../Consumer/Service/InteractionRegistry.php | 26 +++++----- 12 files changed, 145 insertions(+), 77 deletions(-) create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php index 105e413b..1a035e7f 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php @@ -14,4 +14,9 @@ public function __construct( protected PactDriverInterface $pactDriver ) { } + + public function getId(): int + { + return $this->id; + } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php index d5a87739..4020bf1d 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php @@ -4,5 +4,7 @@ interface DriverInterface { + public function getId(): int; + public function newInteraction(string $description): void; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php index 66279746..aeb51c27 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -2,13 +2,8 @@ namespace PhpPact\Consumer\Driver\Interaction; -use PhpPact\Consumer\Exception\InteractionBodyNotAddedException; - class InteractionDriver extends AbstractDriver implements InteractionDriverInterface { - private const REQUEST = 'InteractionPart_Request'; - private const RESPONSE = 'InteractionPart_Response'; - public function newInteraction(string $description): void { $this->id = $this->client->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); @@ -19,17 +14,6 @@ public function uponReceiving(string $description): void $this->client->call('pactffi_upon_receiving', $this->id, $description); } - public function withBody(bool $isRequest, ?string $contentType = null, ?string $body = null): void - { - if (is_null($body)) { - return; - } - $success = $this->client->call('pactffi_with_body', $this->id, $this->getPart($isRequest), $contentType, $body); - if (!$success) { - throw new InteractionBodyNotAddedException(); - } - } - public function given(array $providerStates): void { foreach ($providerStates as $providerState) { @@ -39,37 +23,4 @@ public function given(array $providerStates): void } } } - - public function withHeaders(bool $isRequest, array $headers): void - { - foreach ($headers as $header => $values) { - foreach (array_values($values) as $index => $value) { - $this->client->call('pactffi_with_header_v2', $this->id, $this->getPart($isRequest), (string) $header, (int) $index, (string) $value); - } - } - } - - public function withQueryParameters(array $queryParams): void - { - foreach ($queryParams as $key => $values) { - foreach (array_values($values) as $index => $value) { - $this->client->call('pactffi_with_query_parameter_v2', $this->id, (string) $key, (int) $index, (string) $value); - } - } - } - - public function withRequest(string $method, string $path): void - { - $this->client->call('pactffi_with_request', $this->id, $method, $path); - } - - public function withResponse(int $status): void - { - $this->client->call('pactffi_response_status', $this->id, $status); - } - - private function getPart(bool $isRequest): int - { - return $this->client->get($isRequest ? self::REQUEST : self::RESPONSE); - } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php index d868cc14..72554701 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -8,24 +8,8 @@ interface InteractionDriverInterface extends DriverInterface { public function uponReceiving(string $description): void; - public function withBody(bool $isRequest, ?string $contentType = null, ?string $body = null): void; - /** * @param ProviderState[] $providerStates */ public function given(array $providerStates): void; - - /** - * @param array $headers - */ - public function withHeaders(bool $isRequest, array $headers): void; - - /** - * @param array $queryParams - */ - public function withQueryParameters(array $queryParams): void; - - public function withRequest(string $method, string $path): void; - - public function withResponse(int $status): void; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php new file mode 100644 index 00000000..a9fc2326 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php @@ -0,0 +1,43 @@ +client->call('pactffi_with_body', $this->getInteractionId(), $this->getPart(), $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } + } + + public function withHeaders(array $headers): void + { + foreach ($headers as $header => $values) { + foreach (array_values($values) as $index => $value) { + $this->client->call('pactffi_with_header_v2', $this->getInteractionId(), $this->getPart(), (string) $header, (int) $index, (string) $value); + } + } + } + + protected function getInteractionId(): int + { + return $this->interactionDriver->getId(); + } + + abstract protected function getPart(): int; +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php new file mode 100644 index 00000000..37d58fc8 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php @@ -0,0 +1,13 @@ + $headers + */ + public function withHeaders(array $headers): void; +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php new file mode 100644 index 00000000..23345a40 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php @@ -0,0 +1,25 @@ + $values) { + foreach (array_values($values) as $index => $value) { + $this->client->call('pactffi_with_query_parameter_v2', $this->getInteractionId(), (string) $key, (int) $index, (string) $value); + } + } + } + + public function withRequest(string $method, string $path): void + { + $this->client->call('pactffi_with_request', $this->getInteractionId(), $method, $path); + } + + protected function getPart(): int + { + return $this->client->get('InteractionPart_Request'); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php new file mode 100644 index 00000000..de19cbb0 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php @@ -0,0 +1,13 @@ + $queryParams + */ + public function withQueryParameters(array $queryParams): void; + + public function withRequest(string $method, string $path): void; +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php new file mode 100644 index 00000000..481fd00d --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php @@ -0,0 +1,16 @@ +client->call('pactffi_response_status', $this->getInteractionId(), $status); + } + + protected function getPart(): int + { + return $this->client->get('InteractionPart_Response'); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php new file mode 100644 index 00000000..71e5a62c --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php @@ -0,0 +1,8 @@ +driver->newInteraction($interaction->getDescription()); + $this->interactionDriver->newInteraction($interaction->getDescription()); return $this; } private function given(Interaction $interaction): self { - $this->driver->given($interaction->getProviderStates()); + $this->interactionDriver->given($interaction->getProviderStates()); return $this; } private function uponReceiving(Interaction $interaction): self { - $this->driver->uponReceiving($interaction->getDescription()); + $this->interactionDriver->uponReceiving($interaction->getDescription()); return $this; } @@ -75,10 +79,10 @@ private function uponReceiving(Interaction $interaction): self private function with(Interaction $interaction): self { $request = $interaction->getRequest(); - $this->driver->withRequest($request->getMethod(), $request->getPath()); - $this->driver->withHeaders(true, $request->getHeaders()); - $this->driver->withQueryParameters($request->getQuery()); - $this->driver->withBody(true, null, $request->getBody()); + $this->requestDriver->withRequest($request->getMethod(), $request->getPath()); + $this->requestDriver->withHeaders($request->getHeaders()); + $this->requestDriver->withQueryParameters($request->getQuery()); + $this->requestDriver->withBody(null, $request->getBody()); return $this; } @@ -86,9 +90,9 @@ private function with(Interaction $interaction): self private function willRespondWith(Interaction $interaction): self { $response = $interaction->getResponse(); - $this->driver->withResponse($response->getStatus()); - $this->driver->withHeaders(false, $response->getHeaders()); - $this->driver->withBody(false, null, $response->getBody()); + $this->responseDriver->withResponse($response->getStatus()); + $this->responseDriver->withHeaders($response->getHeaders()); + $this->responseDriver->withBody(null, $response->getBody()); return $this; } From 770e6e0c50cc69a846af597c376261f6500f7473 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 9 May 2023 10:33:24 +0700 Subject: [PATCH 45/78] Extract interaction contents drivers --- .../Contents/AbstractBodyDriver.php | 29 +++++++ .../Contents/ContentsDriverInterface.php | 8 ++ .../Contents/RequestBodyDriver.php | 11 +++ .../Contents/ResponseBodyDriver.php | 11 +++ .../Driver/Interaction/DriverInterface.php | 2 +- .../Driver/Interaction/InteractionDriver.php | 56 ++++++++++++- .../InteractionDriverInterface.php | 10 ++- .../Interaction/Part/AbstractPartDriver.php | 21 +++-- .../Interaction/Part/PartDriverInterface.php | 4 +- .../Driver/Interaction/Part/RequestDriver.php | 21 ++++- .../Part/RequestDriverInterface.php | 4 +- .../Interaction/Part/ResponseDriver.php | 17 +++- .../Part/ResponseDriverInterface.php | 2 +- .../Factory/InteractionRegistryFactory.php | 6 +- .../Consumer/Service/InteractionRegistry.php | 80 +++---------------- 15 files changed, 182 insertions(+), 100 deletions(-) create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Contents/AbstractBodyDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/AbstractBodyDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Contents/AbstractBodyDriver.php new file mode 100644 index 00000000..44782cff --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Contents/AbstractBodyDriver.php @@ -0,0 +1,29 @@ +client->call('pactffi_with_body', $this->interactionDriver->getId(), $this->getPart(), $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } + } + + abstract protected function getPart(): int; +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php new file mode 100644 index 00000000..5b87acea --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php @@ -0,0 +1,8 @@ +client->get('InteractionPart_Request'); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php new file mode 100644 index 00000000..78d8c90d --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php @@ -0,0 +1,11 @@ +client->get('InteractionPart_Response'); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php index 4020bf1d..b7a85311 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php @@ -6,5 +6,5 @@ interface DriverInterface { public function getId(): int; - public function newInteraction(string $description): void; + public function newInteraction(string $description): static; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php index aeb51c27..f4e554d5 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -2,19 +2,46 @@ namespace PhpPact\Consumer\Driver\Interaction; +use PhpPact\Consumer\Driver\Interaction\Part\RequestDriver; +use PhpPact\Consumer\Driver\Interaction\Part\RequestDriverInterface; +use PhpPact\Consumer\Driver\Interaction\Part\ResponseDriver; +use PhpPact\Consumer\Driver\Interaction\Part\ResponseDriverInterface; +use PhpPact\Consumer\Driver\Pact\PactDriverInterface; +use PhpPact\Consumer\Model\ConsumerRequest; +use PhpPact\Consumer\Model\ProviderResponse; +use PhpPact\FFI\ClientInterface; + class InteractionDriver extends AbstractDriver implements InteractionDriverInterface { - public function newInteraction(string $description): void + private RequestDriverInterface $requestDriver; + private ResponseDriverInterface $responseDriver; + + public function __construct( + ClientInterface $client, + PactDriverInterface $pactDriver, + ?RequestDriverInterface $requestDriver = null, + ?ResponseDriverInterface $responseDriver = null, + ) { + parent::__construct($client, $pactDriver); + $this->requestDriver = $requestDriver ?? new RequestDriver($client, $this); + $this->responseDriver = $responseDriver ?? new ResponseDriver($client, $this); + } + + public function newInteraction(string $description): static { $this->id = $this->client->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); + + return $this; } - public function uponReceiving(string $description): void + public function uponReceiving(string $description): self { $this->client->call('pactffi_upon_receiving', $this->id, $description); + + return $this; } - public function given(array $providerStates): void + public function given(array $providerStates): self { foreach ($providerStates as $providerState) { $this->client->call('pactffi_given', $this->id, $providerState->getName()); @@ -22,5 +49,28 @@ public function given(array $providerStates): void $this->client->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); } } + + return $this; + } + + public function with(ConsumerRequest $request): self + { + $this->requestDriver + ->withRequest($request->getMethod(), $request->getPath()) + ->withQueryParameters($request->getQuery()) + ->withHeaders($request->getHeaders()) + ->withBody(null, $request->getBody()); + + return $this; + } + + public function willRespondWith(ProviderResponse $response): self + { + $this->responseDriver + ->withResponse($response->getStatus()) + ->withHeaders($response->getHeaders()) + ->withBody(null, $response->getBody()); + + return $this; } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php index 72554701..762ff99d 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -2,14 +2,20 @@ namespace PhpPact\Consumer\Driver\Interaction; +use PhpPact\Consumer\Model\ConsumerRequest; +use PhpPact\Consumer\Model\ProviderResponse; use PhpPact\Consumer\Model\ProviderState; interface InteractionDriverInterface extends DriverInterface { - public function uponReceiving(string $description): void; + public function uponReceiving(string $description): self; /** * @param ProviderState[] $providerStates */ - public function given(array $providerStates): void; + public function given(array $providerStates): self; + + public function with(ConsumerRequest $request): self; + + public function willRespondWith(ProviderResponse $response): self; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php index a9fc2326..79b27e03 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/AbstractPartDriver.php @@ -2,36 +2,35 @@ namespace PhpPact\Consumer\Driver\Interaction\Part; +use PhpPact\Consumer\Driver\Interaction\Contents\ContentsDriverInterface; use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; -use PhpPact\Consumer\Exception\InteractionBodyNotAddedException; use PhpPact\FFI\ClientInterface; abstract class AbstractPartDriver implements PartDriverInterface { public function __construct( protected ClientInterface $client, - protected InteractionDriverInterface $interactionDriver + protected InteractionDriverInterface $interactionDriver, + private ContentsDriverInterface $contentsDriver ) { } - public function withBody(?string $contentType = null, ?string $body = null): void + public function withBody(?string $contentType = null, ?string $body = null): self { - if (is_null($body)) { - return; - } - $success = $this->client->call('pactffi_with_body', $this->getInteractionId(), $this->getPart(), $contentType, $body); - if (!$success) { - throw new InteractionBodyNotAddedException(); - } + $this->contentsDriver->withContents($contentType, $body); + + return $this; } - public function withHeaders(array $headers): void + public function withHeaders(array $headers): self { foreach ($headers as $header => $values) { foreach (array_values($values) as $index => $value) { $this->client->call('pactffi_with_header_v2', $this->getInteractionId(), $this->getPart(), (string) $header, (int) $index, (string) $value); } } + + return $this; } protected function getInteractionId(): int diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php index 37d58fc8..417b18cb 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php @@ -4,10 +4,10 @@ interface PartDriverInterface { - public function withBody(?string $contentType = null, ?string $body = null): void; + public function withBody(?string $contentType = null, ?string $body = null): self; /** * @param array $headers */ - public function withHeaders(array $headers): void; + public function withHeaders(array $headers): self; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php index 23345a40..37e790be 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php @@ -2,20 +2,37 @@ namespace PhpPact\Consumer\Driver\Interaction\Part; +use PhpPact\Consumer\Driver\Interaction\Contents\ContentsDriverInterface; +use PhpPact\Consumer\Driver\Interaction\Contents\RequestBodyDriver; +use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; +use PhpPact\FFI\ClientInterface; + class RequestDriver extends AbstractPartDriver implements RequestDriverInterface { - public function withQueryParameters(array $queryParams): void + public function __construct( + ClientInterface $client, + InteractionDriverInterface $interactionDriver, + ?ContentsDriverInterface $requestBodyDriver = null + ) { + parent::__construct($client, $interactionDriver, $requestBodyDriver ?? new RequestBodyDriver($client, $interactionDriver)); + } + + public function withQueryParameters(array $queryParams): self { foreach ($queryParams as $key => $values) { foreach (array_values($values) as $index => $value) { $this->client->call('pactffi_with_query_parameter_v2', $this->getInteractionId(), (string) $key, (int) $index, (string) $value); } } + + return $this; } - public function withRequest(string $method, string $path): void + public function withRequest(string $method, string $path): self { $this->client->call('pactffi_with_request', $this->getInteractionId(), $method, $path); + + return $this; } protected function getPart(): int diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php index de19cbb0..febd69aa 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriverInterface.php @@ -7,7 +7,7 @@ interface RequestDriverInterface extends PartDriverInterface /** * @param array $queryParams */ - public function withQueryParameters(array $queryParams): void; + public function withQueryParameters(array $queryParams): self; - public function withRequest(string $method, string $path): void; + public function withRequest(string $method, string $path): self; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php index 481fd00d..9e59702e 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php @@ -2,11 +2,26 @@ namespace PhpPact\Consumer\Driver\Interaction\Part; +use PhpPact\Consumer\Driver\Interaction\Contents\ContentsDriverInterface; +use PhpPact\Consumer\Driver\Interaction\Contents\ResponseBodyDriver; +use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; +use PhpPact\FFI\ClientInterface; + class ResponseDriver extends AbstractPartDriver implements ResponseDriverInterface { - public function withResponse(int $status): void + public function __construct( + ClientInterface $client, + InteractionDriverInterface $interactionDriver, + ?ContentsDriverInterface $responseBodyDriver = null + ) { + parent::__construct($client, $interactionDriver, $responseBodyDriver ?? new ResponseBodyDriver($client, $interactionDriver)); + } + + public function withResponse(int $status): self { $this->client->call('pactffi_response_status', $this->getInteractionId(), $status); + + return $this; } protected function getPart(): int diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php index 71e5a62c..c6e80856 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php @@ -4,5 +4,5 @@ interface ResponseDriverInterface extends PartDriverInterface { - public function withResponse(int $status): void; + public function withResponse(int $status): self; } diff --git a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php index c7baf157..bdf392f1 100644 --- a/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php +++ b/src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php @@ -3,8 +3,6 @@ namespace PhpPact\Consumer\Factory; use PhpPact\Consumer\Driver\Interaction\InteractionDriver; -use PhpPact\Consumer\Driver\Interaction\Part\RequestDriver; -use PhpPact\Consumer\Driver\Interaction\Part\ResponseDriver; use PhpPact\Consumer\Driver\Pact\PactDriver; use PhpPact\Consumer\Service\InteractionRegistry; use PhpPact\Consumer\Service\InteractionRegistryInterface; @@ -19,10 +17,8 @@ public static function create(MockServerConfigInterface $config): InteractionReg $client = new Client(); $pactDriver = new PactDriver($client, $config); $interactionDriver = new InteractionDriver($client, $pactDriver); - $requestDriver = new RequestDriver($client, $interactionDriver); - $responseDriver = new ResponseDriver($client, $interactionDriver); $mockServer = new MockServer($client, $pactDriver, $config); - return new InteractionRegistry($interactionDriver, $requestDriver, $responseDriver, $mockServer); + return new InteractionRegistry($interactionDriver, $mockServer); } } diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index 4e3a0f9f..e46e7f80 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -3,16 +3,12 @@ namespace PhpPact\Consumer\Service; use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; -use PhpPact\Consumer\Driver\Interaction\Part\RequestDriverInterface; -use PhpPact\Consumer\Driver\Interaction\Part\ResponseDriverInterface; use PhpPact\Consumer\Model\Interaction; class InteractionRegistry implements InteractionRegistryInterface { public function __construct( private InteractionDriverInterface $interactionDriver, - private RequestDriverInterface $requestDriver, - private ResponseDriverInterface $responseDriver, private MockServerInterface $mockServer ) { } @@ -23,10 +19,10 @@ public function verifyInteractions(): bool try { if ($matched) { - $this->writePact(); + $this->mockServer->writePact(); } } finally { - $this->cleanUp(); + $this->mockServer->cleanUp(); } return $matched; @@ -34,71 +30,15 @@ public function verifyInteractions(): bool public function registerInteraction(Interaction $interaction): bool { - $this - ->newInteraction($interaction) - ->given($interaction) - ->uponReceiving($interaction) - ->with($interaction) - ->willRespondWith($interaction) - ->startMockServer(); + $this->interactionDriver + ->newInteraction($interaction->getDescription()) + ->given($interaction->getProviderStates()) + ->uponReceiving($interaction->getDescription()) + ->with($interaction->getRequest()) + ->willRespondWith($interaction->getResponse()); - return true; - } - - private function cleanUp(): void - { - $this->mockServer->cleanUp(); - } - - private function writePact(): void - { - $this->mockServer->writePact(); - } - - private function newInteraction(Interaction $interaction): self - { - $this->interactionDriver->newInteraction($interaction->getDescription()); - - return $this; - } - - private function given(Interaction $interaction): self - { - $this->interactionDriver->given($interaction->getProviderStates()); - - return $this; - } - - private function uponReceiving(Interaction $interaction): self - { - $this->interactionDriver->uponReceiving($interaction->getDescription()); - - return $this; - } - - private function with(Interaction $interaction): self - { - $request = $interaction->getRequest(); - $this->requestDriver->withRequest($request->getMethod(), $request->getPath()); - $this->requestDriver->withHeaders($request->getHeaders()); - $this->requestDriver->withQueryParameters($request->getQuery()); - $this->requestDriver->withBody(null, $request->getBody()); - - return $this; - } - - private function willRespondWith(Interaction $interaction): self - { - $response = $interaction->getResponse(); - $this->responseDriver->withResponse($response->getStatus()); - $this->responseDriver->withHeaders($response->getHeaders()); - $this->responseDriver->withBody(null, $response->getBody()); - - return $this; - } - - private function startMockServer(): void - { $this->mockServer->start(); + + return true; } } From 29f7b4cea8ffa154f9a03c5b7e5237bd80043009 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 9 May 2023 11:24:59 +0700 Subject: [PATCH 46/78] Extract interaction part traits for reusing --- .../Driver/Interaction/Contents/RequestBodyDriver.php | 7 +++---- .../Interaction/Contents/ResponseBodyDriver.php | 7 +++---- .../Driver/Interaction/Part/RequestDriver.php | 7 ++----- .../Driver/Interaction/Part/RequestPartTrait.php | 11 +++++++++++ .../Driver/Interaction/Part/ResponseDriver.php | 7 ++----- .../Driver/Interaction/Part/ResponsePartTrait.php | 11 +++++++++++ 6 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/RequestPartTrait.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php index aca0f7e7..f4c21803 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php @@ -2,10 +2,9 @@ namespace PhpPact\Consumer\Driver\Interaction\Contents; +use PhpPact\Consumer\Driver\Interaction\Part\RequestPartTrait; + class RequestBodyDriver extends AbstractBodyDriver { - protected function getPart(): int - { - return $this->client->get('InteractionPart_Request'); - } + use RequestPartTrait; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php index 78d8c90d..d2ae9116 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php @@ -2,10 +2,9 @@ namespace PhpPact\Consumer\Driver\Interaction\Contents; +use PhpPact\Consumer\Driver\Interaction\Part\ResponsePartTrait; + class ResponseBodyDriver extends AbstractBodyDriver { - protected function getPart(): int - { - return $this->client->get('InteractionPart_Response'); - } + use ResponsePartTrait; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php index 37e790be..d69df087 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestDriver.php @@ -9,6 +9,8 @@ class RequestDriver extends AbstractPartDriver implements RequestDriverInterface { + use RequestPartTrait; + public function __construct( ClientInterface $client, InteractionDriverInterface $interactionDriver, @@ -34,9 +36,4 @@ public function withRequest(string $method, string $path): self return $this; } - - protected function getPart(): int - { - return $this->client->get('InteractionPart_Request'); - } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/RequestPartTrait.php b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestPartTrait.php new file mode 100644 index 00000000..bec1ee95 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/RequestPartTrait.php @@ -0,0 +1,11 @@ +client->get('InteractionPart_Request'); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php index 9e59702e..ff758e37 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php @@ -9,6 +9,8 @@ class ResponseDriver extends AbstractPartDriver implements ResponseDriverInterface { + use ResponsePartTrait; + public function __construct( ClientInterface $client, InteractionDriverInterface $interactionDriver, @@ -23,9 +25,4 @@ public function withResponse(int $status): self return $this; } - - protected function getPart(): int - { - return $this->client->get('InteractionPart_Response'); - } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php new file mode 100644 index 00000000..515cdf4b --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php @@ -0,0 +1,11 @@ +client->get('InteractionPart_Response'); + } +} From 768b77201903be2d2cbb9d945b5b53d6e4b6cd41 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 9 May 2023 14:18:52 +0700 Subject: [PATCH 47/78] Allow sub-class access properties --- src/PhpPact/Consumer/Driver/Pact/PactDriver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php index eda28ab6..3433e662 100644 --- a/src/PhpPact/Consumer/Driver/Pact/PactDriver.php +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriver.php @@ -12,8 +12,8 @@ class PactDriver implements PactDriverInterface protected int $id; public function __construct( - private ClientInterface $client, - private PactConfigInterface $config + protected ClientInterface $client, + protected PactConfigInterface $config ) { $this->setUp(); } From a96c56a997e4c387f357453d3643b745783a5227 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 9 May 2023 22:47:36 +0700 Subject: [PATCH 48/78] Move common code back to MockServer for reusing. Remove methods from MockServerInterface --- .../Consumer/Service/InteractionRegistry.php | 12 +----- src/PhpPact/Consumer/Service/MockServer.php | 38 ++++++++++++------- .../Consumer/Service/MockServerInterface.php | 6 +-- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index e46e7f80..ccd6cade 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -15,17 +15,7 @@ public function __construct( public function verifyInteractions(): bool { - $matched = $this->mockServer->isMatched(); - - try { - if ($matched) { - $this->mockServer->writePact(); - } - } finally { - $this->mockServer->cleanUp(); - } - - return $matched; + return $this->mockServer->verify(); } public function registerInteraction(Interaction $interaction): bool diff --git a/src/PhpPact/Consumer/Service/MockServer.php b/src/PhpPact/Consumer/Service/MockServer.php index 31378257..fa268d6e 100644 --- a/src/PhpPact/Consumer/Service/MockServer.php +++ b/src/PhpPact/Consumer/Service/MockServer.php @@ -35,12 +35,32 @@ public function start(): void $this->config->setPort($port); } - public function isMatched(): bool + public function verify(): bool { - return $this->client->call('pactffi_mock_server_matched', $this->config->getPort()); + $matched = $this->client->call('pactffi_mock_server_matched', $this->config->getPort()); + + try { + if ($matched) { + $this->writePact(); + } + } finally { + $this->cleanUp(); + } + + return $matched; + } + + protected function getTransport(): string + { + return $this->config->isSecure() ? 'https' : 'http'; } - public function writePact(): void + protected function getTransportConfig(): ?string + { + return null; + } + + private function writePact(): void { $error = $this->client->call( 'pactffi_write_pact_file', @@ -53,19 +73,9 @@ public function writePact(): void } } - public function cleanUp(): void + private function cleanUp(): void { $this->client->call('pactffi_cleanup_mock_server', $this->config->getPort()); $this->pactDriver->cleanUp(); } - - protected function getTransport(): string - { - return $this->config->isSecure() ? 'https' : 'http'; - } - - protected function getTransportConfig(): ?string - { - return null; - } } diff --git a/src/PhpPact/Consumer/Service/MockServerInterface.php b/src/PhpPact/Consumer/Service/MockServerInterface.php index c1670d38..5737269c 100644 --- a/src/PhpPact/Consumer/Service/MockServerInterface.php +++ b/src/PhpPact/Consumer/Service/MockServerInterface.php @@ -6,9 +6,5 @@ interface MockServerInterface { public function start(): void; - public function isMatched(): bool; - - public function writePact(): void; - - public function cleanUp(): void; + public function verify(): bool; } From 264018ac736ac7973c5f4d320900209a860ae4b4 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 9 May 2023 23:55:06 +0700 Subject: [PATCH 49/78] Move code from registry to driver --- .../Driver/Interaction/AbstractDriver.php | 2 ++ .../Driver/Interaction/DriverInterface.php | 2 -- .../Driver/Interaction/InteractionDriver.php | 25 +++++++++++++++---- .../InteractionDriverInterface.php | 15 ++--------- .../Consumer/Service/InteractionRegistry.php | 8 +----- 5 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php index 1a035e7f..bfade7a1 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php @@ -19,4 +19,6 @@ public function getId(): int { return $this->id; } + + abstract protected function newInteraction(string $description): self; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php index b7a85311..2d7e3c90 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php @@ -5,6 +5,4 @@ interface DriverInterface { public function getId(): int; - - public function newInteraction(string $description): static; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php index f4e554d5..dfab000b 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -8,7 +8,9 @@ use PhpPact\Consumer\Driver\Interaction\Part\ResponseDriverInterface; use PhpPact\Consumer\Driver\Pact\PactDriverInterface; use PhpPact\Consumer\Model\ConsumerRequest; +use PhpPact\Consumer\Model\Interaction; use PhpPact\Consumer\Model\ProviderResponse; +use PhpPact\Consumer\Model\ProviderState; use PhpPact\FFI\ClientInterface; class InteractionDriver extends AbstractDriver implements InteractionDriverInterface @@ -27,21 +29,34 @@ public function __construct( $this->responseDriver = $responseDriver ?? new ResponseDriver($client, $this); } - public function newInteraction(string $description): static + public function registerInteraction(Interaction $interaction): void + { + $this + ->newInteraction($interaction->getDescription()) + ->given($interaction->getProviderStates()) + ->uponReceiving($interaction->getDescription()) + ->with($interaction->getRequest()) + ->willRespondWith($interaction->getResponse()); + } + + protected function newInteraction(string $description): self { $this->id = $this->client->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); return $this; } - public function uponReceiving(string $description): self + private function uponReceiving(string $description): self { $this->client->call('pactffi_upon_receiving', $this->id, $description); return $this; } - public function given(array $providerStates): self + /** + * @param ProviderState[] $providerStates + */ + private function given(array $providerStates): self { foreach ($providerStates as $providerState) { $this->client->call('pactffi_given', $this->id, $providerState->getName()); @@ -53,7 +68,7 @@ public function given(array $providerStates): self return $this; } - public function with(ConsumerRequest $request): self + private function with(ConsumerRequest $request): self { $this->requestDriver ->withRequest($request->getMethod(), $request->getPath()) @@ -64,7 +79,7 @@ public function with(ConsumerRequest $request): self return $this; } - public function willRespondWith(ProviderResponse $response): self + private function willRespondWith(ProviderResponse $response): self { $this->responseDriver ->withResponse($response->getStatus()) diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php index 762ff99d..f4168ad8 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -2,20 +2,9 @@ namespace PhpPact\Consumer\Driver\Interaction; -use PhpPact\Consumer\Model\ConsumerRequest; -use PhpPact\Consumer\Model\ProviderResponse; -use PhpPact\Consumer\Model\ProviderState; +use PhpPact\Consumer\Model\Interaction; interface InteractionDriverInterface extends DriverInterface { - public function uponReceiving(string $description): self; - - /** - * @param ProviderState[] $providerStates - */ - public function given(array $providerStates): self; - - public function with(ConsumerRequest $request): self; - - public function willRespondWith(ProviderResponse $response): self; + public function registerInteraction(Interaction $interaction): void; } diff --git a/src/PhpPact/Consumer/Service/InteractionRegistry.php b/src/PhpPact/Consumer/Service/InteractionRegistry.php index ccd6cade..cbc0725e 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Service/InteractionRegistry.php @@ -20,13 +20,7 @@ public function verifyInteractions(): bool public function registerInteraction(Interaction $interaction): bool { - $this->interactionDriver - ->newInteraction($interaction->getDescription()) - ->given($interaction->getProviderStates()) - ->uponReceiving($interaction->getDescription()) - ->with($interaction->getRequest()) - ->willRespondWith($interaction->getResponse()); - + $this->interactionDriver->registerInteraction($interaction); $this->mockServer->start(); return true; From 3a3afda37661a576ac238811d85a2df36dd3d950 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 10 May 2023 07:53:23 +0700 Subject: [PATCH 50/78] Switch term 'Driver' <--> 'Registry' --- .../Contents/RequestBodyDriver.php | 10 -- .../Contents/ResponseBodyDriver.php | 10 -- .../Driver/Interaction/DriverInterface.php | 8 -- .../Driver/Interaction/InteractionDriver.php | 85 +++-------------- .../InteractionDriverInterface.php | 6 +- .../Interaction/Part/ResponseDriver.php | 28 ------ .../Part/ResponseDriverInterface.php | 8 -- .../Consumer/Driver/Pact/PactDriver.php | 37 +++----- .../Driver/Pact/PactDriverInterface.php | 2 +- .../Exception/PactNotRegisteredException.php | 9 ++ .../Factory/InteractionDriverFactory.php | 26 ++++++ .../Factory/InteractionRegistryFactory.php | 24 ----- src/PhpPact/Consumer/InteractionBuilder.php | 14 +-- .../Interaction/AbstractRegistry.php} | 8 +- .../Contents/AbstractBodyRegistry.php} | 10 +- .../Contents/ContentsRegistryInterface.php} | 4 +- .../Contents/RequestBodyRegistry.php | 10 ++ .../Contents/ResponseBodyRegistry.php | 10 ++ .../Interaction/InteractionRegistry.php | 93 +++++++++++++++++++ .../InteractionRegistryInterface.php | 6 +- .../Part/AbstractPartRegistry.php} | 16 ++-- .../Part/PartRegistryInterface.php} | 4 +- .../Interaction/Part/RequestPartTrait.php | 2 +- .../Interaction/Part/RequestRegistry.php} | 16 ++-- .../Part/RequestRegistryInterface.php} | 4 +- .../Interaction/Part/ResponsePartTrait.php | 2 +- .../Interaction/Part/ResponseRegistry.php | 28 ++++++ .../Part/ResponseRegistryInterface.php | 8 ++ .../Interaction/RegistryInterface.php | 8 ++ .../Consumer/Registry/Pact/PactRegistry.php | 50 ++++++++++ .../Registry/Pact/PactRegistryInterface.php | 12 +++ .../Consumer/Service/InteractionRegistry.php | 28 ------ src/PhpPact/Consumer/Service/MockServer.php | 8 +- 33 files changed, 331 insertions(+), 263 deletions(-) delete mode 100644 src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php delete mode 100644 src/PhpPact/Consumer/Driver/Interaction/Contents/ResponseBodyDriver.php delete mode 100644 src/PhpPact/Consumer/Driver/Interaction/DriverInterface.php delete mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php delete mode 100644 src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php create mode 100644 src/PhpPact/Consumer/Exception/PactNotRegisteredException.php create mode 100644 src/PhpPact/Consumer/Factory/InteractionDriverFactory.php delete mode 100644 src/PhpPact/Consumer/Factory/InteractionRegistryFactory.php rename src/PhpPact/Consumer/{Driver/Interaction/AbstractDriver.php => Registry/Interaction/AbstractRegistry.php} (58%) rename src/PhpPact/Consumer/{Driver/Interaction/Contents/AbstractBodyDriver.php => Registry/Interaction/Contents/AbstractBodyRegistry.php} (62%) rename src/PhpPact/Consumer/{Driver/Interaction/Contents/ContentsDriverInterface.php => Registry/Interaction/Contents/ContentsRegistryInterface.php} (53%) create mode 100644 src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/Contents/ResponseBodyRegistry.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php rename src/PhpPact/Consumer/{Service => Registry/Interaction}/InteractionRegistryInterface.php (50%) rename src/PhpPact/Consumer/{Driver/Interaction/Part/AbstractPartDriver.php => Registry/Interaction/Part/AbstractPartRegistry.php} (59%) rename src/PhpPact/Consumer/{Driver/Interaction/Part/PartDriverInterface.php => Registry/Interaction/Part/PartRegistryInterface.php} (71%) rename src/PhpPact/Consumer/{Driver => Registry}/Interaction/Part/RequestPartTrait.php (73%) rename src/PhpPact/Consumer/{Driver/Interaction/Part/RequestDriver.php => Registry/Interaction/Part/RequestRegistry.php} (54%) rename src/PhpPact/Consumer/{Driver/Interaction/Part/RequestDriverInterface.php => Registry/Interaction/Part/RequestRegistryInterface.php} (64%) rename src/PhpPact/Consumer/{Driver => Registry}/Interaction/Part/ResponsePartTrait.php (73%) create mode 100644 src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistryInterface.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/RegistryInterface.php create mode 100644 src/PhpPact/Consumer/Registry/Pact/PactRegistry.php create mode 100644 src/PhpPact/Consumer/Registry/Pact/PactRegistryInterface.php delete mode 100644 src/PhpPact/Consumer/Service/InteractionRegistry.php diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php deleted file mode 100644 index f4c21803..00000000 --- a/src/PhpPact/Consumer/Driver/Interaction/Contents/RequestBodyDriver.php +++ /dev/null @@ -1,10 +0,0 @@ -requestDriver = $requestDriver ?? new RequestDriver($client, $this); - $this->responseDriver = $responseDriver ?? new ResponseDriver($client, $this); } - public function registerInteraction(Interaction $interaction): void + public function verifyInteractions(): bool { - $this - ->newInteraction($interaction->getDescription()) - ->given($interaction->getProviderStates()) - ->uponReceiving($interaction->getDescription()) - ->with($interaction->getRequest()) - ->willRespondWith($interaction->getResponse()); - } - - protected function newInteraction(string $description): self - { - $this->id = $this->client->call('pactffi_new_interaction', $this->pactDriver->getId(), $description); - - return $this; - } - - private function uponReceiving(string $description): self - { - $this->client->call('pactffi_upon_receiving', $this->id, $description); - - return $this; - } - - /** - * @param ProviderState[] $providerStates - */ - private function given(array $providerStates): self - { - foreach ($providerStates as $providerState) { - $this->client->call('pactffi_given', $this->id, $providerState->getName()); - foreach ($providerState->getParams() as $key => $value) { - $this->client->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); - } - } - - return $this; - } - - private function with(ConsumerRequest $request): self - { - $this->requestDriver - ->withRequest($request->getMethod(), $request->getPath()) - ->withQueryParameters($request->getQuery()) - ->withHeaders($request->getHeaders()) - ->withBody(null, $request->getBody()); - - return $this; + return $this->mockServer->verify(); } - private function willRespondWith(ProviderResponse $response): self + public function registerInteraction(Interaction $interaction): bool { - $this->responseDriver - ->withResponse($response->getStatus()) - ->withHeaders($response->getHeaders()) - ->withBody(null, $response->getBody()); + $this->pactDriver->setUp(); + $this->interactionRegistry->registerInteraction($interaction); + $this->mockServer->start(); - return $this; + return true; } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php index f4168ad8..fe5a3e07 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -4,7 +4,9 @@ use PhpPact\Consumer\Model\Interaction; -interface InteractionDriverInterface extends DriverInterface +interface InteractionDriverInterface { - public function registerInteraction(Interaction $interaction): void; + public function registerInteraction(Interaction $interaction): bool; + + public function verifyInteractions(): bool; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php deleted file mode 100644 index ff758e37..00000000 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriver.php +++ /dev/null @@ -1,28 +0,0 @@ -client->call('pactffi_response_status', $this->getInteractionId(), $status); - - return $this; - } -} diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php deleted file mode 100644 index c6e80856..00000000 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponseDriverInterface.php +++ /dev/null @@ -1,8 +0,0 @@ -setUp(); - } - - public function getId(): int - { - return $this->id; } public function cleanUp(): void { - $this->client->call('pactffi_free_pact_handle', $this->id); - unset($this->id); + $this->pactRegistry->deletePact(); } public function writePact(): void { $error = $this->client->call( 'pactffi_pact_handle_write_file', - $this->id, + $this->pactRegistry->getId(), $this->config->getPactDir(), $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE ); @@ -42,12 +35,11 @@ public function writePact(): void } } - protected function setUp(): void + public function setUp(): void { $this ->initWithLogLevel() - ->newPact() - ->withSpecification(); + ->registerPact(); } protected function getSpecification(): int @@ -81,16 +73,13 @@ private function initWithLogLevel(): self return $this; } - private function newPact(): self - { - $this->id = $this->client->call('pactffi_new_pact', $this->config->getConsumer(), $this->config->getProvider()); - - return $this; - } - - private function withSpecification(): self + private function registerPact(): self { - $this->client->call('pactffi_with_specification', $this->id, $this->getSpecification()); + $this->pactRegistry->registerPact( + $this->config->getConsumer(), + $this->config->getProvider(), + $this->getSpecification() + ); return $this; } diff --git a/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php b/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php index 6b06d013..69ccdc42 100644 --- a/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php @@ -4,7 +4,7 @@ interface PactDriverInterface { - public function getId(): int; + public function setUp(): void; public function cleanUp(): void; diff --git a/src/PhpPact/Consumer/Exception/PactNotRegisteredException.php b/src/PhpPact/Consumer/Exception/PactNotRegisteredException.php new file mode 100644 index 00000000..8af9a00b --- /dev/null +++ b/src/PhpPact/Consumer/Exception/PactNotRegisteredException.php @@ -0,0 +1,9 @@ +registry = $registry instanceof InteractionRegistryInterface ? $registry : InteractionRegistryFactory::create($registry); + $this->driver = $driver instanceof InteractionDriverInterface ? $driver : InteractionDriverFactory::create($driver); $this->interaction = new Interaction(); } @@ -64,7 +64,7 @@ public function willRespondWith(ProviderResponse $response): bool { $this->interaction->setResponse($response); - return $this->registry->registerInteraction($this->interaction); + return $this->driver->registerInteraction($this->interaction); } /** @@ -72,6 +72,6 @@ public function willRespondWith(ProviderResponse $response): bool */ public function verify(): bool { - return $this->registry->verifyInteractions(); + return $this->driver->verifyInteractions(); } } diff --git a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php b/src/PhpPact/Consumer/Registry/Interaction/AbstractRegistry.php similarity index 58% rename from src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php rename to src/PhpPact/Consumer/Registry/Interaction/AbstractRegistry.php index bfade7a1..82e1d76d 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/AbstractDriver.php +++ b/src/PhpPact/Consumer/Registry/Interaction/AbstractRegistry.php @@ -1,17 +1,17 @@ client->call('pactffi_with_body', $this->interactionDriver->getId(), $this->getPart(), $contentType, $body); + $success = $this->client->call('pactffi_with_body', $this->interactionRegistry->getId(), $this->getPart(), $contentType, $body); if (!$success) { throw new InteractionBodyNotAddedException(); } diff --git a/src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php similarity index 53% rename from src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php rename to src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php index 5b87acea..3e3394ff 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Contents/ContentsDriverInterface.php +++ b/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php @@ -1,8 +1,8 @@ requestRegistry = $requestRegistry ?? new RequestRegistry($client, $this); + $this->responseRegistry = $responseRegistry ?? new ResponseRegistry($client, $this); + } + + public function registerInteraction(Interaction $interaction): bool + { + $this + ->newInteraction($interaction->getDescription()) + ->given($interaction->getProviderStates()) + ->uponReceiving($interaction->getDescription()) + ->with($interaction->getRequest()) + ->willRespondWith($interaction->getResponse()); + + return true; + } + + protected function newInteraction(string $description): self + { + $this->id = $this->client->call('pactffi_new_interaction', $this->pactRegistry->getId(), $description); + + return $this; + } + + private function uponReceiving(string $description): self + { + $this->client->call('pactffi_upon_receiving', $this->id, $description); + + return $this; + } + + /** + * @param ProviderState[] $providerStates + */ + private function given(array $providerStates): self + { + foreach ($providerStates as $providerState) { + $this->client->call('pactffi_given', $this->id, $providerState->getName()); + foreach ($providerState->getParams() as $key => $value) { + $this->client->call('pactffi_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); + } + } + + return $this; + } + + private function with(ConsumerRequest $request): self + { + $this->requestRegistry + ->withRequest($request->getMethod(), $request->getPath()) + ->withQueryParameters($request->getQuery()) + ->withHeaders($request->getHeaders()) + ->withBody(null, $request->getBody()); + + return $this; + } + + private function willRespondWith(ProviderResponse $response): self + { + $this->responseRegistry + ->withResponse($response->getStatus()) + ->withHeaders($response->getHeaders()) + ->withBody(null, $response->getBody()); + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Service/InteractionRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistryInterface.php similarity index 50% rename from src/PhpPact/Consumer/Service/InteractionRegistryInterface.php rename to src/PhpPact/Consumer/Registry/Interaction/InteractionRegistryInterface.php index 54159b90..c3f1c7fc 100644 --- a/src/PhpPact/Consumer/Service/InteractionRegistryInterface.php +++ b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistryInterface.php @@ -1,12 +1,10 @@ contentsDriver->withContents($contentType, $body); + $this->contentsRegistry->withContents($contentType, $body); return $this; } @@ -35,7 +35,7 @@ public function withHeaders(array $headers): self protected function getInteractionId(): int { - return $this->interactionDriver->getId(); + return $this->interactionRegistry->getId(); } abstract protected function getPart(): int; diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php similarity index 71% rename from src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php rename to src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php index 417b18cb..90a6516b 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/PartDriverInterface.php +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php @@ -1,8 +1,8 @@ $queryParams diff --git a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponsePartTrait.php similarity index 73% rename from src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php rename to src/PhpPact/Consumer/Registry/Interaction/Part/ResponsePartTrait.php index 515cdf4b..389fe949 100644 --- a/src/PhpPact/Consumer/Driver/Interaction/Part/ResponsePartTrait.php +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponsePartTrait.php @@ -1,6 +1,6 @@ client->call('pactffi_response_status', $this->getInteractionId(), $status); + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistryInterface.php new file mode 100644 index 00000000..05f56e52 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistryInterface.php @@ -0,0 +1,8 @@ +id)) { + throw new PactNotRegisteredException('New pact must be registered.'); + } + return $this->id; + } + + public function deletePact(): void + { + $this->client->call('pactffi_free_pact_handle', $this->id); + unset($this->id); + } + + public function registerPact(string $consumer, string $provider, int $specification): void + { + $this + ->newPact($consumer, $provider) + ->withSpecification($specification); + } + + private function newPact(string $consumer, string $provider): self + { + $this->id = $this->client->call('pactffi_new_pact', $consumer, $provider); + + return $this; + } + + private function withSpecification(int $specification): self + { + $this->client->call('pactffi_with_specification', $this->id, $specification); + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Registry/Pact/PactRegistryInterface.php b/src/PhpPact/Consumer/Registry/Pact/PactRegistryInterface.php new file mode 100644 index 00000000..c9ffb883 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Pact/PactRegistryInterface.php @@ -0,0 +1,12 @@ +mockServer->verify(); - } - - public function registerInteraction(Interaction $interaction): bool - { - $this->interactionDriver->registerInteraction($interaction); - $this->mockServer->start(); - - return true; - } -} diff --git a/src/PhpPact/Consumer/Service/MockServer.php b/src/PhpPact/Consumer/Service/MockServer.php index fa268d6e..44aa9179 100644 --- a/src/PhpPact/Consumer/Service/MockServer.php +++ b/src/PhpPact/Consumer/Service/MockServer.php @@ -3,9 +3,9 @@ namespace PhpPact\Consumer\Service; use PhpPact\Config\PactConfigInterface; -use PhpPact\Consumer\Driver\Pact\PactDriverInterface; use PhpPact\Consumer\Exception\MockServerNotStartedException; use PhpPact\Consumer\Exception\MockServerNotWrotePactFileException; +use PhpPact\Consumer\Registry\Pact\PactRegistryInterface; use PhpPact\FFI\ClientInterface; use PhpPact\Standalone\MockService\MockServerConfigInterface; @@ -13,7 +13,7 @@ class MockServer implements MockServerInterface { public function __construct( private ClientInterface $client, - private PactDriverInterface $pactDriver, + private PactRegistryInterface $pactRegistry, private MockServerConfigInterface $config ) { } @@ -22,7 +22,7 @@ public function start(): void { $port = $this->client->call( 'pactffi_create_mock_server_for_transport', - $this->pactDriver->getId(), + $this->pactRegistry->getId(), $this->config->getHost(), $this->config->getPort(), $this->getTransport(), @@ -76,6 +76,6 @@ private function writePact(): void private function cleanUp(): void { $this->client->call('pactffi_cleanup_mock_server', $this->config->getPort()); - $this->pactDriver->cleanUp(); + $this->pactRegistry->deletePact(); } } From 1d6d39eb36b824e6e960c10c1bf2000932979675 Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 12 May 2023 07:27:51 +0700 Subject: [PATCH 51/78] Inject driver factory into builder --- .../{ => Builder}/InteractionDriverFactory.php | 6 +++--- .../Builder/InteractionDriverFactoryInterface.php | 11 +++++++++++ src/PhpPact/Consumer/InteractionBuilder.php | 7 ++++--- 3 files changed, 18 insertions(+), 6 deletions(-) rename src/PhpPact/Consumer/Factory/{ => Builder}/InteractionDriverFactory.php (79%) create mode 100644 src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactoryInterface.php diff --git a/src/PhpPact/Consumer/Factory/InteractionDriverFactory.php b/src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactory.php similarity index 79% rename from src/PhpPact/Consumer/Factory/InteractionDriverFactory.php rename to src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactory.php index acfd1a31..6a1a2efe 100644 --- a/src/PhpPact/Consumer/Factory/InteractionDriverFactory.php +++ b/src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactory.php @@ -1,6 +1,6 @@ driver = $driver instanceof InteractionDriverInterface ? $driver : InteractionDriverFactory::create($driver); + $this->driver = ($driverFactory ?? new InteractionDriverFactory())->create($config); $this->interaction = new Interaction(); } From f844be0f301f313d926d075aeeb379e7851273ae Mon Sep 17 00:00:00 2001 From: tienvx Date: Fri, 12 May 2023 13:06:26 +0700 Subject: [PATCH 52/78] Revert namespace change --- .../Factory/{Builder => }/InteractionDriverFactory.php | 2 +- .../{Builder => }/InteractionDriverFactoryInterface.php | 2 +- src/PhpPact/Consumer/InteractionBuilder.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/PhpPact/Consumer/Factory/{Builder => }/InteractionDriverFactory.php (95%) rename src/PhpPact/Consumer/Factory/{Builder => }/InteractionDriverFactoryInterface.php (86%) diff --git a/src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactory.php b/src/PhpPact/Consumer/Factory/InteractionDriverFactory.php similarity index 95% rename from src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactory.php rename to src/PhpPact/Consumer/Factory/InteractionDriverFactory.php index 6a1a2efe..f911c5fc 100644 --- a/src/PhpPact/Consumer/Factory/Builder/InteractionDriverFactory.php +++ b/src/PhpPact/Consumer/Factory/InteractionDriverFactory.php @@ -1,6 +1,6 @@ Date: Wed, 28 Dec 2022 09:37:14 +0700 Subject: [PATCH 53/78] Use Rust FFI: Message Consumer --- README.md | 4 +- .../ExampleMessageConsumer.php | 4 +- .../ExampleMessageConsumerTest.php | 32 +------ .../Consumer/AbstractMessageBuilder.php | 59 ++++++++++++ .../Driver/Interaction/MessageDriver.php | 37 ++++++++ .../Interaction/MessageDriverInterface.php | 14 +++ .../Consumer/Factory/MessageDriverFactory.php | 24 +++++ .../Factory/MessageDriverFactoryInterface.php | 11 +++ src/PhpPact/Consumer/MessageBuilder.php | 88 +++--------------- src/PhpPact/Consumer/Model/Message.php | 12 ++- .../Contents/MessageContentsRegistry.php | 25 +++++ .../Registry/Interaction/MessageRegistry.php | 91 +++++++++++++++++++ .../Interaction/MessageRegistryInterface.php | 10 ++ src/PhpPact/FFI/Model/StringData.php | 35 +++++++ .../Standalone/Installer/Model/Scripts.php | 5 - .../Standalone/PactMessage/PactMessage.php | 47 ---------- tests/PhpPact/Consumer/Model/MessageTest.php | 34 +++++++ 17 files changed, 374 insertions(+), 158 deletions(-) create mode 100644 src/PhpPact/Consumer/AbstractMessageBuilder.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/MessageDriver.php create mode 100644 src/PhpPact/Consumer/Driver/Interaction/MessageDriverInterface.php create mode 100644 src/PhpPact/Consumer/Factory/MessageDriverFactory.php create mode 100644 src/PhpPact/Consumer/Factory/MessageDriverFactoryInterface.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/Contents/MessageContentsRegistry.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php create mode 100644 src/PhpPact/Consumer/Registry/Interaction/MessageRegistryInterface.php create mode 100644 src/PhpPact/FFI/Model/StringData.php delete mode 100644 src/PhpPact/Standalone/PactMessage/PactMessage.php create mode 100644 tests/PhpPact/Consumer/Model/MessageTest.php diff --git a/README.md b/README.md index e208e86d..5a0c9573 100644 --- a/README.md +++ b/README.md @@ -373,7 +373,9 @@ $consumerMessage = new ExampleMessageConsumer(); $callback = [$consumerMessage, 'ProcessSong']; $builder->setCallback($callback); -$builder->verify(); +$verifyResult = $builder->verify(); + +$this->assertTrue($verifyResult); ``` diff --git a/example/src/MessageConsumer/ExampleMessageConsumer.php b/example/src/MessageConsumer/ExampleMessageConsumer.php index 0a821944..77851aa6 100644 --- a/example/src/MessageConsumer/ExampleMessageConsumer.php +++ b/example/src/MessageConsumer/ExampleMessageConsumer.php @@ -4,7 +4,7 @@ class ExampleMessageConsumer { - public function ProcessText($message) + public function ProcessText(string $message): object { $obj = \json_decode($message); print ' [x] Processed ' . \print_r($obj->contents->text, true) . "\n"; @@ -12,7 +12,7 @@ public function ProcessText($message) return $obj; } - public function ProcessSong($message) + public function ProcessSong(string $message): object { $obj = \json_decode($message); print ' [x] Processed ' . \print_r($obj->contents->song, true) . "\n"; diff --git a/example/tests/MessageConsumer/ExampleMessageConsumerTest.php b/example/tests/MessageConsumer/ExampleMessageConsumerTest.php index e9bda5d0..0056d35d 100644 --- a/example/tests/MessageConsumer/ExampleMessageConsumerTest.php +++ b/example/tests/MessageConsumer/ExampleMessageConsumerTest.php @@ -2,8 +2,6 @@ namespace MessageConsumer; -require_once __DIR__ . '/../../src/MessageConsumer/ExampleMessageConsumer.php'; - use Exception; use PhpPact\Consumer\MessageBuilder; use PhpPact\Config\PactConfigInterface; @@ -28,18 +26,6 @@ public static function setUpBeforeClass(): void ->setPactDir(__DIR__ . '/../../output/'); } - public static function tearDownAfterClass(): void - { - parent::tearDownAfterClass(); - - // build out brokerHttpService as your example - /* - $brokerHttpService = new BrokerHttpClient(new GuzzleClient(), new Uri($pactBrokerUri)); - $brokerHttpService->publishJson($json, $consumerVersion); - $brokerHttpService->tag($this->mockServerConfig->getConsumer(), $consumerVersion, $tag); - */ - } - /** * @throws Exception */ @@ -53,7 +39,7 @@ public function testProcessText() $metadata = ['queue'=>'wind cries', 'routing_key'=>'wind cries']; $builder - ->given('a message', ['foo']) + ->given('a message', ['foo' => 'bar']) ->expectsToReceive('an alligator named Mary exists') ->withMetadata($metadata) ->withContent($contents); @@ -63,11 +49,9 @@ public function testProcessText() $callback = [$consumerMessage, 'ProcessText']; $builder->setCallback($callback); - $hasException = false; - - $builder->verify(); + $verifyResult = $builder->verify(); - $this->assertTrue(true, 'Expects to reach this true statement by running verify()'); + $this->assertTrue($verifyResult); } /** @@ -93,14 +77,8 @@ public function testProcessSong() $callback = [$consumerMessage, 'ProcessSong']; $builder->setCallback($callback); - $hasException = false; - - try { - $builder->verify(); - } catch (Exception $e) { - $hasException = true; - } + $verifyResult = $builder->verify(); - $this->assertFalse($hasException, 'Expects verification to pass without exceptions being thrown'); + $this->assertTrue($verifyResult); } } diff --git a/src/PhpPact/Consumer/AbstractMessageBuilder.php b/src/PhpPact/Consumer/AbstractMessageBuilder.php new file mode 100644 index 00000000..71a23275 --- /dev/null +++ b/src/PhpPact/Consumer/AbstractMessageBuilder.php @@ -0,0 +1,59 @@ +message = new Message(); + } + + /** + * @param string $name what is given to the request + * @param array $params for that request + * @param bool $overwrite clear pass states completely and start this array + */ + public function given(string $name, array $params = [], bool $overwrite = false): self + { + $this->message->setProviderState($name, $params, $overwrite); + + return $this; + } + + /** + * @param string $description what is received when the request is made + */ + public function expectsToReceive(string $description): self + { + $this->message->setDescription($description); + + return $this; + } + + /** + * @param array $metadata what is the additional metadata of the message + */ + public function withMetadata(array $metadata): self + { + $this->message->setMetadata($metadata); + + return $this; + } + + /** + * Make the http request to the Mock Service to register the message. Content is required. + * + * @param mixed $contents required to be in the message + */ + public function withContent(mixed $contents): self + { + $this->message->setContents($contents); + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/MessageDriver.php b/src/PhpPact/Consumer/Driver/Interaction/MessageDriver.php new file mode 100644 index 00000000..6ba771ff --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/MessageDriver.php @@ -0,0 +1,37 @@ +client->call('pactffi_message_reify', $this->messageRegistry->getId()); + } + + public function writePactAndCleanUp(): bool + { + $this->pactDriver->writePact(); + $this->pactDriver->cleanUp(); + + return true; + } + + public function registerMessage(Message $message): void + { + $this->pactDriver->setUp(); + $this->messageRegistry->registerMessage($message); + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/MessageDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/MessageDriverInterface.php new file mode 100644 index 00000000..be9a3089 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/MessageDriverInterface.php @@ -0,0 +1,14 @@ + */ protected array $callback; - private Message $message; - - public function __construct(PactConfigInterface $config) + public function __construct(PactConfigInterface $config, ?MessageDriverFactoryInterface $driverFactory = null) { - $this->config = $config; - $this->message = new Message(); - $this->pactMessage = new PactMessage(); + parent::__construct(); + $this->driver = ($driverFactory ?? new MessageDriverFactory())->create($config); } /** @@ -45,56 +41,14 @@ public function setCallback(callable $callback, ?string $description = null): se return $this; } - /** - * @param string $name what is given to the request - * @param array $params for that request - * @param bool $overwrite clear pass states completely and start this array - */ - public function given(string $name, array $params = [], bool $overwrite = false): self - { - $this->message->setProviderState($name, $params, $overwrite); - - return $this; - } - - /** - * @param string $description what is received when the request is made - */ - public function expectsToReceive(string $description): self - { - $this->message->setDescription($description); - - return $this; - } - - /** - * @param array $metadata what is the additional metadata of the message - */ - public function withMetadata(array $metadata): self - { - $this->message->setMetadata($metadata); - - return $this; - } - - /** - * Make the http request to the Mock Service to register the message. Content is required. - * - * @param mixed $contents required to be in the message - */ - public function withContent($contents): self - { - $this->message->setContents($contents); - - return $this; - } - /** * Run reify to create an example pact from the message (i.e. create messages from matchers) */ public function reify(): string { - return $this->pactMessage->reify($this->message); + $this->driver->registerMessage($this->message); + + return $this->driver->reify(); } /** @@ -107,18 +61,16 @@ public function verifyMessage(callable $callback, ?string $description = null): { $this->setCallback($callback, $description); - return $this->verify($description); + return $this->verify(); } /** * Verify the use of the pact by calling the callback * It also calls finalize to write the pact * - * @param null|string $description description of the pact and thus callback - * * @throws \Exception if callback is not set */ - public function verify(?string $description = null): bool + public function verify(): bool { if (\count($this->callback) < 1) { throw new \Exception('Callbacks need to exist to run verify.'); @@ -133,21 +85,9 @@ public function verify(?string $description = null): bool \call_user_func($callback, $pactJson); } - return $this->writePact(); + return $this->driver->writePactAndCleanUp(); } catch (\Exception $e) { return false; } } - - /** - * Write the Pact without deleting the interactions. - * @throws \JsonException - */ - public function writePact(): bool - { - // you do not want to save the reified json - $pactJson = \json_encode($this->message, JSON_THROW_ON_ERROR); - - return $this->pactMessage->update($pactJson, $this->config->getConsumer(), $this->config->getProvider(), $this->config->getPactDir()); - } } diff --git a/src/PhpPact/Consumer/Model/Message.php b/src/PhpPact/Consumer/Model/Message.php index af2c047d..1b8b3a4e 100644 --- a/src/PhpPact/Consumer/Model/Message.php +++ b/src/PhpPact/Consumer/Model/Message.php @@ -14,7 +14,7 @@ class Message /** * @var array */ - private array $metadata; + private array $metadata = []; private mixed $contents; @@ -43,11 +43,19 @@ public function getMetadata(): array */ public function setMetadata(array $metadata): self { - $this->metadata = $metadata; + $this->metadata = []; + foreach ($metadata as $key => $value) { + $this->setMetadataValue($key, $value); + } return $this; } + private function setMetadataValue(string $key, string $value): void + { + $this->metadata[$key] = $value; + } + public function getContents(): mixed { return $this->contents; diff --git a/src/PhpPact/Consumer/Registry/Interaction/Contents/MessageContentsRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Contents/MessageContentsRegistry.php new file mode 100644 index 00000000..e3f8c355 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Contents/MessageContentsRegistry.php @@ -0,0 +1,25 @@ +client->call('pactffi_message_with_contents', $this->messageRegistry->getId(), $contentType, $data->getValue(), $data->getSize()); + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php new file mode 100644 index 00000000..11f70f91 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php @@ -0,0 +1,91 @@ +messageContentsRegistry = $messageContentsRegistry ?? new MessageContentsRegistry($client, $this); + } + + + public function registerMessage(Message $message): void + { + if (\is_string($message->getContents())) { + $contents = $message->getContents(); + $contentType = 'text/plain'; + } else { + $contents = \json_encode($message->getContents(), JSON_THROW_ON_ERROR); + $contentType = 'application/json'; + } + + $this + ->newInteraction($message->getDescription()) + ->given($message->getProviderStates()) + ->expectsToReceive($message->getDescription()) + ->withMetadata($message->getMetadata()) + ->withContents($contentType, $contents); + } + + protected function newInteraction(string $description): self + { + $this->id = $this->client->call('pactffi_new_message_interaction', $this->pactRegistry->getId(), $description); + + return $this; + } + + private function withContents(?string $contentType = null, ?string $contents = null): self + { + $this->messageContentsRegistry->withContents($contentType, $contents); + + return $this; + } + + private function expectsToReceive(string $description): self + { + $this->client->call('pactffi_message_expects_to_receive', $this->id, $description); + + return $this; + } + + /** + * @param ProviderState[] $providerStates + */ + private function given(array $providerStates): self + { + foreach ($providerStates as $providerState) { + $this->client->call('pactffi_message_given', $this->id, $providerState->getName()); + foreach ($providerState->getParams() as $key => $value) { + $this->client->call('pactffi_message_given_with_param', $this->id, $providerState->getName(), (string) $key, (string) $value); + } + } + + return $this; + } + + /** + * @param array $metadata + */ + private function withMetadata(array $metadata): self + { + foreach ($metadata as $key => $value) { + $this->client->call('pactffi_message_with_metadata', $this->id, (string) $key, (string) $value); + } + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/MessageRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistryInterface.php new file mode 100644 index 00000000..ca3af5c2 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistryInterface.php @@ -0,0 +1,10 @@ +value; + } + + public function getSize(): int + { + return $this->size; + } + + public static function createFrom(string $value): ?self + { + $length = \strlen($value); + $size = $length + 1; + $cData = FFI::new("uint8_t[{$size}]"); + FFI::memcpy($cData, $value, $length); + + return new self($cData, $size); + } +} diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 243bf45a..02932804 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -41,11 +41,6 @@ public static function getBroker(): string return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker' . self::getSuffix(); } - public static function getPactMessage(): string - { - return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-message' . self::getSuffix(); - } - private static function getSuffix(): string { return (PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); diff --git a/src/PhpPact/Standalone/PactMessage/PactMessage.php b/src/PhpPact/Standalone/PactMessage/PactMessage.php deleted file mode 100644 index b96b29d3..00000000 --- a/src/PhpPact/Standalone/PactMessage/PactMessage.php +++ /dev/null @@ -1,47 +0,0 @@ -runBlocking(); - - $output = $process->getOutput(); - \preg_replace("/\r|\n/", '', $output); - - return $output; - } - - /** - * Update a pact with the given message, or create the pact if it does not exist. The MESSAGE_JSON may be in the legacy Ruby JSON format or the v2+ format. - */ - public function update(string $pactJson, string $consumer, string $provider, string $pactDir): bool - { - $arguments = []; - $arguments[] = 'update'; - $arguments[] = "--consumer={$consumer}"; - $arguments[] = "--provider={$provider}"; - $arguments[] = "--pact-dir={$pactDir}"; - $arguments[] = "'" . $pactJson . "'"; - - $process = new ProcessRunner(Scripts::getPactMessage(), $arguments); - $process->runBlocking(); - - \sleep(1); - - return true; - } -} diff --git a/tests/PhpPact/Consumer/Model/MessageTest.php b/tests/PhpPact/Consumer/Model/MessageTest.php new file mode 100644 index 00000000..165ed8a8 --- /dev/null +++ b/tests/PhpPact/Consumer/Model/MessageTest.php @@ -0,0 +1,34 @@ + 'bar']; + $metadata = ['queue' => 'foo', 'routing_key' => 'bar']; + $contents = 'test'; + + $subject = (new Message()) + ->setDescription($description) + ->addProviderState($providerStateName, $providerStateParams) + ->setMetadata($metadata) + ->setContents($contents); + + static::assertSame($description, $subject->getDescription()); + $providerStates = $subject->getProviderStates(); + static::assertCount(1, $providerStates); + static::assertContainsOnlyInstancesOf(ProviderState::class, $providerStates); + static::assertEquals($providerStateName, $providerStates[0]->getName()); + static::assertEquals($providerStateParams, $providerStates[0]->getParams()); + static::assertSame($metadata, $subject->getMetadata()); + static::assertSame($contents, $subject->getContents()); + } +} From 6aad4567b834b831babcf9e0af0ea2b10f5a96c5 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 2 Jan 2023 21:05:49 +0700 Subject: [PATCH 54/78] Use Rust FFI: Provider --- .github/workflows/build.yml | 3 + README.md | 156 +++---- UPGRADE-9.0.md | 44 ++ composer.json | 12 +- example/README.md | 4 - example/pacts/someconsumer-someprovider.json | 2 +- .../pacts/test_consumer-test_provider.json | 2 +- example/phpunit.all.xml | 3 - example/phpunit.message.provider.xml | 8 - example/src/Consumer/publish_json_example.php | 16 - .../ExampleMessageProvider.php | 34 +- example/src/Provider/ExampleProvider.php | 66 +++ example/src/Provider/public/index.php | 33 +- .../ExampleMessageProviderTest.php | 61 --- example/tests/Provider/PactVerifyTest.php | 35 +- .../Broker/Service/BrokerHttpClient.php | 109 ----- .../Service/BrokerHttpClientInterface.php | 49 -- .../Exception/CDataNotCreatedException.php | 9 + src/PhpPact/FFI/Model/ArrayData.php | 61 +++ src/PhpPact/Provider/MessageVerifier.php | 208 --------- .../Standalone/Installer/Model/Scripts.php | 5 - .../Model/Config/CallingApp.php | 33 ++ .../Model/Config/CallingAppInterface.php | 14 + .../Model/Config/ConsumerFilters.php | 33 ++ .../Model/Config/ConsumerFiltersInterface.php | 18 + .../Model/Config/FilterInfo.php | 46 ++ .../Model/Config/FilterInfoInterface.php | 18 + .../Model/Config/PluginDirTrait.php | 20 + .../Model/Config/ProviderInfo.php | 72 +++ .../Model/Config/ProviderInfoInterface.php | 26 ++ .../Model/Config/ProviderState.php | 48 ++ .../Model/Config/ProviderStateInterface.php | 20 + .../Model/Config/ProviderTransport.php | 59 +++ .../Config/ProviderTransportInterface.php | 28 ++ .../Model/Config/PublishOptions.php | 74 +++ .../Model/Config/PublishOptionsInterface.php | 32 ++ .../Model/Config/VerificationOptions.php | 33 ++ .../Config/VerificationOptionsInterface.php | 14 + .../Model/ConsumerVersionSelectors.php | 16 +- .../ProviderVerifier/Model/Source/Broker.php | 118 +++++ .../Model/Source/BrokerInterface.php | 52 +++ .../ProviderVerifier/Model/Source/Url.php | 61 +++ .../Model/Source/UrlInterface.php | 24 + .../ProviderVerifier/Model/VerifierConfig.php | 439 +++--------------- .../Model/VerifierConfigInterface.php | 221 ++------- .../ProviderVerifier/ProcessRunnerFactory.php | 30 -- .../Standalone/ProviderVerifier/Verifier.php | 367 +++++++-------- .../ProviderVerifier/VerifierProcess.php | 57 --- .../Broker/Service/BrokerHttpClientTest.php | 53 --- tests/PhpPact/FFI/Model/ArrayDataTest.php | 21 + .../Model/Source/BrokerTest.php | 37 ++ .../ProviderVerifier/Model/Source/UrlTest.php | 29 ++ .../Model/VerifierConfigTest.php | 88 ++++ .../ProviderVerifier/VerifierProcessTest.php | 77 --- .../ProviderVerifier/VerifierTest.php | 232 ++------- .../Standalone/ProviderVerifier/verifier.bat | 8 - .../Standalone/ProviderVerifier/verifier.sh | 10 - .../Service/StubServerHttpServiceTest.php | 2 +- tests/_public/index.php | 10 + 59 files changed, 1638 insertions(+), 1822 deletions(-) delete mode 100644 example/phpunit.message.provider.xml delete mode 100644 example/src/Consumer/publish_json_example.php create mode 100644 example/src/Provider/ExampleProvider.php delete mode 100644 example/tests/MessageProvider/ExampleMessageProviderTest.php delete mode 100644 src/PhpPact/Broker/Service/BrokerHttpClient.php delete mode 100644 src/PhpPact/Broker/Service/BrokerHttpClientInterface.php create mode 100644 src/PhpPact/FFI/Exception/CDataNotCreatedException.php create mode 100644 src/PhpPact/FFI/Model/ArrayData.php delete mode 100644 src/PhpPact/Provider/MessageVerifier.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingApp.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingAppInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ConsumerFilters.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ConsumerFiltersInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfo.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfoInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/PluginDirTrait.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfo.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfoInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderState.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderStateInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderTransport.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderTransportInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/PublishOptions.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/PublishOptionsInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptions.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptionsInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Source/Broker.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerInterface.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Source/Url.php create mode 100644 src/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlInterface.php delete mode 100644 src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php delete mode 100644 src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php delete mode 100644 tests/PhpPact/Broker/Service/BrokerHttpClientTest.php create mode 100644 tests/PhpPact/FFI/Model/ArrayDataTest.php create mode 100644 tests/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerTest.php create mode 100644 tests/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlTest.php create mode 100644 tests/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigTest.php delete mode 100644 tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php delete mode 100755 tests/PhpPact/Standalone/ProviderVerifier/verifier.bat delete mode 100755 tests/PhpPact/Standalone/ProviderVerifier/verifier.sh create mode 100644 tests/_public/index.php diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff996ef3..427531c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,6 +27,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + coverage: none - uses: ramsey/composer-install@v2 with: @@ -48,6 +49,7 @@ jobs: operating-system: [ ubuntu-latest, macos-latest, windows-latest ] php: [ '8.0', '8.1', '8.2' ] dependencies: [ 'lowest', 'locked' ] + timeout-minutes: 5 name: PHP ${{ matrix.php }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies @@ -60,6 +62,7 @@ jobs: with: extensions: openssl, sockets, curl, zip, ffi php-version: ${{ matrix.php }} + coverage: none - name: Composer install uses: ramsey/composer-install@v2 diff --git a/README.md b/README.md index 5a0c9573..29e72c00 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Table of contents - [Start API](#start-api) - [Provider Verification](#provider-verification) - [Verify From Pact Broker](#verify-from-pact-broker) - - [Verify All from Pact Broker](#verify-all-from-pact-broker) + - [Verify Files in Directory](#verify-files-in-directory) - [Verify Files by Path](#verify-files-by-path) - [Tips](#tips) - [Starting API Asynchronously](#starting-api-asynchronously) @@ -191,7 +191,7 @@ Verify that all interactions took place that were registered. This typically should be in each test, that way the test that failed to verify is marked correctly. ```php -$verifyResult = $builder->verify(); +$verifyResult = $verifier->verify(); $this->assertTrue($verifyResult); ``` @@ -230,51 +230,62 @@ $config = new VerifierConfig(); $config ->setProviderName('someProvider') // Providers name to fetch. ->setProviderVersion('1.0.0') // Providers version. - ->setProviderBranch('main') // Providers git branch name. - ->setProviderBaseUrl(new Uri('http://localhost:58000')) // URL of the Provider. - ->setBrokerUri(new Uri('http://localhost')) // URL of the Pact Broker to publish results. - ->setPublishResults(true) // Flag the verifier service to publish the results to the Pact Broker. - ->setProcessTimeout(60) // Set process timeout (optional) - default 60 - ->setProcessIdleTimeout(10) // Set process idle timeout (optional) - default 10 - ->setEnablePending(true) // Flag to enable pending pacts feature (check pact docs for further info) - ->setIncludeWipPactSince('2020-01-30') //Start date of WIP Pacts (check pact docs for further info) - ->setRequestFilter( - function (RequestInterface $r) { - return $r->withHeader('MY_SPECIAL_HEADER', 'my special value'); - } - ); -// Verify that the Consumer 'someConsumer' that is tagged with 'master' is valid. + ->setProviderTags('prod' ,'dev') + ->setProviderBranch('main') + ->setScheme('http') + ->setHost('localhost') + ->setPort(58000) + ->setBasePath('/') + ->setStateChangeUrl(new Uri('http://localhost:58000/change-state')) + ->setBuildUrl(new Uri('http://build.domain.com')) + ->setFilterConsumerNames('someConsumer', 'otherConsumer') + ->setFilterDescription('Send POST to create') + ->setFilterNoState(true) + ->setFilterState('state') + ->setPublishResults(true) + ->setDisableSslVerification(true) + ->setStateChangeAsBody(false) + ->setStateChangeTeardown(true) + ->setRequestTimeout(500); + $verifier = new Verifier($config); -$verifier->verify('someConsumer', 'master'); // The tag is option. If no tag is set it will just grab the latest. -// This will not be reached if the PACT verifier throws an error, otherwise it was successful. -$this->assertTrue(true, 'Pact Verification has failed.'); +$selectors = (new ConsumerVersionSelectors()) + ->addSelector('{"tag":"foo","latest":true}') + ->addSelector('{"tag":"bar","latest":true}'); + +$broker = new Broker(); +$broker + ->setUrl(new Uri('http://localhost')) + ->setUsername('user') + ->setPassword('pass') + ->setToken('token') + ->setEnablePending(true) + ->setIncludeWipPactSince('2020-01-30') + ->setProviderTags(['prod']) + ->setProviderBranch('main') + ->setConsumerVersionSelectors($selectors) + ->setConsumerVersionTags(['dev']); + +$verifier->addBroker($broker); + +$verifyResult = $verifier->verify(); + +$this->assertTrue($verifyResult); ``` -##### Verify All from Pact Broker +##### Verify Files in Directory -This will grab every Pact file associated with the given provider. +This allows local Pact file testing. ```php -public function testPactVerifyAll() +public function testPactVerifyFilesInDirectory() { - $config = new VerifierConfig(); - $config - ->setProviderName('someProvider') // Providers name to fetch. - ->setProviderVersion('1.0.0') // Providers version. - ->setProviderBranch('main') // Providers git branch name. - ->setProviderBaseUrl(new Uri('http://localhost:58000')) // URL of the Provider. - ->setBrokerUri(new Uri('http://localhost')) // URL of the Pact Broker to publish results. - ->setPublishResults(true) // Flag the verifier service to publish the results to the Pact Broker. - ->setEnablePending(true) // Flag to enable pending pacts feature (check pact docs for further info) - ->setIncludeWipPactSince('2020-01-30') //Start date of WIP Pacts (check pact docs for further info) - - // Verify that all consumers of 'someProvider' are valid. - $verifier = new Verifier($config); - $verifier->verifyAll(); - - // This will not be reached if the PACT verifier throws an error, otherwise it was successful. - $this->assertTrue(true, 'Pact Verification has failed.'); + $verifier->addDirectory('C:\SomePath'); + + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); } ``` @@ -283,25 +294,13 @@ public function testPactVerifyAll() This allows local Pact file testing. ```php -public function testPactVerifyAll() +public function testPactVerifyFiles() { - $config = new VerifierConfig(); - $config - ->setProviderName('someProvider') // Providers name to fetch. - ->setProviderVersion('1.0.0') // Providers version. - ->setProviderBranch('main') // Providers git branch name. - ->setProviderBaseUrl(new Uri('http://localhost:58000')) // URL of the Provider. - ->setBrokerUri(new Uri('http://localhost')) // URL of the Pact Broker to publish results. - ->setPublishResults(true); // Flag the verifier service to publish the results to the Pact Broker. - ->setEnablePending(true) // Flag to enable pending pacts feature (check pact docs for further info) - ->setIncludeWipPactSince('2020-01-30') //Start date of WIP Pacts (check pact docs for further info) - - // Verify that the files in the array are valid. - $verifier = new Verifier($config); - $verifier->verifyFiles(['C:\SomePath\consumer-provider.json']); - - // This will not be reached if the PACT verifier throws an error, otherwise it was successful. - $this->assertTrue(true, 'Pact Verification has failed.'); + $verifier->addFile('C:\SomePath\consumer-provider.json'); + + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); } ``` @@ -333,7 +332,6 @@ There is a separate repository with an end to end example for both the 2.X and 3 - [2.2.1 tag](https://github.com/mattermack/pact-php-example/tree/2.2.1) for 2.X examples ## Message support -This feature is preliminary as the Pact community as a whole is flushing this out. The goal is not to test the transmission of an object over a bus but instead vet the contents of the message. While examples included focus on a Rabbit MQ, the exact message queue is irrelevant. Initial comparisons require a certain object type to be created by the Publisher/Producer and the Consumer of the message. This includes a metadata set where you @@ -380,42 +378,16 @@ $this->assertTrue($verifyResult); ### Provider Side Message Validation -This may evolve as we work through this implementation. The provider relies heavily on callbacks. -Some of the complexity lies in a consumer and provider having many messages and states between the each other in a single pact. - -For each message, one needs to provide a single provider state. The name of this provider state must be the key to run -a particular message callback on the provider side. See example\tests\MessageProvider - -1. Create your callbacks and states wrapped in a callable object - 1. The array key is a provider state / given() on the consumer side - 1. It is helpful to wrap the whole thing in a lambda if you need to customize paramaters to be passed in -1. Choose your verification method -1. If nothing explodes, #winning - -```php - - $callbacks = array(); +Handle these requests on your provider: - // a hello message is a provider state / given() on the consumer side - $callbacks["a hello message"] = function() { - $content = new \stdClass(); - $content->text ="Hello Mary"; +1. POST /pact-change-state + 1. Set up your database to meet the expectations of the request + 2. Reset the database to its original state. +2. POST /pact-messages + 1. Return message's content in body + 2. Return message's metadata in header `PACT-MESSAGE-METADATA` - $metadata = array(); - $metadata['queue'] = "myKey"; - - $provider = (new ExampleMessageProvider()) - ->setContents($content) - ->setMetadata($metadata); - - return $provider->Build(); - }; - - $verifier = (new MessageVerifier($config)) - ->setCallbacks($callbacks) - ->verifyFiles([__DIR__ . '/../../output/test_consumer-test_provider.json']); - -``` +[Click here](/example/src/Provider/public/index.php) to see the full sample file. ## Usage for the optional `pact-stub-service` diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md index 0285dbe7..aca9d300 100644 --- a/UPGRADE-9.0.md +++ b/UPGRADE-9.0.md @@ -6,3 +6,47 @@ UPGRADE FROM 8.x to 9.0 * PACT_CORS * PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT * PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC + +* Verifier + * Different pacts sources can be configured via `addXxx` methods + + Example Usage: + ```php + $config = new VerifierConfig(); + $config + ->setPort(8000) + ->setProviderName('someProvider') + ->setProviderVersion('1.0.0'); + + $url = new Url(); + $url + ->setUrl(new Uri('http://localhost')) + ->setProviderName('someProvider') + ->setUsername('user') + ->setPassword('pass') + ->setToken('token'); + + $selectors = (new ConsumerVersionSelectors()) + ->addSelector('{"tag":"foo","latest":true}') + ->addSelector('{"tag":"bar","latest":true}'); + + $broker = new Broker(); + $broker + ->setUrl(new Uri('http://localhost')) + ->setProviderName('someProvider') + ->setUsername('user') + ->setPassword('pass') + ->setToken('token') + ->setConsumerVersionSelectors($selectors); + + $verifier = new Verifier($config); + $verifier + ->addFile('C:\SomePath\consumer-provider.json'); + ->addDirectory('C:\OtherPath'); + ->addUrl($url); + ->addBroker($broker); + + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); + ``` diff --git a/composer.json b/composer.json index 3deedf5e..93b18c8b 100644 --- a/composer.json +++ b/composer.json @@ -24,23 +24,14 @@ "composer/semver": "^1.4.0|^3.2.0", "amphp/amp": "^2.5.1", "amphp/byte-stream": "^1.8", - "amphp/dns": "^1.2.3", - "amphp/hpack": "^3.1.0", - "amphp/http-server": "^2.1", "amphp/log": "^1.1", "amphp/process": "^1.1.1", - "amphp/serialization": "^1.0", - "amphp/socket": "^1.1.3", - "amphp/sync": "^1.4.0", - "amphp/cache": "^1.4.0", - "amphp/windows-registry": "v0.3.3", "guzzlehttp/guzzle": "^6.5.8|^7.4.5", "phpunit/phpunit": ">=8.5.23 <10", "tienvx/composer-downloads-plugin": "^1.1.0" }, "require-dev": { "roave/security-advisories": "dev-latest", - "mockery/mockery": "^1.4.2", "slim/slim": "^4.6", "slim/psr7": "^1.2.0", "friendsofphp/php-cs-fixer": "^3.0", @@ -66,6 +57,9 @@ "MessageProvider\\": [ "example/src/MessageProvider", "example/tests/MessageProvider" + ], + "Provider\\": [ + "example/src/Provider" ] } }, diff --git a/example/README.md b/example/README.md index bb79b88e..ab66a43b 100644 --- a/example/README.md +++ b/example/README.md @@ -16,10 +16,6 @@ All examples could be run within tests. ## Consumer Tests for Message Processing vendor/bin/phpunit -c example/phpunit.message.consumer.xml - -## Provider Verification Tests for Message Processing - - vendor/bin/phpunit -c example/phpunit.message.provider.xml ## All tests together diff --git a/example/pacts/someconsumer-someprovider.json b/example/pacts/someconsumer-someprovider.json index c502e5ba..f9a78bce 100644 --- a/example/pacts/someconsumer-someprovider.json +++ b/example/pacts/someconsumer-someprovider.json @@ -47,7 +47,7 @@ "matchingRules": { "$.body.message": { "match": "regex", - "regex": "(Hello, )[A-Za-z]" + "regex": "(Hello, )[A-Za-z]+" } } }, diff --git a/example/pacts/test_consumer-test_provider.json b/example/pacts/test_consumer-test_provider.json index a015e9ae..ae5e2dab 100644 --- a/example/pacts/test_consumer-test_provider.json +++ b/example/pacts/test_consumer-test_provider.json @@ -47,7 +47,7 @@ ], "metadata": { "pactSpecification": { - "version": "2.0.0" + "version": "3.0.0" } } } diff --git a/example/phpunit.all.xml b/example/phpunit.all.xml index 02a047d5..3cc67cbf 100644 --- a/example/phpunit.all.xml +++ b/example/phpunit.all.xml @@ -13,9 +13,6 @@ ./tests/MessageConsumer - - ./tests/MessageProvider - diff --git a/example/phpunit.message.provider.xml b/example/phpunit.message.provider.xml deleted file mode 100644 index 2844c97d..00000000 --- a/example/phpunit.message.provider.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - ./tests/MessageProvider - - - diff --git a/example/src/Consumer/publish_json_example.php b/example/src/Consumer/publish_json_example.php deleted file mode 100644 index 9e91327b..00000000 --- a/example/src/Consumer/publish_json_example.php +++ /dev/null @@ -1,16 +0,0 @@ - 'someConsumer', - 'provider' => 'someProvider' -]); - -$httpService->publishJson('1.0.0', $json); diff --git a/example/src/MessageProvider/ExampleMessageProvider.php b/example/src/MessageProvider/ExampleMessageProvider.php index d038e8f2..a8f8c60e 100644 --- a/example/src/MessageProvider/ExampleMessageProvider.php +++ b/example/src/MessageProvider/ExampleMessageProvider.php @@ -4,32 +4,20 @@ class ExampleMessageProvider { - /** @var array */ - private $metadata; + private array $metadata; - /** - * @var mixed - */ - private $contents; + private mixed $contents; - public function __construct($metadata = []) + public function __construct(array $metadata = []) { $this->metadata = $metadata; } - /** - * @return array - */ public function getMetadata(): array { return $this->metadata; } - /** - * @param array $metadata - * - * @return ExampleMessageProvider - */ public function setMetadata(array $metadata): self { $this->metadata = $metadata; @@ -37,20 +25,12 @@ public function setMetadata(array $metadata): self return $this; } - /** - * @return mixed - */ - public function getContents() + public function getContents(): mixed { return $this->contents; } - /** - * @param mixed $contents - * - * @return ExampleMessageProvider - */ - public function setContents($contents) + public function setContents(mixed $contents): self { $this->contents = $contents; @@ -59,10 +39,8 @@ public function setContents($contents) /** * Build metadata and content for message - * - * @return string */ - public function Build() + public function Build(): string { $obj = new \stdClass(); $obj->metadata = $this->metadata; diff --git a/example/src/Provider/ExampleProvider.php b/example/src/Provider/ExampleProvider.php new file mode 100644 index 00000000..c7fddb72 --- /dev/null +++ b/example/src/Provider/ExampleProvider.php @@ -0,0 +1,66 @@ +messages = [ + 'an alligator named Mary exists' => [ + 'metadata' => [ + 'queue' => 'wind cries', + 'routing_key' => 'wind cries', + ], + 'contents' => [ + 'text' => 'Hello Mary', + ] + ], + 'footprints dressed in red' => [ + 'metadata' => [ + 'queue' => 'And the clowns have all gone to bed', + 'routing_key' => 'And the clowns have all gone to bed', + ], + 'contents' => [ + 'song' => 'And the wind whispers Mary', + ] + ], + ]; + } + + public function sayHello(string $name): string + { + return "Hello, {$name}"; + } + + public function sayGoodbye(string $name): string + { + return "Goodbye, {$name}"; + } + + public function dispatchMessage(string $description, array $providerStates): ?ExampleMessageProvider + { + if (!isset($this->messages[$description])) { + return null; + } + + return (new ExampleMessageProvider()) + ->setMetadata($this->messages[$description]['metadata']) + ->setContents($this->messages[$description]['contents']); + } + + public function changeSate(string $action, string $state, array $params): void + { + $this->currentState = [ + 'action' => $action, + 'state' => $state, + 'params' => $params, + ]; + } +} diff --git a/example/src/Provider/public/index.php b/example/src/Provider/public/index.php index 2a5406ed..48e8a188 100644 --- a/example/src/Provider/public/index.php +++ b/example/src/Provider/public/index.php @@ -1,5 +1,6 @@ addBodyParsingMiddleware(); -$app->get('/hello/{name}', function (Request $request, Response $response) { +$provider = new ExampleProvider(); + +$app->get('/hello/{name}', function (Request $request, Response $response) use ($provider) { $name = $request->getAttribute('name'); - $response->getBody()->write(\json_encode(['message' => "Hello, {$name}"])); + $response->getBody()->write(\json_encode(['message' => $provider->sayHello($name)])); return $response->withHeader('Content-Type', 'application/json'); }); -$app->get('/goodbye/{name}', function (Request $request, Response $response) { +$app->get('/goodbye/{name}', function (Request $request, Response $response) use ($provider) { $name = $request->getAttribute('name'); - $response->getBody()->write(\json_encode(['message' => "Goodbye, {$name}"])); + $response->getBody()->write(\json_encode(['message' => $provider->sayGoodbye($name)])); return $response->withHeader('Content-Type', 'application/json'); }); +$app->post('/pact-messages', function (Request $request, Response $response) use ($provider) { + $body = $request->getParsedBody(); + $message = $provider->dispatchMessage($body['description'], $body['providerStates']); + if ($message) { + $response->getBody()->write(\json_encode($message->getContents())); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withHeader('Pact-Message-Metadata', \base64_encode(\json_encode($message->getMetadata()))); + } + + return $response; +}); + +$app->post('/pact-change-state', function (Request $request, Response $response) use ($provider) { + $body = $request->getParsedBody(); + $provider->changeSate($body['action'], $body['state'], $body['params']); + + return $response; +}); + $app->run(); diff --git a/example/tests/MessageProvider/ExampleMessageProviderTest.php b/example/tests/MessageProvider/ExampleMessageProviderTest.php deleted file mode 100644 index 21906049..00000000 --- a/example/tests/MessageProvider/ExampleMessageProviderTest.php +++ /dev/null @@ -1,61 +0,0 @@ -text ='Hello Mary'; - - $metadata = []; - $metadata['queue'] = 'myKey'; - - $provider = (new ExampleMessageProvider()) - ->setContents($content) - ->setMetadata($metadata); - - return $provider->Build(); - }; - - $callbacks['footprints dressed in red'] = function () { - $content = new \stdClass(); - $content->song ='And the wind whispers Mary'; - - $metadata = []; - $metadata['queue'] = 'myKey'; - - $provider = (new ExampleMessageProvider()) - ->setContents($content) - ->setMetadata($metadata); - - return $provider->Build(); - }; - - $config = new VerifierConfig(); - $config - ->setProviderName('someProvider') // Providers name to fetch. - ->setPublishResults(false); // Flag the verifier service to publish the results to the Pact Broker. - - // Verify that the Consumer 'someConsumer' that is tagged with 'master' is valid. - $verifier = (new MessageVerifier($config)) - ->setCallbacks($callbacks) - ->verifyFiles([__DIR__ . '/../../pacts/test_consumer-test_provider.json']); - - // This will not be reached if the PACT verifier throws an error, otherwise it was successful. - $this->assertTrue(true, 'Expects to reach true by running verification'); - } -} diff --git a/example/tests/Provider/PactVerifyTest.php b/example/tests/Provider/PactVerifyTest.php index f1927caa..e09a09d7 100644 --- a/example/tests/Provider/PactVerifyTest.php +++ b/example/tests/Provider/PactVerifyTest.php @@ -3,6 +3,7 @@ namespace Provider; use GuzzleHttp\Psr7\Uri; +use PhpPact\Standalone\ProviderVerifier\Model\Config\ProviderTransport; use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; use PhpPact\Standalone\ProviderVerifier\Verifier; use PhpPact\Standalone\Runner\ProcessRunner; @@ -15,7 +16,7 @@ class PactVerifyTest extends TestCase { /** @var ProcessRunner */ - private $processRunner; + private ProcessRunner $processRunner; /** * Run the PHP build-in web server. @@ -27,6 +28,7 @@ protected function setUp(): void $this->processRunner = new ProcessRunner('php', ['-S', 'localhost:7202', '-t', $publicPath]); $this->processRunner->run(); + \usleep(300000); // wait for server to start } /** @@ -43,18 +45,31 @@ protected function tearDown(): void public function testPactVerifyConsumer() { $config = new VerifierConfig(); - $config - ->setProviderName('someProvider') // Providers name to fetch. - ->setProviderVersion('1.0.0') // Providers version. - ->setProviderBranch('main') // Providers git branch - ->setProviderBaseUrl(new Uri('http://localhost:7202')) // URL of the Provider. - ; // Flag the verifier service to publish the results to the Pact Broker. + $config->getProviderInfo() + ->setName('someProvider') // Providers name to fetch. + ->setHost('localhost') + ->setPort(7202); + $config->getProviderState() + ->setStateChangeUrl(new Uri('http://localhost:7202/pact-change-state')) + ; + $config->addProviderTransport( + (new ProviderTransport()) + ->setProtocol(ProviderTransport::MESSAGE_PROTOCOL) + ->setPort(7202) + ->setPath('/pact-messages') + ->setScheme('http') + ); + if ($level = \getenv('PACT_LOGLEVEL')) { + $config->setLogLevel($level); + } // Verify that the Consumer 'someConsumer' that is tagged with 'master' is valid. $verifier = new Verifier($config); - $verifier->verifyFiles([__DIR__ . '/../../pacts/someconsumer-someprovider.json']); + $verifier->addFile(__DIR__ . '/../../pacts/someconsumer-someprovider.json'); + $verifier->addFile(__DIR__ . '/../../pacts/test_consumer-test_provider.json'); - // This will not be reached if the PACT verifier throws an error, otherwise it was successful. - $this->assertTrue(true, 'Pact Verification has failed.'); + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); } } diff --git a/src/PhpPact/Broker/Service/BrokerHttpClient.php b/src/PhpPact/Broker/Service/BrokerHttpClient.php deleted file mode 100644 index 53be68e7..00000000 --- a/src/PhpPact/Broker/Service/BrokerHttpClient.php +++ /dev/null @@ -1,109 +0,0 @@ - - */ - private array $headers; - - /** - * {@inheritdoc} - */ - public function __construct(ClientInterface $httpClient, UriInterface $baseUri, array $headers = []) - { - $this->httpClient = $httpClient; - $this->baseUri = $baseUri; - $this->headers = $headers; - - if (!\array_key_exists('Content-Type', $headers)) { - $this->headers['Content-Type'] = 'application/json'; - } - } - - /** - * {@inheritdoc} - */ - public function publishJson(string $version, string $json): void - { - $array = \json_decode($json, true, 512, JSON_THROW_ON_ERROR); - $consumer = $array['consumer']['name']; - $provider = $array['provider']['name']; - - $uri = $this->baseUri->withPath("/pacts/provider/{$provider}/consumer/{$consumer}/version/{$version}"); - - $this->httpClient->put($uri, [ - 'headers' => $this->headers, - 'body' => $json, - ]); - } - - /** - * {@inheritdoc} - */ - public function tag(string $consumer, string $version, string $tag): void - { - $uri = $this->baseUri->withPath("/pacticipants/{$consumer}/versions/{$version}/tags/{$tag}"); - $this->httpClient->put($uri, [ - 'headers' => $this->headers, - ]); - } - - /** - * {@inheritdoc} - */ - public function getAllConsumerUrls(string $provider, string $version = 'latest'): array - { - if ($version !== 'latest') { - @\trigger_error(\sprintf('The second argument "version" in "%s()" method makes no sense and will be removed in any upcoming major version', __METHOD__), E_USER_DEPRECATED); - } - - $uri = $this->baseUri->withPath("/pacts/provider/{$provider}/latest"); - - $response = $this->httpClient->get($uri, [ - 'headers' => $this->headers, - ]); - - $json = \json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); - - $urls = []; - foreach ($json['_links']['pacts'] as $pact) { - $urls[] = $pact['href']; - } - - return $urls; - } - - /** - * {@inheritdoc} - */ - public function getAllConsumerUrlsForTag(string $provider, string $tag): array - { - $uri = $this->baseUri->withPath("/pacts/provider/{$provider}/latest/{$tag}"); - - $response = $this->httpClient->get($uri, [ - 'headers' => $this->headers, - ]); - - $json = \json_decode($response->getBody()->getContents(), true, 512, JSON_THROW_ON_ERROR); - - $urls = []; - foreach ($json['_links']['pacts'] as $pact) { - $urls[] = $pact['href']; - } - - return $urls; - } -} diff --git a/src/PhpPact/Broker/Service/BrokerHttpClientInterface.php b/src/PhpPact/Broker/Service/BrokerHttpClientInterface.php deleted file mode 100644 index e4baa16a..00000000 --- a/src/PhpPact/Broker/Service/BrokerHttpClientInterface.php +++ /dev/null @@ -1,49 +0,0 @@ - $headers additional headers - */ - public function __construct(ClientInterface $client, UriInterface $baseUri, array $headers); - - /** - * Publish JSON. - * - * @param string $version Consumer version - * @param string $json PACT File JSON - */ - public function publishJson(string $version, string $json): void; - - /** - * Tag a consumer version with a tag. - */ - public function tag(string $consumer, string $version, string $tag): void; - - /** - * Get all Pact urls for the consumer. - * - * @param string $provider provider name - * @param string $version version of the provider - * - * @return array - */ - public function getAllConsumerUrls(string $provider, string $version = 'latest'): array; - - /** - * Get all Pact URLs for a specific tag. - * - * @return array - */ - public function getAllConsumerUrlsForTag(string $provider, string $tag): array; -} diff --git a/src/PhpPact/FFI/Exception/CDataNotCreatedException.php b/src/PhpPact/FFI/Exception/CDataNotCreatedException.php new file mode 100644 index 00000000..2e17f429 --- /dev/null +++ b/src/PhpPact/FFI/Exception/CDataNotCreatedException.php @@ -0,0 +1,9 @@ +items; + } + + public function getSize(): int + { + return $this->size; + } + + /** + * @param array $values + */ + public static function createFrom(array $values): ?self + { + $size = count($values); + if ($size === 0) { + return null; + } + + $items = FFI::new("char*[{$size}]"); + if ($items === null) { + return throw new CDataNotCreatedException(); + } + foreach ($values as $index => $value) { + $length = \strlen($value); + $itemSize = $length + 1; + $item = FFI::new("char[{$itemSize}]", false); + if ($item === null) { + return throw new CDataNotCreatedException(); + } + FFI::memcpy($item, $value, $length); + $items[$index] = $item; // @phpstan-ignore-line + } + + return new self($items, $size); + } + + public function __destruct() + { + for ($i=0; $i < $this->size; $i++) { + FFI::free($this->items[$i]); // @phpstan-ignore-line + } + } +} diff --git a/src/PhpPact/Provider/MessageVerifier.php b/src/PhpPact/Provider/MessageVerifier.php deleted file mode 100644 index 7c5a7113..00000000 --- a/src/PhpPact/Provider/MessageVerifier.php +++ /dev/null @@ -1,208 +0,0 @@ - */ - protected array $callbacks = []; - - /** - * Default host name for the proxy server - */ - protected string $defaultProxyHost = 'localhost'; - - /** - * Default port for the proxy server to listen on - */ - protected int $defaultProxyPort = 7201; - - /** - * floor(provider-verification timeout / this value) = default verificationDelaySec - */ - protected int $defaultDelayFactor = 3; - - /** - * Set the number of seconds to delay the verification test to allow the proxy server to be stood up - * - * By default, it is a third of the provider-verification timeout - */ - protected float $verificationDelaySec; - - private ?LoggerInterface $logger = null; - - public function __construct(VerifierConfigInterface $config) - { - parent::__construct($config); - - $this->callbacks = []; - - $baseUrl = $this->config->getProviderBaseUrl(); - if ($baseUrl === null) { - $config->setProviderBaseUrl(new Uri("http://{$this->defaultProxyHost}:{$this->defaultProxyPort}")); - } - - // default verification delay - $this->setVerificationDelaySec(\floor($config->getProcessIdleTimeout() / $this->defaultDelayFactor)); - } - - /** - * @param array $callbacks - */ - public function setCallbacks(array $callbacks): self - { - $this->callbacks = $callbacks; - - return $this; - } - - /** - * Add an individual call back - * - * @throws \Exception - */ - public function addCallback(string $key, callable $callback): self - { - if (isset($this->callbacks[$key])) { - throw new \Exception("Callback with key ($key) already exists"); - } - - $this->callbacks[$key] = $callback; - - return $this; - } - - public function setVerificationDelaySec(float $verificationDelaySec): self - { - $this->verificationDelaySec = $verificationDelaySec; - - return $this; - } - - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } - - /** - * @throws \Exception - */ - protected function verifyAction(array $arguments): void - { - if (\count($this->callbacks) < 1) { - throw new \Exception('Callback needs to bet set when using message pacts'); - } - - $callbacks = $this->callbacks; - $uri = $this->config->getProviderBaseUrl(); - - $arguments = \array_merge([Scripts::getProviderVerifier()], $arguments); - - /** - * @throws \Amp\Socket\SocketException - * @throws \Error - * @throws \TypeError - * - * @return \Generator - */ - $lambdaLoop = function () use ($callbacks, $arguments, $uri) { - // spin up a server - $url = "{$uri->getHost()}:{$uri->getPort()}"; - $servers = [ - Socket\Server::listen($url) - ]; - - $logger = $this->getLogger(); - - $server = new Server($servers, new CallableRequestHandler(function (Request $request) use ($callbacks) { - if (\count($callbacks) === 1) { - $callback = \array_pop($callbacks); - } else { - $payload = new Payload($request->getBody()); - $requestBody = yield $payload->buffer(); - $requestBody = \json_decode($requestBody); - $description = $requestBody->description; - - $callback = false; - - if (isset($this->callbacks[$description])) { - $callback = $this->callbacks[$description]; - } - - if ($callback === false) { - throw new \Exception("Pacts with multiple states need to have callbacks key'ed by the description"); - } - } - - //@todo pass $providerStates to the call back - $out = \call_user_func($callback); - - // return response should only happen if the \call_user_fun() - return new Response(Status::OK, [ - 'content-type' => 'application/json;', - ], $out); - }), $logger); - - yield $server->start(); - - // delay long enough for the server to be stood up - $delay = (int) ($this->verificationDelaySec * 1000); - - // call the provider-verification cmd - Loop::delay($delay, function () use ($arguments) { - $cmd = \implode(' ', $arguments); - $process = new Process($cmd); - yield $process->start(); - - $payload = new Payload($process->getStdout()); - print yield $payload->buffer(); - - $code = yield $process->join(); - - // if the provider verification cmd returns a non-zero number, the test failed - if ($code !== 0) { - $this->getLogger()->warning(yield $process->getStderr()->read()); - - throw new \Exception("Pact failed to validate. Exit code: {$code}"); - } - - Loop::stop(); - }); - }; - - Loop::run($lambdaLoop); - } - - private function getLogger(): LoggerInterface - { - if (null === $this->logger) { - $logHandler = new StreamHandler(new ResourceOutputStream(\STDOUT)); - $logHandler->setFormatter(new ConsoleFormatter(null, null, true)); - $this->logger = new Logger('server'); - $this->logger->pushHandler($logHandler); - } - - return $this->logger; - } -} diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 02932804..80084fe4 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -31,11 +31,6 @@ public static function getStubService(): string return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-stub-service' . self::getSuffix(); } - public static function getProviderVerifier(): string - { - return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-provider-verifier' . self::getSuffix(); - } - public static function getBroker(): string { return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker' . self::getSuffix(); diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingApp.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingApp.php new file mode 100644 index 00000000..2b7630fa --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingApp.php @@ -0,0 +1,33 @@ +name; + } + + public function setName(?string $name): CallingAppInterface + { + $this->name = $name; + + return $this; + } + + public function getVersion(): ?string + { + return $this->version; + } + + public function setVersion(?string $version): CallingAppInterface + { + $this->version = $version; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingAppInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingAppInterface.php new file mode 100644 index 00000000..bae0ec05 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/CallingAppInterface.php @@ -0,0 +1,14 @@ + + */ + private array $filterConsumerNames = []; + + public function setFilterConsumerNames(array $filterConsumerNames): self + { + $this->filterConsumerNames = []; + foreach ($filterConsumerNames as $filterConsumerName) { + $this->addFilterConsumerName($filterConsumerName); + } + + return $this; + } + + public function addFilterConsumerName(string $filterConsumerName): self + { + $this->filterConsumerNames[] = $filterConsumerName; + + return $this; + } + + public function getFilterConsumerNames(): array + { + return $this->filterConsumerNames; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ConsumerFiltersInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ConsumerFiltersInterface.php new file mode 100644 index 00000000..91402e7b --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ConsumerFiltersInterface.php @@ -0,0 +1,18 @@ + $filterConsumerNames + */ + public function setFilterConsumerNames(array $filterConsumerNames): self; + + public function addFilterConsumerName(string $filterConsumerName): self; + + /** + * @return array + */ + public function getFilterConsumerNames(): array; +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfo.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfo.php new file mode 100644 index 00000000..1f5cdce6 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfo.php @@ -0,0 +1,46 @@ +filterDescription; + } + + public function setFilterDescription(?string $filterDescription): self + { + $this->filterDescription = $filterDescription; + + return $this; + } + + public function getFilterNoState(): bool + { + return $this->filterNoState; + } + + public function setFilterNoState(bool $filterNoState): self + { + $this->filterNoState = $filterNoState; + + return $this; + } + + public function getFilterState(): ?string + { + return $this->filterState; + } + + public function setFilterState(?string $filterState): self + { + $this->filterState = $filterState; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfoInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfoInterface.php new file mode 100644 index 00000000..51420ae8 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/FilterInfoInterface.php @@ -0,0 +1,18 @@ +pluginDir; + } + + public function setPluginDir(?string $pluginDir): self + { + $this->pluginDir = $pluginDir; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfo.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfo.php new file mode 100644 index 00000000..375af81c --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfo.php @@ -0,0 +1,72 @@ +name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getHost(): ?string + { + return $this->host; + } + + public function setHost(string $host): self + { + $this->host = $host; + + return $this; + } + + public function getScheme(): ?string + { + return $this->scheme; + } + + public function setScheme(?string $scheme): self + { + $this->scheme = $scheme; + + return $this; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function setPort(?int $port): self + { + $this->port = $port; + + return $this; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function setPath(?string $path): self + { + $this->path = $path; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfoInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfoInterface.php new file mode 100644 index 00000000..d76d297a --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderInfoInterface.php @@ -0,0 +1,26 @@ +stateChangeUrl; + } + + public function setStateChangeUrl(?UriInterface $stateChangeUrl): self + { + $this->stateChangeUrl = $stateChangeUrl; + + return $this; + } + + public function setStateChangeAsBody(bool $stateChangeAsBody): self + { + $this->stateChangeAsBody = $stateChangeAsBody; + + return $this; + } + + public function isStateChangeAsBody(): bool + { + return $this->stateChangeAsBody; + } + + public function setStateChangeTeardown(bool $stateChangeTeardown): self + { + $this->stateChangeTeardown = $stateChangeTeardown; + + return $this; + } + + public function isStateChangeTeardown(): bool + { + return $this->stateChangeTeardown; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderStateInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderStateInterface.php new file mode 100644 index 00000000..a51f8619 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderStateInterface.php @@ -0,0 +1,20 @@ +protocol; + } + + public function setProtocol(?string $protocol): self + { + $this->protocol = $protocol; + + return $this; + } + + public function getScheme(): ?string + { + return $this->scheme; + } + + public function setScheme(?string $scheme): self + { + $this->scheme = $scheme; + + return $this; + } + + public function getPort(): ?int + { + return $this->port; + } + + public function setPort(?int $port): self + { + $this->port = $port; + + return $this; + } + + public function getPath(): ?string + { + return $this->path; + } + + public function setPath(?string $path): self + { + $this->path = $path; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderTransportInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderTransportInterface.php new file mode 100644 index 00000000..ad269b16 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/ProviderTransportInterface.php @@ -0,0 +1,28 @@ + + */ + private array $providerTags = []; + private string $providerVersion; + private ?UriInterface $buildUrl = null; + private ?string $providerBranch = null; + + public function getProviderTags(): array + { + return $this->providerTags; + } + + public function setProviderTags(array $providerTags): self + { + $this->providerTags = []; + foreach ($providerTags as $providerTag) { + $this->addProviderTag($providerTag); + } + + return $this; + } + + public function addProviderTag(string $providerTag): self + { + $this->providerTags[] = $providerTag; + + return $this; + } + + public function getProviderVersion(): string + { + return $this->providerVersion; + } + + public function setProviderVersion(string $providerVersion): self + { + $this->providerVersion = $providerVersion; + + return $this; + } + + public function getBuildUrl(): ?UriInterface + { + return $this->buildUrl; + } + + public function setBuildUrl(?UriInterface $buildUrl): self + { + $this->buildUrl = $buildUrl; + + return $this; + } + + public function getProviderBranch(): ?string + { + return $this->providerBranch; + } + + public function setProviderBranch(?string $providerBranch): self + { + $this->providerBranch = $providerBranch; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/PublishOptionsInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/PublishOptionsInterface.php new file mode 100644 index 00000000..00429e89 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/PublishOptionsInterface.php @@ -0,0 +1,32 @@ + + */ + public function getProviderTags(): array; + + /** + * @param array $providerTags + */ + public function setProviderTags(array $providerTags): self; + + public function addProviderTag(string $providerTag): self; + + public function getProviderVersion(): string; + + public function setProviderVersion(string $providerVersion): self; + + public function getBuildUrl(): ?UriInterface; + + public function setBuildUrl(UriInterface $buildUrl): self; + + public function getProviderBranch(): ?string; + + public function setProviderBranch(?string $providerBranch): self; +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptions.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptions.php new file mode 100644 index 00000000..5e6c25a8 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptions.php @@ -0,0 +1,33 @@ +disableSslVerification; + } + + public function setDisableSslVerification(bool $disableSslVerification): self + { + $this->disableSslVerification = $disableSslVerification; + + return $this; + } + + public function setRequestTimeout(int $requestTimeout): self + { + $this->requestTimeout = $requestTimeout; + + return $this; + } + + public function getRequestTimeout(): int + { + return $this->requestTimeout; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptionsInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptionsInterface.php new file mode 100644 index 00000000..093fbbf7 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Config/VerificationOptionsInterface.php @@ -0,0 +1,14 @@ +> */ - private array $selectors; + /** @var array */ + private array $selectors = []; /** * @param array $selectors @@ -30,37 +30,31 @@ public function addSelector(string $selector): self return $this; } - #[\ReturnTypeWillChange] - public function current() + public function current(): string { return $this->selectors[$this->position]; } - #[\ReturnTypeWillChange] - public function next() + public function next(): void { ++$this->position; } - #[\ReturnTypeWillChange] public function key(): int { return $this->position; } - #[\ReturnTypeWillChange] public function valid(): bool { return isset($this->selectors[$this->position]); } - #[\ReturnTypeWillChange] - public function rewind() + public function rewind(): void { $this->position = 0; } - #[\ReturnTypeWillChange] public function count(): int { return \count($this->selectors); diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Broker.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Broker.php new file mode 100644 index 00000000..8b1cc29d --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Broker.php @@ -0,0 +1,118 @@ + + */ + private array $providerTags = []; + protected ?string $providerBranch = null; + protected ConsumerVersionSelectors $consumerVersionSelectors; + /** + * @var array + */ + private array $consumerVersionTags = []; + + public function __construct() + { + $this->consumerVersionSelectors = new ConsumerVersionSelectors(); + } + + public function isEnablePending(): bool + { + return $this->enablePending; + } + + public function setEnablePending(bool $enablePending): self + { + $this->enablePending = $enablePending; + + return $this; + } + + public function setIncludeWipPactSince(?string $date): self + { + $this->wipPactSince = $date; + + return $this; + } + + public function getIncludeWipPactSince(): ?string + { + return $this->wipPactSince; + } + + public function getProviderTags(): array + { + return $this->providerTags; + } + + public function setProviderTags(array $providerTags): self + { + $this->providerTags = []; + foreach ($providerTags as $providerTag) { + $this->addProviderTag($providerTag); + } + + return $this; + } + + public function addProviderTag(string $providerTag): self + { + $this->providerTags[] = $providerTag; + + return $this; + } + + public function getProviderBranch(): ?string + { + return $this->providerBranch; + } + + public function setProviderBranch(?string $providerBranch): self + { + $this->providerBranch = $providerBranch; + + return $this; + } + + public function getConsumerVersionSelectors(): ConsumerVersionSelectors + { + return $this->consumerVersionSelectors; + } + + public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): self + { + $this->consumerVersionSelectors = $selectors; + + return $this; + } + + public function getConsumerVersionTags(): array + { + return $this->consumerVersionTags; + } + + public function setConsumerVersionTags(array $consumerVersionTags): self + { + $this->consumerVersionTags = []; + foreach ($consumerVersionTags as $consumerVersionTag) { + $this->addConsumerVersionTag($consumerVersionTag); + } + + return $this; + } + + public function addConsumerVersionTag(string $consumerVersionTag): self + { + $this->consumerVersionTags[] = $consumerVersionTag; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerInterface.php new file mode 100644 index 00000000..45419b23 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerInterface.php @@ -0,0 +1,52 @@ + + */ + public function getProviderTags(): array; + + /** + * @param array $providerTags + */ + public function setProviderTags(array $providerTags): self; + + public function addProviderTag(string $providerTag): self; + + public function getProviderBranch(): ?string; + + public function setProviderBranch(?string $providerBranch): self; + + public function getConsumerVersionSelectors(): ConsumerVersionSelectors; + + public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): self; + + /** + * @return array + */ + public function getConsumerVersionTags(): array; + + /** + * @param array $consumerVersionTags + */ + public function setConsumerVersionTags(array $consumerVersionTags): self; + + public function addConsumerVersionTag(string $consumerVersionTag): self; +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Url.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Url.php new file mode 100644 index 00000000..eb3dd1fb --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Url.php @@ -0,0 +1,61 @@ +url; + } + + public function setUrl(UriInterface $url): self + { + $this->url = $url; + + return $this; + } + + public function getToken(): ?string + { + return $this->token; + } + + public function setToken(?string $token): self + { + $this->token = $token; + + return $this; + } + + public function getUsername(): ?string + { + return $this->username; + } + + public function setUsername(string $username): self + { + $this->username = $username; + + return $this; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function setPassword(string $password): self + { + $this->password = $password; + + return $this; + } +} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlInterface.php new file mode 100644 index 00000000..cfc8effa --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlInterface.php @@ -0,0 +1,24 @@ + - */ - private array $providerVersionTag = []; - - private bool $publishResults = false; - - private ?UriInterface $brokerUri = null; - - private ?string $brokerToken = null; - - private ?string $brokerUsername = null; - - private ?string $brokerPassword = null; + private CallingAppInterface $callingApp; + private ProviderInfoInterface $providerInfo; /** - * @var array + * @var array */ - private array $customProviderHeaders = []; - - private bool $verbose = false; - - private ?string $logDirectory = null; - - private ?string $format = null; - - private int $processTimeout = 60; - - private int $processIdleTimeout = 10; + private array $providerTransports = []; - private bool $enablePending = false; - - private ?string $wipPactSince = null; - - /** - * @var array - */ - private array $consumerVersionTag = []; - - private ConsumerVersionSelectors $consumerVersionSelectors; - - /** @var null|callable */ - private $requestFilter = null; + private FilterInfoInterface $filterInfo; + private ProviderStateInterface $providerState; + private VerificationOptionsInterface $verificationOptions; + private ?PublishOptionsInterface $publishOptions = null; + private ConsumerFiltersInterface $consumerFilters; public function __construct() { - $this->consumerVersionSelectors = new ConsumerVersionSelectors(); - } - - /** - * {@inheritdoc} - */ - public function getProviderBaseUrl(): ?UriInterface - { - return $this->providerBaseUrl; - } - - /** - * {@inheritdoc} - */ - public function setProviderBaseUrl(UriInterface $providerBaseUrl): VerifierConfigInterface - { - $this->providerBaseUrl = $providerBaseUrl; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProviderStatesSetupUrl(): ?string - { - return $this->providerStatesSetupUrl; - } - - /** - * {@inheritdoc} - */ - public function setProviderStatesSetupUrl(string $providerStatesSetupUrl): VerifierConfigInterface - { - $this->providerStatesSetupUrl = $providerStatesSetupUrl; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProviderName(): ?string - { - return $this->providerName; - } - - /** - * {@inheritdoc} - */ - public function setProviderName(string $providerName): VerifierConfigInterface - { - $this->providerName = $providerName; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProviderVersion(): ?string - { - return $this->providerVersion; - } - - /** - * {@inheritdoc} - */ - public function setProviderVersion(string $providerVersion): VerifierConfigInterface - { - $this->providerVersion = $providerVersion; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProviderVersionTag(): array - { - return $this->providerVersionTag; - } - - /** - * {@inheritdoc} - */ - public function setProviderVersionTag(string $providerVersionTag): VerifierConfigInterface - { - return $this->addProviderVersionTag($providerVersionTag); - } - - /** - * {@inheritdoc} - */ - public function getConsumerVersionTag(): array - { - return $this->consumerVersionTag; - } - - /** - * {@inheritdoc} - */ - public function addConsumerVersionTag(string $consumerVersionTag): VerifierConfigInterface - { - $this->consumerVersionTag[] = $consumerVersionTag; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function addProviderVersionTag(string $providerVersionTag): VerifierConfigInterface - { - $this->providerVersionTag[] = $providerVersionTag; - - return $this; - } - - public function setConsumerVersionTag(string $consumerVersionTag): VerifierConfigInterface - { - return $this->addConsumerVersionTag($consumerVersionTag); - } - - public function getConsumerVersionSelectors(): ConsumerVersionSelectors - { - return $this->consumerVersionSelectors; - } - - public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): VerifierConfigInterface - { - $this->consumerVersionSelectors = $selectors; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function isPublishResults(): bool - { - return $this->publishResults; + $this->callingApp = new CallingApp(); + $this->providerInfo = new ProviderInfo(); + $this->filterInfo = new FilterInfo(); + $this->providerState = new ProviderState(); + $this->verificationOptions = new VerificationOptions(); + $this->consumerFilters = new ConsumerFilters(); } - /** - * {@inheritdoc} - */ - public function setPublishResults(bool $publishResults): VerifierConfigInterface + public function setCallingApp(CallingAppInterface $callingApp): self { - $this->publishResults = $publishResults; + $this->callingApp = $callingApp; return $this; } - /** - * {@inheritdoc} - */ - public function getBrokerUri(): ?UriInterface + public function getCallingApp(): CallingAppInterface { - return $this->brokerUri; + return $this->callingApp; } - /** - * {@inheritdoc} - */ - public function setBrokerUri(UriInterface $brokerUri): VerifierConfigInterface + public function setProviderInfo(ProviderInfoInterface $providerInfo): self { - $this->brokerUri = $brokerUri; + $this->providerInfo = $providerInfo; return $this; } - /** - * {@inheritdoc}} - */ - public function getBrokerToken(): ?string + public function getProviderInfo(): ProviderInfoInterface { - return $this->brokerToken; - } - - /** - * {@inheritdoc } - */ - public function setBrokerToken(?string $brokerToken): VerifierConfigInterface - { - $this->brokerToken = $brokerToken; - - return $this; + return $this->providerInfo; } /** * {@inheritdoc} */ - public function getBrokerUsername(): ?string + public function setProviderTransports(array $providerTransports): self { - return $this->brokerUsername; - } - - /** - * {@inheritdoc} - */ - public function setBrokerUsername(string $brokerUsername): VerifierConfigInterface - { - $this->brokerUsername = $brokerUsername; + $this->providerTransports = []; + foreach ($providerTransports as $providerTransport) { + $this->addProviderTransport($providerTransport); + } return $this; } - /** - * {@inheritdoc} - */ - public function getBrokerPassword(): ?string + public function addProviderTransport(ProviderTransportInterface $providerTransport): self { - return $this->brokerPassword; - } - - /** - * {@inheritdoc} - */ - public function setBrokerPassword(string $brokerPassword): self - { - $this->brokerPassword = $brokerPassword; + $this->providerTransports[] = $providerTransport; return $this; } @@ -294,169 +95,73 @@ public function setBrokerPassword(string $brokerPassword): self /** * {@inheritdoc} */ - public function getCustomProviderHeaders(): array - { - return $this->customProviderHeaders; - } - - /** - * {@inheritdoc} - */ - public function setCustomProviderHeaders(array $customProviderHeaders): VerifierConfigInterface + public function getProviderTransports(): array { - $this->customProviderHeaders = $customProviderHeaders; - - return $this; + return $this->providerTransports; } - public function addCustomProviderHeader(string $name, string $value): VerifierConfigInterface + public function setFilterInfo(FilterInfoInterface $filterInfo): self { - $this->customProviderHeaders[] = "$name: $value"; + $this->filterInfo = $filterInfo; return $this; } - /** - * {@inheritdoc} - */ - public function isVerbose(): bool + public function getFilterInfo(): FilterInfoInterface { - return $this->verbose; + return $this->filterInfo; } - /** - * {@inheritdoc} - */ - public function setVerbose(bool $verbose): VerifierConfigInterface + public function setProviderState(ProviderStateInterface $providerState): self { - $this->verbose = $verbose; + $this->providerState = $providerState; return $this; } - /** - * {@inheritdoc} - */ - public function getLogDirectory(): ?string + public function getProviderState(): ProviderStateInterface { - return $this->logDirectory; + return $this->providerState; } - /** - * {@inheritdoc} - */ - public function setLogDirectory(string $log): VerifierConfigInterface - { - $this->logDirectory = $log; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getFormat(): ?string - { - return $this->format; - } - - /** - * {@inheritdoc} - */ - public function setFormat(string $format): VerifierConfigInterface + public function setPublishOptions(?PublishOptionsInterface $publishOptions): self { - $this->format = $format; + $this->publishOptions = $publishOptions; return $this; } - public function setProcessTimeout(int $timeout): VerifierConfigInterface - { - $this->processTimeout = $timeout; - - return $this; - } - - public function setProcessIdleTimeout(int $timeout): VerifierConfigInterface - { - $this->processIdleTimeout = $timeout; - - return $this; - } - - public function getProcessTimeout(): int - { - return $this->processTimeout; - } - - public function getProcessIdleTimeout(): int - { - return $this->processIdleTimeout; - } - - /** - * {@inheritdoc} - */ - public function isEnablePending(): bool + public function getPublishOptions(): ?PublishOptionsInterface { - return $this->enablePending; + return $this->publishOptions; } - /** - * {@inheritdoc} - */ - public function setEnablePending(bool $pending): VerifierConfigInterface + public function isPublishResults(): bool { - $this->enablePending = $pending; - - return $this; + return $this->publishOptions !== null; } - /** - * {@inheritdoc} - */ - public function setIncludeWipPactSince(string $date): VerifierConfigInterface + public function setConsumerFilters(ConsumerFiltersInterface $consumerFilters): self { - $this->wipPactSince = $date; + $this->consumerFilters = $consumerFilters; return $this; } - /** - * {@inheritdoc} - */ - public function getIncludeWipPactSince(): ?string - { - return $this->wipPactSince; - } - - public function getRequestFilter(): ?callable + public function getConsumerFilters(): ConsumerFiltersInterface { - return $this->requestFilter; + return $this->consumerFilters; } - public function setRequestFilter(callable $requestFilter): VerifierConfigInterface + public function setVerificationOptions(VerificationOptionsInterface $verificationOptions): self { - $this->requestFilter = $requestFilter; + $this->verificationOptions = $verificationOptions; return $this; } - /** - * {@inheritdoc} - */ - public function setProviderBranch(string $providerBranch): VerifierConfigInterface - { - $this->providerBranch = $providerBranch; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProviderBranch(): ?string + public function getVerificationOptions(): VerificationOptionsInterface { - return $this->providerBranch; + return $this->verificationOptions; } } diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php index 9b748f9e..6f13cb83 100644 --- a/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php @@ -2,223 +2,64 @@ namespace PhpPact\Standalone\ProviderVerifier\Model; -use Psr\Http\Message\UriInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\CallingAppInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\ConsumerFiltersInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\FilterInfoInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\ProviderInfoInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\ProviderStateInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\ProviderTransportInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\PublishOptionsInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Config\VerificationOptionsInterface; -/** - * Configuration to use with the verifier server. - */ interface VerifierConfigInterface { - /** - * @return null|UriInterface providers base url - */ - public function getProviderBaseUrl(): ?UriInterface; + public function setCallingApp(CallingAppInterface $callingApp): self; - /** - * @param UriInterface $providerBaseUrl providers base url - */ - public function setProviderBaseUrl(UriInterface $providerBaseUrl): self; + public function getCallingApp(): CallingAppInterface; - /** - * @return null|string Base URL to setup the provider states at - */ - public function getProviderStatesSetupUrl(): ?string; + public function setProviderInfo(ProviderInfoInterface $providerInfo): self; - /** - * @param string $providerStatesSetupUrl Base URL to setup the provider states at - */ - public function setProviderStatesSetupUrl(string $providerStatesSetupUrl): self; + public function getProviderInfo(): ProviderInfoInterface; /** - * @return null|string name of the provider + * @param array $providerTransports */ - public function getProviderName(): ?string; + public function setProviderTransports(array $providerTransports): self; - /** - * @param string $providerName Name of the provider - */ - public function setProviderName(string $providerName): self; + public function addProviderTransport(ProviderTransportInterface $providerTransport): self; /** - * @return null|string providers version + * @return array */ - public function getProviderVersion(): ?string; + public function getProviderTransports(): array; - /** - * @param string $providerVersion providers version - */ - public function setProviderVersion(string $providerVersion): self; + public function setFilterInfo(FilterInfoInterface $filterInfo): self; - /** - * @param string $providerBranch providers branch name - */ - public function setProviderBranch(string $providerBranch): self; - - /** - * @return array providers version tag - */ - public function getProviderVersionTag(): array; - - /** - * @return null|string providers branch name - */ - public function getProviderBranch(): ?string; - - /** - * @param string $providerVersionTag providers version tag - */ - public function setProviderVersionTag(string $providerVersionTag): self; - - /** - * @return array consumers version tag - */ - public function getConsumerVersionTag(): array; + public function getFilterInfo(): FilterInfoInterface; - /** - * @param string $consumerVersionTag consumers version tag - */ - public function addConsumerVersionTag(string $consumerVersionTag): self; + public function setProviderState(ProviderStateInterface $providerState): self; - /** - * @param string $providerVersionTag provider version tag - */ - public function addProviderVersionTag(string $providerVersionTag): self; + public function getProviderState(): ProviderStateInterface; - public function getConsumerVersionSelectors(): ConsumerVersionSelectors; + public function setPublishOptions(?PublishOptionsInterface $publishOptions): self; - /** - * @param ConsumerVersionSelectors $selectors Consumer version selectors - */ - public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): self; + public function getPublishOptions(): ?PublishOptionsInterface; - /** - * @return bool are results going to be published - */ public function isPublishResults(): bool; - /** - * @param bool $publishResults flag to publish results - */ - public function setPublishResults(bool $publishResults): self; - - /** - * @return null|UriInterface url to the pact broker - */ - public function getBrokerUri(): ?UriInterface; - - /** - * @param UriInterface $brokerUri uri to the pact broker - */ - public function setBrokerUri(UriInterface $brokerUri): self; - - /** - * @return null|string token for the pact broker - */ - public function getBrokerToken(): ?string; - - /** - * @param null|string $brokerToken token for the pact broker - */ - public function setBrokerToken(?string $brokerToken): self; - - /** - * @return null|string username for the pact broker if secured - */ - public function getBrokerUsername(): ?string; - - /** - * @param string $brokerUsername username for the pact broker if secured - */ - public function setBrokerUsername(string $brokerUsername): self; - - /** - * @return null|string password for the pact broker if secured - */ - public function getBrokerPassword(): ?string; - - /** - * @param string $brokerPassword password for the pact broker if secured - */ - public function setBrokerPassword(string $brokerPassword): self; - - /** - * @return array custom headers for the request to the provider such as authorization - */ - public function getCustomProviderHeaders(): array; - - /** - * @param array $customProviderHeaders custom headers for the requests to the provider such as authorization - */ - public function setCustomProviderHeaders(array $customProviderHeaders): self; - - public function addCustomProviderHeader(string $name, string $value): self; - - /** - * @return bool is verbosity level increased - */ - public function isVerbose(): bool; - - /** - * @param bool $verbose increase verbosity level - */ - public function setVerbose(bool $verbose): self; - - /** - * @return null|string set the directory for the pact.log file - */ - public function getLogDirectory(): ?string; - - /** - * @param string $log set the directory for the pact.log file - */ - public function setLogDirectory(string $log): self; + public function setConsumerFilters(ConsumerFiltersInterface $consumerFilters): self; - /** - * @return null|string RSpec formatter. Defaults to custom Pact formatter. json and RspecJunitFormatter may also be used - */ - public function getFormat(): ?string; + public function getConsumerFilters(): ConsumerFiltersInterface; - /** - * @param string $format RSpec formatter. Defaults to custom Pact formatter. json and RspecJunitFormatter may also be used - */ - public function setFormat(string $format): self; - - public function setProcessTimeout(int $timeout): self; + public function setVerificationOptions(VerificationOptionsInterface $verificationOptions): self; - public function setProcessIdleTimeout(int $timeout): self; + public function getVerificationOptions(): VerificationOptionsInterface; - public function getProcessTimeout(): int; + public function getLogLevel(): ?string; - public function getProcessIdleTimeout(): int; + public function setLogLevel(string $logLevel): self; - /** - * @param bool $pending allow pacts which are in pending state to be verified without causing the overall task to fail - */ - public function setEnablePending(bool $pending): self; + public function getPluginDir(): ?string; - /** - * @return bool is enabled pending pacts - */ - public function isEnablePending(): bool; - - /** - * @param string $date Includes pact marked as WIP since this date. - * Accepted formats: Y-m-d (2020-01-30) or c (ISO 8601 date 2004-02-12T15:19:21+00:00) - */ - public function setIncludeWipPactSince(string $date): self; - - /** - * @return null|string get start date of included WIP Pacts - */ - public function getIncludeWipPactSince(); - - /** - * @return null|callable - */ - public function getRequestFilter(): ?callable; - - /** - * @param callable $requestFilter - */ - public function setRequestFilter(callable $requestFilter): self; + public function setPluginDir(?string $pluginDir): self; } diff --git a/src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php b/src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php deleted file mode 100644 index 257e44a6..00000000 --- a/src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php +++ /dev/null @@ -1,30 +0,0 @@ -providerVerifier = $providerVerifier ?: Scripts::getProviderVerifier(); - } - - /** - * @param array $arguments - */ - public function createRunner(array $arguments, LoggerInterface $logger = null): ProcessRunner - { - $processRunner = new ProcessRunner($this->providerVerifier, $arguments); - if ($logger) { - $processRunner->setLogger($logger); - } - - return $processRunner; - } -} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Verifier.php b/src/PhpPact/Standalone/ProviderVerifier/Verifier.php index 2bc678b3..d5b6d900 100644 --- a/src/PhpPact/Standalone/ProviderVerifier/Verifier.php +++ b/src/PhpPact/Standalone/ProviderVerifier/Verifier.php @@ -2,264 +2,223 @@ namespace PhpPact\Standalone\ProviderVerifier; -use GuzzleHttp\HandlerStack; -use GuzzleHttp\Middleware; -use PhpPact\Broker\Service\BrokerHttpClient; -use PhpPact\Broker\Service\BrokerHttpClientInterface; -use PhpPact\Http\GuzzleClient; +use FFI\CData; +use PhpPact\FFI\Client; +use PhpPact\FFI\ClientInterface; +use PhpPact\FFI\Model\ArrayData; +use PhpPact\Standalone\ProviderVerifier\Model\Source\BrokerInterface; +use PhpPact\Standalone\ProviderVerifier\Model\Source\UrlInterface; use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfigInterface; -/** - * Wrapper for the Ruby Standalone Verifier service. - */ class Verifier { - protected int $processTimeout = 60; + protected ClientInterface $client; + protected CData $handle; - protected int $processIdleTimeout = 10; - - protected VerifierConfigInterface $config; - - protected ?BrokerHttpClientInterface $brokerHttpClient = null; - - protected ?VerifierProcess $verifierProcess = null; - - public function __construct( - VerifierConfigInterface $config, - VerifierProcess $verifierProcess = null, - BrokerHttpClient $brokerHttpClient = null - ) { - $this->config = $config; - $this->verifierProcess = $verifierProcess ?: new VerifierProcess(); - $this->processTimeout = $config->getProcessTimeout(); - $this->processIdleTimeout = $config->getProcessIdleTimeout(); - - if ($brokerHttpClient) { - $this->brokerHttpClient = $brokerHttpClient; - } + public function __construct(VerifierConfigInterface $config) + { + $this->client = new Client(); + $this + ->newHandle($config) + ->setProviderInfo($config) + ->setProviderTransports($config) + ->setFilterInfo($config) + ->setProviderState($config) + ->setVerificationOptions($config) + ->setPublishOptions($config) + ->setConsumerFilters($config) + ->setLogLevel($config) + ->setPluginDir($config); } - /** - * @throws \Exception - * - * @return array parameters to be passed into the process - */ - public function getArguments(): array + private function newHandle(VerifierConfigInterface $config): self { - $parameters = []; - - if ($this->config->getProviderName() !== null) { - $parameters[] = "--provider='{$this->config->getProviderName()}'"; - } - - if ($this->config->getProviderBaseUrl() !== null) { - $parameters[] = "--provider-base-url={$this->config->getProviderBaseUrl()}"; - } - - if ($this->config->getProviderVersion() !== null) { - $parameters[] = "--provider-app-version={$this->config->getProviderVersion()}"; - } - - if ($this->config->getProviderBranch() !== null) { - $parameters[] = "--provider-version-branch={$this->config->getProviderBranch()}"; - } - - if (\count($this->config->getConsumerVersionTag()) > 0) { - foreach ($this->config->getConsumerVersionTag() as $tag) { - $parameters[] = "--consumer-version-tag={$tag}"; - } - } + $this->handle = $this->client->call( + 'pactffi_verifier_new_for_application', + $config->getCallingApp()->getName(), + $config->getCallingApp()->getVersion() + ); - if (\count($this->config->getConsumerVersionSelectors()) > 0) { - foreach ($this->config->getConsumerVersionSelectors() as $selector) { - $parameters[] = "--consumer-version-selector='{$selector}'"; - } - } + return $this; + } - if (\count($this->config->getProviderVersionTag()) > 0) { - foreach ($this->config->getProviderVersionTag() as $tag) { - $parameters[] = "--provider-version-tag={$tag}"; - } - } + private function setProviderInfo(VerifierConfigInterface $config): self + { + $this->client->call( + 'pactffi_verifier_set_provider_info', + $this->handle, + $config->getProviderInfo()->getName(), + $config->getProviderInfo()->getScheme(), + $config->getProviderInfo()->getHost(), + $config->getProviderInfo()->getPort(), + $config->getProviderInfo()->getPath() + ); - if ($this->config->getProviderStatesSetupUrl() !== null) { - $parameters[] = "--provider-states-setup-url={$this->config->getProviderStatesSetupUrl()}"; - } + return $this; + } - if ($this->config->isPublishResults() === true) { - $parameters[] = '--publish-verification-results'; + private function setProviderTransports(VerifierConfigInterface $config): self + { + foreach ($config->getProviderTransports() as $transport) { + $this->client->call( + 'pactffi_verifier_add_provider_transport', + $this->handle, + $transport->getProtocol(), + $transport->getPort(), + $transport->getPath(), + $transport->getScheme() + ); } - if ($this->config->getBrokerToken() !== null) { - $parameters[] = "--broker-token={$this->config->getBrokerToken()}"; - } + return $this; + } - if ($this->config->getBrokerUsername() !== null) { - $parameters[] = "--broker-username={$this->config->getBrokerUsername()}"; - } + private function setFilterInfo(VerifierConfigInterface $config): self + { + $this->client->call( + 'pactffi_verifier_set_provider_state', + $this->handle, + $config->getProviderState()->getStateChangeUrl() ? (string) $config->getProviderState()->getStateChangeUrl() : null, + $config->getProviderState()->isStateChangeTeardown(), + $config->getProviderState()->isStateChangeAsBody() + ); - if ($this->config->getBrokerPassword() !== null) { - $parameters[] = "--broker-password={$this->config->getBrokerPassword()}"; - } + return $this; + } - if (count($this->config->getCustomProviderHeaders()) > 0) { - foreach ($this->config->getCustomProviderHeaders() as $customProviderHeader) { - $parameters[] = "--custom-provider-header=\"{$customProviderHeader}\""; - } - } + private function setProviderState(VerifierConfigInterface $config): self + { + $this->client->call( + 'pactffi_verifier_set_filter_info', + $this->handle, + $config->getFilterInfo()->getFilterDescription(), + $config->getFilterInfo()->getFilterState(), + $config->getFilterInfo()->getFilterNoState() + ); - if ($this->config->isVerbose() === true) { - $parameters[] = '--verbose=VERBOSE'; - } + return $this; + } - if ($this->config->getLogDirectory() !== null) { - $parameters[] = "--log-dir={$this->config->getLogDirectory()}"; - } + private function setVerificationOptions(VerifierConfigInterface $config): self + { + $this->client->call( + 'pactffi_verifier_set_verification_options', + $this->handle, + $config->getVerificationOptions()->isDisableSslVerification(), + $config->getVerificationOptions()->getRequestTimeout() + ); - if ($this->config->getFormat() !== null) { - $parameters[] = "--format={$this->config->getFormat()}"; - } + return $this; + } - if ($this->config->isEnablePending() === true) { - $parameters[] = '--enable-pending'; + private function setPublishOptions(VerifierConfigInterface $config): self + { + if ($config->isPublishResults()) { + $providerTags = ArrayData::createFrom($config->getPublishOptions()->getProviderTags()); + $this->client->call( + 'pactffi_verifier_set_publish_options', + $this->handle, + $config->getPublishOptions()->getProviderVersion(), + $config->getPublishOptions()->getBuildUrl(), + $providerTags?->getItems(), + $providerTags?->getSize(), + $config->getPublishOptions()->getProviderBranch() + ); } - if ($this->config->getIncludeWipPactSince() !== null) { - $parameters[] = "--include-wip-pacts-since={$this->config->getIncludeWipPactSince()}"; - } + return $this; + } - if ($this->config->getBrokerUri() !== null) { - $parameters[] = "--pact-broker-base-url={$this->config->getBrokerUri()->__toString()}"; - } + private function setConsumerFilters(VerifierConfigInterface $config): self + { + $filterConsumerNames = ArrayData::createFrom($config->getConsumerFilters()->getFilterConsumerNames()); + $this->client->call( + 'pactffi_verifier_set_consumer_filters', + $this->handle, + $filterConsumerNames?->getItems(), + $filterConsumerNames?->getSize() + ); - return $parameters; + return $this; } - /** - * Make the request to the PACT Verifier Service to run a Pact file tests from the Pact Broker. - * - * @param string $consumerName name of the consumer to be compared against - * @param null|string $tag optional tag of the consumer such as a branch name - * @param null|string $consumerVersion optional specific version of the consumer; this is overridden by tag - * @throws \Exception - */ - public function verify(string $consumerName, string $tag = null, string $consumerVersion = null): self + private function setLogLevel(VerifierConfigInterface $config): self { - $path = "/pacts/provider/{$this->config->getProviderName()}/consumer/{$consumerName}/"; - - if ($tag) { - $path .= "latest/{$tag}/"; - } elseif ($consumerVersion) { - $path .= "version/{$consumerVersion}/"; - } else { - $path .= 'latest/'; + if ($logLevel = $config->getLogLevel()) { + $this->client->call('pactffi_init_with_log_level', $logLevel); } - $uri = $this->config->getBrokerUri()->withPath($path); - - $arguments = \array_merge([$uri->__toString()], $this->getArguments()); - - $this->verifyAction($arguments); - return $this; } - /** - * Provides a way to validate local Pact JSON files. - * - * @param array $files paths to pact json files - * @throws \Exception - */ - public function verifyFiles(array $files): self + private function setPluginDir(VerifierConfigInterface $config): self { - $arguments = \array_merge($files, $this->getArguments()); - - $this->verifyAction($arguments); + if ($pluginDir = $config->getPluginDir()) { + \putenv("PACT_PLUGIN_DIR={$pluginDir}"); + } return $this; } - /** - * Verify all Pacts from the Pact Broker are valid for the Provider. - * @throws \Exception - */ - public function verifyAll(): void + public function addFile(string $file): self { - $arguments = $this->getBrokerHttpClient()->getAllConsumerUrls($this->config->getProviderName()); + $this->client->call('pactffi_verifier_add_file_source', $this->handle, $file); - $arguments = \array_merge($arguments, $this->getArguments()); - - $this->verifyAction($arguments); + return $this; } - /** - * Verify all PACTs for a given tag. - * @throws \Exception - */ - public function verifyAllForTag(string $tag): void + public function addDirectory(string $directory): self { - $arguments = $this->getBrokerHttpClient()->getAllConsumerUrlsForTag($this->config->getProviderName(), $tag); + $this->client->call('pactffi_verifier_add_directory_source', $this->handle, $directory); - $arguments = \array_merge($arguments, $this->getArguments()); - - $this->verifyAction($arguments); + return $this; } - /** - * Verify all PACTs that match the VerifierConfig - * @throws \Exception - */ - public function verifyFromConfig(): void + public function addUrl(UrlInterface $url): self { - $this->verifyAction($this->getArguments()); - } + $this->client->call( + 'pactffi_verifier_url_source', + $this->handle, + (string) $url->getUrl(), + $url->getUsername(), + $url->getPassword(), + $url->getToken() + ); - /** - * @return array - */ - public function getTimeoutValues(): array - { - return ['process_timeout' => $this->processTimeout, 'process_idle_timeout' => $this->processIdleTimeout]; + return $this; } - /** - * Trigger execution of the Pact Verifier Service. - * - * @param array $arguments - * @throws \Exception - */ - protected function verifyAction(array $arguments): void + public function addBroker(BrokerInterface $broker): self { - $this->verifierProcess->run($arguments, $this->processTimeout, $this->processIdleTimeout); + $providerTags = ArrayData::createFrom($broker->getProviderTags()); + $consumerVersionSelectors = ArrayData::createFrom(iterator_to_array($broker->getConsumerVersionSelectors())); + $consumerVersionTags = ArrayData::createFrom($broker->getConsumerVersionTags()); + $this->client->call( + 'pactffi_verifier_broker_source_with_selectors', + $this->handle, + (string) $broker->getUrl(), + $broker->getUsername(), + $broker->getPassword(), + $broker->getToken(), + $broker->isEnablePending(), + $broker->getIncludeWipPactSince(), + $providerTags?->getItems(), + $providerTags?->getSize(), + $broker->getProviderBranch(), + $consumerVersionSelectors?->getItems(), + $consumerVersionSelectors?->getSize(), + $consumerVersionTags?->getItems(), + $consumerVersionTags?->getSize() + ); + + return $this; } - protected function getBrokerHttpClient(): BrokerHttpClientInterface + public function verify(): bool { - if (!$this->brokerHttpClient) { - $user = $this->config->getBrokerUsername(); - $password = $this->config->getBrokerPassword(); - $token = $this->config->getBrokerToken(); - $reqFilter = $this->config->getRequestFilter(); - - $config = []; - if (\strlen($token) > 0) { - $config = ['headers' => ['Authorization' => 'Bearer ' . $token]]; - } elseif ($user && $password) { - $config = ['auth' => [$user, $password]]; - } - if (\is_callable($reqFilter)) { - $stack = HandlerStack::create(); - $stack->push(Middleware::mapRequest($reqFilter), 'requestFilter'); - $config['handler'] = $stack; - } - if (($sslVerify = \getenv('PACT_BROKER_SSL_VERIFY'))) { - $client['verify'] = $sslVerify !== 'no'; - } - $client = new GuzzleClient($config); - - $this->brokerHttpClient = new BrokerHttpClient($client, $this->config->getBrokerUri()); - } + $error = $this->client->call('pactffi_verifier_execute', $this->handle); + $this->client->call('pactffi_verifier_shutdown', $this->handle); - return $this->brokerHttpClient; + return !$error; } } diff --git a/src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php b/src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php deleted file mode 100644 index 6201c53f..00000000 --- a/src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php +++ /dev/null @@ -1,57 +0,0 @@ -processRunnerFactory = $processRunnerFactory ?: new ProcessRunnerFactory(); - } - - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } - - /** - * @param array $arguments - * @throws \Exception - */ - public function run(array $arguments, ?int $processTimeout = null, ?int $processIdleTimeout = null): void - { - $logger = $this->getLogger(); - $processRunner = $this->processRunnerFactory->createRunner( - $arguments, - $logger - ); - - $logger->info("Verifying PACT with script:\n{$processRunner->getCommand()}\n\n"); - - $processRunner->runBlocking(); - } - - private function getLogger(): LoggerInterface - { - if (null === $this->logger) { - $logHandler = new StreamHandler(new ResourceOutputStream(\STDOUT)); - $logHandler->setFormatter(new ConsoleFormatter(null, null, true)); - $this->logger = new Logger('console'); - $this->logger->pushHandler($logHandler); - } - - return $this->logger; - } -} diff --git a/tests/PhpPact/Broker/Service/BrokerHttpClientTest.php b/tests/PhpPact/Broker/Service/BrokerHttpClientTest.php deleted file mode 100644 index 7c510e79..00000000 --- a/tests/PhpPact/Broker/Service/BrokerHttpClientTest.php +++ /dev/null @@ -1,53 +0,0 @@ - [ - 'pacts' => [ - ['href' => 'pact-url-1'], - ['href' => 'pact-url-2'], - ], - ], - ] - ); - - $streamMock = $this->createMock(StreamInterface::class); - $streamMock->expects($this->once()) - ->method('getContents') - ->will($this->returnValue($expectedContents)); - - $responseMock = $this->createMock(ResponseInterface::class); - $responseMock->expects($this->once()) - ->method('getBody') - ->will($this->returnValue($streamMock)); - - $httpClientMock = $this->createMock(ClientInterface::class); - $httpClientMock->expects($this->once()) - ->method('get') - ->will($this->returnValue($responseMock)); - - $uriMock = $this->createMock(UriInterface::class); - $uriMock->expects($this->once()) - ->method('withPath') - ->with($this->equalTo($expectedPath)) - ->will($this->returnValue($uriMock)); - - $broker = new BrokerHttpClient($httpClientMock, $uriMock); - $broker->getAllConsumerUrls($provider); - } -} diff --git a/tests/PhpPact/FFI/Model/ArrayDataTest.php b/tests/PhpPact/FFI/Model/ArrayDataTest.php new file mode 100644 index 00000000..b55a5f65 --- /dev/null +++ b/tests/PhpPact/FFI/Model/ArrayDataTest.php @@ -0,0 +1,21 @@ +assertSame(count($branches), $arrayData->getSize()); + foreach ($branches as $index => $branch) { + $this->assertSame($branch, FFI::string($arrayData->getItems()[$index])); + } + } +} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerTest.php b/tests/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerTest.php new file mode 100644 index 00000000..23106df1 --- /dev/null +++ b/tests/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerTest.php @@ -0,0 +1,37 @@ +addSelector('{"tag":"foo","latest":true}') + ->addSelector('{"tag":"bar","latest":true}'); + $consumerVersionTags = ['dev']; + + $subject = (new Broker()) + ->setEnablePending($enablePending) + ->setIncludeWipPactSince($wipPactSince) + ->setProviderTags($providerTags) + ->setProviderBranch($providerBranch) + ->setConsumerVersionSelectors($consumerVersionSelectors) + ->setConsumerVersionTags($consumerVersionTags); + + static::assertSame($enablePending, $subject->isEnablePending()); + static::assertSame($wipPactSince, $subject->getIncludeWipPactSince()); + static::assertSame($providerTags, $subject->getProviderTags()); + static::assertSame($providerBranch, $subject->getProviderBranch()); + static::assertSame($consumerVersionSelectors, $subject->getConsumerVersionSelectors()); + static::assertSame($consumerVersionTags, $subject->getConsumerVersionTags()); + } +} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlTest.php b/tests/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlTest.php new file mode 100644 index 00000000..20716b8a --- /dev/null +++ b/tests/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlTest.php @@ -0,0 +1,29 @@ +setUrl($url) + ->setToken($token) + ->setUsername($username) + ->setPassword($password); + + static::assertSame($url, $subject->getUrl()); + static::assertSame($token, $subject->getToken()); + static::assertSame($username, $subject->getUsername()); + static::assertSame($password, $subject->getPassword()); + } +} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigTest.php b/tests/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigTest.php new file mode 100644 index 00000000..48446b0a --- /dev/null +++ b/tests/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigTest.php @@ -0,0 +1,88 @@ +getProviderInfo() + ->setName($providerName) + ->setScheme($scheme) + ->setHost($host) + ->setPort($port) + ->setPath($basePath); + $subject->getFilterInfo() + ->setFilterDescription($filterDescription) + ->setFilterNoState($filterNoState) + ->setFilterState($filterState); + $subject->getProviderState() + ->setStateChangeUrl($stateChangeUrl) + ->setStateChangeAsBody($stateChangeAsBody) + ->setStateChangeTeardown($stateChangeTeardown); + $subject->getVerificationOptions() + ->setRequestTimeout($requestTimeout) + ->setDisableSslVerification($disableSslVerification); + $publishOptions = new PublishOptions(); + $publishOptions + ->setProviderTags($providerTags) + ->setProviderVersion($providerVersion) + ->setBuildUrl($buildUrl) + ->setProviderBranch($providerBranch); + $subject->setPublishOptions($publishOptions); + $subject->getConsumerFilters() + ->setFilterConsumerNames($filterConsumerNames); + + $providerInfo = $subject->getProviderInfo(); + static::assertSame($providerName, $providerInfo->getName()); + static::assertSame($scheme, $providerInfo->getScheme()); + static::assertSame($host, $providerInfo->getHost()); + static::assertSame($port, $providerInfo->getPort()); + static::assertSame($basePath, $providerInfo->getPath()); + $filterInfo = $subject->getFilterInfo(); + static::assertSame($filterDescription, $filterInfo->getFilterDescription()); + static::assertSame($filterNoState, $filterInfo->getFilterNoState()); + static::assertSame($filterState, $filterInfo->getFilterState()); + $providerState = $subject->getProviderState(); + static::assertSame($stateChangeUrl, $providerState->getStateChangeUrl()); + static::assertSame($stateChangeAsBody, $providerState->isStateChangeAsBody()); + static::assertSame($stateChangeTeardown, $providerState->isStateChangeTeardown()); + $verificationOptions = $subject->getVerificationOptions(); + static::assertSame($requestTimeout, $verificationOptions->getRequestTimeout()); + static::assertSame($disableSslVerification, $verificationOptions->isDisableSslVerification()); + static::assertSame($publishResults, $subject->isPublishResults()); + $publishOptions = $subject->getPublishOptions(); + static::assertSame($providerTags, $publishOptions->getProviderTags()); + static::assertSame($providerVersion, $publishOptions->getProviderVersion()); + static::assertSame($buildUrl, $publishOptions->getBuildUrl()); + static::assertSame($providerBranch, $publishOptions->getProviderBranch()); + $consumerFilters = $subject->getConsumerFilters(); + static::assertSame($filterConsumerNames, $consumerFilters->getFilterConsumerNames()); + } +} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php b/tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php deleted file mode 100644 index 72449939..00000000 --- a/tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php +++ /dev/null @@ -1,77 +0,0 @@ - 'bar']; - - $logger = $this->createMock(LoggerInterface::class); - - $processRunner = $this->createMock(ProcessRunner::class); - - $processRunnerFactory = $this->createMock(ProcessRunnerFactory::class); - $processRunnerFactory->expects($this->once()) - ->method('createRunner') - ->with($this->equalTo($arguments), $this->equalTo($logger)) - ->will($this->returnValue($processRunner)); - - $process = new VerifierProcess($processRunnerFactory); - $process->setLogger($logger); - $process->run($arguments, 42, 23); - } - - public function testRunWithDefaultLogger() - { - $arguments = ['foo' => 'bar']; - - $processRunner = $this->createMock(ProcessRunner::class); - - $processRunnerFactory = $this->createMock(ProcessRunnerFactory::class); - $processRunnerFactory->expects($this->once()) - ->method('createRunner') - ->with($this->equalTo($arguments)) - ->will($this->returnValue($processRunner)); - - $process = new VerifierProcess($processRunnerFactory); - $process->run($arguments, 42, 23); - } - - public function testRunForwardsException() - { - $this->expectExceptionMessage('foo'); - $this->expectException(\RuntimeException::class); - - $arguments = ['foo' => 'bar']; - - $expectedException = new \RuntimeException('foo'); - - $processRunner = $this->createMock(ProcessRunner::class); - $processRunner->expects($this->once()) - ->method('runBlocking') - ->will( - $this->returnCallback( - function () use ($expectedException) { - throw $expectedException; - } - ) - ); - - $processRunnerFactory = $this->createMock(ProcessRunnerFactory::class); - $processRunnerFactory->expects($this->once()) - ->method('createRunner') - ->with($this->equalTo($arguments)) - ->will($this->returnValue($processRunner)); - - $process = new VerifierProcess($processRunnerFactory); - $process->run($arguments, 42, 23); - } -} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php index 8a5d896a..c6cf11f2 100644 --- a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php +++ b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php @@ -2,235 +2,55 @@ namespace PhpPactTest\Standalone\ProviderVerifier; -use GuzzleHttp\Psr7\Uri; -use Monolog\Handler\TestHandler; -use Monolog\Logger; -use PhpPact\Broker\Service\BrokerHttpClient; -use PhpPact\Broker\Service\BrokerHttpClientInterface; -use PhpPact\Standalone\ProviderVerifier\Model\ConsumerVersionSelectors; use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; -use PhpPact\Standalone\ProviderVerifier\ProcessRunnerFactory; use PhpPact\Standalone\ProviderVerifier\Verifier; -use PhpPact\Standalone\ProviderVerifier\VerifierProcess; +use PhpPact\Standalone\Runner\ProcessRunner; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; class VerifierTest extends TestCase { - public function testGetArguments() - { - $consumerVersionSelectors = (new ConsumerVersionSelectors()) - ->addSelector('{"tag":"foo","latest":true}') - ->addSelector('{"tag":"bar","latest":true}'); - - $config = new VerifierConfig(); - $config - ->setProviderName('some provider with whitespace') - ->setProviderVersion('1.0.0') - ->setProviderBranch('main') - ->addProviderVersionTag('prod') - ->addProviderVersionTag('dev') - ->addConsumerVersionTag('dev') - ->setProviderBaseUrl(new Uri('http://myprovider:1234')) - ->setProviderStatesSetupUrl(new Uri('http://someurl:1234')) - ->setPublishResults(true) - ->setBrokerToken('someToken') - ->setBrokerUsername('someusername') - ->setBrokerPassword('somepassword') - ->setBrokerUri(new Uri('https://example.broker/')) - ->addCustomProviderHeader('key1', 'value1') - ->addCustomProviderHeader('key2', 'value2') - ->setVerbose(true) - ->setLogDirectory('my/log/directory') - ->setFormat('someformat') - ->setProcessTimeout(30) - ->setProcessIdleTimeout(5) - ->setEnablePending(true) - ->setIncludeWipPactSince('2020-01-30') - ->setRequestFilter( - function (RequestInterface $r) { - return $r->withHeader('MY_SPECIAL_HEADER', 'my special value'); - } - ) - ->setConsumerVersionSelectors($consumerVersionSelectors); - - /** @var BrokerHttpClientInterface $brokerHttpService */ - $server = new Verifier($config); - $arguments = $server->getArguments(); - - $this->assertContains('--provider-base-url=http://myprovider:1234', $arguments); - $this->assertContains('--provider-states-setup-url=http://someurl:1234', $arguments); - $this->assertContains('--publish-verification-results', $arguments); - $this->assertContains('--broker-token=someToken', $arguments); - $this->assertContains('--broker-username=someusername', $arguments); - $this->assertContains('--broker-password=somepassword', $arguments); - $this->assertContains('--custom-provider-header="key1: value1"', $arguments); - $this->assertContains('--custom-provider-header="key2: value2"', $arguments); - $this->assertContains('--verbose=VERBOSE', $arguments); - $this->assertContains('--log-dir=my/log/directory', $arguments); - $this->assertContains('--format=someformat', $arguments); - $this->assertContains('--provider-version-tag=prod', $arguments); - $this->assertContains('--provider-version-tag=dev', $arguments); - $this->assertContains('--provider-version-branch=main', $arguments); - $this->assertContains('--consumer-version-tag=dev', $arguments); - $this->assertSame(['process_timeout' => 30, 'process_idle_timeout' => 5], $server->getTimeoutValues()); - $this->assertContains('--enable-pending', $arguments); - $this->assertContains('--include-wip-pacts-since=2020-01-30', $arguments); - $this->assertContains('--consumer-version-selector=\'{"tag":"foo","latest":true}\'', $this->stripSpaces($arguments)); - $this->assertContains('--consumer-version-selector=\'{"tag":"bar","latest":true}\'', $this->stripSpaces($arguments)); - $this->assertContains('--provider=\'some provider with whitespace\'', $arguments); - $this->assertContains('--pact-broker-base-url=https://example.broker/', $arguments); - } + /** @var ProcessRunner */ + private ProcessRunner $processRunner; /** - * Strip spaces for Windows CMD + * Run the PHP build-in web server. */ - private function stripSpaces($arr) - { - $newArr = []; - foreach ($arr as $str) { - $newArr[] = str_ireplace(' ', '', $str); - } - return $newArr; - } - - public function testGetArgumentsEmptyConfig() + protected function setUp(): void { - $this->assertEmpty((new Verifier(new VerifierConfig()))->getArguments()); - } - - /** - * @dataProvider dataProviderForBrokerPathTest - * - * @param string $consumerName - * @param string $providerName - * @param null|string $tag - * @param null|string $version - * @param string $path - */ - public function testBuildValidPathToPactBroker($consumerName, $providerName, $tag, $version, $path) - { - $expectedUrltoBroker = 'http://mock/' . $path; - - /** @var Uri $uriMock */ - $uriMock = $this->createMock(Uri::class); - $uriMock->expects($this->once()) - ->method('withPath') - ->with($path) - ->willReturn($uriMock); - - $uriMock->expects($this->any()) - ->method('__toString') - ->willReturn($expectedUrltoBroker); - - $verifierProcessMock = $this->createMock(VerifierProcess::class); - $verifierProcessMock->expects($this->once()) - ->method('run') - ->with( - $this->callback(function ($args) use ($expectedUrltoBroker) { - return \in_array($expectedUrltoBroker, $args); - }) - ); - - $config = new VerifierConfig(); - $config->setProviderName($providerName) - ->setProviderBaseUrl(new Uri('http://myprovider:1234')) - ->setProviderStatesSetupUrl(new Uri('http://someurl:1234')) - ->setBrokerUri($uriMock) - ->setVerbose(true); - - $verifier = new Verifier($config, $verifierProcessMock); + $publicPath = __DIR__ . '/../../../_public/'; - $verifier->verify($consumerName, $tag, $version); - } - - public function dataProviderForBrokerPathTest() - { - $consumerName = 'someProviderName'; - $providerName = 'someProviderName'; - $tag = '1.0.0'; - $version = '11111'; + $this->processRunner = new ProcessRunner('php', ['-S', 'localhost:7202', '-t', $publicPath]); - return [ - [$consumerName, $providerName, null, $version, "/pacts/provider/$providerName/consumer/$consumerName/version/$version/"], - [$consumerName, $providerName, $tag, null, "/pacts/provider/$providerName/consumer/$consumerName/latest/$tag/"], - [$consumerName, $providerName, $tag, $version, "/pacts/provider/$providerName/consumer/$consumerName/latest/$tag/"], - [$consumerName, $providerName, null, null, "/pacts/provider/$providerName/consumer/$consumerName/latest/"], - ]; + $this->processRunner->run(); + \usleep(300000); // wait for server to start } /** - * @dataProvider provideDataForVerifyAll - * - * @param string $providerName - * @param string $providerVersion - * @param bool $forceLatest - * @param mixed $expectedProviderVersion + * Stop the web server process once complete. */ - public function testIfDataForVerifyAllIsConvertedCorrectly($providerName, $providerVersion) + protected function tearDown(): void { - $expectedUrl1 = 'expectedUrl1'; - $expectedUrl2 = 'expectedUrl2'; - $expectedPactUrls = [$expectedUrl1, $expectedUrl2]; - - $verifierProcessMock = $this->createMock(VerifierProcess::class); - $verifierProcessMock->expects($this->once()) - ->method('run') - ->with( - $this->callback(function ($args) use ($expectedUrl1, $expectedUrl2) { - return \in_array($expectedUrl1, $args) && \in_array($expectedUrl2, $args); - }) - ); - - $brokerHttpClient = $this->createMock(BrokerHttpClient::class); - - $brokerHttpClient->expects($this->once()) - ->method('getAllConsumerUrls') - ->with($this->equalTo($providerName)) - ->willReturn($expectedPactUrls); - - $config = new VerifierConfig(); - $config->setProviderName($providerName); - $config->setProviderVersion($providerVersion); - - $verifier = new Verifier($config, $verifierProcessMock, $brokerHttpClient); - $verifier->verifyAll(); + $this->processRunner->stop(); } - public function provideDataForVerifyAll() + public function testVerify(): void { - return [ - ['someProvider', '1.0.0'], - ['someProvider', '1.2.3'], - ]; - } - - public function testRunShouldLogOutputIfCmdFails() - { - if ('\\' !== \DIRECTORY_SEPARATOR) { - $cmd = __DIR__ . \DIRECTORY_SEPARATOR . 'verifier.sh'; - } else { - $cmd = 'cmd /c' . __DIR__ . \DIRECTORY_SEPARATOR . 'verifier.bat'; - } - - $process = new VerifierProcess(new ProcessRunnerFactory($cmd)); - - $logger = new Logger('console', [$handler = new TestHandler()]); - $process->setLogger($logger); - - try { - $exception = null; - $process->run([], 60, 10); - } catch (\Exception $e) { - $exception = $e; + $config = new VerifierConfig(); + $config->getProviderInfo() + ->setName('someProvider') + ->setHost('localhost') + ->setPort(7202) + ->setScheme('http') + ->setPath('/'); + if ($level = \getenv('PACT_LOGLEVEL')) { + $config->setLogLevel($level); } - $logMessages = $handler->getRecords(); + $verifier = new Verifier($config); + $verifier->addDirectory(__DIR__ . '/../../../_resources'); - $this->assertGreaterThan(2, \count($logMessages)); - $this->assertStringContainsString('first line', $logMessages[\count($logMessages) - 2]['message']); - $this->assertStringContainsString('second line', $logMessages[\count($logMessages) - 1]['message']); + $verifyResult = $verifier->verify(); - $this->assertNotNull($exception); + $this->assertTrue($verifyResult); } } diff --git a/tests/PhpPact/Standalone/ProviderVerifier/verifier.bat b/tests/PhpPact/Standalone/ProviderVerifier/verifier.bat deleted file mode 100755 index 4cdede33..00000000 --- a/tests/PhpPact/Standalone/ProviderVerifier/verifier.bat +++ /dev/null @@ -1,8 +0,0 @@ -@ECHO OFF - -REM this script simulates a command (like pact-verifier) which prints several lines to stdout and stderr - -ECHO "first line" -ECHO "second line" 1>&2 - -exit 42 \ No newline at end of file diff --git a/tests/PhpPact/Standalone/ProviderVerifier/verifier.sh b/tests/PhpPact/Standalone/ProviderVerifier/verifier.sh deleted file mode 100755 index c196300a..00000000 --- a/tests/PhpPact/Standalone/ProviderVerifier/verifier.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -# this script simulates a command (like pact-verifier) which prints several lines to stdout and stderr - -echoerr() { echo "$@" 1>&2; } - -echo "first line" -echoerr "second line" - -exit 42 \ No newline at end of file diff --git a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php b/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php index 2990bd58..8aa3a00b 100644 --- a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php +++ b/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php @@ -40,7 +40,7 @@ protected function setUp(): void ->setEndpoint($endpoint); $this->stubServer = new StubServer($this->config); - $this->stubServer->start(10); + $this->stubServer->start(); $this->service = new StubServerHttpService(new GuzzleClient(), $this->config); } diff --git a/tests/_public/index.php b/tests/_public/index.php new file mode 100644 index 00000000..cc71ddcc --- /dev/null +++ b/tests/_public/index.php @@ -0,0 +1,10 @@ + [ + [ + 'name' => 'g', + ], + ], +]); From e96453fd7947926799ebf418a00c98d691682c5b Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 3 Jan 2023 22:38:12 +0700 Subject: [PATCH 55/78] Use Rust FFI: Stub Server --- README.md | 10 +- composer.json | 21 +- .../Standalone/Installer/Model/Scripts.php | 9 +- .../Service/StubServerHttpService.php | 25 -- .../StubServerHttpServiceInterface.php | 9 - .../Standalone/StubService/StubServer.php | 64 ++++- .../StubService/StubServerConfig.php | 252 ++++++++++++++---- .../StubService/StubServerConfigInterface.php | 131 +++++++-- .../Service/StubServerHttpServiceTest.php | 22 +- .../StubServer/StubServerConfigTest.php | 60 ++++- .../Standalone/StubServer/StubServerTest.php | 12 +- 11 files changed, 450 insertions(+), 165 deletions(-) diff --git a/README.md b/README.md index 29e72c00..f4c998c6 100644 --- a/README.md +++ b/README.md @@ -394,14 +394,12 @@ Handle these requests on your provider: If you would like to test with fixtures, you can use the `pact-stub-service` like this: ```php -$pactLocation = __DIR__ . '/someconsumer-someprovider.json'; -$host = 'localhost'; -$port = 7201; -$endpoint = 'test'; +$files = [__DIR__ . '/someconsumer-someprovider.json']; +$port = 7201; +$endpoint = 'test'; $config = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) + ->setFiles($files) ->setPort($port) ->setEndpoint($endpoint); diff --git a/composer.json b/composer.json index 93b18c8b..68129809 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "amphp/process": "^1.1.1", "guzzlehttp/guzzle": "^6.5.8|^7.4.5", "phpunit/phpunit": ">=8.5.23 <10", - "tienvx/composer-downloads-plugin": "^1.1.0" + "tienvx/composer-downloads-plugin": "^1.2.0" }, "require-dev": { "roave/security-advisories": "dev-latest", @@ -77,10 +77,15 @@ "variables": { "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'win32' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", "{$architecture}": "PHP_OS === 'Linux' ? '-x86_64' : ''", - "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'zip' : 'tar.gz'" + "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'zip' : 'tar.gz'", + "{$keep}": "PHP_OS_FAMILY === 'Windows' ? 'pact-broker.bat' : 'pact-broker'" }, "url": "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", - "path": "bin/pact-ruby-standalone" + "path": "bin/pact-ruby-standalone", + "ignore": [ + "bin/*", + "!bin/{$keep}" + ] }, "pact-ffi-headers": { "version": "0.4.4", @@ -97,6 +102,16 @@ }, "url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/{$prefix}-{$os}-{$architecture}.{$extension}.gz", "path": "bin/pact-ffi-lib/pact.{$extension}" + }, + "pact-stub-server": { + "version": "0.5.3", + "variables": { + "{$os}": "PHP_OS === 'Darwin' ? 'osx' : strtolower(PHP_OS_FAMILY)", + "{$extension}": "PHP_OS_FAMILY === 'Windows' ? '.exe' : ''" + }, + "url": "https://github.com/pact-foundation/pact-stub-server/releases/download/v{$version}/pact-stub-server-{$os}-x86_64{$extension}.gz", + "path": "bin/pact-stub-server/pact-stub-server{$extension}", + "executable": true } } }, diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 80084fe4..67e96568 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -28,16 +28,11 @@ public static function getLibrary(): string public static function getStubService(): string { - return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-stub-service' . self::getSuffix(); + return self::$destinationDir . '/bin/pact-stub-server/pact-stub-server' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : ''); } public static function getBroker(): string { - return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker' . self::getSuffix(); - } - - private static function getSuffix(): string - { - return (PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); + return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker' . (PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); } } diff --git a/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php index 8aa9a849..81f5f726 100644 --- a/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php +++ b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php @@ -2,7 +2,6 @@ namespace PhpPact\Standalone\StubService\Service; -use PhpPact\Exception\ConnectionException; use PhpPact\Http\ClientInterface; use PhpPact\Standalone\StubService\StubServerConfigInterface; @@ -23,30 +22,6 @@ public function __construct(ClientInterface $client, StubServerConfigInterface $ $this->config = $config; } - /** - * {@inheritdoc} - */ - public function healthCheck(): bool - { - $uri = $this->config->getBaseUri()->withPath('/'); - - $response = $this->client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - $body = $response->getBody()->getContents(); - - if ($response->getStatusCode() !== 200 - || $body !== "Mock service running\n") { - throw new ConnectionException('Failed to receive a successful response from the Stub Server.'); - } - - return true; - } - /** * {@inheritdoc} * @throws \JsonException diff --git a/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php b/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php index 307a9448..e1b84aa0 100644 --- a/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php +++ b/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php @@ -2,17 +2,8 @@ namespace PhpPact\Standalone\StubService\Service; -use PhpPact\Exception\ConnectionException; - interface StubServerHttpServiceInterface { - /** - * Verify that the Ruby PhpPact Stub Server is running. - * - * @throws ConnectionException - */ - public function healthCheck(): bool; - /** * Get the current state of the PACT JSON file and write it to disk. */ diff --git a/src/PhpPact/Standalone/StubService/StubServer.php b/src/PhpPact/Standalone/StubService/StubServer.php index f19b4c67..4805770c 100644 --- a/src/PhpPact/Standalone/StubService/StubServer.php +++ b/src/PhpPact/Standalone/StubService/StubServer.php @@ -43,6 +43,8 @@ public function start(int $wait = 1): int /** * Stop the Stub Server process. * + * @throws ProcessException + * * @return bool Was stopping successful? * @throws ProcessException */ @@ -60,12 +62,64 @@ private function getArguments(): array { $results = []; - $results[] = $this->config->getPactLocation(); - $results[] = "--host={$this->config->getHost()}"; - $results[] = "--port={$this->config->getPort()}"; + if ($this->config->getBrokerUrl() !== null) { + $results[] = "--broker-url={$this->config->getBrokerUrl()}"; + } + + foreach ($this->config->getDirs() as $dir) { + $results[] = "--dir={$dir}"; + } + + if ($this->config->getExtension() !== null) { + $results[] = "--extension={$this->config->getExtension()}"; + } + + foreach ($this->config->getFiles() as $file) { + $results[] = "--file={$file}"; + } + + if ($this->config->getLogLevel() !== null) { + $results[] = "--loglevel={$this->config->getLogLevel()}"; + } + + if ($this->config->getPort() !== null) { + $results[] = "--port={$this->config->getPort()}"; + } + + if ($this->config->getProviderState() !== null) { + $results[] = "--provider-state={$this->config->getProviderState()}"; + } + + if ($this->config->getProviderStateHeaderName() !== null) { + $results[] = "--provider-state-header-name={$this->config->getProviderStateHeaderName()}"; + } + + if ($this->config->getToken() !== null) { + $results[] = "--token={$this->config->getToken()}"; + } + + foreach ($this->config->getUrls() as $url) { + $results[] = "--url={$url}"; + } + + if ($this->config->getUser() !== null) { + $results[] = "--user={$this->config->getUser()}"; + } + + if ($this->config->isCors()) { + $results[] = '--cors'; + } + + if ($this->config->isCorsReferer()) { + $results[] = '--cors-referer'; + } + + if ($this->config->isEmptyProviderState()) { + $results[] = '--empty-provider-state'; + } - if ($this->config->getLog() !== null) { - $results[] = "--log={$this->config->getLog()}"; + if ($this->config->isInsecureTls()) { + $results[] = '--insecure-tls'; } return $results; diff --git a/src/PhpPact/Standalone/StubService/StubServerConfig.php b/src/PhpPact/Standalone/StubService/StubServerConfig.php index e12779a6..beb55ee4 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfig.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfig.php @@ -10,55 +10,109 @@ */ class StubServerConfig implements StubServerConfigInterface { + private ?UriInterface $brokerUrl = null; + private ?int $port = null; + + private ?string $extension = null; + private ?string $logLevel = null; + private ?string $providerState = null; + private ?string $providerStateHeaderName = null; + private ?string $token = null; + private ?string $user = null; + /** - * Host on which to bind the service. + * @var array */ - private string $host = 'localhost'; - + private array $dirs = []; /** - * Port on which to run the service. + * @var array */ - private int $port = 7201; - - private bool $secure = false; - + private array $files = []; + /** + * @var array + */ + private array $urls = []; + /** + * @var array + */ + private array $consumerNames = []; /** - * File to which to log output. + * @var array */ - private ?string $log = null; + private array $providerNames = []; + + private bool $cors = false; + private bool $corsReferer = false; + private bool $emptyProviderState = false; + private bool $insecureTls = false; - private string $pactLocation; private string $endpoint; - /** - * {@inheritdoc} - */ - public function getHost(): string + public function getBrokerUrl(): ?UriInterface { - return $this->host; + return $this->brokerUrl; } - /** - * {@inheritdoc} - */ - public function setHost(string $host): StubServerConfigInterface + public function setBrokerUrl(UriInterface $brokerUrl): StubServerConfigInterface { - $this->host = $host; + $this->brokerUrl = $brokerUrl; return $this; } - /** - * {@inheritdoc} - */ - public function getPort(): int + public function setDirs(array $dirs): StubServerConfigInterface + { + $this->dirs = array_map(fn (string $dir) => $dir, $dirs); + + return $this; + } + + public function getDirs(): array + { + return $this->dirs; + } + + public function getExtension(): ?string + { + return $this->extension; + } + + public function setExtension(string $extension): StubServerConfigInterface + { + $this->extension = $extension; + + return $this; + } + + public function setFiles(array $files): StubServerConfigInterface + { + $this->files = array_map(fn (string $file) => $file, $files); + + return $this; + } + + public function getFiles(): array + { + return $this->files; + } + + public function setLogLevel(string $logLevel): StubServerConfigInterface + { + $this->logLevel = $logLevel; + + return $this; + } + + public function getLogLevel(): ?string + { + return $this->logLevel; + } + + public function getPort(): ?int { return $this->port; } - /** - * {@inheritdoc} - */ public function setPort(int $port): StubServerConfigInterface { $this->port = $port; @@ -66,70 +120,150 @@ public function setPort(int $port): StubServerConfigInterface return $this; } - /** - * {@inheritdoc} - */ - public function isSecure(): bool + public function getProviderState(): ?string { - return $this->secure; + return $this->providerState; } - /** - * {@inheritdoc} - */ - public function setSecure(bool $secure): StubServerConfigInterface + public function setProviderState(string $providerState): StubServerConfigInterface { - $this->secure = $secure; + $this->providerState = $providerState; return $this; } - /** - * {@inheritdoc} - */ - public function getBaseUri(): UriInterface + public function getProviderStateHeaderName(): ?string + { + return $this->providerStateHeaderName; + } + + public function setProviderStateHeaderName(string $providerStateHeaderName): StubServerConfigInterface { - $protocol = $this->secure ? 'https' : 'http'; + $this->providerStateHeaderName = $providerStateHeaderName; - return new Uri("{$protocol}://{$this->getHost()}:{$this->getPort()}"); + return $this; } - /** - * {@inheritdoc} - */ - public function getLog(): ?string + public function getToken(): ?string { - return $this->log; + return $this->token; } - /** - * {@inheritdoc} - */ - public function setLog(string $log): StubServerConfigInterface + public function setToken(?string $token): StubServerConfigInterface + { + $this->token = $token; + + return $this; + } + + public function setUrls(array $urls): StubServerConfigInterface + { + $this->urls = array_map(fn (string $url) => $url, $urls); + + return $this; + } + + public function getUrls(): array + { + return $this->urls; + } + + public function getUser(): ?string + { + return $this->user; + } + + public function setUser(string $user): StubServerConfigInterface + { + $this->user = $user; + + return $this; + } + + public function isCors(): bool + { + return $this->cors; + } + + public function setCors(bool $cors): StubServerConfigInterface { - $this->log = $log; + $this->cors = $cors; return $this; } - public function getPactLocation(): string + public function isCorsReferer(): bool { - return $this->pactLocation; + return $this->corsReferer; } - public function setPactLocation(string $location): self + public function setCorsReferer(bool $corsReferer): StubServerConfigInterface { - $this->pactLocation = $location; + $this->corsReferer = $corsReferer; return $this; } + public function isEmptyProviderState(): bool + { + return $this->emptyProviderState; + } + + public function setEmptyProviderState(bool $emptyProviderState): StubServerConfigInterface + { + $this->emptyProviderState = $emptyProviderState; + + return $this; + } + + public function isInsecureTls(): bool + { + return $this->insecureTls; + } + + public function setInsecureTls(bool $insecureTls): StubServerConfigInterface + { + $this->insecureTls = $insecureTls; + + return $this; + } + + public function setConsumerNames(array $consumerNames): StubServerConfigInterface + { + $this->consumerNames = array_map(fn (string $consumerName) => $consumerName, $consumerNames); + + return $this; + } + + public function getConsumerNames(): array + { + return $this->consumerNames; + } + + public function setProviderNames(array $providerNames): StubServerConfigInterface + { + $this->providerNames = array_map(fn (string $providerName) => $providerName, $providerNames); + + return $this; + } + + public function getProviderNames(): array + { + return $this->providerNames; + } + + public function getBaseUri(): UriInterface + { + return new Uri("http://localhost:{$this->getPort()}"); + } + + public function getEndpoint(): string { return $this->endpoint; } - public function setEndpoint(string $endpoint): self + public function setEndpoint(string $endpoint): StubServerConfigInterface { $this->endpoint = $endpoint; diff --git a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php index 7f232923..ecfe26c0 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php @@ -10,53 +10,146 @@ interface StubServerConfigInterface { /** - * @return string the host of the stub service + * @return null|UriInterface url to the pact broker */ - public function getHost(): string; + public function getBrokerUrl(): ?UriInterface; /** - * @param string $host The host of the stub service + * @param UriInterface $brokerUrl URL of the pact broker to fetch pacts from */ - public function setHost(string $host): self; + public function setBrokerUrl(UriInterface $brokerUrl): self; /** - * @return int the port of the stub service + * @param array $dirs Directory of pact files to load */ - public function getPort(): int; + public function setDirs(array $dirs): self; /** - * @param int $port the port of the stub service + * @return array + */ + public function getDirs(): array; + + public function getExtension(): ?string; + + /** + * @param string $extension File extension to use when loading from a directory (default is json) + */ + public function setExtension(string $extension): self; + + /** + * @param array $files Pact file to load + */ + public function setFiles(array $files): self; + + /** + * @return array + */ + public function getFiles(): array; + + public function getLogLevel(): ?string; + + /** + * @param string $logLevel Log level (defaults to info) [possible values: error, warn, info, debug, trace, none] + */ + public function setLogLevel(string $logLevel): self; + + /** + * @return null|int the port of the stub service + */ + public function getPort(): ?int; + + /** + * @param int $port Port to run on (defaults to random port assigned by the OS) */ public function setPort(int $port): self; /** - * @return bool true if https + * @return null|string state of the provider */ - public function isSecure(): bool; + public function getProviderState(): ?string; /** - * @param bool $secure set to true for https + * @param string $providerState Provider state regular expression to filter the responses by */ - public function setSecure(bool $secure): self; + public function setProviderState(string $providerState): self; /** - * @return UriInterface + * @return null|string name of the header */ - public function getBaseUri(): UriInterface; + public function getProviderStateHeaderName(): ?string; + + /** + * @param string $providerStateHeaderName Name of the header parameter containing the provider state to be used in case multiple matching interactions are found + */ + public function setProviderStateHeaderName(string $providerStateHeaderName): self; + + /** + * @return null|string token for the pact broker + */ + public function getToken(): ?string; + + /** + * @param null|string $token Bearer token to use when fetching pacts from URLS or Pact Broker + */ + public function setToken(?string $token): self; + + /** + * @param array $urls URL of pact file to fetch + */ + public function setUrls(array $urls): self; + + /** + * @return array + */ + public function getUrls(): array; + + /** + * @return null|string user and password + */ + public function getUser(): ?string; + + /** + * @param string $user User and password to use when fetching pacts from URLS or Pact Broker in user:password form + */ + public function setUser(string $user): self; + + public function isCors(): bool; + + public function setCors(bool $cors): self; + + public function isCorsReferer(): bool; + + public function setCorsReferer(bool $corsReferer): self; + + public function isEmptyProviderState(): bool; + + public function setEmptyProviderState(bool $emptyProviderState): self; + + public function isInsecureTls(): bool; + + public function setInsecureTls(bool $insecureTls): self; /** - * @return ?string directory for log output + * @param array $consumerNames Consumer name to use to filter the Pacts fetched from the Pact broker */ - public function getLog(): ?string; + public function setConsumerNames(array $consumerNames): self; /** - * @param string $log directory for log output + * @return array */ - public function setLog(string $log): self; + public function getConsumerNames(): array; - public function getPactLocation(): string; + /** + * @param array $providerNames Provider name to use to filter the Pacts fetched from the Pact broker + */ + public function setProviderNames(array $providerNames): self; - public function setPactLocation(string $location): self; + /** + * @return array + */ + public function getProviderNames(): array; + + public function getBaseUri(): UriInterface; public function getEndpoint(): string; diff --git a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php b/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php index 8aa3a00b..9a9769b0 100644 --- a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php +++ b/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php @@ -14,13 +14,13 @@ class StubServerHttpServiceTest extends TestCase { /** @var StubServerHttpServiceInterface */ - private $service; + private StubServerHttpServiceInterface $service; /** @var StubServer */ - private $stubServer; + private StubServer $stubServer; /** @var StubServerConfigInterface */ - private $config; + private StubServerConfigInterface $config; /** * @throws MissingEnvVariableException @@ -28,14 +28,12 @@ class StubServerHttpServiceTest extends TestCase */ protected function setUp(): void { - $pactLocation = __DIR__ . '/../../../../_resources/someconsumer-someprovider.json'; - $host = 'localhost'; - $port = 7201; - $endpoint = 'test'; + $files = [__DIR__ . '/../../../../_resources/someconsumer-someprovider.json']; + $port = 7201; + $endpoint = 'test'; $this->config = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) + ->setFiles($files) ->setPort($port) ->setEndpoint($endpoint); @@ -49,12 +47,6 @@ protected function tearDown(): void $this->stubServer->stop(); } - public function testHealthCheck() - { - $result = $this->service->healthCheck(); - $this->assertTrue($result); - } - public function testGetJson() { $result = $this->service->getJson(); diff --git a/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php b/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php index db9aed12..6ae7c400 100644 --- a/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php +++ b/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php @@ -2,6 +2,7 @@ namespace PhpPactTest\Standalone\StubServer; +use GuzzleHttp\Psr7\Uri; use PhpPact\Standalone\StubService\StubServerConfig; use PHPUnit\Framework\TestCase; @@ -9,20 +10,59 @@ class StubServerConfigTest extends TestCase { public function testSetters() { - $pactLocation = __DIR__ . '/../../../_resources/someconsumer-someprovider.json'; - $host = 'test-host'; - $port = 1234; - $log = 'test-log-dir/'; + $brokerUrl = new Uri('http://localhost'); + $port = 1234; + $extension = 'json'; + $logLevel = 'debug'; + $providerState = 'state'; + $providerStateHeaderName = 'header'; + $token = 'token'; + $user = 'user:password'; + $dirs = [__DIR__ . '/../../../_resources']; + $files = ['/path/to/pact.json']; + $urls = ['http://example.com/path/to/file.json']; + $consumerNames = ['consumer-1', 'consumer-2']; + $providerNames = ['provider-1', 'provider-2']; + $cors = true; + $corsReferer = true; + $emptyProviderState = true; + $insecureTls = true; $subject = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) + ->setBrokerUrl($brokerUrl) ->setPort($port) - ->setLog($log); + ->setExtension($extension) + ->setLogLevel($logLevel) + ->setProviderState($providerState) + ->setProviderStateHeaderName($providerStateHeaderName) + ->setToken($token) + ->setUser($user) + ->setDirs($dirs) + ->setFiles($files) + ->setUrls($urls) + ->setConsumerNames($consumerNames) + ->setProviderNames($providerNames) + ->setCors($cors) + ->setCorsReferer($corsReferer) + ->setEmptyProviderState($emptyProviderState) + ->setInsecureTls($insecureTls); - static::assertSame($pactLocation, $subject->getPactLocation()); - static::assertSame($host, $subject->getHost()); + static::assertSame($brokerUrl, $subject->getBrokerUrl()); static::assertSame($port, $subject->getPort()); - static::assertSame($log, $subject->getLog()); + static::assertSame($extension, $subject->getExtension()); + static::assertSame($logLevel, $subject->getLogLevel()); + static::assertSame($providerState, $subject->getProviderState()); + static::assertSame($providerStateHeaderName, $subject->getProviderStateHeaderName()); + static::assertSame($token, $subject->getToken()); + static::assertSame($user, $subject->getUser()); + static::assertSame($dirs, $subject->getDirs()); + static::assertSame($files, $subject->getFiles()); + static::assertSame($urls, $subject->getUrls()); + static::assertSame($consumerNames, $subject->getConsumerNames()); + static::assertSame($providerNames, $subject->getProviderNames()); + static::assertSame($cors, $subject->isCors()); + static::assertSame($corsReferer, $subject->isCorsReferer()); + static::assertSame($emptyProviderState, $subject->isEmptyProviderState()); + static::assertSame($insecureTls, $subject->isInsecureTls()); } } diff --git a/tests/PhpPact/Standalone/StubServer/StubServerTest.php b/tests/PhpPact/Standalone/StubServer/StubServerTest.php index 963fabf8..687e5800 100644 --- a/tests/PhpPact/Standalone/StubServer/StubServerTest.php +++ b/tests/PhpPact/Standalone/StubServer/StubServerTest.php @@ -14,16 +14,14 @@ class StubServerTest extends TestCase public function testStartAndStop() { try { - $pactLocation = __DIR__ . '/../../../_resources/someconsumer-someprovider.json'; - $host = 'localhost'; - $port = 7201; - $endpoint = 'test'; + $files = [__DIR__ . '/../../../_resources/someconsumer-someprovider.json']; + $port = 7201; + $endpoint= 'test'; $subject = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) + ->setFiles($files) ->setPort($port) - ->setEndpoint($endpoint); + ->setEndpoint($endpoint); $stubServer = new StubServer($subject); $pid = $stubServer->start(); From 6d19fa86d5871a14bdf0ceb4ed98030132384d29 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 3 Jan 2023 23:32:48 +0700 Subject: [PATCH 56/78] Use Rust FFI: Offload process --- .github/workflows/build.yml | 2 +- composer.json | 6 +- example/tests/Provider/PactVerifyTest.php | 14 +- phpstan.neon | 6 + src/PhpPact/Standalone/Broker/Broker.php | 271 +++++++----------- .../Standalone/Installer/Model/Scripts.php | 4 +- .../Standalone/Runner/ProcessRunner.php | 193 ------------- .../Standalone/StubService/StubServer.php | 30 +- .../PhpPact/Standalone/Broker/BrokerTest.php | 11 +- .../ProviderVerifier/VerifierTest.php | 14 +- .../Standalone/Runner/ProcessRunnerTest.php | 74 ----- tests/PhpPact/Standalone/Runner/verifier.bat | 11 - tests/PhpPact/Standalone/Runner/verifier.sh | 13 - 13 files changed, 143 insertions(+), 506 deletions(-) delete mode 100644 src/PhpPact/Standalone/Runner/ProcessRunner.php delete mode 100644 tests/PhpPact/Standalone/Runner/ProcessRunnerTest.php delete mode 100755 tests/PhpPact/Standalone/Runner/verifier.bat delete mode 100755 tests/PhpPact/Standalone/Runner/verifier.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 427531c8..b75b69f7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - extensions: openssl, sockets, curl, zip, ffi + extensions: sockets, curl, zip, ffi php-version: ${{ matrix.php }} coverage: none diff --git a/composer.json b/composer.json index 68129809..b1ce6078 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,9 @@ ], "require": { "php": "^8.0", - "ext-openssl": "*", "ext-json": "*", "composer/semver": "^1.4.0|^3.2.0", - "amphp/amp": "^2.5.1", - "amphp/byte-stream": "^1.8", - "amphp/log": "^1.1", - "amphp/process": "^1.1.1", + "symfony/process": "^4.4|^5.4|^6.0", "guzzlehttp/guzzle": "^6.5.8|^7.4.5", "phpunit/phpunit": ">=8.5.23 <10", "tienvx/composer-downloads-plugin": "^1.2.0" diff --git a/example/tests/Provider/PactVerifyTest.php b/example/tests/Provider/PactVerifyTest.php index e09a09d7..4f61e93b 100644 --- a/example/tests/Provider/PactVerifyTest.php +++ b/example/tests/Provider/PactVerifyTest.php @@ -6,8 +6,8 @@ use PhpPact\Standalone\ProviderVerifier\Model\Config\ProviderTransport; use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; use PhpPact\Standalone\ProviderVerifier\Verifier; -use PhpPact\Standalone\Runner\ProcessRunner; use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Process; /** * This is an example on how you could use the included amphp/process wrapper to start your API to run PACT verification against a Provider. @@ -15,8 +15,8 @@ */ class PactVerifyTest extends TestCase { - /** @var ProcessRunner */ - private ProcessRunner $processRunner; + /** @var Process */ + private Process $process; /** * Run the PHP build-in web server. @@ -25,10 +25,10 @@ protected function setUp(): void { $publicPath = __DIR__ . '/../../src/Provider/public/'; - $this->processRunner = new ProcessRunner('php', ['-S', 'localhost:7202', '-t', $publicPath]); + $this->process = new Process(['php', '-S', '127.0.0.1:7202', '-t', $publicPath]); - $this->processRunner->run(); - \usleep(300000); // wait for server to start + $this->process->start(); + $this->process->waitUntil(fn () => is_resource(@fsockopen('127.0.0.1', 7202))); } /** @@ -36,7 +36,7 @@ protected function setUp(): void */ protected function tearDown(): void { - $this->processRunner->stop(); + $this->process->stop(); } /** diff --git a/phpstan.neon b/phpstan.neon index 1d5823a9..02aff4b2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,3 +2,9 @@ parameters: level: 7 paths: - src + ignoreErrors: + - + messages: + - '#Method PhpPact\\Standalone\\Broker\\Broker::[a-zA-Z0-9\\_]+\(\) return type has no value type specified in iterable type array\.#' + - '#Method PhpPact\\Standalone\\Broker\\Broker::[a-zA-Z0-9\\_]+\(\) has parameter \$options with no value type specified in iterable type array\.#' + path: src/PhpPact/Standalone/Broker/Broker.php diff --git a/src/PhpPact/Standalone/Broker/Broker.php b/src/PhpPact/Standalone/Broker/Broker.php index f7caa83e..529bf80d 100644 --- a/src/PhpPact/Standalone/Broker/Broker.php +++ b/src/PhpPact/Standalone/Broker/Broker.php @@ -2,8 +2,9 @@ namespace PhpPact\Standalone\Broker; +use Exception; use PhpPact\Standalone\Installer\Model\Scripts; -use PhpPact\Standalone\Runner\ProcessRunner; +use Symfony\Component\Process\Process; class Broker { @@ -17,25 +18,13 @@ public function __construct(BrokerConfig $config) $this->command = Scripts::getBroker(); } - /** - * @throws \Exception - */ - public function canIDeploy(): mixed + public function canIDeploy(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'can-i-deploy', - '--pacticipant=\'' . $this->config->getPacticipant().'\'', - '--version=' . $this->config->getVersion() - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'can-i-deploy', + '--pacticipant', $this->config->getPacticipant(), + '--version', $this->config->getVersion(), + ]); } /** @@ -46,161 +35,93 @@ public function getArguments(): array $parameters = []; if ($this->config->getBrokerUri() !== null) { - $parameters[] = "--broker-base-url={$this->config->getBrokerUri()}"; + $parameters[] = '--broker-base-url'; + $parameters[] = $this->config->getBrokerUri(); } if ($this->config->getBrokerToken() !== null) { - $parameters[] = "--broker-token={$this->config->getBrokerToken()}"; + $parameters[] = '--broker-token'; + $parameters[] = $this->config->getBrokerToken(); } if ($this->config->getBrokerUsername() !== null) { - $parameters[] = "--broker-username={$this->config->getBrokerUsername()}"; + $parameters[] = '--broker-username'; + $parameters[] = $this->config->getBrokerUsername(); } if ($this->config->getBrokerPassword() !== null) { - $parameters[] = "--broker-password={$this->config->getBrokerPassword()}"; + $parameters[] = '--broker-password'; + $parameters[] = $this->config->getBrokerPassword(); } return $parameters; } - /** - * @throws \Exception - */ - public function createOrUpdatePacticipant(): mixed + public function createOrUpdatePacticipant(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'create-or-update-pacticipant', - '--name=' . $this->config->getName(), - '--repository-url=' . $this->config->getRepositoryUrl(), - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'create-or-update-pacticipant', + '--name', $this->config->getName(), + '--repository-url', $this->config->getRepositoryUrl(), + ]); } - /** - * @throws \Exception - */ - public function createOrUpdateWebhook(): mixed + public function createOrUpdateWebhook(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'create-or-update-webhook', - $this->config->getUrl(), - '--request=' . $this->config->getRequest(), - '--header=' . $this->config->getHeader(), - '--data=' . $this->config->getData(), - '--user=' . $this->config->getUser(), - '--consumer=' . $this->config->getConsumer(), - '--provider=' . $this->config->getProvider(), - '--description=' . $this->config->getDescription(), - '--uuid=' . $this->config->getUuid(), - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'create-or-update-webhook', + $this->config->getUrl(), + '--request', $this->config->getRequest(), + '--header', $this->config->getHeader(), + '--data', $this->config->getData(), + '--user', $this->config->getUser(), + '--consumer', $this->config->getConsumer(), + '--provider', $this->config->getProvider(), + '--description', $this->config->getDescription(), + '--uuid', $this->config->getUuid(), + ]); } - /** - * @throws \Exception - */ - public function createVersionTag(): mixed + public function createVersionTag(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'create-version-tag', - '--pacticipant=\'' . $this->config->getPacticipant().'\'', - '--version=' . $this->config->getVersion(), - '--tag=' . $this->config->getTag(), - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'create-version-tag', + '--pacticipant', $this->config->getPacticipant(), + '--version', $this->config->getVersion(), + '--tag', $this->config->getTag(), + ]); } - /** - * @throws \Exception - */ - public function createWebhook(): mixed + public function createWebhook(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'create-webhook', - $this->config->getUrl(), - '--request=' . $this->config->getRequest(), - '--header=' . $this->config->getHeader(), - '--data=' . $this->config->getData(), - '--user=' . $this->config->getUser(), - '--consumer=' . $this->config->getConsumer(), - '--provider=' . $this->config->getProvider(), - '--description=' . $this->config->getDescription(), - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'create-webhook', + $this->config->getUrl(), + '--request', $this->config->getRequest(), + '--header', $this->config->getHeader(), + '--data', $this->config->getData(), + '--user', $this->config->getUser(), + '--consumer', $this->config->getConsumer(), + '--provider', $this->config->getProvider(), + '--description', $this->config->getDescription(), + ]); } - /** - * @throws \Exception - */ - public function describeVersion(): mixed + public function describeVersion(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'describe-version', - '--pacticipant=\'' . $this->config->getPacticipant().'\'', - '--output=json', - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'describe-version', + '--pacticipant', $this->config->getPacticipant(), + '--output', 'json', + ]); } - /** - * @throws \Exception - */ - public function listLatestPactVersions(): mixed + public function listLatestPactVersions(): array { - $runner = new ProcessRunner( - $this->command, - \array_merge( - [ - 'list-latest-pact-versions', - '--output=json', - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); - - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $this->runThenDecodeJson([ + 'list-latest-pact-versions', + '--output', 'json', + ]); } public function publish(): void @@ -208,53 +129,53 @@ public function publish(): void $options = [ 'publish', $this->config->getPactLocations(), - '--consumer-app-version=' . $this->config->getConsumerVersion(), + '--consumer-app-version', $this->config->getConsumerVersion(), ]; if (null !== $this->config->getBranch()) { - $options[] = '--branch=' . $this->config->getBranch(); + $options[] = '--branch'; + $options[] = $this->config->getBranch(); } if (null !== $this->config->getTag()) { - $options[] = '--tag=' . $this->config->getTag(); + $options[] = '--tag'; + $options[] = $this->config->getTag(); } - $runner = new ProcessRunner( - $this->command, - \array_merge( - $options, - $this->getArguments() - ) - ); + $this->run($options); + } + + public function testWebhook(): array + { + return $this->runThenDecodeJson([ + 'test-webhook', + '--uuid', $this->config->getUuid(), + ]); + } - $runner->runBlocking(); + public function generateUuid(): string + { + return \rtrim($this->run(['generate-uuid'])); } - /** - * @throws \Exception - */ - public function testWebhook(): mixed + private function run(array $options): string { - $runner = new ProcessRunner( + $process = new Process([ $this->command, - \array_merge( - [ - 'test-webhook', - '--uuid=' . $this->config->getUuid(), - ], - $this->getArguments() - ) - ); - $runner->runBlocking(); + ...$options, + ...$this->getArguments(), + ]); + $process->run(); + + if (!$process->isSuccessful()) { + throw new Exception("PactPHP Process returned non-zero exit code: {$process->getExitCode()}", $process->getExitCode()); + } - return \json_decode($runner->getOutput(), true, 512, JSON_THROW_ON_ERROR); + return $process->getOutput(); } - public function generateUuid(): string + private function runThenDecodeJson(array $options): array { - $runner = new ProcessRunner($this->command, ['generate-uuid']); - $runner->runBlocking(); - - return \rtrim($runner->getOutput()); + return \json_decode($this->run($options), true, 512, JSON_THROW_ON_ERROR); } } diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 67e96568..3c7747c1 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -28,11 +28,11 @@ public static function getLibrary(): string public static function getStubService(): string { - return self::$destinationDir . '/bin/pact-stub-server/pact-stub-server' . (PHP_OS_FAMILY === 'Windows' ? '.exe' : ''); + return self::$destinationDir . '/bin/pact-stub-server/pact-stub-server'; } public static function getBroker(): string { - return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker' . (PHP_OS_FAMILY === 'Windows' ? '.bat' : ''); + return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker'; } } diff --git a/src/PhpPact/Standalone/Runner/ProcessRunner.php b/src/PhpPact/Standalone/Runner/ProcessRunner.php deleted file mode 100644 index 2da9eb15..00000000 --- a/src/PhpPact/Standalone/Runner/ProcessRunner.php +++ /dev/null @@ -1,193 +0,0 @@ - $arguments - */ - public function __construct(string $command, array $arguments) - { - $this->exitCode = -1; - $this->process = new Process($command . ' ' . \implode(' ', $arguments)); - } - - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } - - public function getOutput(): string - { - return $this->output; - } - - public function setOutput(string $output): void - { - $this->output = $output; - } - - public function getExitCode(): int - { - return $this->exitCode; - } - - public function setExitCode(int $exitCode): void - { - $this->exitCode = $exitCode; - } - - public function getCommand(): string - { - return $this->process->getCommand(); - } - - public function getStderr(): string - { - return $this->stderr; - } - - public function setStderr(string $stderr): void - { - $this->stderr = $stderr; - } - - /** - * Run a blocking, synchronous process - */ - public function runBlocking(): int - { - $logger = $this->getLogger(); - $pid = null; - $lambdaLoop = function () use ($logger, &$pid) { - $logger->debug("Process command: {$this->process->getCommand()}"); - - $pid = yield $this->process->start(); - - $this->output .= yield ByteStream\buffer($this->process->getStdout()); - $this->stderr .= yield ByteStream\buffer($this->process->getStderr()); - - $exitCode = yield $this->process->join(); - $this->setExitCode($exitCode); - $logger->debug("Exit code: {$this->getExitCode()}"); - - if ($this->getExitCode() !== 0) { - $this->logger->info('out > ' . $this->getOutput()); - $this->logger->error('err > ' . $this->getStderr()); - throw new \Exception("PactPHP Process returned non-zero exit code: {$this->getExitCode()}", $this->getExitCode()); - } - - Loop::stop(); - }; - - Loop::run($lambdaLoop); - - return $pid; - } - - /** - * Run a blocking, synchronous process - */ - public function runNonBlocking(): int - { - $logger = $this->getLogger(); - - $pid = null; - - $lambdaLoop = function () use ($logger, &$pid) { - $logger->debug("start background command: {$this->process->getCommand()}"); - - $pid = yield $this->process->start(); - - $this->process->getStdout()->read()->onResolve(function (\Throwable $reason = null, $value) { - $this->output .= $value; - }); - $this->process->getStderr()->read()->onResolve(function (\Throwable $reason = null, $value) { - $this->output .= $value; - }); - - Loop::stop(); - }; - - Loop::run($lambdaLoop); - - $logger->debug("started process pid=$pid"); - - return $pid; - } - - /** - * Run the process and set output - * - * @return int Process Id - */ - public function run(bool $blocking = false): int - { - return $blocking - ? $this->runBlocking() - : $this->runNonBlocking(); - } - - /** - * Stop the running process - * - * @throws ProcessException - */ - public function stop(): bool - { - $pid = $this->process->getPid(); - - print "\nStopping Process Id: {$pid}\n"; - - if ('\\' === \DIRECTORY_SEPARATOR) { - \exec(\sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); - } - - $this->process->kill(); - - if ($this->process->isRunning()) { - throw new ProcessException(\sprintf('Error while killing process "%s".', $pid)); - } - - return true; - } - - private function getLogger(): LoggerInterface - { - if (null === $this->logger) { - $logHandler = new StreamHandler(new ResourceOutputStream(\STDOUT)); - $logHandler->setFormatter(new ConsoleFormatter(null, null, true)); - $this->logger = new Logger('server'); - $this->logger->pushHandler($logHandler); - } - - return $this->logger; - } -} diff --git a/src/PhpPact/Standalone/StubService/StubServer.php b/src/PhpPact/Standalone/StubService/StubServer.php index 4805770c..551e884a 100644 --- a/src/PhpPact/Standalone/StubService/StubServer.php +++ b/src/PhpPact/Standalone/StubService/StubServer.php @@ -2,10 +2,9 @@ namespace PhpPact\Standalone\StubService; -use Amp\Process\ProcessException; use Exception; use PhpPact\Standalone\Installer\Model\Scripts; -use PhpPact\Standalone\Runner\ProcessRunner; +use Symfony\Component\Process\Process; /** * Ruby Standalone Stub Server Wrapper @@ -14,7 +13,7 @@ class StubServer { private StubServerConfigInterface $config; - private ProcessRunner $processRunner; + private Process $process; public function __construct(StubServerConfigInterface $config) { @@ -24,33 +23,34 @@ public function __construct(StubServerConfigInterface $config) /** * Start the Stub Server. Verify that it is running. * - * @param int $wait seconds to delay for the server to come up - * * @throws Exception * - * @return int process ID of the started Stub Server + * @return int|null process ID of the started Stub Server if running, null otherwise */ - public function start(int $wait = 1): int + public function start(): ?int { - $this->processRunner = new ProcessRunner(Scripts::getStubService(), $this->getArguments()); + $this->process = new Process([Scripts::getStubService(), ...$this->getArguments()]); - $processId = $this->processRunner->run(); - \sleep($wait); // wait for server to start + $this->process->start(function (string $type, string $buffer) { + echo $buffer; + }); + $this->process->waitUntil(function (string $type, string $output) { + return false !== \strpos($output, 'Server started on port'); + }); - return $processId; + return $this->process->getPid(); } /** * Stop the Stub Server process. * - * @throws ProcessException - * * @return bool Was stopping successful? - * @throws ProcessException */ public function stop(): bool { - return $this->processRunner->stop(); + $this->process->stop(); + + return true; } /** diff --git a/tests/PhpPact/Standalone/Broker/BrokerTest.php b/tests/PhpPact/Standalone/Broker/BrokerTest.php index 13b67b38..bdbd7908 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerTest.php @@ -21,9 +21,14 @@ public function getArguments(): void ->setBrokerPassword('somepassword') ))->getArguments(); - $this->assertContains('--broker-token=someToken', $arguments); - $this->assertContains('--broker-username=someusername', $arguments); - $this->assertContains('--broker-password=somepassword', $arguments); + $this->assertSame([ + '--broker-token', + 'someToken', + '--broker-username', + 'someusername', + '--broker-password', + 'somepassword', + ], $arguments); } /** diff --git a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php index c6cf11f2..ba3fb34c 100644 --- a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php +++ b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php @@ -4,13 +4,13 @@ use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; use PhpPact\Standalone\ProviderVerifier\Verifier; -use PhpPact\Standalone\Runner\ProcessRunner; use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Process; class VerifierTest extends TestCase { - /** @var ProcessRunner */ - private ProcessRunner $processRunner; + /** @var Process */ + private Process $process; /** * Run the PHP build-in web server. @@ -19,10 +19,10 @@ protected function setUp(): void { $publicPath = __DIR__ . '/../../../_public/'; - $this->processRunner = new ProcessRunner('php', ['-S', 'localhost:7202', '-t', $publicPath]); + $this->process = new Process(['php', '-S', '127.0.0.1:7202', '-t', $publicPath]); - $this->processRunner->run(); - \usleep(300000); // wait for server to start + $this->process->start(); + $this->process->waitUntil(fn () => is_resource(@fsockopen('127.0.0.1', 7202))); } /** @@ -30,7 +30,7 @@ protected function setUp(): void */ protected function tearDown(): void { - $this->processRunner->stop(); + $this->process->stop(); } public function testVerify(): void diff --git a/tests/PhpPact/Standalone/Runner/ProcessRunnerTest.php b/tests/PhpPact/Standalone/Runner/ProcessRunnerTest.php deleted file mode 100644 index ff1b45f4..00000000 --- a/tests/PhpPact/Standalone/Runner/ProcessRunnerTest.php +++ /dev/null @@ -1,74 +0,0 @@ -runBlocking(); - $exitCode = $p->getExitCode(); - - $this->assertEquals($exitCode, 0, 'Expect the exit code to be 0'); - $this->assertStringContainsString($expectedOutput, $p->getOutput(), "Expect '{$expectedOutput}' to be in the output"); - $this->assertEquals(null, $p->getStderr(), 'Expect a null stderr'); - - // try an app that does not exists - if ('\\' !== \DIRECTORY_SEPARATOR) { - $p = new ProcessRunner('failedApp', []); - $expectedErr = 'failedApp'; - } else { - $p = new ProcessRunner('cmd /c echo myError 1>&2 && exit 42', []); - $expectedErr = 'myError'; - } - - try { - $p->runBlocking(); - } catch (\Exception $e) { - $exitCode = $p->getExitCode(); - $this->assertEquals($exitCode, $e->getCode()); - $this->assertStringContainsString("PactPHP Process returned non-zero exit code: $exitCode", $e->getMessage()); - $this->assertNotEquals($exitCode, 0, 'Expect the exit code to be non-zero: ' . $exitCode); - $this->assertStringContainsString($expectedErr, $p->getStderr(), "Expect '{$expectedErr}' to be in the stderr"); - $this->assertEquals(null, $p->getOutput(), 'Expect a null stdout'); - } - } - - /** - * @throws \Exception - */ - public function testProcessRunnerShouldReturnCompleteOutput() - { - if ('\\' !== \DIRECTORY_SEPARATOR) { - $cmd = __DIR__ . \DIRECTORY_SEPARATOR . 'verifier.sh'; - } else { - $cmd = 'cmd /c' . __DIR__ . \DIRECTORY_SEPARATOR . 'verifier.bat'; - } - - $p = new ProcessRunner($cmd, []); - $expectedOutput = 'third line'; - $expectedErr = 'fourth line'; - try { - $p->runBlocking(); - } catch (\Exception $e) { - $this->assertEquals(42, $e->getCode()); - $this->assertStringContainsString("PactPHP Process returned non-zero exit code: 42", $e->getMessage()); - } - $this->assertTrue((\stripos($p->getOutput(), $expectedOutput) !== false), "Expect '{$expectedOutput}' to be in the output:"); - $this->assertTrue((\stripos($p->getStderr(), $expectedErr) !== false), "Expect '{$expectedErr}' to be in the stderr"); - } -} diff --git a/tests/PhpPact/Standalone/Runner/verifier.bat b/tests/PhpPact/Standalone/Runner/verifier.bat deleted file mode 100755 index 3639d40f..00000000 --- a/tests/PhpPact/Standalone/Runner/verifier.bat +++ /dev/null @@ -1,11 +0,0 @@ -@ECHO OFF - -REM this script simulates a command (like pact-verifier) which prints several lines to stdout and stderr - -ECHO "first line" -ECHO "second line" 1>&2 -ECHO "third line" -ECHO "fourth line" 1>&2 -ECHO "fifth line" - -exit 42 \ No newline at end of file diff --git a/tests/PhpPact/Standalone/Runner/verifier.sh b/tests/PhpPact/Standalone/Runner/verifier.sh deleted file mode 100755 index 8290c308..00000000 --- a/tests/PhpPact/Standalone/Runner/verifier.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -# this script simulates a command (like pact-verifier) which prints several lines to stdout and stderr - -echoerr() { echo "$@" 1>&2; } - -echo "first line" -echoerr "second line" -echo "third line" -echoerr "fourth line" -echo "fifth line" - -exit 42 \ No newline at end of file From e40184753fe4f7fecd39f93839af738ce588d749 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 13 Mar 2023 11:16:31 +0700 Subject: [PATCH 57/78] Use Rust FFI: Remove pact broker --- README.md | 37 +- UPGRADE-9.0.md | 13 + composer.json | 24 +- example/phpunit.all.xml | 13 - example/phpunit.consumer.xml | 14 - example/phpunit.core.xml | 13 - phpstan.neon | 6 - phpunit.xml | 3 - src/PhpPact/Config/PactConfig.php | 2 +- .../Consumer/Listener/PactTestListener.php | 101 ------ src/PhpPact/Standalone/Broker/Broker.php | 181 ---------- .../Standalone/Broker/BrokerConfig.php | 316 ------------------ .../Standalone/Installer/Model/Scripts.php | 5 - .../Standalone/Broker/BrokerConfigTest.php | 88 ----- .../PhpPact/Standalone/Broker/BrokerTest.php | 104 ------ 15 files changed, 50 insertions(+), 870 deletions(-) delete mode 100644 src/PhpPact/Consumer/Listener/PactTestListener.php delete mode 100644 src/PhpPact/Standalone/Broker/Broker.php delete mode 100644 src/PhpPact/Standalone/Broker/BrokerConfig.php delete mode 100644 tests/PhpPact/Standalone/Broker/BrokerConfigTest.php delete mode 100644 tests/PhpPact/Standalone/Broker/BrokerTest.php diff --git a/README.md b/README.md index f4c998c6..c081aee2 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ Table of contents - [Specifications](#specifications) - [Installation](#installation) - [Basic Consumer Usage](#basic-consumer-usage) - - [Publish Contracts To Pact Broker](#publish-contracts-to-pact-broker) - [Create Consumer Unit Test](#create-consumer-unit-test) - [Create Mock Request](#create-mock-request) - [Create Mock Response](#create-mock-response) @@ -25,6 +24,10 @@ Table of contents - [Make the Request](#make-the-request) - [Verify Interactions](#verify-interactions) - [Make Assertions](#make-assertions) + - [Delete Old Pact](#delete-old-pact) + - [Publish Contracts To Pact Broker](#publish-contracts-to-pact-broker) + - [CLI](#cli) + - [Github Actions](#github-actions) - [Basic Provider Usage](#basic-provider-usage) - [Create Unit Test](#create-unit-test) - [Start API](#start-api) @@ -81,12 +84,6 @@ Composer hosts older versions under `mattersight/phppact`, which is abandoned. P All of the following code will be used exclusively for the Consumer. -### Publish Contracts To Pact Broker - -When all tests in test suite are passed, you may want to publish generated contract files to pact broker automatically. - -The easiest way to configure this is to use a [PHPUnit Listener](https://phpunit.de/manual/current/en/appendixes.configuration.html#appendixes.configuration.test-listeners). A default listener is included in this project, see [PactTestListener.php](/src/PhpPact/Consumer/Listener/PactTestListener.php). This utilizes environmental variables for configurations. These env variables can either be added to the system or to the phpunit.xml configuration file. Here is an example [phpunit.xml](/example/phpunit.consumer.xml) file configured to use the default. Keep in mind that both the test suite and the arguments array must be the same value. - ### Create Consumer Unit Test Create a standard PHPUnit test case class and function. @@ -203,6 +200,32 @@ Verify that the data you would expect given the response configured is correct. $this->assertEquals('Hello, Bob', $result); // Make your assertions. ``` +### Delete Old Pact + +If the value of `PACT_FILE_WRITE_MODE` is `merge`, before running the test, we need to delete the old pact manually: + +```shell +rm /path/to/pacts/consumer-provider.json +``` + +### Publish Contracts To Pact Broker + +When all tests in test suite are passed, you may want to publish generated contract files to pact broker. + +#### CLI + +Run this command using CLI tool: + +```shell +pact-broker publish /path/to/pacts/consumer-provider.json --consumer-app-version 1.0.0 --branch main --broker-base-url https://test.pactflow.io --broker-token SomeToken +``` + +See more at https://docs.pact.io/pact_broker/publishing_and_retrieving_pacts#publish-using-cli-tools + +#### Github Actions + +See how to use at https://github.com/pactflow/actions/tree/main/publish-pact-files + ## Basic Provider Usage All of the following code will be used exclusively for Providers. This will run the Pacts against the real Provider and either verify or fail validation on the Pact Broker. diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md index aca9d300..630b8282 100644 --- a/UPGRADE-9.0.md +++ b/UPGRADE-9.0.md @@ -50,3 +50,16 @@ UPGRADE FROM 8.x to 9.0 $this->assertTrue($verifyResult); ``` + +* Consumer + * Pact file write mode has been changed from 'overwrite' to 'merge'. Make sure old pact files are removed before running tests. + + ```shell + rm /path/to/pacts/*.json + ``` + + * Pact files now can ONLY be uploaded to Pact Broker by downloading and running Pact CLI manually. + + ```shell + pact-broker publish /path/to/pacts/*.json --consumer-app-version 1.0.0 --branch main --broker-base-url https://test.pactflow.io --broker-token SomeToken + ``` diff --git a/composer.json b/composer.json index b1ce6078..65ad4cd1 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,6 @@ "composer/semver": "^1.4.0|^3.2.0", "symfony/process": "^4.4|^5.4|^6.0", "guzzlehttp/guzzle": "^6.5.8|^7.4.5", - "phpunit/phpunit": ">=8.5.23 <10", "tienvx/composer-downloads-plugin": "^1.2.0" }, "require-dev": { @@ -32,7 +31,8 @@ "slim/psr7": "^1.2.0", "friendsofphp/php-cs-fixer": "^3.0", "php-amqplib/php-amqplib": "^3.0", - "phpstan/phpstan": "^1.9" + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": ">=8.5.23 <10" }, "autoload": { "psr-4": { @@ -64,25 +64,13 @@ "static-code-analysis": "phpstan", "lint": "php-cs-fixer fix --dry-run", "fix": "php-cs-fixer fix", - "test": "phpunit --debug -c example/phpunit.all.xml" + "test": [ + "php -r \"array_map('unlink', glob('./example/output/*.json'));\"", + "phpunit --debug -c example/phpunit.all.xml" + ] }, "extra": { "downloads": { - "pact-ruby-standalone": { - "version": "1.91.0", - "variables": { - "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'win32' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", - "{$architecture}": "PHP_OS === 'Linux' ? '-x86_64' : ''", - "{$extension}": "PHP_OS_FAMILY === 'Windows' ? 'zip' : 'tar.gz'", - "{$keep}": "PHP_OS_FAMILY === 'Windows' ? 'pact-broker.bat' : 'pact-broker'" - }, - "url": "https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v{$version}/pact-{$version}-{$os}{$architecture}.{$extension}", - "path": "bin/pact-ruby-standalone", - "ignore": [ - "bin/*", - "!bin/{$keep}" - ] - }, "pact-ffi-headers": { "version": "0.4.4", "url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/pact.h", diff --git a/example/phpunit.all.xml b/example/phpunit.all.xml index 3cc67cbf..91e84d44 100644 --- a/example/phpunit.all.xml +++ b/example/phpunit.all.xml @@ -14,23 +14,10 @@ ./tests/MessageConsumer - - - - - - PhpPact Consumer Example Tests - - - - - - - diff --git a/example/phpunit.consumer.xml b/example/phpunit.consumer.xml index 0e521db3..8975f039 100644 --- a/example/phpunit.consumer.xml +++ b/example/phpunit.consumer.xml @@ -5,25 +5,11 @@ ./tests/Consumer - - - - - - PhpPact Example Tests - - - - - - - - diff --git a/example/phpunit.core.xml b/example/phpunit.core.xml index 57e6e2e4..49403bdb 100644 --- a/example/phpunit.core.xml +++ b/example/phpunit.core.xml @@ -5,23 +5,10 @@ ../tests - - - - - - PhpPact Consumer Example Tests - - - - - - - diff --git a/phpstan.neon b/phpstan.neon index 02aff4b2..1d5823a9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,9 +2,3 @@ parameters: level: 7 paths: - src - ignoreErrors: - - - messages: - - '#Method PhpPact\\Standalone\\Broker\\Broker::[a-zA-Z0-9\\_]+\(\) return type has no value type specified in iterable type array\.#' - - '#Method PhpPact\\Standalone\\Broker\\Broker::[a-zA-Z0-9\\_]+\(\) has parameter \$options with no value type specified in iterable type array\.#' - path: src/PhpPact/Standalone/Broker/Broker.php diff --git a/phpunit.xml b/phpunit.xml index af9ebf68..2c3b6972 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,10 +21,7 @@ - - - diff --git a/src/PhpPact/Config/PactConfig.php b/src/PhpPact/Config/PactConfig.php index 833f91fb..08c53897 100644 --- a/src/PhpPact/Config/PactConfig.php +++ b/src/PhpPact/Config/PactConfig.php @@ -39,7 +39,7 @@ class PactConfig implements PactConfigInterface * pact file is deleted before running tests when using this option so that * interactions deleted from the code are not maintained in the file. */ - private string $pactFileWriteMode = self::MODE_OVERWRITE; + private string $pactFileWriteMode = self::MODE_MERGE; /** * {@inheritdoc} diff --git a/src/PhpPact/Consumer/Listener/PactTestListener.php b/src/PhpPact/Consumer/Listener/PactTestListener.php deleted file mode 100644 index f4a19c6b..00000000 --- a/src/PhpPact/Consumer/Listener/PactTestListener.php +++ /dev/null @@ -1,101 +0,0 @@ - - */ - private array $testSuiteNames = []; - - private MockServerEnvConfig $mockServerConfig; - - private bool $failed = false; - - /** - * @param array $testSuiteNames test suite names that need evaluated with the listener - * - * @throws MissingEnvVariableException - */ - public function __construct(array $testSuiteNames) - { - $this->testSuiteNames = $testSuiteNames; - $this->mockServerConfig = new MockServerEnvConfig(); - } - - public function addError(Test $test, Throwable $t, float $time): void - { - $this->failed = true; - } - - public function addFailure(Test $test, AssertionFailedError $e, float $time): void - { - $this->failed = true; - } - - /** - * Publish JSON results to PACT Broker and stop the Mock Server. - */ - public function endTestSuite(TestSuite $suite): void - { - if (in_array($suite->getName(), $this->testSuiteNames)) { - if ($this->failed === true) { - print 'A unit test has failed. Skipping PACT file upload.'; - } elseif (!($pactBrokerUri = getenv('PACT_BROKER_URI'))) { - print 'PACT_BROKER_URI environment variable was not set. Skipping PACT file upload.'; - } elseif (!($consumerVersion = getenv('PACT_CONSUMER_VERSION'))) { - print 'PACT_CONSUMER_VERSION environment variable was not set. Skipping PACT file upload.'; - } elseif (!($tag = getenv('PACT_CONSUMER_TAG'))) { - print 'PACT_CONSUMER_TAG environment variable was not set. Skipping PACT file upload.'; - } else { - $brokerConfig = new BrokerConfig(); - $brokerConfig->setPacticipant($this->mockServerConfig->getConsumer()); - $brokerConfig->setPactLocations($this->mockServerConfig->getPactDir()); - $brokerConfig->setBrokerUri(new Uri($pactBrokerUri)); - $brokerConfig->setConsumerVersion($consumerVersion); - $brokerConfig->setVersion($consumerVersion); - $brokerConfig->setTag($tag); - if (($user = getenv('PACT_BROKER_HTTP_AUTH_USER')) && - ($pass = getenv('PACT_BROKER_HTTP_AUTH_PASS')) - ) { - $brokerConfig->setBrokerUsername($user); - $brokerConfig->setBrokerPassword($pass); - } - - if ($bearerToken = getenv('PACT_BROKER_BEARER_TOKEN')) { - $brokerConfig->setBrokerToken($bearerToken); - } - - $broker = new Broker($brokerConfig); - $broker->createVersionTag(); - $broker->publish(); - print 'Pact file has been uploaded to the Broker successfully.'; - } - } - } -} diff --git a/src/PhpPact/Standalone/Broker/Broker.php b/src/PhpPact/Standalone/Broker/Broker.php deleted file mode 100644 index 529bf80d..00000000 --- a/src/PhpPact/Standalone/Broker/Broker.php +++ /dev/null @@ -1,181 +0,0 @@ -config = $config; - $this->command = Scripts::getBroker(); - } - - public function canIDeploy(): array - { - return $this->runThenDecodeJson([ - 'can-i-deploy', - '--pacticipant', $this->config->getPacticipant(), - '--version', $this->config->getVersion(), - ]); - } - - /** - * @return array parameters to be passed into the process - */ - public function getArguments(): array - { - $parameters = []; - - if ($this->config->getBrokerUri() !== null) { - $parameters[] = '--broker-base-url'; - $parameters[] = $this->config->getBrokerUri(); - } - - if ($this->config->getBrokerToken() !== null) { - $parameters[] = '--broker-token'; - $parameters[] = $this->config->getBrokerToken(); - } - - if ($this->config->getBrokerUsername() !== null) { - $parameters[] = '--broker-username'; - $parameters[] = $this->config->getBrokerUsername(); - } - - if ($this->config->getBrokerPassword() !== null) { - $parameters[] = '--broker-password'; - $parameters[] = $this->config->getBrokerPassword(); - } - - return $parameters; - } - - public function createOrUpdatePacticipant(): array - { - return $this->runThenDecodeJson([ - 'create-or-update-pacticipant', - '--name', $this->config->getName(), - '--repository-url', $this->config->getRepositoryUrl(), - ]); - } - - public function createOrUpdateWebhook(): array - { - return $this->runThenDecodeJson([ - 'create-or-update-webhook', - $this->config->getUrl(), - '--request', $this->config->getRequest(), - '--header', $this->config->getHeader(), - '--data', $this->config->getData(), - '--user', $this->config->getUser(), - '--consumer', $this->config->getConsumer(), - '--provider', $this->config->getProvider(), - '--description', $this->config->getDescription(), - '--uuid', $this->config->getUuid(), - ]); - } - - public function createVersionTag(): array - { - return $this->runThenDecodeJson([ - 'create-version-tag', - '--pacticipant', $this->config->getPacticipant(), - '--version', $this->config->getVersion(), - '--tag', $this->config->getTag(), - ]); - } - - public function createWebhook(): array - { - return $this->runThenDecodeJson([ - 'create-webhook', - $this->config->getUrl(), - '--request', $this->config->getRequest(), - '--header', $this->config->getHeader(), - '--data', $this->config->getData(), - '--user', $this->config->getUser(), - '--consumer', $this->config->getConsumer(), - '--provider', $this->config->getProvider(), - '--description', $this->config->getDescription(), - ]); - } - - public function describeVersion(): array - { - return $this->runThenDecodeJson([ - 'describe-version', - '--pacticipant', $this->config->getPacticipant(), - '--output', 'json', - ]); - } - - public function listLatestPactVersions(): array - { - return $this->runThenDecodeJson([ - 'list-latest-pact-versions', - '--output', 'json', - ]); - } - - public function publish(): void - { - $options = [ - 'publish', - $this->config->getPactLocations(), - '--consumer-app-version', $this->config->getConsumerVersion(), - ]; - - if (null !== $this->config->getBranch()) { - $options[] = '--branch'; - $options[] = $this->config->getBranch(); - } - - if (null !== $this->config->getTag()) { - $options[] = '--tag'; - $options[] = $this->config->getTag(); - } - - $this->run($options); - } - - public function testWebhook(): array - { - return $this->runThenDecodeJson([ - 'test-webhook', - '--uuid', $this->config->getUuid(), - ]); - } - - public function generateUuid(): string - { - return \rtrim($this->run(['generate-uuid'])); - } - - private function run(array $options): string - { - $process = new Process([ - $this->command, - ...$options, - ...$this->getArguments(), - ]); - $process->run(); - - if (!$process->isSuccessful()) { - throw new Exception("PactPHP Process returned non-zero exit code: {$process->getExitCode()}", $process->getExitCode()); - } - - return $process->getOutput(); - } - - private function runThenDecodeJson(array $options): array - { - return \json_decode($this->run($options), true, 512, JSON_THROW_ON_ERROR); - } -} diff --git a/src/PhpPact/Standalone/Broker/BrokerConfig.php b/src/PhpPact/Standalone/Broker/BrokerConfig.php deleted file mode 100644 index 48eac520..00000000 --- a/src/PhpPact/Standalone/Broker/BrokerConfig.php +++ /dev/null @@ -1,316 +0,0 @@ -repositoryUrl; - } - - public function setRepositoryUrl(?string $repositoryUrl): self - { - $this->repositoryUrl = $repositoryUrl; - - return $this; - } - - public function getUrl(): ?string - { - return $this->url; - } - - public function setUrl(?string $url): self - { - $this->url = $url; - - return $this; - } - - public function getVersion(): ?string - { - return $this->version; - } - - public function setVersion(?string $version): self - { - $this->version = $version; - - return $this; - } - - public function getBranch(): ?string - { - return $this->branch; - } - - public function setBranch(?string $branch): self - { - $this->branch = $branch; - - return $this; - } - - public function getTag(): ?string - { - return $this->tag; - } - - public function setTag(?string $tag): self - { - $this->tag = $tag; - - return $this; - } - - public function getName(): ?string - { - return $this->name; - } - - public function setName(?string $name): self - { - $this->name = $name; - - return $this; - } - - public function getRequest(): ?string - { - return $this->request; - } - - public function setRequest(?string $request): self - { - $this->request = $request; - - return $this; - } - - public function getHeader(): ?string - { - return $this->header; - } - - public function setHeader(?string $header): self - { - $this->header = $header; - - return $this; - } - - public function getData(): ?string - { - return $this->data; - } - - public function setData(?string $data): self - { - $this->data = $data; - - return $this; - } - - public function getUser(): ?string - { - return $this->user; - } - - public function setUser(?string $user): self - { - $this->user = $user; - - return $this; - } - - public function getConsumer(): ?string - { - return $this->consumer; - } - - public function setConsumer(?string $consumer): self - { - $this->consumer = $consumer; - - return $this; - } - - public function getProvider(): ?string - { - return $this->provider; - } - - public function setProvider(?string $provider): self - { - $this->provider = $provider; - - return $this; - } - - public function getDescription(): ?string - { - return $this->description; - } - - public function setDescription(?string $description): self - { - $this->description = $description; - - return $this; - } - - public function getUuid(): ?string - { - return $this->uuid; - } - - public function setUuid(?string $uuid): self - { - $this->uuid = $uuid; - - return $this; - } - - public function isVerbose(): bool - { - return $this->verbose; - } - - public function setVerbose(bool $verbose): self - { - $this->verbose = $verbose; - - return $this; - } - - public function getBrokerUri(): ?UriInterface - { - return $this->brokerUri; - } - - public function setBrokerUri(?UriInterface $brokerUri): self - { - $this->brokerUri = $brokerUri; - - return $this; - } - - public function getBrokerToken(): ?string - { - return $this->brokerToken; - } - - public function setBrokerToken(?string $brokerToken): self - { - $this->brokerToken = $brokerToken; - - return $this; - } - - public function getBrokerUsername(): ?string - { - return $this->brokerUsername; - } - - public function setBrokerUsername(?string $brokerUsername): self - { - $this->brokerUsername = $brokerUsername; - - return $this; - } - - public function getBrokerPassword(): ?string - { - return $this->brokerPassword; - } - - public function setBrokerPassword(?string $brokerPassword): self - { - $this->brokerPassword = $brokerPassword; - - return $this; - } - - public function getPacticipant(): string - { - return $this->pacticipant; - } - - public function setPacticipant(?string $pacticipant): self - { - $this->pacticipant = $pacticipant; - - return $this; - } - - public function getConsumerVersion(): ?string - { - return $this->consumerVersion; - } - - public function setConsumerVersion(?string $consumerVersion): self - { - $this->consumerVersion = $consumerVersion; - - return $this; - } - - public function getPactLocations(): ?string - { - return $this->pactLocations; - } - - public function setPactLocations(string $locations): self - { - $this->pactLocations = $locations; - - return $this; - } -} diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index 3c7747c1..ac33f2a7 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -30,9 +30,4 @@ public static function getStubService(): string { return self::$destinationDir . '/bin/pact-stub-server/pact-stub-server'; } - - public static function getBroker(): string - { - return self::$destinationDir . '/bin/pact-ruby-standalone/bin/pact-broker'; - } } diff --git a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php b/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php deleted file mode 100644 index 2e799e12..00000000 --- a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php +++ /dev/null @@ -1,88 +0,0 @@ -setBrokerUri($brokerUri) - ->setBrokerToken($brokerToken) - ->setBrokerUsername($brokerUsername) - ->setBrokerPassword($brokerPassword) - ->setVerbose($verbose) - ->setPacticipant($pacticipant) - ->setRequest($request) - ->setHeader($header) - ->setData($data) - ->setUser($user) - ->setUrl($url) - ->setConsumer($consumer) - ->setProvider($provider) - ->setDescription($description) - ->setUuid($uuid) - ->setVersion($version) - ->setBranch($branch) - ->setTag($tag) - ->setName($name) - ->setRepositoryUrl($repositoryUrl) - ->setConsumerVersion($consumerVersion) - ->setPactLocations($pactLocations); - - static::assertSame($brokerUri, $subject->getBrokerUri()); - static::assertSame($brokerToken, $subject->getBrokerToken()); - static::assertSame($brokerUsername, $subject->getBrokerUsername()); - static::assertSame($brokerPassword, $subject->getBrokerPassword()); - static::assertSame($verbose, $subject->isVerbose()); - static::assertSame($pacticipant, $subject->getPacticipant()); - static::assertSame($request, $subject->getRequest()); - static::assertSame($header, $subject->getHeader()); - static::assertSame($data, $subject->getData()); - static::assertSame($user, $subject->getUser()); - static::assertSame($url, $subject->getUrl()); - static::assertSame($consumer, $subject->getConsumer()); - static::assertSame($provider, $subject->getProvider()); - static::assertSame($description, $subject->getDescription()); - static::assertSame($uuid, $subject->getUuid()); - static::assertSame($version, $subject->getVersion()); - static::assertSame($branch, $subject->getBranch()); - static::assertSame($tag, $subject->getTag()); - static::assertSame($name, $subject->getName()); - static::assertSame($repositoryUrl, $subject->getRepositoryUrl()); - static::assertSame($consumerVersion, $subject->getConsumerVersion()); - static::assertSame($pactLocations, $subject->getPactLocations()); - } -} diff --git a/tests/PhpPact/Standalone/Broker/BrokerTest.php b/tests/PhpPact/Standalone/Broker/BrokerTest.php deleted file mode 100644 index bdbd7908..00000000 --- a/tests/PhpPact/Standalone/Broker/BrokerTest.php +++ /dev/null @@ -1,104 +0,0 @@ -setBrokerToken('someToken') - ->setBrokerUsername('someusername') - ->setBrokerPassword('somepassword') - ))->getArguments(); - - $this->assertSame([ - '--broker-token', - 'someToken', - '--broker-username', - 'someusername', - '--broker-password', - 'somepassword', - ], $arguments); - } - - /** - * @test - */ - public function getArgumentsEmptyConfig(): void - { - $this->assertEmpty((new Broker(new BrokerConfig()))->getArguments()); - } - - /** - * @test - */ - //public function generateUuid(): void - //{ - // $this->assertContains('-', (new Broker(new BrokerConfig()))->generateUuid()); - //} - - /** - * @test - * - * @throws \Exception - */ - public function describeVersion(): void - { - $config = new BrokerConfig(); - $config->setPacticipant('Animal Profile Service') - ->setBrokerUri(new Uri('https://test.pactflow.io')) - ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') - ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); - $broker = new Broker($config); - - $result = $broker->describeVersion(); - - $this->assertArrayHasKey('number', $result); - } - - /** - * @test - * - * @throws \Exception - */ - public function listLatestPactVersions(): void - { - $config = new BrokerConfig(); - $config->setPacticipant("\"Animal Profile Service\"") - ->setBrokerUri(new Uri('https://test.pactflow.io')) - ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') - ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1'); - $broker = new Broker($config); - - $result = $broker->listLatestPactVersions(); - $this->assertArrayHasKey('pacts', $result); - } - - /** - * @test - * - * @throws \Exception - */ - public function publishLogsStdError(): void - { - $config = new BrokerConfig(); - $config->setPactLocations('not a directory'); - $broker = new Broker($config); - try { - $broker->publish(); - } catch(\Exception $e) { - $this->assertEquals(1, $e->getCode()); - $this->assertStringContainsString("PactPHP Process returned non-zero exit code: 1", $e->getMessage()); - } - } -} From e799d37061f1ef460c4358a2278ca02a4891c235 Mon Sep 17 00:00:00 2001 From: tienvx Date: Wed, 15 Mar 2023 23:58:03 +0700 Subject: [PATCH 58/78] Use Rust FFI: Allow set content type --- .../Consumer/AbstractMessageBuilder.php | 7 + .../Consumer/Model/ConsumerRequest.php | 161 ++---------------- .../Consumer/Model/Interaction/BodyTrait.php | 36 ++++ .../Model/Interaction/ContentTypeTrait.php | 20 +++ .../Model/Interaction/HeadersTrait.php | 52 ++++++ .../Model/Interaction/MethodTrait.php | 20 +++ .../Consumer/Model/Interaction/PathTrait.php | 27 +++ .../Consumer/Model/Interaction/QueryTrait.php | 52 ++++++ .../Model/Interaction/StatusTrait.php | 20 +++ src/PhpPact/Consumer/Model/Message.php | 20 ++- .../Consumer/Model/ProviderResponse.php | 92 +--------- .../Interaction/InteractionRegistry.php | 4 +- .../Registry/Interaction/MessageRegistry.php | 10 +- 13 files changed, 274 insertions(+), 247 deletions(-) create mode 100644 src/PhpPact/Consumer/Model/Interaction/BodyTrait.php create mode 100644 src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php create mode 100644 src/PhpPact/Consumer/Model/Interaction/HeadersTrait.php create mode 100644 src/PhpPact/Consumer/Model/Interaction/MethodTrait.php create mode 100644 src/PhpPact/Consumer/Model/Interaction/PathTrait.php create mode 100644 src/PhpPact/Consumer/Model/Interaction/QueryTrait.php create mode 100644 src/PhpPact/Consumer/Model/Interaction/StatusTrait.php diff --git a/src/PhpPact/Consumer/AbstractMessageBuilder.php b/src/PhpPact/Consumer/AbstractMessageBuilder.php index 71a23275..d25a743c 100644 --- a/src/PhpPact/Consumer/AbstractMessageBuilder.php +++ b/src/PhpPact/Consumer/AbstractMessageBuilder.php @@ -56,4 +56,11 @@ public function withContent(mixed $contents): self return $this; } + + public function withContentType(?string $contentType): self + { + $this->message->setContentType($contentType); + + return $this; + } } diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index 4632dee1..58d98740 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -2,159 +2,22 @@ namespace PhpPact\Consumer\Model; -use JsonException; +use PhpPact\Consumer\Model\Interaction\BodyTrait; +use PhpPact\Consumer\Model\Interaction\ContentTypeTrait; +use PhpPact\Consumer\Model\Interaction\HeadersTrait; +use PhpPact\Consumer\Model\Interaction\MethodTrait; +use PhpPact\Consumer\Model\Interaction\PathTrait; +use PhpPact\Consumer\Model\Interaction\QueryTrait; /** * Request initiated by the consumer. */ class ConsumerRequest { - private string $method; - - private string $path; - - /** - * @var array - */ - private array $headers = []; - - private ?string $body = null; - - /** - * @var array - */ - private array $query = []; - - public function getMethod(): string - { - return $this->method; - } - - public function setMethod(string $method): self - { - $this->method = $method; - - return $this; - } - - public function getPath(): string - { - return $this->path; - } - - /** - * @param string|array $path - * - * @throws JsonException - */ - public function setPath(string|array $path): self - { - $this->path = is_array($path) ? json_encode($path, JSON_THROW_ON_ERROR) : $path; - - return $this; - } - - /** - * @return array - */ - public function getHeaders(): array - { - return $this->headers; - } - - /** - * @param array $headers - */ - public function setHeaders(array $headers): self - { - $this->headers = []; - foreach ($headers as $header => $value) { - $this->addHeader($header, $value); - } - - return $this; - } - - /** - * @param string|string[] $value - */ - public function addHeader(string $header, array|string $value): self - { - $this->headers[$header] = []; - if (is_array($value)) { - array_walk($value, fn (string $value) => $this->addHeaderValue($header, $value)); - } else { - $this->addHeaderValue($header, $value); - } - - return $this; - } - - private function addHeaderValue(string $header, string $value): void - { - $this->headers[$header][] = $value; - } - - public function getBody(): ?string - { - return $this->body; - } - - /** - * @param array|string|null $body - * - * @throws JsonException - */ - public function setBody(array|string|null $body): self - { - if (\is_string($body) || \is_null($body)) { - $this->body = $body; - } else { - $this->body = \json_encode($body, JSON_THROW_ON_ERROR); - $this->addHeader('Content-Type', 'application/json'); - } - - return $this; - } - - /** - * @return array - */ - public function getQuery(): array - { - return $this->query; - } - - /** - * @param array $query - */ - public function setQuery(array $query): self - { - $this->query = []; - foreach ($query as $key => $value) { - $this->addQueryParameter($key, $value); - } - - return $this; - } - - /** - * @param string|string[] $value - */ - public function addQueryParameter(string $key, array|string $value): self - { - $this->query[$key] = []; - if (is_array($value)) { - array_walk($value, fn (string $value) => $this->addQueryParameterValue($key, $value)); - } else { - $this->addQueryParameterValue($key, $value); - } - - return $this; - } - - private function addQueryParameterValue(string $key, string $value): void - { - $this->query[$key][] = $value; - } + use HeadersTrait; + use BodyTrait; + use ContentTypeTrait; + use MethodTrait; + use PathTrait; + use QueryTrait; } diff --git a/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php b/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php new file mode 100644 index 00000000..3a7c6b37 --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php @@ -0,0 +1,36 @@ +body; + } + + /** + * @param array|string|null $body + * + * @throws JsonException + */ + public function setBody(array|string|null $body): self + { + if (\is_string($body) || \is_null($body)) { + $this->body = $body; + } else { + $this->body = \json_encode($body, JSON_THROW_ON_ERROR); + if (!isset($this->contentType)) { + $this->setContentType('application/json'); + } + } + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php b/src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php new file mode 100644 index 00000000..257f6cbe --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php @@ -0,0 +1,20 @@ +contentType; + } + + public function setContentType(?string $contentType): self + { + $this->contentType = $contentType; + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Model/Interaction/HeadersTrait.php b/src/PhpPact/Consumer/Model/Interaction/HeadersTrait.php new file mode 100644 index 00000000..ebfa0486 --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/HeadersTrait.php @@ -0,0 +1,52 @@ + + */ + private array $headers = []; + + /** + * @return array + */ + public function getHeaders(): array + { + return $this->headers; + } + + /** + * @param array $headers + */ + public function setHeaders(array $headers): self + { + $this->headers = []; + foreach ($headers as $header => $value) { + $this->addHeader($header, $value); + } + + return $this; + } + + /** + * @param string[]|string $value + */ + public function addHeader(string $header, array|string $value): self + { + $this->headers[$header] = []; + if (is_array($value)) { + array_walk($value, fn (string $value) => $this->addHeaderValue($header, $value)); + } else { + $this->addHeaderValue($header, $value); + } + + return $this; + } + + private function addHeaderValue(string $header, string $value): void + { + $this->headers[$header][] = $value; + } +} diff --git a/src/PhpPact/Consumer/Model/Interaction/MethodTrait.php b/src/PhpPact/Consumer/Model/Interaction/MethodTrait.php new file mode 100644 index 00000000..84ab36cc --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/MethodTrait.php @@ -0,0 +1,20 @@ +method; + } + + public function setMethod(string $method): self + { + $this->method = $method; + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Model/Interaction/PathTrait.php b/src/PhpPact/Consumer/Model/Interaction/PathTrait.php new file mode 100644 index 00000000..9eceebe2 --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/PathTrait.php @@ -0,0 +1,27 @@ +path; + } + + /** + * @param string|array $path + * + * @throws JsonException + */ + public function setPath(array|string $path): self + { + $this->path = is_array($path) ? json_encode($path, JSON_THROW_ON_ERROR) : $path; + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php b/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php new file mode 100644 index 00000000..5d907857 --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/QueryTrait.php @@ -0,0 +1,52 @@ + + */ + private array $query = []; + + /** + * @return array + */ + public function getQuery(): array + { + return $this->query; + } + + /** + * @param array $query + */ + public function setQuery(array $query): self + { + $this->query = []; + foreach ($query as $key => $value) { + $this->addQueryParameter($key, $value); + } + + return $this; + } + + /** + * @param string|string[] $value + */ + public function addQueryParameter(string $key, array|string $value): self + { + $this->query[$key] = []; + if (is_array($value)) { + array_walk($value, fn (string $value) => $this->addQueryParameterValue($key, $value)); + } else { + $this->addQueryParameterValue($key, $value); + } + + return $this; + } + + private function addQueryParameterValue(string $key, string $value): void + { + $this->query[$key][] = $value; + } +} diff --git a/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php b/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php new file mode 100644 index 00000000..31e68f4b --- /dev/null +++ b/src/PhpPact/Consumer/Model/Interaction/StatusTrait.php @@ -0,0 +1,20 @@ +status; + } + + public function setStatus(int $status): self + { + $this->status = $status; + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Model/Message.php b/src/PhpPact/Consumer/Model/Message.php index 1b8b3a4e..796a683f 100644 --- a/src/PhpPact/Consumer/Model/Message.php +++ b/src/PhpPact/Consumer/Model/Message.php @@ -2,12 +2,16 @@ namespace PhpPact\Consumer\Model; +use JsonException; +use PhpPact\Consumer\Model\Interaction\ContentTypeTrait; + /** * Message metadata and contents to be posted to the Mock Server for PACT tests. */ class Message { use ProviderStates; + use ContentTypeTrait; private string $description; @@ -16,7 +20,7 @@ class Message */ private array $metadata = []; - private mixed $contents; + private ?string $contents = null; public function getDescription(): string { @@ -56,14 +60,24 @@ private function setMetadataValue(string $key, string $value): void $this->metadata[$key] = $value; } - public function getContents(): mixed + public function getContents(): ?string { return $this->contents; } + /** + * @throws JsonException + */ public function setContents(mixed $contents): self { - $this->contents = $contents; + if (\is_string($contents) || \is_null($contents)) { + $this->contents = $contents; + } else { + $this->contents = \json_encode($contents, JSON_THROW_ON_ERROR); + if (!isset($this->contentType)) { + $this->setContentType('application/json'); + } + } return $this; } diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index 4ec6ba51..3201811f 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -2,94 +2,18 @@ namespace PhpPact\Consumer\Model; -use JsonException; +use PhpPact\Consumer\Model\Interaction\BodyTrait; +use PhpPact\Consumer\Model\Interaction\ContentTypeTrait; +use PhpPact\Consumer\Model\Interaction\HeadersTrait; +use PhpPact\Consumer\Model\Interaction\StatusTrait; /** * Response expectation that would be in response to a Consumer request from the Provider. */ class ProviderResponse { - private int $status; - - /** - * @var array - */ - private array $headers = []; - - private ?string $body = null; - - public function getStatus(): int - { - return $this->status; - } - - public function setStatus(int $status): self - { - $this->status = $status; - - return $this; - } - - /** - * @return array - */ - public function getHeaders(): array - { - return $this->headers; - } - - /** - * @param array $headers - */ - public function setHeaders(array $headers): self - { - $this->headers = []; - foreach ($headers as $header => $value) { - $this->addHeader($header, $value); - } - - return $this; - } - - /** - * @param string[]|string $value - */ - public function addHeader(string $header, array|string $value): self - { - $this->headers[$header] = []; - if (is_array($value)) { - array_walk($value, fn (string $value) => $this->addHeaderValue($header, $value)); - } else { - $this->addHeaderValue($header, $value); - } - - return $this; - } - - private function addHeaderValue(string $header, string $value): void - { - $this->headers[$header][] = $value; - } - - public function getBody(): ?string - { - return $this->body; - } - - /** - * @param array|string|null $body - * - * @throws JsonException - */ - public function setBody(array|string|null $body): self - { - if (\is_string($body) || \is_null($body)) { - $this->body = $body; - } else { - $this->body = \json_encode($body, JSON_THROW_ON_ERROR); - $this->addHeader('Content-Type', 'application/json'); - } - - return $this; - } + use HeadersTrait; + use BodyTrait; + use ContentTypeTrait; + use StatusTrait; } diff --git a/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php index 313d8c90..7a917416 100644 --- a/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php +++ b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php @@ -76,7 +76,7 @@ private function with(ConsumerRequest $request): self ->withRequest($request->getMethod(), $request->getPath()) ->withQueryParameters($request->getQuery()) ->withHeaders($request->getHeaders()) - ->withBody(null, $request->getBody()); + ->withBody($request->getContentType(), $request->getBody()); return $this; } @@ -86,7 +86,7 @@ private function willRespondWith(ProviderResponse $response): self $this->responseRegistry ->withResponse($response->getStatus()) ->withHeaders($response->getHeaders()) - ->withBody(null, $response->getBody()); + ->withBody($response->getContentType(), $response->getBody()); return $this; } diff --git a/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php index 11f70f91..2da4e9b5 100644 --- a/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php +++ b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php @@ -25,20 +25,12 @@ public function __construct( public function registerMessage(Message $message): void { - if (\is_string($message->getContents())) { - $contents = $message->getContents(); - $contentType = 'text/plain'; - } else { - $contents = \json_encode($message->getContents(), JSON_THROW_ON_ERROR); - $contentType = 'application/json'; - } - $this ->newInteraction($message->getDescription()) ->given($message->getProviderStates()) ->expectsToReceive($message->getDescription()) ->withMetadata($message->getMetadata()) - ->withContents($contentType, $contents); + ->withContents($message->getContentType(), $message->getContents()); } protected function newInteraction(string $description): self From 6f53e7a73edb1dfc83f76ca2ca16897fcb2e7883 Mon Sep 17 00:00:00 2001 From: tienvx Date: Thu, 2 Mar 2023 11:52:51 +0700 Subject: [PATCH 59/78] Default port zero --- src/PhpPact/Standalone/MockService/MockServerConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpPact/Standalone/MockService/MockServerConfig.php b/src/PhpPact/Standalone/MockService/MockServerConfig.php index 39bbe743..fb062e6f 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfig.php @@ -19,7 +19,7 @@ class MockServerConfig extends PactConfig implements MockServerConfigInterface /** * Port on which to run the service. A value of zero will result in the operating system allocating an available port. */ - private int $port = 7200; + private int $port = 0; /** * @var bool From cbe6de11ad1624aafbd18b0c6edbfff51313ab7b Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 13 Mar 2023 17:12:42 +0700 Subject: [PATCH 60/78] Make mock server host and port optional --- example/phpunit.all.xml | 2 -- example/phpunit.consumer.xml | 2 -- example/phpunit.core.xml | 2 -- phpunit.xml | 2 -- .../Standalone/MockService/MockServerEnvConfig.php | 10 ++++++++-- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/example/phpunit.all.xml b/example/phpunit.all.xml index 02a047d5..e36de9e7 100644 --- a/example/phpunit.all.xml +++ b/example/phpunit.all.xml @@ -29,8 +29,6 @@ - - diff --git a/example/phpunit.consumer.xml b/example/phpunit.consumer.xml index 0e521db3..7e45945e 100644 --- a/example/phpunit.consumer.xml +++ b/example/phpunit.consumer.xml @@ -17,8 +17,6 @@ - - diff --git a/example/phpunit.core.xml b/example/phpunit.core.xml index 57e6e2e4..e33f650e 100644 --- a/example/phpunit.core.xml +++ b/example/phpunit.core.xml @@ -17,8 +17,6 @@ - - diff --git a/phpunit.xml b/phpunit.xml index af9ebf68..a2b66f81 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -18,8 +18,6 @@ - - diff --git a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php index 7e90a93d..a8183b2a 100644 --- a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php @@ -14,8 +14,14 @@ class MockServerEnvConfig extends MockServerConfig */ public function __construct() { - $this->setHost($this->parseEnv('PACT_MOCK_SERVER_HOST')); - $this->setPort((int) $this->parseEnv('PACT_MOCK_SERVER_PORT')); + if ($host = $this->parseEnv('PACT_MOCK_SERVER_HOST', false)) { + $this->setHost($host); + } + + if ($port = $this->parseEnv('PACT_MOCK_SERVER_PORT', false)) { + $this->setPort((int) $port); + } + $this->setConsumer($this->parseEnv('PACT_CONSUMER_NAME')); $this->setProvider($this->parseEnv('PACT_PROVIDER_NAME')); $this->setPactDir($this->parseEnv('PACT_OUTPUT_DIR', false)); From b373a320a587244e9b8ff9294b53d993a25670d2 Mon Sep 17 00:00:00 2001 From: tienvx Date: Sun, 7 May 2023 14:33:39 +0700 Subject: [PATCH 61/78] Add generators and matchers --- src/PhpPact/Consumer/Matcher/HttpStatus.php | 30 + src/PhpPact/Consumer/Matcher/Matcher.php | 407 ++++++++++++- .../PhpPact/Consumer/Matcher/MatcherTest.php | 538 +++++++++++++++++- 3 files changed, 945 insertions(+), 30 deletions(-) create mode 100644 src/PhpPact/Consumer/Matcher/HttpStatus.php diff --git a/src/PhpPact/Consumer/Matcher/HttpStatus.php b/src/PhpPact/Consumer/Matcher/HttpStatus.php new file mode 100644 index 00000000..527bd13e --- /dev/null +++ b/src/PhpPact/Consumer/Matcher/HttpStatus.php @@ -0,0 +1,30 @@ + + */ + public static function all(): array + { + return [ + self::INFORMATION, + self::SUCCESS, + self::REDIRECT, + self::CLIENT_ERROR, + self::SERVER_ERROR, + self::NON_ERROR, + self::ERROR, + ]; + } +} diff --git a/src/PhpPact/Consumer/Matcher/Matcher.php b/src/PhpPact/Consumer/Matcher/Matcher.php index c3a41538..a8da98f2 100644 --- a/src/PhpPact/Consumer/Matcher/Matcher.php +++ b/src/PhpPact/Consumer/Matcher/Matcher.php @@ -58,32 +58,80 @@ public function like(mixed $value): array /** * Expect an array of similar data as the value passed in. * + * @return array + */ + public function eachLike(mixed $value): array + { + return $this->atLeastLike($value, 1); + } + + /** + * @param mixed $value example of what the expected data would be + * @param int $min minimum number of objects to verify against + * + * @return array + */ + public function atLeastLike(mixed $value, int $min): array + { + return [ + 'value' => array_fill(0, $min, $value), + 'pact:matcher:type' => 'type', + 'min' => $min, + ]; + } + + /** + * @return array + */ + public function atMostLike(mixed $value, int $max): array + { + return [ + 'value' => [$value], + 'pact:matcher:type' => 'type', + 'max' => $max, + ]; + } + + /** * @param mixed $value example of what the expected data would be * @param int $min minimum number of objects to verify against * * @return array */ - public function eachLike(mixed $value, int $min = 1): array + public function atLeastAndMostLike(mixed $value, int $min, int $max): array { + if ($min <= 0 || $min > $max) { + throw new Exception('Invalid minimum number of elements'); + } + return [ 'value' => array_fill(0, $min, $value), 'pact:matcher:type' => 'type', 'min' => $min, + 'max' => $max, ]; } /** * Validate that a value will match a regex pattern. * - * @param mixed $value example of what the expected data would be + * @param string|null $value example of what the expected data would be * @param string $pattern valid Ruby regex pattern * * @return array * * @throws Exception */ - public function term(mixed $value, string $pattern): array + public function term(?string $value, string $pattern): array { + if (null === $value) { + return [ + 'regex' => $pattern, + 'pact:matcher:type' => 'regex', + 'pact:generator:type' => 'Regex', + ]; + } + $result = preg_match("/$pattern/", $value); if ($result === false || $result === 0) { @@ -106,7 +154,7 @@ public function term(mixed $value, string $pattern): array * * @throws Exception */ - public function regex(mixed $value, string $pattern): array + public function regex(?string $value, string $pattern): array { return $this->term($value, $pattern); } @@ -211,14 +259,74 @@ public function decimal(float $float = 13.01): array return $this->like($float); } + /** + * @return array + */ + public function booleanV3(?bool $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'RandomBoolean', + 'pact:matcher:type' => 'boolean', + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'boolean', + ]; + } + + /** + * @return array + */ + public function integerV3(?int $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'RandomInt', + 'pact:matcher:type' => 'integer', + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'integer', + ]; + } + + /** + * @return array + */ + public function decimalV3(?float $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'RandomDecimal', + 'pact:matcher:type' => 'decimal', + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'decimal', + ]; + } + /** * @return array * * @throws Exception */ - public function hexadecimal(string $hex = '3F'): array + public function hexadecimal(?string $value = null): array { - return $this->term($hex, self::HEX_FORMAT); + if (null === $value) { + return [ + 'pact:generator:type' => 'RandomHexadecimal', + ] + $this->term(null, self::HEX_FORMAT); + } + + return $this->term($value, self::HEX_FORMAT); } /** @@ -226,9 +334,15 @@ public function hexadecimal(string $hex = '3F'): array * * @throws Exception */ - public function uuid(string $uuid = 'ce118b6e-d8e1-11e7-9296-cec278b6b50a'): array + public function uuid(?string $value = null): array { - return $this->term($uuid, self::UUID_V4_FORMAT); + if (null === $value) { + return [ + 'pact:generator:type' => 'Uuid', + ] + $this->term(null, self::UUID_V4_FORMAT); + } + + return $this->term($value, self::UUID_V4_FORMAT); } /** @@ -260,4 +374,281 @@ public function email(string $email = 'hello@pact.io'): array { return $this->term($email, self::EMAIL_FORMAT); } + + /** + * @return array + * + * @throws Exception + */ + public function ipv4AddressV3(?string $ip = null): array + { + if (null === $ip) { + return $this->term(null, self::IPV4_FORMAT); + } + + return $this->ipv4Address($ip); + } + + /** + * @return array + * + * @throws Exception + */ + public function ipv6AddressV3(?string $ip = null): array + { + if (null === $ip) { + return $this->term(null, self::IPV6_FORMAT); + } + + return $this->ipv6Address($ip); + } + + /** + * @return array + * + * @throws Exception + */ + public function emailV3(?string $email = null): array + { + if (null === $email) { + return $this->term(null, self::EMAIL_FORMAT); + } + + return $this->email($email); + } + + /** + * Value that must be null. This will only match the JSON Null value. For other content types, it will + * match if the attribute is missing. + * + * @return array + */ + public function nullValue(): array + { + return [ + 'pact:matcher:type' => 'null', + ]; + } + + /** + * @return array + */ + public function date(string $format = 'yyyy-MM-dd', ?string $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'Date', + 'pact:matcher:type' => 'date', + 'format' => $format, + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'date', + 'format' => $format, + ]; + } + + /** + * @return array + */ + public function time(string $format = 'HH:mm::ss', ?string $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'Time', + 'pact:matcher:type' => 'time', + 'format' => $format, + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'time', + 'format' => $format, + ]; + } + + /** + * @return array + */ + public function datetime(string $format = "YYYY-mm-DD'T'HH:mm:ss", ?string $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'DateTime', + 'pact:matcher:type' => 'datetime', + 'format' => $format, + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'datetime', + 'format' => $format, + ]; + } + + /** + * @return array + */ + public function string(?string $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'RandomString', + ] + $this->like('some string'); // No matcher for string? + } + + return $this->like($value); // No matcher for string? + } + + /** + * @param array $macher + * + * @return array + */ + public function fromProviderState(array $macher, string $expression): array + { + return $macher + [ + 'pact:generator:type' => 'ProviderState', + 'expression' => $expression, + ]; + } + + /** + * Value that must be equal to the example. This is mainly used to reset the matching rules which cascade. + * + * @return array + */ + public function equal(mixed $value): array + { + return [ + 'pact:matcher:type' => 'equality', + 'value' => $value, + ]; + } + + /** + * Value that must include the example value as a substring. + * + * @return array + */ + public function includes(string $value): array + { + return [ + 'pact:matcher:type' => 'include', + 'value' => $value, + ]; + } + + /** + * Value must be a number + * + * @param int|float|null $value Example value. If omitted a random integer value will be generated. + * + * @return array + */ + public function number(int|float|null $value = null): array + { + if (null === $value) { + return [ + 'pact:generator:type' => 'RandomInt', + 'pact:matcher:type' => 'number', + ]; + } + + return [ + 'value' => $value, + 'pact:matcher:type' => 'number', + ]; + } + + /** + * Matches the items in an array against a number of variants. Matching is successful if each variant + * occurs once in the array. Variants may be objects containing matching rules. + * + * @param array $variants + * + * @return array + */ + public function arrayContaining(array $variants): array + { + return [ + 'pact:matcher:type' => 'arrayContains', + 'variants' => $variants, + ]; + } + + /** + * Value must be present and not empty (not null or the empty string or empty array or empty object) + * + * @return array + */ + public function notEmpty(mixed $value): array + { + return [ + 'value' => $value, + 'pact:matcher:type' => 'notEmpty', + ]; + } + + /** + * Value must be valid based on the semver specification + * + * @return array + */ + public function semver(string $value): array + { + return [ + 'value' => $value, + 'pact:matcher:type' => 'semver', + ]; + } + + /** + * Matches the response status code. + * + * @return array + */ + public function statusCode(string $status): array + { + if (!in_array($status, HttpStatus::all())) { + throw new Exception(sprintf("Status '%s' is not supported. Supported status are: %s", $status, implode(', ', HttpStatus::all()))); + } + + return [ + 'status' => $status, + 'pact:matcher:type' => 'statusCode', + ]; + } + + /** + * Match the values in a map, ignoring the keys + * + * @param array $values + * + * @return array + */ + public function values(array $values): array + { + return [ + 'value' => $values, + 'pact:matcher:type' => 'values', + ]; + } + + /** + * Match binary data by its content type (magic file check) + * + * @return array + */ + public function contentType(string $contentType): array + { + return [ + 'value' => $contentType, + 'pact:matcher:type' => 'contentType', + ]; + } } diff --git a/tests/PhpPact/Consumer/Matcher/MatcherTest.php b/tests/PhpPact/Consumer/Matcher/MatcherTest.php index 6ad9025a..39b0e168 100644 --- a/tests/PhpPact/Consumer/Matcher/MatcherTest.php +++ b/tests/PhpPact/Consumer/Matcher/MatcherTest.php @@ -3,6 +3,7 @@ namespace PhpPactTest\Consumer\Matcher; use Exception; +use PhpPact\Consumer\Matcher\HttpStatus; use PhpPact\Consumer\Matcher\Matcher; use PHPUnit\Framework\TestCase; @@ -35,14 +36,10 @@ public function testLike() } /** - * @throws Exception + * @dataProvider dataProviderForEachLikeTest */ - public function testEachLikeStdClass() + public function testEachLike(object|array $value) { - $object = new \stdClass(); - $object->value1 = $this->matcher->like(1); - $object->value2 = 2; - $expected = \json_encode([ 'value' => [ [ @@ -51,27 +48,70 @@ public function testEachLikeStdClass() 'pact:matcher:type' => 'type', ], 'value2' => 2, - ] + ], ], 'pact:matcher:type' => 'type', 'min' => 1, ]); - $actual = \json_encode($this->matcher->eachLike($object, 1)); + $actual = \json_encode($this->matcher->eachLike($value)); $this->assertEquals($expected, $actual); } + public function dataProviderForEachLikeTest() + { + $value1Matcher = [ + 'value' => 1, + 'pact:matcher:type' => 'type', + ]; + + $object = new \stdClass(); + $object->value1 = $value1Matcher; + $object->value2 = 2; + + $array = [ + 'value1' => $value1Matcher, + 'value2' => 2, + ]; + + return [ + [$object], + [$array], + ]; + } + /** - * @throws Exception + * @dataProvider dataProviderForEachLikeTest */ - public function testEachLikeArray() + public function testAtLeastLike(object|array $value) { - $object = [ - 'value1' => $this->matcher->like(1), + $eachValueMatcher = [ + 'value1' => [ + 'value' => 1, + 'pact:matcher:type' => 'type', + ], 'value2' => 2, ]; + $expected = \json_encode([ + 'value' => [ + $eachValueMatcher, + $eachValueMatcher, + ], + 'pact:matcher:type' => 'type', + 'min' => 2, + ]); + $actual = \json_encode($this->matcher->atLeastLike($value, 2)); + + $this->assertEquals($expected, $actual); + } + + /** + * @dataProvider dataProviderForEachLikeTest + */ + public function testAtMostLike(object|array $value) + { $expected = \json_encode([ 'value' => [ [ @@ -80,13 +120,50 @@ public function testEachLikeArray() 'pact:matcher:type' => 'type', ], 'value2' => 2, - ] + ], ], 'pact:matcher:type' => 'type', - 'min' => 1, + 'max' => 2, + ]); + + $actual = \json_encode($this->matcher->atMostLike($value, 2)); + + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testAtLeastAndMostLikeInvalidMin() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('Invalid minimum number of elements'); + $this->matcher->atLeastAndMostLike('text', 10, 1); + } + + /** + * @dataProvider dataProviderForEachLikeTest + */ + public function testAtLeastAndMostLike(object|array $value) + { + $eachValueMatcher = [ + 'value1' => [ + 'value' => 1, + 'pact:matcher:type' => 'type', + ], + 'value2' => 2, + ]; + $expected = \json_encode([ + 'value' => [ + $eachValueMatcher, + $eachValueMatcher, + ], + 'pact:matcher:type' => 'type', + 'min' => 2, + 'max' => 4, ]); - $actual = \json_encode($this->matcher->eachLike($object, 1)); + $actual = \json_encode($this->matcher->atLeastAndMostLike($value, 2, 4)); $this->assertEquals($expected, $actual); } @@ -97,6 +174,7 @@ public function testEachLikeArray() public function testRegexNoMatch() { $this->expectException(Exception::class); + $this->expectExceptionMessage('The pattern BadPattern is not valid for value SomeWord. Failed with error code 0.'); $this->matcher->regex('SomeWord', 'BadPattern'); } @@ -119,7 +197,7 @@ public function testRegex() /** * @throws Exception */ - public function testDate() + public function testDateISO8601() { $expected = [ 'value' => '2010-01-17', @@ -137,7 +215,7 @@ public function testDate() * * @throws Exception */ - public function testTime($time) + public function testTimeISO8601($time) { $expected = [ 'value' => $time, @@ -171,7 +249,7 @@ public function dataProviderForTimeTest() * * @throws Exception */ - public function testDateTime($dateTime) + public function testDateTimeISO8601($dateTime) { $expected = [ 'value' => $dateTime, @@ -203,7 +281,7 @@ public function dataProviderForDateTimeTest() * * @throws Exception */ - public function testDateTimeWithMillis($dateTime) + public function testDateTimeWithMillisISO8601($dateTime) { $expected = [ 'value' => $dateTime, @@ -276,6 +354,72 @@ public function testDecimal() $this->assertEquals('{"value":13.01,"pact:matcher:type":"type"}', $json); } + public function testIntegerV3() + { + $expected = [ + 'value' => 13, + 'pact:matcher:type' => 'integer', + ]; + $actual = $this->matcher->integerV3(13); + + $this->assertEquals($expected, $actual); + } + + public function testRandomIntegerV3() + { + $expected = [ + 'pact:generator:type' => 'RandomInt', + 'pact:matcher:type' => 'integer', + ]; + $actual = $this->matcher->integerV3(); + + $this->assertEquals($expected, $actual); + } + + public function testBooleanV3() + { + $expected = [ + 'value' => true, + 'pact:matcher:type' => 'boolean', + ]; + $actual = $this->matcher->booleanV3(true); + + $this->assertEquals($expected, $actual); + } + + public function testRandomBooleanV3() + { + $expected = [ + 'pact:generator:type' => 'RandomBoolean', + 'pact:matcher:type' => 'boolean', + ]; + $actual = $this->matcher->booleanV3(); + + $this->assertEquals($expected, $actual); + } + + public function testDecimalV3() + { + $expected = [ + 'value' => 13.01, + 'pact:matcher:type' => 'decimal', + ]; + $actual = $this->matcher->decimalV3(13.01); + + $this->assertEquals($expected, $actual); + } + + public function testRandomDecimalV3() + { + $expected = [ + 'pact:generator:type' => 'RandomDecimal', + 'pact:matcher:type' => 'decimal', + ]; + $actual = $this->matcher->decimalV3(); + + $this->assertEquals($expected, $actual); + } + /** * @throws Exception */ @@ -286,8 +430,24 @@ public function testHexadecimal() 'regex' => '^[0-9a-fA-F]+$', 'pact:matcher:type' => 'regex', ]; + $actual = $this->matcher->hexadecimal('3F'); + + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testRandomHexadecimal() + { + $expected = [ + 'regex' => '^[0-9a-fA-F]+$', + 'pact:matcher:type' => 'regex', + 'pact:generator:type' => 'RandomHexadecimal', + ]; + $actual = $this->matcher->hexadecimal(); - $this->assertEquals($expected, $this->matcher->hexadecimal()); + $this->assertEquals($expected, $actual); } /** @@ -300,8 +460,24 @@ public function testUuid() 'regex' => '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$', 'pact:matcher:type' => 'regex', ]; + $actual = $this->matcher->uuid('ce118b6e-d8e1-11e7-9296-cec278b6b50a'); - $this->assertEquals($expected, $this->matcher->uuid()); + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testRandomUuid() + { + $expected = [ + 'pact:generator:type' => 'Uuid', + 'regex' => '^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$', + 'pact:matcher:type' => 'regex', + ]; + $actual = $this->matcher->uuid(); + + $this->assertEquals($expected, $actual); } /** @@ -332,7 +508,6 @@ public function testIpv6Address() $this->assertEquals($expected, $this->matcher->ipv6Address()); } - /** * @throws Exception */ @@ -345,4 +520,323 @@ public function testEmail() ]; $this->assertEquals($expected, $this->matcher->email()); } + + /** + * @throws Exception + */ + public function testIpv4AddressV3() + { + $expected = $this->matcher->ipv4Address(); + $actual = $this->matcher->ipv4AddressV3('127.0.0.13'); + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testIpv6AddressV3() + { + $expected = $this->matcher->ipv6Address(); + $actual = $this->matcher->ipv6AddressV3('::ffff:192.0.2.128'); + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testEmailV3() + { + $expected = $this->matcher->email(); + $actual = $this->matcher->emailV3('hello@pact.io'); + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testRandomIpv4AddressV3() + { + $expected = [ + 'regex' => '^(\\d{1,3}\\.)+\\d{1,3}$', + 'pact:matcher:type' => 'regex', + 'pact:generator:type' => 'Regex', + ]; + $actual = $this->matcher->ipv4AddressV3(); + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testRandomIpv6AddressV3() + { + $expected = [ + 'regex' => '^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$', + 'pact:matcher:type' => 'regex', + 'pact:generator:type' => 'Regex', + ]; + $actual = $this->matcher->ipv6AddressV3(); + $this->assertEquals($expected, $actual); + } + + /** + * @throws Exception + */ + public function testRandomEmailV3() + { + $expected = [ + 'regex' => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$', + 'pact:matcher:type' => 'regex', + 'pact:generator:type' => 'Regex', + ]; + $actual = $this->matcher->emailV3(); + $this->assertEquals($expected, $actual); + } + + public function testNullValue() + { + $expected = [ + 'pact:matcher:type' => 'null', + ]; + $actual = $this->matcher->nullValue(); + $this->assertEquals($expected, $actual); + } + + public function testDate() + { + $expected = [ + 'value' => '2022-11-21', + 'pact:matcher:type' => 'date', + 'format' => 'yyyy-MM-dd', + ]; + $actual = $this->matcher->date('yyyy-MM-dd', '2022-11-21'); + + $this->assertEquals($expected, $actual); + } + + public function testRandomDate() + { + $expected = [ + 'pact:generator:type' => 'Date', + 'pact:matcher:type' => 'date', + 'format' => 'yyyy-MM-dd', + ]; + $actual = $this->matcher->date(); + + $this->assertEquals($expected, $actual); + } + + public function testTime() + { + $expected = [ + 'value' => '21:45::31', + 'pact:matcher:type' => 'time', + 'format' => 'HH:mm::ss', + ]; + $actual = $this->matcher->time('HH:mm::ss', '21:45::31'); + + $this->assertEquals($expected, $actual); + } + + public function testRandomTime() + { + $expected = [ + 'pact:generator:type' => 'Time', + 'pact:matcher:type' => 'time', + 'format' => 'HH:mm::ss', + ]; + $actual = $this->matcher->time(); + + $this->assertEquals($expected, $actual); + } + + public function testDateTime() + { + $expected = [ + 'value' => '2015-08-06T16:53:10', + 'pact:matcher:type' => 'datetime', + 'format' => "YYYY-mm-DD'T'HH:mm:ss", + ]; + $actual = $this->matcher->datetime("YYYY-mm-DD'T'HH:mm:ss", '2015-08-06T16:53:10'); + + $this->assertEquals($expected, $actual); + } + + public function testRandomDateTime() + { + $expected = [ + 'pact:generator:type' => 'DateTime', + 'pact:matcher:type' => 'datetime', + 'format' => "YYYY-mm-DD'T'HH:mm:ss", + ]; + $actual = $this->matcher->datetime(); + + $this->assertEquals($expected, $actual); + } + + public function testString() + { + $expected = [ + 'pact:matcher:type' => 'type', + 'value' => 'test string', + ]; + $actual = $this->matcher->string('test string'); + + $this->assertEquals($expected, $actual); + } + + public function testRandomString() + { + $expected = [ + 'pact:generator:type' => 'RandomString', + 'pact:matcher:type' => 'type', + 'value' => 'some string', + ]; + $actual = $this->matcher->string(); + + $this->assertEquals($expected, $actual); + } + + public function testFromProviderState() + { + $expected = [ + 'regex' => Matcher::UUID_V4_FORMAT, + 'pact:matcher:type' => 'regex', + 'value' => 'f2392c53-6e55-48f7-8e08-18e4bf99c795', + 'pact:generator:type' => 'ProviderState', + 'expression' => '${id}', + ]; + $actual = $this->matcher->fromProviderState($this->matcher->uuid('f2392c53-6e55-48f7-8e08-18e4bf99c795'), '${id}'); + + $this->assertEquals($expected, $actual); + } + + public function testEqual() + { + $expected = [ + 'pact:matcher:type' => 'equality', + 'value' => 'test string', + ]; + $actual = $this->matcher->equal('test string'); + + $this->assertEquals($expected, $actual); + } + + public function testIncludes() + { + $expected = [ + 'pact:matcher:type' => 'include', + 'value' => 'test string', + ]; + $actual = $this->matcher->includes('test string'); + + $this->assertEquals($expected, $actual); + } + + public function testNumber() + { + $expected = [ + 'value' => 13.01, + 'pact:matcher:type' => 'number', + ]; + $actual = $this->matcher->number(13.01); + + $this->assertEquals($expected, $actual); + } + + public function testRandomNumber() + { + $expected = [ + 'pact:generator:type' => 'RandomInt', + 'pact:matcher:type' => 'number', + ]; + $actual = $this->matcher->number(); + + $this->assertEquals($expected, $actual); + } + + public function testArrayContaining() + { + $expected = [ + 'pact:matcher:type' => 'arrayContains', + 'variants' => [ + 'item 1', + 'item 2' + ], + ]; + $actual = $this->matcher->arrayContaining([ + 'item 1', + 'item 2' + ]); + + $this->assertEquals($expected, $actual); + } + + public function testNotEmpty() + { + $expected = [ + 'value' => 'not empty string', + 'pact:matcher:type' => 'notEmpty', + ]; + $actual = $this->matcher->notEmpty('not empty string'); + + $this->assertEquals($expected, $actual); + } + + public function testSemver() + { + $expected = [ + 'value' => '1.2.3', + 'pact:matcher:type' => 'semver', + ]; + $actual = $this->matcher->semver('1.2.3'); + + $this->assertEquals($expected, $actual); + } + + public function testInvalidStatusCode() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage("Status 'invalid' is not supported. Supported status are: info, success, redirect, clientError, serverError, nonError, error"); + $this->matcher->statusCode('invalid'); + } + + public function testValidStatusCode() + { + $expected = [ + 'status' => 'success', + 'pact:matcher:type' => 'statusCode', + ]; + $actual = $this->matcher->statusCode(HttpStatus::SUCCESS); + + $this->assertEquals($expected, $actual); + } + + public function testValues() + { + $expected = [ + 'pact:matcher:type' => 'values', + 'value' => [ + 'item 1', + 'item 2' + ], + ]; + $actual = $this->matcher->values([ + 'item 1', + 'item 2' + ]); + + $this->assertEquals($expected, $actual); + } + + public function testContentType() + { + $expected = [ + 'value' => 'image/jpeg', + 'pact:matcher:type' => 'contentType', + ]; + $actual = $this->matcher->contentType('image/jpeg'); + + $this->assertEquals($expected, $actual); + } } From cd499130c54da33c1498e5b3a4786bb49e452e96 Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 6 Mar 2023 16:26:26 +0700 Subject: [PATCH 62/78] Move endpoint from config to parameter --- README.md | 5 ++--- UPGRADE-9.0.md | 7 +++++++ .../StubService/Service/StubServerHttpService.php | 4 ++-- .../Service/StubServerHttpServiceInterface.php | 2 +- .../Standalone/StubService/StubServerConfig.php | 15 --------------- .../StubService/StubServerConfigInterface.php | 4 ---- .../Service/StubServerHttpServiceTest.php | 11 +++++------ .../Standalone/StubServer/StubServerTest.php | 8 +++----- 8 files changed, 20 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index f4c998c6..5df03992 100644 --- a/README.md +++ b/README.md @@ -400,13 +400,12 @@ $endpoint = 'test'; $config = (new StubServerConfig()) ->setFiles($files) - ->setPort($port) - ->setEndpoint($endpoint); + ->setPort($port); $stubServer = new StubServer($config); $stubServer->start(); $service = new StubServerHttpService(new GuzzleClient(), $config); -echo $service->getJson(); // output: {"results":[{"name":"Games"}]} +echo $service->getJson($endpoint); // output: {"results":[{"name":"Games"}]} ``` diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md index aca9d300..323b034a 100644 --- a/UPGRADE-9.0.md +++ b/UPGRADE-9.0.md @@ -50,3 +50,10 @@ UPGRADE FROM 8.x to 9.0 $this->assertTrue($verifyResult); ``` + +* Stub Server + * Endpoint now can be set by: + ```php + $service = new StubServerHttpService(new GuzzleClient(), $this->config); + $service->getJson($endpoint); + ``` diff --git a/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php index 81f5f726..f40100a1 100644 --- a/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php +++ b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php @@ -26,9 +26,9 @@ public function __construct(ClientInterface $client, StubServerConfigInterface $ * {@inheritdoc} * @throws \JsonException */ - public function getJson(): string + public function getJson(string $endpoint): string { - $uri = $this->config->getBaseUri()->withPath('/' . $this->config->getEndpoint()); + $uri = $this->config->getBaseUri()->withPath('/' . $endpoint); $response = $this->client->get($uri, [ 'headers' => [ 'Content-Type' => 'application/json', diff --git a/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php b/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php index e1b84aa0..325637e2 100644 --- a/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php +++ b/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php @@ -7,5 +7,5 @@ interface StubServerHttpServiceInterface /** * Get the current state of the PACT JSON file and write it to disk. */ - public function getJson(): string; + public function getJson(string $endpoint): string; } diff --git a/src/PhpPact/Standalone/StubService/StubServerConfig.php b/src/PhpPact/Standalone/StubService/StubServerConfig.php index beb55ee4..42d62315 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfig.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfig.php @@ -46,8 +46,6 @@ class StubServerConfig implements StubServerConfigInterface private bool $emptyProviderState = false; private bool $insecureTls = false; - private string $endpoint; - public function getBrokerUrl(): ?UriInterface { return $this->brokerUrl; @@ -256,17 +254,4 @@ public function getBaseUri(): UriInterface { return new Uri("http://localhost:{$this->getPort()}"); } - - - public function getEndpoint(): string - { - return $this->endpoint; - } - - public function setEndpoint(string $endpoint): StubServerConfigInterface - { - $this->endpoint = $endpoint; - - return $this; - } } diff --git a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php index ecfe26c0..25d0decb 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php @@ -150,8 +150,4 @@ public function setProviderNames(array $providerNames): self; public function getProviderNames(): array; public function getBaseUri(): UriInterface; - - public function getEndpoint(): string; - - public function setEndpoint(string $endpoint): self; } diff --git a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php b/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php index 9a9769b0..734e978c 100644 --- a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php +++ b/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php @@ -28,14 +28,12 @@ class StubServerHttpServiceTest extends TestCase */ protected function setUp(): void { - $files = [__DIR__ . '/../../../../_resources/someconsumer-someprovider.json']; - $port = 7201; - $endpoint = 'test'; + $files = [__DIR__ . '/../../../../_resources/someconsumer-someprovider.json']; + $port = 7201; $this->config = (new StubServerConfig()) ->setFiles($files) - ->setPort($port) - ->setEndpoint($endpoint); + ->setPort($port); $this->stubServer = new StubServer($this->config); $this->stubServer->start(); @@ -49,7 +47,8 @@ protected function tearDown(): void public function testGetJson() { - $result = $this->service->getJson(); + $endpoint = 'test'; + $result = $this->service->getJson($endpoint); $this->assertEquals('{"results":[{"name":"Games"}]}', $result); } } diff --git a/tests/PhpPact/Standalone/StubServer/StubServerTest.php b/tests/PhpPact/Standalone/StubServer/StubServerTest.php index 687e5800..4ea6cb51 100644 --- a/tests/PhpPact/Standalone/StubServer/StubServerTest.php +++ b/tests/PhpPact/Standalone/StubServer/StubServerTest.php @@ -14,14 +14,12 @@ class StubServerTest extends TestCase public function testStartAndStop() { try { - $files = [__DIR__ . '/../../../_resources/someconsumer-someprovider.json']; - $port = 7201; - $endpoint= 'test'; + $files = [__DIR__ . '/../../../_resources/someconsumer-someprovider.json']; + $port = 7201; $subject = (new StubServerConfig()) ->setFiles($files) - ->setPort($port) - ->setEndpoint($endpoint); + ->setPort($port); $stubServer = new StubServer($subject); $pid = $stubServer->start(); From 05fca684607eb5c6ca87640cfe33aa85c88347fc Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 6 Mar 2023 17:39:54 +0700 Subject: [PATCH 63/78] Allow random port for stub server --- .../Standalone/StubService/StubServer.php | 7 ++++++- .../StubService/StubServerConfig.php | 4 ++-- .../StubService/StubServerConfigInterface.php | 4 ++-- .../Standalone/StubServer/StubServerTest.php | 20 +++++++++++++++++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/PhpPact/Standalone/StubService/StubServer.php b/src/PhpPact/Standalone/StubService/StubServer.php index 551e884a..7e97dacd 100644 --- a/src/PhpPact/Standalone/StubService/StubServer.php +++ b/src/PhpPact/Standalone/StubService/StubServer.php @@ -35,7 +35,12 @@ public function start(): ?int echo $buffer; }); $this->process->waitUntil(function (string $type, string $output) { - return false !== \strpos($output, 'Server started on port'); + $result = preg_match('/Server started on port (\d+)/', $output, $matches); + if ($result === 1 && $this->config->getPort() === 0) { + $this->config->setPort((int)$matches[1]); + } + + return $result; }); return $this->process->getPid(); diff --git a/src/PhpPact/Standalone/StubService/StubServerConfig.php b/src/PhpPact/Standalone/StubService/StubServerConfig.php index beb55ee4..9fb29ae7 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfig.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfig.php @@ -11,7 +11,7 @@ class StubServerConfig implements StubServerConfigInterface { private ?UriInterface $brokerUrl = null; - private ?int $port = null; + private int $port = 0; private ?string $extension = null; private ?string $logLevel = null; @@ -108,7 +108,7 @@ public function getLogLevel(): ?string return $this->logLevel; } - public function getPort(): ?int + public function getPort(): int { return $this->port; } diff --git a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php index ecfe26c0..8f1f0798 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php @@ -54,9 +54,9 @@ public function getLogLevel(): ?string; public function setLogLevel(string $logLevel): self; /** - * @return null|int the port of the stub service + * @return int the port of the stub service */ - public function getPort(): ?int; + public function getPort(): int; /** * @param int $port Port to run on (defaults to random port assigned by the OS) diff --git a/tests/PhpPact/Standalone/StubServer/StubServerTest.php b/tests/PhpPact/Standalone/StubServer/StubServerTest.php index 687e5800..5f0e130f 100644 --- a/tests/PhpPact/Standalone/StubServer/StubServerTest.php +++ b/tests/PhpPact/Standalone/StubServer/StubServerTest.php @@ -31,4 +31,24 @@ public function testStartAndStop() $this->assertTrue($result); } } + + /** + * @throws \Exception + */ + public function testRandomPort(): void + { + try { + $files = [__DIR__ . '/../../../_resources/someconsumer-someprovider.json']; + + $subject = (new StubServerConfig()) + ->setFiles($files); + + $stubServer = new StubServer($subject); + $stubServer->start(); + $this->assertGreaterThan(0, $subject->getPort()); + } finally { + $result = $stubServer->stop(); + $this->assertTrue($result); + } + } } From 194ad08b56605f44667929858ad242d222021b03 Mon Sep 17 00:00:00 2001 From: tienvx Date: Tue, 4 Apr 2023 10:01:34 +0700 Subject: [PATCH 64/78] Fix stub server hang on too low log level --- .../LogLevelNotSupportedException.php | 9 ++++++++ .../Standalone/StubService/StubServer.php | 23 +++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/PhpPact/Standalone/StubService/Exception/LogLevelNotSupportedException.php diff --git a/src/PhpPact/Standalone/StubService/Exception/LogLevelNotSupportedException.php b/src/PhpPact/Standalone/StubService/Exception/LogLevelNotSupportedException.php new file mode 100644 index 00000000..05de2a78 --- /dev/null +++ b/src/PhpPact/Standalone/StubService/Exception/LogLevelNotSupportedException.php @@ -0,0 +1,9 @@ +process->start(function (string $type, string $buffer) { echo $buffer; }); - $this->process->waitUntil(function (string $type, string $output) { - $result = preg_match('/Server started on port (\d+)/', $output, $matches); - if ($result === 1 && $this->config->getPort() === 0) { - $this->config->setPort((int)$matches[1]); - } - return $result; - }); + $logLevel = $this->config->getLogLevel(); + if (is_null($logLevel) || in_array($logLevel, ['info', 'debug', 'trace'])) { + $this->process->waitUntil(function (string $type, string $output) { + $result = preg_match('/Server started on port (\d+)/', $output, $matches); + if ($result === 1 && $this->config->getPort() === 0) { + $this->config->setPort((int)$matches[1]); + } + + return $result; + }); + } else { + if ($this->config->getPort() === 0) { + throw new LogLevelNotSupportedException(sprintf("Setting random port for stub server required log level 'info', 'debug' or 'trace'. '%s' given.", $logLevel)); + } + } return $this->process->getPid(); } From 64399fea3415913f32e4c5ae3802643752e13e80 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Thu, 18 May 2023 23:58:42 +0100 Subject: [PATCH 65/78] test: v2.0.1 (pact-2.0.1) - pact-ruby-standalone --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 76afe264..feeac8f0 100644 --- a/composer.json +++ b/composer.json @@ -79,7 +79,7 @@ "extra": { "downloads": { "pact-ruby-standalone": { - "version": "2.0.0", + "version": "2.0.1", "variables": { "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'windows' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64')", From 9dbbde197aed79009fb1e08fdbfa0bc4ab6ab1ec Mon Sep 17 00:00:00 2001 From: tienvx Date: Mon, 29 May 2023 22:49:33 +0700 Subject: [PATCH 66/78] Fix stub server always load pact files from pact broker --- src/PhpPact/Standalone/StubService/StubServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpPact/Standalone/StubService/StubServer.php b/src/PhpPact/Standalone/StubService/StubServer.php index 551e884a..52994774 100644 --- a/src/PhpPact/Standalone/StubService/StubServer.php +++ b/src/PhpPact/Standalone/StubService/StubServer.php @@ -29,7 +29,7 @@ public function __construct(StubServerConfigInterface $config) */ public function start(): ?int { - $this->process = new Process([Scripts::getStubService(), ...$this->getArguments()]); + $this->process = new Process([Scripts::getStubService(), ...$this->getArguments()], null, ['PACT_BROKER_BASE_URL' => false]); $this->process->start(function (string $type, string $buffer) { echo $buffer; From 444a84f24bbdd00e789deb71f4e5aa5f48014aee Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Mon, 10 Jul 2023 14:05:07 +0100 Subject: [PATCH 67/78] chore: update pact-ruby-standalone to 2.0.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index feeac8f0..b3980d27 100644 --- a/composer.json +++ b/composer.json @@ -79,7 +79,7 @@ "extra": { "downloads": { "pact-ruby-standalone": { - "version": "2.0.1", + "version": "2.0.2", "variables": { "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'windows' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64')", From 64a99f6d3d96db0fabf73dca4c54e5efe90fa78e Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Wed, 12 Jul 2023 13:16:35 +0100 Subject: [PATCH 68/78] deps(ci): create dependabot config --- .github/dependabot.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..5d94c326 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# Dependabot Config + +version: 2 +updates: + + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + + # Maintain dependencies for Composer + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "weekly" From 7ebfafd0d204cf13bad8c79876f54583d0d4e891 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Wed, 12 Jul 2023 13:32:23 +0100 Subject: [PATCH 69/78] docs: releasing instructions --- RELEASING.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 RELEASING.md diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..4ae5d59e --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,18 @@ +# Pact-PHP Releasing + +Pact-PHP packages are published to Packagist + +- https://packagist.org/packages/pact-foundation/pact-php + +## Release Process + +The release process is automated via GitHub Release tags, and Packagist Webhooks. + +1. Create a tag from the master branch in GitHub + - New versions of your package are automatically fetched from tags you create in your VCS repository. +2. Release to Packagist + - A GitHub webhook will inform packagist that the package has been updated. +3. Create a release from the tag in GitHub + - Set it to `latest` + - Click `Generate CHANGELOG` to fill out a changelog. + - Click `Publish Release` From a853e390646f854f8a2f80a44aa494b749e7156f Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Fri, 14 Jul 2023 16:47:54 +0100 Subject: [PATCH 70/78] chore: lint test --- composer.json | 2 +- tests/PhpPact/Standalone/Broker/BrokerTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index b3980d27..6499fd38 100644 --- a/composer.json +++ b/composer.json @@ -79,7 +79,7 @@ "extra": { "downloads": { "pact-ruby-standalone": { - "version": "2.0.2", + "version": "2.0.3", "variables": { "{$os}": "PHP_OS_FAMILY === 'Windows' ? 'windows' : (PHP_OS === 'Darwin' ? 'osx' : 'linux')", "{$architecture}": "strtolower(php_uname('m')) === 'arm64' || strtolower(php_uname('m')) === 'aarch64' ? '-arm64' : (strtolower(php_uname('m')) === 'x86' && PHP_OS_FAMILY === 'Windows' ? '-x86' : '-x86_64')", diff --git a/tests/PhpPact/Standalone/Broker/BrokerTest.php b/tests/PhpPact/Standalone/Broker/BrokerTest.php index 6e991af6..b61a4cf2 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerTest.php @@ -43,7 +43,7 @@ public function getArgumentsEmptyConfig(): void //} - /** + /** * @test * * @throws \Exception From 1965a1a2d53ffbab972e315ff49981ecf9738c70 Mon Sep 17 00:00:00 2001 From: Steve Taylor Date: Mon, 12 Jun 2023 00:36:25 +0000 Subject: [PATCH 71/78] feat: MockServerConfig improvements - allow setting of defaults * Fix config setters returning a interface which prevents chaining when static analysis tools are in use as the interfaces are only a subset of what the class can do. * Set defaults similar to MockServerEnvConfig for timeouts as these are typed but not set to a value by default, forcing you to set them to avoid an error. --- .../Standalone/MockService/MockServerConfig.php | 12 ++++++------ .../Standalone/MockServer/MockServerConfigTest.php | 14 +++++++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/PhpPact/Standalone/MockService/MockServerConfig.php b/src/PhpPact/Standalone/MockService/MockServerConfig.php index b575800f..723efebf 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfig.php @@ -62,12 +62,12 @@ class MockServerConfig implements MockServerConfigInterface, PactConfigInterface /** * The max allowed attempts the mock server has to be available in. Otherwise it is considered as sick. */ - private int $healthCheckTimeout; + private int $healthCheckTimeout = 10; /** * The seconds between health checks of mock server */ - private int $healthCheckRetrySec; + private int $healthCheckRetrySec = 1; private ?string $logLevel = null; @@ -118,7 +118,7 @@ public function isSecure(): bool /** * {@inheritdoc} */ - public function setSecure(bool $secure): MockServerConfigInterface + public function setSecure(bool $secure): self { $this->secure = $secure; @@ -274,7 +274,7 @@ public function getLogLevel(): ?string return $this->logLevel; } - public function setLogLevel(string $logLevel): PactConfigInterface + public function setLogLevel(string $logLevel): self { $logLevel = \strtoupper($logLevel); if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { @@ -303,7 +303,7 @@ public function setCors(mixed $flag): self return $this; } - public function setHealthCheckTimeout(int $timeout): MockServerConfigInterface + public function setHealthCheckTimeout(int $timeout): self { $this->healthCheckTimeout = $timeout; @@ -315,7 +315,7 @@ public function getHealthCheckTimeout(): int return $this->healthCheckTimeout; } - public function setHealthCheckRetrySec(int $seconds): MockServerConfigInterface + public function setHealthCheckRetrySec(int $seconds): self { $this->healthCheckRetrySec = $seconds; diff --git a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php index 30a43b0b..14954ff9 100644 --- a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php +++ b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php @@ -15,21 +15,30 @@ public function testSetters() $consumer = 'test-consumer'; $pactDir = 'test-pact-dir/'; $pactFileWriteMode = 'merge'; + $logLevel = 'INFO'; $log = 'test-log-dir/'; $cors = true; $pactSpecificationVersion = '2.0'; + $healthCheckTimeout = 20; + $healthCheckRetrySec = 2; + $secure = false; $subject = (new MockServerConfig()) + ->setSecure(false) ->setHost($host) ->setPort($port) ->setProvider($provider) ->setConsumer($consumer) ->setPactDir($pactDir) ->setPactFileWriteMode($pactFileWriteMode) + ->setLogLevel($logLevel) ->setLog($log) ->setPactSpecificationVersion($pactSpecificationVersion) - ->setCors($cors); + ->setCors($cors) + ->setHealthCheckTimeout(20) + ->setHealthCheckRetrySec(2); + static::assertSame($secure, $subject->isSecure()); static::assertSame($host, $subject->getHost()); static::assertSame($port, $subject->getPort()); static::assertSame($provider, $subject->getProvider()); @@ -37,7 +46,10 @@ public function testSetters() static::assertSame($pactDir, $subject->getPactDir()); static::assertSame($pactFileWriteMode, $subject->getPactFileWriteMode()); static::assertSame($log, $subject->getLog()); + static::assertSame($logLevel, $subject->getLogLevel()); static::assertSame($pactSpecificationVersion, $subject->getPactSpecificationVersion()); static::assertSame($cors, $subject->hasCors()); + static::assertSame($healthCheckTimeout, $subject->getHealthCheckTimeout()); + static::assertSame($healthCheckRetrySec, $subject->getHealthCheckRetrySec()); } } From be5dd8d619227196bd46b411a203d56f0818f2c6 Mon Sep 17 00:00:00 2001 From: Steve Taylor Date: Mon, 12 Jun 2023 04:19:46 +0000 Subject: [PATCH 72/78] feat: Speed up pact-php - allow floats in healthcheck sleep Pact consumer tests are supposed to be like unit tests but mine were taking 2 seconds+ each, the majority of which was a php sleep() Lots of unnecessary sleeps were slowing down my api and message tests. * Allow SetHealthCheckRetrySec / PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC to be a float so it can be set to 0.1 instead of 1. * Use usleep instead of sleep so that the retry seconds can be a < 1 second. * Increase the total number of attempts so it still tries for ~10 seconds. * Remove sleeps when PactMessage which do not appear to be needed. --- src/PhpPact/Standalone/MockService/MockServer.php | 6 +----- src/PhpPact/Standalone/MockService/MockServerConfig.php | 8 ++++---- .../Standalone/MockService/MockServerConfigInterface.php | 4 ++-- .../Standalone/MockService/MockServerEnvConfig.php | 4 ++-- src/PhpPact/Standalone/PactMessage/PactMessage.php | 2 -- .../Standalone/MockServer/MockServerConfigTest.php | 2 +- 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/PhpPact/Standalone/MockService/MockServer.php b/src/PhpPact/Standalone/MockService/MockServer.php index cdc1c1fa..e08c6bca 100644 --- a/src/PhpPact/Standalone/MockService/MockServer.php +++ b/src/PhpPact/Standalone/MockService/MockServer.php @@ -42,10 +42,6 @@ public function start(): int $processId = $this->processRunner->run(); $result = $this->verifyHealthCheck(); - if ($result) { - $retrySec = $this->config->getHealthCheckRetrySec(); - \sleep($retrySec); - } return $processId; } @@ -122,7 +118,7 @@ private function verifyHealthCheck(): bool try { return $service->healthCheck(); } catch (ConnectionException $e) { - \sleep($retrySec); + \usleep(round($retrySec * 1000000)); } } while ($tries <= $maxTries); diff --git a/src/PhpPact/Standalone/MockService/MockServerConfig.php b/src/PhpPact/Standalone/MockService/MockServerConfig.php index 723efebf..f736f104 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfig.php @@ -62,12 +62,12 @@ class MockServerConfig implements MockServerConfigInterface, PactConfigInterface /** * The max allowed attempts the mock server has to be available in. Otherwise it is considered as sick. */ - private int $healthCheckTimeout = 10; + private int $healthCheckTimeout = 100; /** * The seconds between health checks of mock server */ - private int $healthCheckRetrySec = 1; + private float $healthCheckRetrySec = 0.1; private ?string $logLevel = null; @@ -315,14 +315,14 @@ public function getHealthCheckTimeout(): int return $this->healthCheckTimeout; } - public function setHealthCheckRetrySec(int $seconds): self + public function setHealthCheckRetrySec(float $seconds): self { $this->healthCheckRetrySec = $seconds; return $this; } - public function getHealthCheckRetrySec(): int + public function getHealthCheckRetrySec(): float { return $this->healthCheckRetrySec; } diff --git a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php index b61f8d1f..dbce087e 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php @@ -59,7 +59,7 @@ public function setHealthCheckTimeout(int $timeout): self; public function getHealthCheckTimeout(): int; - public function setHealthCheckRetrySec(int $seconds): self; + public function setHealthCheckRetrySec(float $seconds): self; - public function getHealthCheckRetrySec(): int; + public function getHealthCheckRetrySec(): float; } diff --git a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php index fae19e83..5407e64e 100644 --- a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php @@ -33,13 +33,13 @@ public function __construct() $timeout = $this->parseEnv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT', false); if (!$timeout) { - $timeout = 10; + $timeout = 100; } $this->setHealthCheckTimeout($timeout); $seconds = $this->parseEnv('PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC', false); if (!$seconds) { - $seconds = 1; + $seconds = 0.1; } $this->setHealthCheckRetrySec($seconds); diff --git a/src/PhpPact/Standalone/PactMessage/PactMessage.php b/src/PhpPact/Standalone/PactMessage/PactMessage.php index b96b29d3..1d4776c4 100644 --- a/src/PhpPact/Standalone/PactMessage/PactMessage.php +++ b/src/PhpPact/Standalone/PactMessage/PactMessage.php @@ -40,8 +40,6 @@ public function update(string $pactJson, string $consumer, string $provider, str $process = new ProcessRunner(Scripts::getPactMessage(), $arguments); $process->runBlocking(); - \sleep(1); - return true; } } diff --git a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php index 14954ff9..79fcd04a 100644 --- a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php +++ b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php @@ -20,7 +20,7 @@ public function testSetters() $cors = true; $pactSpecificationVersion = '2.0'; $healthCheckTimeout = 20; - $healthCheckRetrySec = 2; + $healthCheckRetrySec = 2.0; $secure = false; $subject = (new MockServerConfig()) From 07b12f4180285cdab5bbd89076cefdf8b967162d Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Fri, 14 Jul 2023 17:13:54 +0100 Subject: [PATCH 73/78] chore: round float to int microseconds for usleep --- src/PhpPact/Standalone/MockService/MockServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PhpPact/Standalone/MockService/MockServer.php b/src/PhpPact/Standalone/MockService/MockServer.php index e08c6bca..dbebe815 100644 --- a/src/PhpPact/Standalone/MockService/MockServer.php +++ b/src/PhpPact/Standalone/MockService/MockServer.php @@ -118,7 +118,7 @@ private function verifyHealthCheck(): bool try { return $service->healthCheck(); } catch (ConnectionException $e) { - \usleep(round($retrySec * 1000000)); + \usleep(intval(round($retrySec * 1000000))); } } while ($tries <= $maxTries); From 4705dedce3059da8ff5f8b98a34b5c8fa8bdcfef Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Fri, 14 Jul 2023 17:44:19 +0100 Subject: [PATCH 74/78] docs: update supported platform docs --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f7230c5f..ea99caa0 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ Table of contents - [Usage for the optional `pact-stub-service`](#usage-for-the-optional-pact-stub-service) ## Versions -9.X updates internal dependencies and libraries. This results in dropping PHP 7.4 + +9.X updates internal dependencies and libraries including pact-ruby-standalone v2.x which adds support for ARM64 CPU's for Linux/MacOS and providing x86 and x86_64 Windows via pact-ruby-standalone v2.x. This results in dropping PHP 7.4 8.X updates internal dependencies and libraries. This results in dropping PHP 7.3 @@ -61,6 +62,17 @@ If you wish to stick with the 2.X implementation, you can continue to pull from The 3.X version is the version of Pact-PHP, not the pact specification version that it supports. Pact-Php 3.X supports [Pact-Specification 2.X](https://github.com/pact-foundation/pact-specification/tree/version-2). +##  Supported Platforms + +| OS | Architecture | Supported | Pact-PHP Version | +| ------- | ------------ | --------- | ---------------- | +| OSX | x86_64 | ✅ | All | +| Linux | x86_64 | ✅ | All | +| OSX | arm64 | ✅ | 9.x + | +| Linux | arm64 | ✅ | 9.x + | +| Windows | x86_64 | ✅ | All | +| Windows | x86 | ✅ | All | + ## Installation Install the latest version with: From 498c2a804093c6fa6cb3ff193aa6adb5014fa44b Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Mon, 24 Jul 2023 15:28:42 +0100 Subject: [PATCH 75/78] chore: support aarch64 in pact-stub-server --- composer.json | 3 +- example/pacts/someconsumer-someprovider.json | 77 +++++++++++-------- .../pacts/test_consumer-test_provider.json | 59 +++++++------- 3 files changed, 78 insertions(+), 61 deletions(-) diff --git a/composer.json b/composer.json index 65ad4cd1..0770643b 100644 --- a/composer.json +++ b/composer.json @@ -91,9 +91,10 @@ "version": "0.5.3", "variables": { "{$os}": "PHP_OS === 'Darwin' ? 'osx' : strtolower(PHP_OS_FAMILY)", + "{$architecture}": "in_array(php_uname('m'), ['arm64', 'aarch64']) ? 'aarch64' : 'x86_64'", "{$extension}": "PHP_OS_FAMILY === 'Windows' ? '.exe' : ''" }, - "url": "https://github.com/pact-foundation/pact-stub-server/releases/download/v{$version}/pact-stub-server-{$os}-x86_64{$extension}.gz", + "url": "https://github.com/pact-foundation/pact-stub-server/releases/download/v{$version}/pact-stub-server-{$os}-{$architecture}{$extension}.gz", "path": "bin/pact-stub-server/pact-stub-server{$extension}", "executable": true } diff --git a/example/pacts/someconsumer-someprovider.json b/example/pacts/someconsumer-someprovider.json index f9a78bce..2d917286 100644 --- a/example/pacts/someconsumer-someprovider.json +++ b/example/pacts/someconsumer-someprovider.json @@ -2,61 +2,76 @@ "consumer": { "name": "someConsumer" }, - "provider": { - "name": "someProvider" - }, "interactions": [ { - "description": "A get request to /goodbye/{name}", - "providerState": "Get Goodbye", + "description": "A get request to /hello/{name}", "request": { - "method": "GET", - "path": "/goodbye/Bob", "headers": { "Content-Type": "application/json" - } + }, + "method": "GET", + "path": "/hello/Bob" }, "response": { - "status": 200, + "body": { + "message": "Hello, Bob" + }, "headers": { "Content-Type": "application/json" }, - "body": { - "message": "Goodbye, Bob" - } - }, - "metadata": null + "matchingRules": { + "body": { + "$.message": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "(Hello, )[A-Za-z]+" + } + ] + } + }, + "header": {} + }, + "status": 200 + } }, { - "description": "A get request to /hello/{name}", + "description": "A get request to /goodbye/{name}", + "providerStates": [ + { + "name": "Get Goodbye" + } + ], "request": { - "method": "GET", - "path": "/hello/Bob", "headers": { "Content-Type": "application/json" - } + }, + "method": "GET", + "path": "/goodbye/Bob" }, "response": { - "status": 200, + "body": { + "message": "Goodbye, Bob" + }, "headers": { "Content-Type": "application/json" }, - "body": { - "message": "Hello, Bob" - }, - "matchingRules": { - "$.body.message": { - "match": "regex", - "regex": "(Hello, )[A-Za-z]+" - } - } - }, - "metadata": null + "status": 200 + } } ], "metadata": { + "pactRust": { + "ffi": "0.4.4", + "mockserver": "1.0.3", + "models": "1.0.13" + }, "pactSpecification": { - "version": "2.0.0" + "version": "3.0.0" } + }, + "provider": { + "name": "someProvider" } } \ No newline at end of file diff --git a/example/pacts/test_consumer-test_provider.json b/example/pacts/test_consumer-test_provider.json index ae5e2dab..13f684cd 100644 --- a/example/pacts/test_consumer-test_provider.json +++ b/example/pacts/test_consumer-test_provider.json @@ -2,52 +2,53 @@ "consumer": { "name": "test_consumer" }, - "provider": { - "name": "test_provider" - }, "messages": [ { - "description": "an alligator named Mary exists", + "contents": { + "song": "And the wind whispers Mary" + }, + "description": "footprints dressed in red", + "metadata": { + "contentType": "application/json", + "queue": "And the clowns have all gone to bed", + "routing_key": "And the clowns have all gone to bed" + }, "providerStates": [ { - "name": "a message" + "name": "You can hear happiness staggering on down the street" } - ], + ] + }, + { "contents": { "text": "Hello Mary" }, - "matchingRules": { - "body": { - } - }, - "metaData": { + "description": "an alligator named Mary exists", + "metadata": { + "contentType": "application/json", "queue": "wind cries", "routing_key": "wind cries" - } - }, - { - "description": "footprints dressed in red", + }, "providerStates": [ { - "name": "You can hear happiness staggering on down the street" - } - ], - "contents": { - "song": "And the wind whispers Mary" - }, - "matchingRules": { - "body": { + "name": "a message", + "params": { + "foo": "bar" + } } - }, - "metaData": { - "queue": "And the clowns have all gone to bed", - "routing_key": "And the clowns have all gone to bed" - } + ] } ], "metadata": { + "pactRust": { + "ffi": "0.4.4", + "models": "1.0.13" + }, "pactSpecification": { "version": "3.0.0" } + }, + "provider": { + "name": "test_provider" } -} +} \ No newline at end of file From 90c10051a321afc3c07e4a4aacf8789b34227c2f Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Mon, 24 Jul 2023 15:29:44 +0100 Subject: [PATCH 76/78] ci: support for arm64 linux - add libffi-dev - add php-ffi ext --- .cirrus.yml | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .cirrus.yml diff --git a/.cirrus.yml b/.cirrus.yml new file mode 100644 index 00000000..4e24d43e --- /dev/null +++ b/.cirrus.yml @@ -0,0 +1,52 @@ +BUILD_TEST_TASK_TEMPLATE: &BUILD_TEST_TASK_TEMPLATE + arch_check_script: + - uname -am + install_script: + - composer install + cirrus_ci_macos_local_script: | + if [ "$(uname -s)" == 'Darwin' ] && [ "$CIRRUS_CLI" == 'true' ]; then + chmod +x vendor/bin/php-cs-fixer + chmod +x vendor/bin/phpstan + chmod +x vendor/bin/phpunit + chmod +x bin/pact-stub-server/pact-stub-server + fi + lint_script: + - composer run lint + static_analysis_script: + - composer run static-code-analysis + test_script: + - composer test + +linux_arm64_task: + env: + COMPOSER_ALLOW_SUPERUSER: 1 + matrix: + - VERSION: 8.2 + - VERSION: 8.1 + - VERSION: 8.0 + container: + image: php:$VERSION + pre_req_script: + - apt update --yes && apt install --yes zip unzip git libffi-dev + - curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php + - php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer + - docker-php-ext-install sockets + - docker-php-ext-install ffi + version_check_script: + - php --version + << : *BUILD_TEST_TASK_TEMPLATE + +macos_arm64_task: +# https://www.markhesketh.com/switching-multiple-php-versions-on-macos/ + env: + matrix: + - VERSION: 8.2 + - VERSION: 8.1 + - VERSION: 8.0 + macos_instance: + image: ghcr.io/cirruslabs/macos-ventura-base:latest + pre_req_script: + - brew install php@$VERSION composer + version_check_script: + - php --version + << : *BUILD_TEST_TASK_TEMPLATE \ No newline at end of file From b8eafd3b33197e3324c3406689214b5dee7949e6 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Mon, 24 Jul 2023 15:30:31 +0100 Subject: [PATCH 77/78] chore(docs): add developer docs --- DEVELOPING.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 DEVELOPING.md diff --git a/DEVELOPING.md b/DEVELOPING.md new file mode 100644 index 00000000..31b4f294 --- /dev/null +++ b/DEVELOPING.md @@ -0,0 +1,57 @@ +# Pact-PHP + +## Pre Reqs + +- PHP 8.x or greater +- FFI and Sockets extensions enabled in your php.ini + +## Steps + +1. Run `composer install` + 1. This will install php dependencies to `vendor` + 2. This will install pact libraries to `bin` +2. Run `composer test` + 1. This will run our unit tests +3. Run `composer lint` + 1. This will run the phpcs-lint +4. Run `composer fix` + 1. This will correct any auto fixable linter errors +5. Run `composer static-code-analysis` + 1. Run static code analysis + +## CI Locally + +### MacOS ARM + +#### Pre Reqs + +- MacOS ARM +- Tart.run +- Cirrus-CLI + +#### Steps + +Run all versions of PHP + +- `cirrus run --output github-actions macos_arm64 -e CIRRUS_CLI=true` + +Run a specified version of PHP + +- `cirrus run --output github-actions 'macos_arm64 VERSION:8.2' -e CIRRUS_CLI=true` + +### Linux ARM + +#### Pre Reqs + +- Docker +- x86_64 or arm64/aarch64 host + +#### Steps + +Run all versions of PHP + +- `cirrus run --output github-actions linux_arm64` + +Run a specified version of PHP + +- `cirrus run --output github-actions 'macos_arm64 VERSION:8.2'` \ No newline at end of file From bcdc7cc56697311d60c3067bdf531e0c0c8e1df4 Mon Sep 17 00:00:00 2001 From: Yousaf Nabi Date: Mon, 24 Jul 2023 16:01:04 +0100 Subject: [PATCH 78/78] chore(docs): update migration doc to reflect 10.x --- UPGRADE-10.0.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++++ UPGRADE-9.0.md | 72 ------------------------- 2 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 UPGRADE-10.0.md delete mode 100644 UPGRADE-9.0.md diff --git a/UPGRADE-10.0.md b/UPGRADE-10.0.md new file mode 100644 index 00000000..d634ea28 --- /dev/null +++ b/UPGRADE-10.0.md @@ -0,0 +1,140 @@ +# UPGRADE FROM 9.x to 10.0 + +We have migrated from the pact-ruby core, to the pact-reference(rust) core. + +This migrates from a CLI driven process for the Pact Framework, to an FFI process based framework. + +- Pre-requisites + + - PHP 8.x + + - PHP FFI Extension installed + +- Environment Variables + + - These environment variables are no longer required can be removed: + - PACT_CORS + - PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT + - PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC + +- Consumer + + - The `PhpPact\Consumer\Listener\PactTestListener` listener should be removed from your phpunit config + - Default Pact file write mode has been changed from 'overwrite' to 'merge'. Make sure old pact files are removed before running tests. + + ```shell + rm /path/to/pacts/*.json + ``` + + - Pact files now can ONLY be uploaded to Pact Broker by downloading and running Pact CLI manually. + + ```shell + pact-broker publish /path/to/pacts/*.json --consumer-app-version 1.0.0 --branch main --broker-base-url https://test.pactflow.io --broker-token SomeToken + ``` + +- Verifier + + - `$config->setProviderName("providerName")` is now available via `$config->getProviderInfo()->setName("backend")` + - This is further chainable with the following options:- + - `->setHost('localhost')` + - `->setPort('8080')` + - `->setScheme('http')` + - `->setPath('/')` + - Different pacts sources can be configured via `addXxx` methods + - NB:- You must add at least one source, otherwise the verifier will pass, but not verify any Pact files. + - Types:- + - `addUrl` - Verify Provider by Pact Url retrieved by Broker (Webhooks) + - `addBroker` Verify Provider by dynamically fetched Pacts (Provider change) + - `addFile` / `addDir` - Verify Provider by local file or directory + + Example Usage: + + ```php + $config = new VerifierConfig(); + $config + ->setLogLevel('DEBUG'); + $config + ->getProviderInfo() + ->setName("personProvider") + ->setHost('localhost') + ->setPort('8080') + ->setScheme('http') + ->setPath('/'); + + if ($isCi = getenv('CI')) { + $publishOptions = new PublishOptions(); + $publishOptions + ->setProviderVersion(exec('git rev-parse --short HEAD')) + ->setProviderBranch(exec('git rev-parse --abbrev-ref HEAD')); + $config->setPublishOptions($publishOptions); + } + + $broker = new Broker(); + $broker->setUsername(getenv('PACT_BROKER_USERNAME')); + $broker->setPassword(getenv('PACT_BROKER_PASSWORD')); + $broker->setUsername(getenv('PACT_BROKER_TOKEN')); + $verifier = new Verifier($config); + + // 1. verify with a broker, but using a pact url to verify a specific pact + // PACT_URL=http://localhost:9292/pacts/provider/personProvider/consumer/personConsumer/latest + if ($pact_url = getenv('PACT_URL')) { + $url = new Url(); + $url->setUrl(new Uri($pact_url)); + $verifier->addUrl($url); + } + // 2. verify files from local directory or file + // results will not be published + else if ($pactDir = getenv('PACT_DIR')) { + $verifier->addDirectory($pactDir); + } else if ($pactFile = getenv('PACT_FILE')) { + $verifier->addFile($pactFile); + } else { + // 2. verify with broker by fetching dynamic pacts (with consumer version selectors) + // if you don't setConsumerVersionSelectors then it will fetch the latest pact for the named provider + if ($pactBrokerBaseUrl = getenv('PACT_BROKER_BASE_URL')) { + $broker->setUrl(new Uri($pactBrokerBaseUrl)); + } else { + $broker->setUrl(new Uri('http://localhost:9292')); + } + // we need to set the provider branch here for PactBrokerWithDynamicConfiguration + // as $publishOptions->setProviderBranch value set above isn't used. + $broker->setProviderBranch(exec('git rev-parse --abbrev-ref HEAD')); + // NOTE - this needs to be a boolean, not a string value, otherwise it doesn't pass through the selector. + // Maybe a pact-php or pact-rust thing + $selectors = (new ConsumerVersionSelectors()) + ->addSelector(' { "mainBranch" : true } ') + ->addSelector(' { "deployedOrReleased" : true } '); + $broker->setConsumerVersionSelectors($selectors); + $broker->setEnablePending(true); + $broker->setIncludeWipPactSince('2020-01-30'); + $verifier->addBroker($broker); + } + + + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); + ``` + +- Stub Server + + - No longer defaults to port 7201, picks free port at random. + - Endpoint now can be set by: + + ```php + $service = new StubServerHttpService(new GuzzleClient(), $this->config); + $service->getJson($endpoint); + ``` + +- Example Migrations to 10.x (Pull Request Diffs) + - PHP Verifier https://github.com/acmachado14/simple-pact/compare/main...YOU54F:simple-pact:ffi-next + - PHP Consumer https://github.com/YOU54F/pact-testing/compare/main...YOU54F:pact-testing:ffi-next + - PHP Consumer & Verifier + - Consumer https://github.com/YOU54F/014-pact-http-consumer-php/compare/main...YOU54F:014-pact-http-consumer-php:ffi-next + - Verifier https://github.com/YOU54F/015-pact-http-producer-php/compare/main...YOU54F:015-pact-http-producer-php:ffi-next + + +Examples of Additional Features now possible + +- Pact Plugins + - CSV https://github.com/tienvx/pact-php-csv + - Protobuf/gRPC https://github.com/tienvx/pact-php-protobuf \ No newline at end of file diff --git a/UPGRADE-9.0.md b/UPGRADE-9.0.md deleted file mode 100644 index 1d43ce8e..00000000 --- a/UPGRADE-9.0.md +++ /dev/null @@ -1,72 +0,0 @@ -UPGRADE FROM 8.x to 9.0 -======================= - -* Environment Variables - * These environment variables can be removed: - * PACT_CORS - * PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT - * PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC - -* Verifier - * Different pacts sources can be configured via `addXxx` methods - - Example Usage: - ```php - $config = new VerifierConfig(); - $config - ->setPort(8000) - ->setProviderName('someProvider') - ->setProviderVersion('1.0.0'); - - $url = new Url(); - $url - ->setUrl(new Uri('http://localhost')) - ->setProviderName('someProvider') - ->setUsername('user') - ->setPassword('pass') - ->setToken('token'); - - $selectors = (new ConsumerVersionSelectors()) - ->addSelector('{"tag":"foo","latest":true}') - ->addSelector('{"tag":"bar","latest":true}'); - - $broker = new Broker(); - $broker - ->setUrl(new Uri('http://localhost')) - ->setProviderName('someProvider') - ->setUsername('user') - ->setPassword('pass') - ->setToken('token') - ->setConsumerVersionSelectors($selectors); - - $verifier = new Verifier($config); - $verifier - ->addFile('C:\SomePath\consumer-provider.json'); - ->addDirectory('C:\OtherPath'); - ->addUrl($url); - ->addBroker($broker); - - $verifyResult = $verifier->verify(); - - $this->assertTrue($verifyResult); - ``` - -* Consumer - * Pact file write mode has been changed from 'overwrite' to 'merge'. Make sure old pact files are removed before running tests. - - ```shell - rm /path/to/pacts/*.json - ``` - - * Pact files now can ONLY be uploaded to Pact Broker by downloading and running Pact CLI manually. - - ```shell - pact-broker publish /path/to/pacts/*.json --consumer-app-version 1.0.0 --branch main --broker-base-url https://test.pactflow.io --broker-token SomeToken - ``` - -* Stub Server - * Endpoint now can be set by: - ```php - $service = new StubServerHttpService(new GuzzleClient(), $this->config); - $service->getJson($endpoint); - ```