diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5a9e172f..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 @@ -58,8 +60,9 @@ 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 }} + coverage: none - name: Composer install uses: ramsey/composer-install@v2 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/README.md b/README.md index f7230c5f..5df03992 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Table of contents - [Specifications](#specifications) - [Installation](#installation) - [Basic Consumer Usage](#basic-consumer-usage) - - [Start and Stop the Mock Server](#start-and-stop-the-mock-server) + - [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) @@ -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) @@ -42,7 +42,7 @@ 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 + 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 @@ -59,7 +59,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 @@ -75,39 +81,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. @@ -193,10 +172,10 @@ 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 an API request to the Mock Server to set the interaction. + ->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 @@ -212,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 -$builder->verify(); +$verifyResult = $verifier->verify(); +$this->assertTrue($verifyResult); ``` ### Make Assertions @@ -250,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); } ``` @@ -303,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); } ``` @@ -353,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 @@ -393,68 +371,41 @@ $consumerMessage = new ExampleMessageConsumer(); $callback = [$consumerMessage, 'ProcessSong']; $builder->setCallback($callback); -$builder->verify(); +$verifyResult = $builder->verify(); + +$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 +Handle these requests on your provider: - $callbacks = array(); +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` - // a hello message is a provider state / given() on the consumer side - $callbacks["a hello message"] = function() { - $content = new \stdClass(); - $content->text ="Hello Mary"; - - $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` 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) - ->setPort($port) - ->setEndpoint($endpoint); + ->setFiles($files) + ->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 new file mode 100644 index 00000000..323b034a --- /dev/null +++ b/UPGRADE-9.0.md @@ -0,0 +1,59 @@ +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); + ``` + +* Stub Server + * Endpoint now can be set by: + ```php + $service = new StubServerHttpService(new GuzzleClient(), $this->config); + $service->getJson($endpoint); + ``` diff --git a/composer.json b/composer.json index 2404f9fa..68129809 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" + "tienvx/composer-downloads-plugin": "^1.2.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,14 +57,17 @@ "MessageProvider\\": [ "example/src/MessageProvider", "example/tests/MessageProvider" + ], + "Provider\\": [ + "example/src/Provider" ] } }, "scripts": { "start-provider": "php -S localhost:58000 -t example/src/Provider/public/", - "static-code-analysis": "phpstan analyse src/ --level=7", - "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": { @@ -83,10 +77,41 @@ "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", + "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.4", + "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}" + }, + "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/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 554c8a46..3cc67cbf 100644 --- a/example/phpunit.all.xml +++ b/example/phpunit.all.xml @@ -13,9 +13,6 @@ ./tests/MessageConsumer - - ./tests/MessageProvider - @@ -36,7 +33,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/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/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/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/Consumer/Service/ConsumerServiceGoodbyeTest.php b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php index e5106f55..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(); - $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 2a80f394..d10782ff 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,13 +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 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. - $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); } } diff --git a/example/tests/MessageConsumer/ExampleMessageConsumerTest.php b/example/tests/MessageConsumer/ExampleMessageConsumerTest.php index 6c17c6ce..0056d35d 100644 --- a/example/tests/MessageConsumer/ExampleMessageConsumerTest.php +++ b/example/tests/MessageConsumer/ExampleMessageConsumerTest.php @@ -2,11 +2,9 @@ namespace MessageConsumer; -require_once __DIR__ . '/../../src/MessageConsumer/ExampleMessageConsumer.php'; - use Exception; use PhpPact\Consumer\MessageBuilder; -use PhpPact\Standalone\PactConfigInterface; +use PhpPact\Config\PactConfigInterface; use PhpPact\Standalone\PactMessage\PactMessageConfig; use PHPUnit\Framework\TestCase; use stdClass; @@ -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/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/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..1d5823a9 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,4 @@ +parameters: + level: 7 + paths: + - src 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/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/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/Config/PactConfig.php b/src/PhpPact/Config/PactConfig.php new file mode 100644 index 00000000..833f91fb --- /dev/null +++ b/src/PhpPact/Config/PactConfig.php @@ -0,0 +1,179 @@ +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 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/Config/PactConfigInterface.php similarity index 65% rename from src/PhpPact/Standalone/PactConfigInterface.php rename to src/PhpPact/Config/PactConfigInterface.php index 63b6b818..0f97bd11 100644 --- a/src/PhpPact/Standalone/PactConfigInterface.php +++ b/src/PhpPact/Config/PactConfigInterface.php @@ -1,12 +1,19 @@ 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/BuilderInterface.php b/src/PhpPact/Consumer/BuilderInterface.php index 5fc039dc..58ef8eda 100644 --- a/src/PhpPact/Consumer/BuilderInterface.php +++ b/src/PhpPact/Consumer/BuilderInterface.php @@ -11,9 +11,4 @@ interface BuilderInterface * Verify that the interactions are valid. */ public function verify(): bool; - - /** - * Write the Pact without deleting the interactions. - */ - public function writePact(): bool; } diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php new file mode 100644 index 00000000..d9fbde15 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriver.php @@ -0,0 +1,32 @@ +mockServer->verify(); + } + + public function registerInteraction(Interaction $interaction): bool + { + $this->pactDriver->setUp(); + $this->interactionRegistry->registerInteraction($interaction); + $this->mockServer->start(); + + return true; + } +} diff --git a/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php new file mode 100644 index 00000000..fe5a3e07 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Interaction/InteractionDriverInterface.php @@ -0,0 +1,12 @@ +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 @@ +pactRegistry->deletePact(); + } + + public function writePact(): void + { + $error = $this->client->call( + 'pactffi_pact_handle_write_file', + $this->pactRegistry->getId(), + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new PactFileNotWroteException($error); + } + } + + public function setUp(): void + { + $this + ->initWithLogLevel() + ->registerPact(); + } + + protected function getSpecification(): int + { + return match (true) { + $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->client->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->client->call('pactffi_init_with_log_level', $logLevel); + } + + return $this; + } + + private function registerPact(): self + { + $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 new file mode 100644 index 00000000..69ccdc42 --- /dev/null +++ b/src/PhpPact/Consumer/Driver/Pact/PactDriverInterface.php @@ -0,0 +1,12 @@ + '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/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); + } +} diff --git a/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php b/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php new file mode 100644 index 00000000..e55a5e09 --- /dev/null +++ b/src/PhpPact/Consumer/Exception/PactFileNotWroteException.php @@ -0,0 +1,19 @@ + '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/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 @@ +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'; - } - - /** - * @throws \JsonException - */ - 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(), null, 512, JSON_THROW_ON_ERROR), JSON_THROW_ON_ERROR); - } -} diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index 0e25dab5..000a08ae 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -2,37 +2,36 @@ namespace PhpPact\Consumer; +use PhpPact\Consumer\Driver\Interaction\InteractionDriverInterface; +use PhpPact\Consumer\Factory\InteractionDriverFactory; +use PhpPact\Consumer\Factory\InteractionDriverFactoryInterface; use PhpPact\Consumer\Model\ConsumerRequest; use PhpPact\Consumer\Model\Interaction; 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 */ class InteractionBuilder implements BuilderInterface { - protected MockServerHttpService $mockServerHttpService; - - protected MockServerConfigInterface $config; - + private InteractionDriverInterface $driver; private Interaction $interaction; - public function __construct(MockServerConfigInterface $config) + public function __construct(MockServerConfigInterface $config, ?InteractionDriverFactoryInterface $driverFactory = null) { - $this->config = $config; - $this->mockServerHttpService = new MockServerHttpService(new GuzzleClient(), $config); - $this->interaction = new Interaction(); + $this->driver = ($driverFactory ?? new InteractionDriverFactory())->create($config); + $this->interaction = new Interaction(); } /** * @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 */ - 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; } @@ -58,18 +57,15 @@ 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 - * @throws \JsonException */ public function willRespondWith(ProviderResponse $response): bool { $this->interaction->setResponse($response); - return $this->mockServerHttpService->registerInteraction($this->interaction); + return $this->driver->registerInteraction($this->interaction); } /** @@ -77,33 +73,6 @@ public function willRespondWith(ProviderResponse $response): bool */ public function verify(): bool { - return $this->mockServerHttpService->verifyInteractions(); - } - - /** - * Writes the file to disk and deletes interactions from mock server. - * @throws \JsonException - */ - public function finalize(): bool - { - // Write the pact file to disk. - $this->mockServerHttpService->getPactJson(); - - // Delete the interactions. - $this->mockServerHttpService->deleteAllInteractions(); - - return true; - } - - /** - * {@inheritdoc} - * @throws \JsonException - */ - public function writePact(): bool - { - // Write the pact file to disk. - $this->mockServerHttpService->getPactJson(); - - return true; + return $this->driver->verifyInteractions(); } } diff --git a/src/PhpPact/Consumer/Listener/PactTestListener.php b/src/PhpPact/Consumer/Listener/PactTestListener.php index a27a1622..f4a19c6b 100644 --- a/src/PhpPact/Consumer/Listener/PactTestListener.php +++ b/src/PhpPact/Consumer/Listener/PactTestListener.php @@ -2,16 +2,11 @@ namespace PhpPact\Consumer\Listener; -use Amp\Process\ProcessException; -use Exception; use GuzzleHttp\Psr7\Uri; -use JsonException; -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; @@ -24,12 +19,13 @@ /** * PACT listener that can be used with environment variables and easily attached to PHPUnit configuration. + * + * @internal */ class PactTestListener implements TestListener { use TestListenerDefaultImplementation; - private MockServer $server; /** * Name of the test suite configured in your phpunit config. * @@ -52,17 +48,6 @@ public function __construct(array $testSuiteNames) $this->mockServerConfig = new MockServerEnvConfig(); } - /** - * @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; @@ -75,20 +60,10 @@ public function addFailure(Test $test, AssertionFailedError $e, float $time): vo /** * Publish JSON results to PACT Broker and stop the Mock Server. - * @throws JsonException|ProcessException */ 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'))) { @@ -98,29 +73,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], - ]; + $brokerConfig->setBrokerUsername($user); + $brokerConfig->setBrokerPassword($pass); } - if (($sslVerify = getenv('PACT_BROKER_SSL_VERIFY'))) { - $clientConfig['verify'] = $sslVerify !== 'no'; - } - - $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 6d27e1d1..c3a41538 100644 --- a/src/PhpPact/Consumer/Matcher/Matcher.php +++ b/src/PhpPact/Consumer/Matcher/Matcher.php @@ -8,7 +8,8 @@ use function preg_match; /** - * 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 { @@ -45,12 +46,12 @@ public function somethingLike(mixed $value): array public function like(mixed $value): array { if ($value === null) { - throw new \Exception('Value must not be null.'); + throw new Exception('Value must not be null.'); } return [ - 'contents' => $value, - 'json_class' => 'Pact::SomethingLike', + 'value' => $value, + 'pact:matcher:type' => 'type', ]; } @@ -64,14 +65,11 @@ public function like(mixed $value): array */ public function eachLike(mixed $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; } /** @@ -95,15 +93,9 @@ public function term(mixed $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', ]; } @@ -262,7 +254,7 @@ public function ipv6Address(string $ip = '::ffff:192.0.2.128'): array /** * @return array * - * @throws \Exception + * @throws Exception */ public function email(string $email = 'hello@pact.io'): array { diff --git a/src/PhpPact/Consumer/MessageBuilder.php b/src/PhpPact/Consumer/MessageBuilder.php index b1b383c6..6d5be6d5 100644 --- a/src/PhpPact/Consumer/MessageBuilder.php +++ b/src/PhpPact/Consumer/MessageBuilder.php @@ -2,31 +2,27 @@ namespace PhpPact\Consumer; -use PhpPact\Consumer\Model\Message; -use PhpPact\Standalone\PactConfigInterface; -use PhpPact\Standalone\PactMessage\PactMessage; +use PhpPact\Consumer\Driver\Interaction\MessageDriverInterface; +use PhpPact\Config\PactConfigInterface; +use PhpPact\Consumer\Factory\MessageDriverFactory; +use PhpPact\Consumer\Factory\MessageDriverFactoryInterface; /** * Build a message and send it to the Ruby Standalone Mock Service */ -class MessageBuilder implements BuilderInterface +class MessageBuilder extends AbstractMessageBuilder { - protected PactMessage $pactMessage; - - protected PactConfigInterface $config; + protected MessageDriverInterface $driver; /** * @var array */ 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/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php index cc5d797a..4632dee1 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -2,26 +2,28 @@ namespace PhpPact\Consumer\Model; +use JsonException; + /** * Request initiated by the consumer. */ -class ConsumerRequest implements \JsonSerializable +class ConsumerRequest { private string $method; - /** - * @var string|array - */ - private string|array $path; + private string $path; /** - * @var array + * @var array */ private array $headers = []; - private mixed $body = null; + private ?string $body = null; - private ?string $query = null; + /** + * @var array + */ + private array $query = []; public function getMethod(): string { @@ -35,26 +37,25 @@ public function setMethod(string $method): self return $this; } - /** - * @return string|array - */ - public function getPath(): string|array + public function getPath(): string { return $this->path; } /** * @param string|array $path + * + * @throws JsonException */ public function setPath(string|array $path): self { - $this->path = $path; + $this->path = is_array($path) ? json_encode($path, JSON_THROW_ON_ERROR) : $path; return $this; } /** - * @return array + * @return array */ public function getHeaders(): array { @@ -62,82 +63,98 @@ public function getHeaders(): array } /** - * @param string[] $headers + * @param array $headers */ public function setHeaders(array $headers): self { - $this->headers = $headers; + $this->headers = []; + foreach ($headers as $header => $value) { + $this->addHeader($header, $value); + } return $this; } - public function addHeader(string $header, string $value): self + /** + * @param string|string[] $value + */ + public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = $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; } - public function getBody(): mixed + private function addHeaderValue(string $header, string $value): void + { + $this->headers[$header][] = $value; + } + + public function getBody(): ?string { return $this->body; } - public function setBody(mixed $body): self + /** + * @param array|string|null $body + * + * @throws JsonException + */ + public function setBody(array|string|null $body): self { - $this->body = $body; + 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; } - public function getQuery(): ?string + /** + * @return array + */ + public function getQuery(): array { return $this->query; } - public function setQuery(string $query): self + /** + * @param array $query + */ + public function setQuery(array $query): self { - $this->query = $query; + $this->query = []; + foreach ($query as $key => $value) { + $this->addQueryParameter($key, $value); + } return $this; } - public function addQueryParameter(string $key, string $value): self + /** + * @param string|string[] $value + */ + public function addQueryParameter(string $key, array|string $value): self { - if ($this->query === null) { - $this->query = "{$key}={$value}"; + $this->query[$key] = []; + if (is_array($value)) { + array_walk($value, fn (string $value) => $this->addQueryParameterValue($key, $value)); } else { - $this->query = "{$this->query}&{$key}={$value}"; + $this->addQueryParameterValue($key, $value); } return $this; } - /** - * @return array - */ - public function jsonSerialize(): array + private function addQueryParameterValue(string $key, string $value): void { - $results = []; - - $results['method'] = $this->getMethod(); - - if (count($this->getHeaders()) > 0) { - $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; + $this->query[$key][] = $value; } } diff --git a/src/PhpPact/Consumer/Model/Interaction.php b/src/PhpPact/Consumer/Model/Interaction.php index 44feaf67..5cc23cab 100644 --- a/src/PhpPact/Consumer/Model/Interaction.php +++ b/src/PhpPact/Consumer/Model/Interaction.php @@ -3,13 +3,13 @@ namespace PhpPact\Consumer\Model; /** - * Request/Response Pair to be posted to the Ruby Standalone Mock Server for PACT tests. + * Request/Response Pair to be posted to the Mock Server for PACT tests. */ -class Interaction implements \JsonSerializable +class Interaction { - private string $description; + use ProviderStates; - private ?string $providerState = null; + private string $description; private ConsumerRequest $request; @@ -27,18 +27,6 @@ public function setDescription(string $description): self return $this; } - public function getProviderState(): ?string - { - return $this->providerState; - } - - public function setProviderState(string $providerState): self - { - $this->providerState = $providerState; - - return $this; - } - public function getRequest(): ConsumerRequest { return $this->request; @@ -62,25 +50,4 @@ public function setResponse(ProviderResponse $response): self return $this; } - - /** - * @return array - */ - 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 8025b20d..1b8b3a4e 100644 --- a/src/PhpPact/Consumer/Model/Message.php +++ b/src/PhpPact/Consumer/Model/Message.php @@ -3,21 +3,18 @@ namespace PhpPact\Consumer\Model; /** - * Request/Response Pair to be posted to the Ruby Standalone Mock Server for PACT tests. + * Message metadata and contents to be posted to the Mock Server for PACT tests. */ -class Message implements \JsonSerializable +class Message { - private string $description; + use ProviderStates; - /** - * @var array - */ - private array $providerStates = []; + private string $description; /** * @var array */ - private array $metadata; + private array $metadata = []; private mixed $contents; @@ -33,46 +30,6 @@ public function setDescription(string $description): self return $this; } - /** - * @return array - */ - public function getProviderStates(): array - { - return $this->providerStates; - } - - /** - * @param array $params - * - * @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 - */ - public function addProviderState(string $name, array $params, bool $overwrite = false): self - { - $providerState = new \stdClass(); - $providerState->name = $name; - $providerState->params = $params; - - if ($overwrite === true) { - $this->providerStates = []; - } - - $this->providerStates[] = $providerState; - - return $this; - } - /** * @return array */ @@ -86,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; @@ -102,29 +67,4 @@ public function setContents(mixed $contents): self return $this; } - - /** - * {@inheritdoc} - * - * @return array - */ - public function jsonSerialize(): array - { - $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 61b19298..4ec6ba51 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -2,22 +2,21 @@ namespace PhpPact\Consumer\Model; +use JsonException; + /** * Response expectation that would be in response to a Consumer request from the Provider. */ -class ProviderResponse implements \JsonSerializable +class ProviderResponse { private int $status; /** - * @var array + * @var array */ private array $headers = []; - /** - * @var ?array - */ - private ?array $body = null; + private ?string $body = null; public function getStatus(): int { @@ -32,7 +31,7 @@ public function setStatus(int $status): self } /** - * @return array + * @return array */ public function getHeaders(): array { @@ -40,57 +39,57 @@ public function getHeaders(): array } /** - * @param array $headers + * @param array $headers */ public function setHeaders(array $headers): self { - $this->headers = $headers; + $this->headers = []; + foreach ($headers as $header => $value) { + $this->addHeader($header, $value); + } return $this; } - public function addHeader(string $header, string $value): self + /** + * @param string[]|string $value + */ + public function addHeader(string $header, array|string $value): self { - $this->headers[$header] = $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; } - /** - * @return ?array - */ - public function getBody(): ?array + private function addHeaderValue(string $header, string $value): void { - return $this->body; + $this->headers[$header][] = $value; } - /** - * @param array $body - */ - public function setBody(array $body): self + public function getBody(): ?string { - $this->body = $body; - - return $this; + return $this->body; } /** - * @return array + * @param array|string|null $body + * + * @throws JsonException */ - public function jsonSerialize(): array + public function setBody(array|string|null $body): self { - $results = [ - 'status' => $this->getStatus(), - ]; - - if (count($this->getHeaders()) > 0) { - $results['headers'] = $this->getHeaders(); - } - - if ($this->getBody() !== null) { - $results['body'] = $this->getBody(); + 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 $results; + return $this; } } diff --git a/src/PhpPact/Consumer/Model/ProviderState.php b/src/PhpPact/Consumer/Model/ProviderState.php new file mode 100644 index 00000000..acfa571d --- /dev/null +++ b/src/PhpPact/Consumer/Model/ProviderState.php @@ -0,0 +1,46 @@ + + */ + private array $params = []; + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + + /** + * @param array $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..09c09ece --- /dev/null +++ b/src/PhpPact/Consumer/Model/ProviderStates.php @@ -0,0 +1,55 @@ + + */ + private array $providerStates = []; + + /** + * @return array + */ + 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/src/PhpPact/Consumer/Registry/Interaction/AbstractRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/AbstractRegistry.php new file mode 100644 index 00000000..82e1d76d --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/AbstractRegistry.php @@ -0,0 +1,24 @@ +id; + } + + abstract protected function newInteraction(string $description): self; +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Contents/AbstractBodyRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Contents/AbstractBodyRegistry.php new file mode 100644 index 00000000..8f096540 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Contents/AbstractBodyRegistry.php @@ -0,0 +1,29 @@ +client->call('pactffi_with_body', $this->interactionRegistry->getId(), $this->getPart(), $contentType, $body); + if (!$success) { + throw new InteractionBodyNotAddedException(); + } + } + + abstract protected function getPart(): int; +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php new file mode 100644 index 00000000..3e3394ff --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php @@ -0,0 +1,8 @@ +client->call('pactffi_message_with_contents', $this->messageRegistry->getId(), $contentType, $data->getValue(), $data->getSize()); + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php new file mode 100644 index 00000000..1a4ed86e --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php @@ -0,0 +1,10 @@ +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/Registry/Interaction/InteractionRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistryInterface.php new file mode 100644 index 00000000..c3f1c7fc --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistryInterface.php @@ -0,0 +1,10 @@ +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 @@ +contentsRegistry->withContents($contentType, $body); + + return $this; + } + + 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 + { + return $this->interactionRegistry->getId(); + } + + abstract protected function getPart(): int; +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php new file mode 100644 index 00000000..90a6516b --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php @@ -0,0 +1,13 @@ + $headers + */ + public function withHeaders(array $headers): self; +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/RequestPartTrait.php b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestPartTrait.php new file mode 100644 index 00000000..89897944 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestPartTrait.php @@ -0,0 +1,11 @@ +client->get('InteractionPart_Request'); + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php new file mode 100644 index 00000000..026dc57c --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php @@ -0,0 +1,39 @@ + $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): self + { + $this->client->call('pactffi_with_request', $this->getInteractionId(), $method, $path); + + return $this; + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistryInterface.php new file mode 100644 index 00000000..548c964f --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistryInterface.php @@ -0,0 +1,13 @@ + $queryParams + */ + public function withQueryParameters(array $queryParams): self; + + public function withRequest(string $method, string $path): self; +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/ResponsePartTrait.php b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponsePartTrait.php new file mode 100644 index 00000000..389fe949 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponsePartTrait.php @@ -0,0 +1,11 @@ +client->get('InteractionPart_Response'); + } +} diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php new file mode 100644 index 00000000..3649a408 --- /dev/null +++ b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php @@ -0,0 +1,28 @@ +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 @@ +client->call( + '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 verify(): bool + { + $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'; + } + + protected function getTransportConfig(): ?string + { + return null; + } + + private function writePact(): void + { + $error = $this->client->call( + 'pactffi_write_pact_file', + $this->config->getPort(), + $this->config->getPactDir(), + $this->config->getPactFileWriteMode() === PactConfigInterface::MODE_OVERWRITE + ); + if ($error) { + throw new MockServerNotWrotePactFileException($error); + } + } + + private function cleanUp(): void + { + $this->client->call('pactffi_cleanup_mock_server', $this->config->getPort()); + $this->pactRegistry->deletePact(); + } +} diff --git a/src/PhpPact/Consumer/Service/MockServerInterface.php b/src/PhpPact/Consumer/Service/MockServerInterface.php new file mode 100644 index 00000000..5737269c --- /dev/null +++ b/src/PhpPact/Consumer/Service/MockServerInterface.php @@ -0,0 +1,10 @@ +ffi = FFI::cdef($code, Scripts::getLibrary()); + } + + 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/FFI/ClientInterface.php b/src/PhpPact/FFI/ClientInterface.php new file mode 100644 index 00000000..c0e767ff --- /dev/null +++ b/src/PhpPact/FFI/ClientInterface.php @@ -0,0 +1,13 @@ + $arguments + */ + public function call(string $name, ...$arguments): mixed; + + public function get(string $name): mixed; +} 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/FFI/Model/StringData.php b/src/PhpPact/FFI/Model/StringData.php new file mode 100644 index 00000000..622f457a --- /dev/null +++ b/src/PhpPact/FFI/Model/StringData.php @@ -0,0 +1,35 @@ +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/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/Broker/BrokerConfig.php b/src/PhpPact/Standalone/Broker/BrokerConfig.php index 1ab80b26..48eac520 100644 --- a/src/PhpPact/Standalone/Broker/BrokerConfig.php +++ b/src/PhpPact/Standalone/Broker/BrokerConfig.php @@ -223,6 +223,13 @@ 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; diff --git a/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php b/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php deleted file mode 100644 index a2e98a23..00000000 --- a/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php +++ /dev/null @@ -1,16 +0,0 @@ -config = $config; - $this->httpService = $httpService ?: new MockServerHttpService(new GuzzleClient(), $this->config); - } - - /** - * 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? - * @throws ProcessException - */ - 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 !== null) { - $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 ($this->config->getLog() !== null) { - $log = \escapeshellarg($this->config->getLog()); - $results[] = \sprintf('--log=%s', $log); - } - - return $results; - } - - /** - * Make sure the server starts as expected. - * - * @throws Exception - */ - 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 { - return $service->healthCheck(); - } catch (ConnectionException $e) { - \sleep($retrySec); - } - } while ($tries <= $maxTries); - - 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 b575800f..39bbe743 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfig.php @@ -2,15 +2,14 @@ namespace PhpPact\Standalone\MockService; -use Composer\Semver\VersionParser; use GuzzleHttp\Psr7\Uri; -use PhpPact\Standalone\PactConfigInterface; +use PhpPact\Config\PactConfig; use Psr\Http\Message\UriInterface; /** * Configuration defining the default PhpPact Ruby Standalone server. */ -class MockServerConfig implements MockServerConfigInterface, PactConfigInterface +class MockServerConfig extends PactConfig implements MockServerConfigInterface { /** * Host on which to bind the service. @@ -18,58 +17,14 @@ class MockServerConfig implements MockServerConfigInterface, PactConfigInterface 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. */ private int $port = 7200; - private bool $secure = false; - - /** - * Consumer name. - */ - private string $consumer; - - /** - * Provider name. - */ - private string $provider; - - /** - * Directory to which the pacts will be written. - */ - private ?string $pactDir = null; - - /** - * `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. - */ - private string $pactFileWriteMode = 'overwrite'; - - /** - * The pact specification version to use when writing the pact. Note that only versions 1 and 2 are currently supported. - */ - private string $pactSpecificationVersion; - - /** - * File to which to log output. - */ - private ?string $log = null; - - private bool $cors = false; - - /** - * The max allowed attempts the mock server has to be available in. Otherwise it is considered as sick. - */ - private int $healthCheckTimeout; - /** - * The seconds between health checks of mock server + * @var bool */ - private int $healthCheckRetrySec; - - private ?string $logLevel = null; + private bool $secure = false; /** * {@inheritdoc} @@ -118,7 +73,7 @@ public function isSecure(): bool /** * {@inheritdoc} */ - public function setSecure(bool $secure): MockServerConfigInterface + public function setSecure(bool $secure): self { $this->secure = $secure; @@ -134,196 +89,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): 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 getPactFileWriteMode(): string - { - return $this->pactFileWriteMode; - } - - /** - * {@inheritdoc} - */ - public function setPactFileWriteMode(string $pactFileWriteMode): self - { - $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(): string - { - return $this->pactSpecificationVersion; - } - - /** - * {@inheritdoc} - */ - 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; - } - - public function getLogLevel(): ?string - { - 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; - } - - public function hasCors(): bool - { - return $this->cors; - } - - public function setCors(mixed $flag): self - { - if ($flag === 'true') { - $this->cors = true; - } elseif ($flag === 'false') { - $this->cors = false; - } else { - $this->cors = (bool) $flag; - } - - return $this; - } - - public function setHealthCheckTimeout(int $timeout): MockServerConfigInterface - { - $this->healthCheckTimeout = $timeout; - - return $this; - } - - public function getHealthCheckTimeout(): int - { - return $this->healthCheckTimeout; - } - - public function setHealthCheckRetrySec(int $seconds): MockServerConfigInterface - { - $this->healthCheckRetrySec = $seconds; - - return $this; - } - - public function getHealthCheckRetrySec(): int - { - return $this->healthCheckRetrySec; - } } diff --git a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php index b61f8d1f..3b25bfe4 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php @@ -2,12 +2,13 @@ namespace PhpPact\Standalone\MockService; +use PhpPact\Config\PactConfigInterface; use Psr\Http\Message\UriInterface; /** * Mock Server configuration interface to allow for simple overrides that are reusable. */ -interface MockServerConfigInterface +interface MockServerConfigInterface extends PactConfigInterface { /** * @return string the host of the mock service @@ -40,26 +41,4 @@ public function isSecure(): bool; public function setSecure(bool $secure): self; 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 - */ - public function setPactFileWriteMode(string $pactFileWriteMode): self; - - public function hasCors(): bool; - - public function setCors(mixed $flag): self; - - public function setHealthCheckTimeout(int $timeout): self; - - public function getHealthCheckTimeout(): int; - - public function setHealthCheckRetrySec(int $seconds): self; - - public function getHealthCheckRetrySec(): int; } diff --git a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php index fae19e83..7e90a93d 100644 --- a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php @@ -9,8 +9,6 @@ */ class MockServerEnvConfig extends MockServerConfig { - public const DEFAULT_SPECIFICATION_VERSION = '2.0.0'; - /** * @throws MissingEnvVariableException */ @@ -21,7 +19,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); @@ -31,18 +28,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; @@ -56,17 +41,12 @@ public function __construct() * * @throws MissingEnvVariableException */ - private function parseEnv(string $variableName, bool $required = true): mixed + private function parseEnv(string $variableName, bool $required = true): ?string { - $result = null; + $result = \getenv($variableName); - if (\getenv($variableName) === 'false') { - $result = false; - } elseif (\getenv($variableName) === 'true') { - $result = true; - } - if (\getenv($variableName) !== false) { - $result = \getenv($variableName); + if (is_bool($result)) { + $result = null; } if ($required === true && $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 c41bb2df..00000000 --- a/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php +++ /dev/null @@ -1,166 +0,0 @@ -client = $client; - $this->config = $config; - } - - /** - * {@inheritdoc} - */ - public function healthCheck(): bool - { - $uri = $this->config->getBaseUri()->withPath('/'); - - try { - $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.'); - } - } catch (RequestException $e) { - throw new ConnectionException('Failed to receive a successful response from the Mock Server.', $e); - } catch (GuzzleConnectionException $e) { - throw new ConnectionException('Failed to receive a successful response from the Mock Server.', $e); - } - - 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(), JSON_THROW_ON_ERROR); - - $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 - */ - public function registerMessage(Message $message): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $body = \json_encode($message->jsonSerialize(), JSON_THROW_ON_ERROR); - - $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} - * @throws \JsonException - */ - 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()), JSON_THROW_ON_ERROR); - } - - /** - * 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. - * @throws \JsonException - */ - 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 caf095f5..00000000 --- a/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php +++ /dev/null @@ -1,36 +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/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php b/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php index d386fe88..56314f6c 100644 --- a/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php +++ b/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php @@ -2,157 +2,11 @@ namespace PhpPact\Standalone\PactMessage; -use Composer\Semver\VersionParser; -use PhpPact\Standalone\PactConfigInterface; +use PhpPact\Config\PactConfig; /** * Configuration defining the default PhpPact Ruby Standalone server. - * Class MockServerConfig. */ -class PactMessageConfig implements PactConfigInterface +class PactMessageConfig extends PactConfig { - /** - * Consumer name. - */ - private string $consumer; - - /** - * Provider name. - */ - private string $provider; - - /** - * Directory to which the pacts will be written. - */ - private ?string $pactDir = null; - - /** - * The pact specification version to use when writing the pact. Note that only versions 1 and 2 are currently supported. - */ - private string $pactSpecificationVersion; - - /** - * File to which to log output. - */ - private string $log; - - private string $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(): string - { - 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(): string - { - 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(): string - { - return $this->log; - } - - /** - * {@inheritdoc} - */ - public function setLog(string $log): PactConfigInterface - { - $this->log = $log; - - return $this; - } - - public function getLogLevel(): string - { - 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/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/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php index 8aa9a849..f40100a1 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,37 +22,13 @@ 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 */ - 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 307a9448..325637e2 100644 --- a/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php +++ b/src/PhpPact/Standalone/StubService/Service/StubServerHttpServiceInterface.php @@ -2,19 +2,10 @@ 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. */ - public function getJson(): string; + public function getJson(string $endpoint): string; } 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..42d62315 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfig.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfig.php @@ -10,55 +10,107 @@ */ 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 = []; /** - * File to which to log output. + * @var array */ - private ?string $log = null; - - private string $pactLocation; - private string $endpoint; - + private array $urls = []; /** - * {@inheritdoc} + * @var array */ - public function getHost(): string + private array $consumerNames = []; + /** + * @var array + */ + private array $providerNames = []; + + private bool $cors = false; + private bool $corsReferer = false; + private bool $emptyProviderState = false; + private bool $insecureTls = false; + + 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,73 +118,140 @@ 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 getEndpoint(): string + public function isEmptyProviderState(): bool { - return $this->endpoint; + return $this->emptyProviderState; } - public function setEndpoint(string $endpoint): self + public function setEmptyProviderState(bool $emptyProviderState): StubServerConfigInterface { - $this->endpoint = $endpoint; + $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()}"); + } } diff --git a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php index 7f232923..25d0decb 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php @@ -10,55 +10,144 @@ 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; /** - * @return ?string directory for log output + * @param null|string $token Bearer token to use when fetching pacts from URLS or Pact Broker */ - public function getLog(): ?string; + public function setToken(?string $token): self; /** - * @param string $log directory for log output + * @param array $urls URL of pact file to fetch */ - public function setLog(string $log): self; + public function setUrls(array $urls): self; - public function getPactLocation(): string; + /** + * @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 setPactLocation(string $location): self; + public function isInsecureTls(): bool; - public function getEndpoint(): string; + public function setInsecureTls(bool $insecureTls): self; - public function setEndpoint(string $endpoint): self; + /** + * @param array $consumerNames Consumer name to use to filter the Pacts fetched from the Pact broker + */ + public function setConsumerNames(array $consumerNames): self; + + /** + * @return array + */ + public function getConsumerNames(): array; + + /** + * @param array $providerNames Provider name to use to filter the Pacts fetched from the Pact broker + */ + public function setProviderNames(array $providerNames): self; + + /** + * @return array + */ + public function getProviderNames(): array; + + public function getBaseUri(): UriInterface; } 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/Config/PactConfigTest.php b/tests/PhpPact/Config/PactConfigTest.php new file mode 100644 index 00000000..105586d3 --- /dev/null +++ b/tests/PhpPact/Config/PactConfigTest.php @@ -0,0 +1,66 @@ +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 VERBOSE not supported.'); + $this->config->setLogLevel('VERBOSE'); + } + + 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'); + } +} diff --git a/tests/PhpPact/Consumer/InteractionBuilderTest.php b/tests/PhpPact/Consumer/InteractionBuilderTest.php index b9bf75b3..6d97ccbf 100644 --- a/tests/PhpPact/Consumer/InteractionBuilderTest.php +++ b/tests/PhpPact/Consumer/InteractionBuilderTest.php @@ -6,37 +6,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 { - private MockServerHttpServiceInterface $service; - - private MockServer $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 @@ -61,13 +36,15 @@ public function testSimpleGet() ->addHeader('Content-Type', 'application/json'); $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder - ->given('A test request.') + $builder + ->given('A test request.', ['key' => 'value']) ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); + $verifyResult = $builder->verify(); + + $this->assertFalse($verifyResult); } /** @@ -99,13 +76,15 @@ public function testPostWithBody() ]); $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder - ->given('A test request.') + $builder + ->given('A test request.', ['key' => 'value']) ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); + $verifyResult = $builder->verify(); + + $this->assertFalse($verifyResult); } /** @@ -133,12 +112,14 @@ public function testBuildWithEachLikeMatcher() ]); $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder - ->given('A test request.') + $builder + ->given('A test request.', ['key' => 'value']) ->uponReceiving('A test response.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); + $verifyResult = $builder->verify(); + + $this->assertFalse($verifyResult); } } diff --git a/tests/PhpPact/Consumer/Matcher/MatcherTest.php b/tests/PhpPact/Consumer/Matcher/MatcherTest.php index 8b72a7ac..6ad9025a 100644 --- a/tests/PhpPact/Consumer/Matcher/MatcherTest.php +++ b/tests/PhpPact/Consumer/Matcher/MatcherTest.php @@ -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()); @@ -394,17 +338,10 @@ public function testIpv6Address() */ public function testEmail() { - $expected = [ - 'data' => [ - 'generate' => 'hello@pact.io', - 'matcher' => [ - 'json_class' => 'Regexp', - 'o' => 0, - 's' => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$', - ], - ], - 'json_class' => 'Pact::Term', + 'value' => 'hello@pact.io', + 'regex' => '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$', + 'pact:matcher:type' => 'regex', ]; $this->assertEquals($expected, $this->matcher->email()); } diff --git a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php index 4e760bbe..f30aeca7 100644 --- a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php +++ b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php @@ -15,16 +15,16 @@ public function testSerializing() ->setMethod('PUT') ->setPath('/somepath') ->addHeader('Content-Type', 'application/json') + ->addQueryParameter('fruit', ['apple', 'banana']) ->setBody([ '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(['fruit' => ['apple', 'banana']], $model->getQuery()); + $this->assertEquals('/somepath', $model->getPath()); + $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } public function testSerializingWhenPathUsingMatcher() @@ -36,17 +36,15 @@ 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', ]); - $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(['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/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()); + } +} diff --git a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php index 49e2c7f1..2da664be 100644 --- a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php +++ b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php @@ -17,10 +17,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/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/Broker/BrokerConfigTest.php b/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php index c5b2bc5c..2e799e12 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php @@ -2,42 +2,87 @@ namespace PhpPactTest\Standalone\Broker; -use PhpPact\Standalone\MockService\MockServerConfig; +use GuzzleHttp\Psr7\Uri; +use PhpPact\Standalone\Broker\BrokerConfig; use PHPUnit\Framework\TestCase; class BrokerConfigTest extends TestCase { public function testSetters() { - $host = 'test-host'; - $port = 1234; - $provider = 'test-provider'; - $consumer = 'test-consumer'; - $pactDir = 'test-pact-dir/'; - $pactFileWriteMode = 'merge'; - $log = 'test-log-dir/'; - $cors = true; - $pactSpecificationVersion = '2.0'; - - $subject = (new MockServerConfig()) - ->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 30a43b0b..b85bca85 100644 --- a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php +++ b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php @@ -16,7 +16,6 @@ public function testSetters() $pactDir = 'test-pact-dir/'; $pactFileWriteMode = 'merge'; $log = 'test-log-dir/'; - $cors = true; $pactSpecificationVersion = '2.0'; $subject = (new MockServerConfig()) @@ -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 6d356742..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(ConnectionException::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 c389dad7..00000000 --- a/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php +++ /dev/null @@ -1,209 +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(): void - { - $result = $this->service->healthCheck(); - $this->assertTrue($result); - } - - public function testRegisterInteraction(): void - { - $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(): void - { - $result = $this->service->deleteAllInteractions(); - $this->assertTrue($result); - } - - public function testVerifyInteractions(): void - { - $result = $this->service->verifyInteractions(); - $this->assertTrue($result); - } - - public function testVerifyInteractionsFailure(): void - { - $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(): void - { - $result = $this->service->getPactJson(); - $this->assertEquals('{"consumer":{"name":"someConsumer"},"provider":{"name":"someProvider"},"interactions":[],"metadata":{"pactSpecification":{"version":"2.0.0"}}}', $result); - } - - public function testFullGetInteraction(): void - { - $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(): void - { - $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/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..734e978c 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,19 +28,15 @@ 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; $this->config = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) - ->setPort($port) - ->setEndpoint($endpoint); + ->setFiles($files) + ->setPort($port); $this->stubServer = new StubServer($this->config); - $this->stubServer->start(10); + $this->stubServer->start(); $this->service = new StubServerHttpService(new GuzzleClient(), $this->config); } @@ -49,15 +45,10 @@ protected function tearDown(): void $this->stubServer->stop(); } - public function testHealthCheck() - { - $result = $this->service->healthCheck(); - $this->assertTrue($result); - } - 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/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..4ea6cb51 100644 --- a/tests/PhpPact/Standalone/StubServer/StubServerTest.php +++ b/tests/PhpPact/Standalone/StubServer/StubServerTest.php @@ -14,16 +14,12 @@ 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; $subject = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) - ->setPort($port) - ->setEndpoint($endpoint); + ->setFiles($files) + ->setPort($port); $stubServer = new StubServer($subject); $pid = $stubServer->start(); 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', + ], + ], +]);