diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 487c246f..32c544b4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - php: [ '7.3', '7.4', '8.0' ] + php: [ '7.4', '8.0' ] steps: - uses: actions/checkout@v2 @@ -39,7 +39,7 @@ jobs: fail-fast: true matrix: operating-system: [ ubuntu-latest, macos-latest, windows-latest ] - php: [ '7.3', '7.4', '8.0' ] + php: [ '7.4', '8.0' ] dependencies: [ 'lowest', 'locked' ] name: PHP ${{ matrix.php }} on ${{ matrix.operating-system }} with ${{ matrix.dependencies }} dependencies @@ -52,6 +52,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} + extensions: ffi - name: Copy the inis if: runner.os == 'Windows' diff --git a/README.md b/README.md index 101d381e..d07e707c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,8 @@ 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) + * [Configure PHPUnit](#configure-phpunit) + * [Create Mock Server Config](#create-mock-server-config) * [Create Consumer Unit Test](#create-consumer-unit-test) * [Create Mock Request](#create-mock-request) * [Create Mock Response](#create-mock-response) @@ -35,6 +36,7 @@ Table of contents * [Examples](#additional-examples) ## Versions +8.x adds support for pact specification 3.X & 4.X via Pact FFI. This results in dropping support for PHP 7.3. 7.x updates internal dependencies and libraries, mostly to Guzzle 7.X. This results in dropping support for PHP 7.2. 6.x updates internal dependencies, mostly surrounding the Amp library. This results in dropping support for PHP 7.1. @@ -48,7 +50,12 @@ 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 supports [Pact-Specification 2.X](https://github.com/pact-foundation/pact-specification/tree/version-2). +Pact-Php 8.X supports: + * [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 @@ -64,37 +71,59 @@ 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 +### Configure PHPUnit + +```xml + + + + + ./tests/Consumer + + + + + + + + PhpPact Example Tests + + + + + + + + + + + + + + +``` + +### Create Mock Server Config + +Create mock server config from environment variables: -This library contains a wrapper for the [Ruby Standalone Mock Service](https://github.com/pact-foundation/pact-mock_service). +```php +use PhpPact\Standalone\MockService\MockServerEnvConfig; -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. +$config = new MockServerEnvConfig(); +$config->setProvider('someProvider'); +``` -Alternatively, you can start and stop as in whatever means you would like by following this example: +Or create it manually: ```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(); +use PhpPact\Standalone\MockService\MockServerConfig; + +$config = new MockServerConfig(); +$config->setConsumer('someConsumer'); +$config->setProvider('someProvider'); +$config->setPactDir('.\example\output'); +$config->setPactSpecificationVersion('3.0.0'); ``` ### Create Consumer Unit Test @@ -108,6 +137,8 @@ Create a standard PHPUnit test case class and function. This will define what the expected request coming from your http service will look like. ```php +use PhpPact\Consumer\Model\ConsumerRequest; + $request = new ConsumerRequest(); $request ->setMethod('GET') @@ -122,6 +153,9 @@ You can also create a body just like you will see in the provider example. This will define what the response from the provider should look like. ```php +use PhpPact\Consumer\Matcher\Matcher; +use PhpPact\Consumer\Model\ProviderResponse; + $matcher = new Matcher(); $response = new ProviderResponse(); @@ -136,6 +170,9 @@ $response In this example, we are using matchers. This allows us to add flexible rules when matching the expectation with the actual value. In the example, you will see regex is used to validate that the response is valid. ```php +use PhpPact\Consumer\Matcher\Matcher; +use PhpPact\Consumer\Model\ProviderResponse; + $matcher = new Matcher(); $response = new ProviderResponse(); @@ -175,22 +212,27 @@ ipv6Address | Regex to match a ipv6 address. | Value (Defaults to ::ffff:192.0.2 Now that we have the request and response, we need to build the interaction and ship it over to the mock server. ```php +use PhpPact\Consumer\InteractionBuilder; +use PhpPact\Standalone\MockService\MockServerEnvConfig; + // Create a configuration that reflects the server that was started. You can // create a custom MockServerConfigInterface if needed. This configuration // is the same that is used via the PactTestListener and uses environment variables. $config = new MockServerEnvConfig(); $builder = new InteractionBuilder($config); $builder + ->newInteraction() // Support multiple interactions per mock server. Call to create new interaction. ->given('a person exists') ->uponReceiving('a get request to /hello/{name}') ->with($request) - ->willRespondWith($response); // This has to be last. This is what makes an API request to the Mock Server to set the interaction. + ->willRespondWith($response) + ->createMockServer(); // This has to be last. ``` ### Make the Request ```php -$service = new HttpClientService($config->getBaseUri()); // Pass in the URL to the Mock Server. +$service = new HttpClientService($builder->getBaseUri()); // Pass in the URL to the Mock Server. $result = $service->getHelloString('Bob'); // Make the real API request against the Mock Server. ``` @@ -200,7 +242,7 @@ Verify that all interactions took place that were registered. This typically should be in each test, that way the test that failed to verify is marked correctly. ```php -$builder->verify(); +$this->assertTrue($builder->verify()); ``` ### Make Assertions @@ -234,29 +276,55 @@ There are three ways to verify Pact files. See the examples below. This will grab the Pact file from a Pact Broker and run the data against the stood up API. ```php +use PhpPact\Standalone\ProviderVerifier\Verifier; +use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; +use PhpPact\Standalone\ProviderVerifier\Model\ConsumerVersionSelectors; +use PhpPact\Standalone\ProviderVerifier\Model\Source\Broker; + +$selectors = (new ConsumerVersionSelectors()) + ->addSelector('{"tag":"foo","latest":true}') + ->addSelector('{"tag":"bar","latest":true}'); + $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. -$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.'); + ->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) + ->setStateChangeAsQuery(true) + ->setStateChangeTeardown(true) + ->setRequestTimeout(500); + +$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 = new Verifier(); +$verifier->newHandle($config); +$verifier->addBroker($broker); + +$this->assertTrue($verifier->verify()); ``` ##### Verify All from Pact Broker @@ -264,25 +332,22 @@ $this->assertTrue(true, 'Pact Verification has failed.'); This will grab every Pact file associated with the given provider. ```php +use PhpPact\Standalone\ProviderVerifier\Verifier; +use PhpPact\Standalone\ProviderVerifier\Model\Source\Broker; + public function testPactVerifyAll() { - $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.'); + $broker = new Broker(); + $broker + ->setUrl(new Uri('http://localhost')) + ->setUsername('user') + ->setPassword('pass') + ->setToken('token'); + + /** @var Verifier $verifier */ + $verifier->addBroker($broker); + + $this->assertTrue($verifier->verify()); } ``` @@ -291,25 +356,14 @@ public function testPactVerifyAll() This allows local Pact file testing. ```php -public function testPactVerifyAll() +use PhpPact\Standalone\ProviderVerifier\Verifier; + +public function testPactVerifyLocal() { - $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.'); + /** @var Verifier $verifier */ + $verifier->addFile('C:\SomePath\consumer-provider.json'); + + $this->assertTrue($verifier->verify()); } ``` @@ -341,7 +395,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 @@ -363,7 +416,11 @@ The examples provided are pretty basic. See examples\tests\MessageConsumer. 1. Run Verify. If nothing blows up, #winning. ```php -$builder = new MessageBuilder(self::$config); +use PhpPact\Consumer\MessageBuilder; +use MessageConsumer\ExampleMessageConsumer; + +$config = new MockServerEnvConfig(); +$builder = new MessageBuilder($config); $contents = new \stdClass(); $contents->song = 'And the wind whispers Mary'; @@ -381,61 +438,39 @@ $consumerMessage = new ExampleMessageConsumer(); $callback = [$consumerMessage, 'ProcessSong']; $builder->setCallback($callback); -$builder->verify(); +$this->assertTrue($builder->verify()); ``` ### Provider Side Message Validation -This may evolve as we work through this implementation. The provider relies heavily on callbacks. -Some of the complexity lies in a consumer and provider having many messages and states between the each other in a single pact. - -For each message, one needs to provide a single provider state. The name of this provider state must be the key to run -a particular message callback on the provider side. See example\tests\MessageProvider - -1. Create your callbacks and states wrapped in a callable object - 1. The array key is a provider state / given() on the consumer side - 1. It is helpful to wrap the whole thing in a lambda if you need to customize paramaters to be passed in -1. Choose your verification method -1. If nothing explodes, #winning -```php - - $callbacks = array(); - - // 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"; +Create a proxy to handle these requests: - $provider = (new ExampleMessageProvider()) - ->setContents($content) - ->setMetadata($metadata); +1. POST /change-state + 1. Set up your database to meet the expectations of the request + 1. Reset the database to its original state. +2. POST / + 1. Return message's content in body + 2. Return message's metadata in header `PACT-MESSAGE-METADATA` +3. If nothing matches, proxy the request to provider - return $provider->Build(); - }; - - $verifier = (new MessageVerifier($config)) - ->setCallbacks($callbacks) - ->verifyFiles([__DIR__ . '/../../output/test_consumer-test_provider.json']); - -``` +[Click here](/example/tests/Provider/public/proxy.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 +use PhpPact\Standalone\StubService\StubServerConfig; +use PhpPact\Standalone\StubService\StubServer; +use PhpPact\Standalone\StubService\Service\StubServerHttpService; + $pactLocation = __DIR__ . '/someconsumer-someprovider.json'; -$host = 'localhost'; $port = 7201; $endpoint = 'test'; $config = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) + ->setFiles($pactLocation) ->setPort($port) ->setEndpoint($endpoint); diff --git a/UPGRADE-8.0.md b/UPGRADE-8.0.md new file mode 100644 index 00000000..6d096454 --- /dev/null +++ b/UPGRADE-8.0.md @@ -0,0 +1,115 @@ +UPGRADE FROM 7.x to 8.0 +======================= + + * Removed `InteractionBuilder::finalize`, use `InteractionBuilder::verify` instead + * Removed `BuilderInterface::writePact`, use `BuilderInterface::verify` instead + * Removed `PactConfigInterface`, use `MockServerConfigInterface` instead + * [BC BREAK] Updated `MockServerConfigInterface`, removed methods related to http mock server + * Removed environment variables: + - PACT_LOG + - PACT_MOCK_SERVER_HOST + - PACT_MOCK_SERVER_PORT + - PACT_CORS + - PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT + - PACT_MOCK_SERVER_HEALTH_CHECK_RETRY_SEC + - PACT_BROKER_SSL_VERIFY + - PACT_PROVIDER_NAME + * [BC BREAK] Removed `MockServerConfig::getBaseUri()`, use `InteractionBuilder::getBaseUri()` instead + * [BC BREAK] Need to call `MockServerConfigInterface::setProvider` in each test case + * [BC BREAK] Added `InteractionBuilder::newInteraction`, required to be called before each interaction. + * [BC BREAK] Removed `MockServer`, use `InteractionBuilder::createMockServer` instead + * Allowed multiple interactions per mock server + + Example: + ```php + $builder = new InteractionBuilder($config); + $builder + ->newInteraction() + ->given('a person exists') + ->uponReceiving('a get request to /hello/{name}') + ->with($request) + ->willRespondWith($response); + $builder + ->newInteraction() + ->given('a person exists') + ->uponReceiving('a get request to /goodbye/{name}') + ->with($request) + ->willRespondWith($response); + $builder->createMockServer(); + ``` + + * Allowed multiple providers per consumer's test suite + + In a test case: + ```php + $config = new MockServerEnvConfig(); + $config->setProvider('someProvider'); + $builder = new InteractionBuilder($config); + ``` + + In another test case: + ```php + $config = new MockServerEnvConfig(); + $config->setProvider('otherProvider'); + $builder = new InteractionBuilder($config); + ``` + + * Removed `VerifierProcess` + * Removed `MessageVerifier`, use `Verifier` instead + * [BC BREAK] Move some methods from `VerifierConfigInterface` to: + - UrlInterface + - BrokerInterface + * [BC BREAK] Updated `Verifier` + + Example: + ```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(); + $verifier->newHandle($config); + $verifier->addFile('C:\SomePath\consumer-provider.json'); + $verifier->addDirectory('C:\OtherPath'); + $verifier->addUrl($url); + $verifier->addBroker($broker); + + $this->assertTrue($verifier->verify()); + ``` + + * Removed `ProcessRunnerFactory` + * Removed `PactMessage` + * Removed `PactMessageConfig`, use `MockServerEnvConfig` or `MockServerConfig` instead + * Removed `MockServerHttpService` + * Removed `MockServerHttpServiceInterface` + * Removed `HealthCheckFailedException` + * Removed `BrokerHttpClient` + * Removed `BrokerHttpClientInterface` + * Added `MockServerNotStartedException` + * Removed `ContractDownloader` + * Removed `Model\Interaction` + * Removed `Model\Message` + * [BC BREAK] Updated `StubServerConfigInterface`, see [pact-stub-server](https://github.com/pact-foundation/pact-stub-server) diff --git a/composer.json b/composer.json index 66ca8c2b..ba5e74fd 100644 --- a/composer.json +++ b/composer.json @@ -18,33 +18,28 @@ } ], "require": { - "php": "^7.3|^8.0", + "php": "^7.4|^8.0", + "ext-ffi": "*", "ext-openssl": "*", "ext-json": "*", - "composer/semver": "^1.4.0|^3.2.0", + "ext-zip": "*", + "ext-zlib": "*", "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": "v1.4.0", - "amphp/windows-registry": "v0.3.3", "guzzlehttp/guzzle": "^6.5|^7.2.0", - "phpunit/phpunit": ">=8.2.3" + "phpunit/phpunit": ">=8.2.3", + "symfony/filesystem": "^5.3" }, "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", "php-amqplib/php-amqplib": "^3.0", - "phpstan/phpstan": "^0.12.90" + "phpstan/phpstan": "^0.12.90", + "symfony/process": "^4.2 | ^5.0.9" }, "autoload": { "psr-4": { @@ -72,8 +67,8 @@ "post-update-cmd": [ "\\PhpPact\\Standalone\\Installer\\InstallManager::uninstall" ], - "start-provider": "php -S localhost:58000 -t example/src/Provider/public/", - "static-code-analysis": "phpstan analyse src/ --level=5", + "start-provider": "php -S localhost:58000 -t example/src/Provider/public/ example/src/Provider/public/proxy.php", + "static-code-analysis": "phpstan analyse src/ --level=5 -c phpstan.neon", "lint": "php-cs-fixer fix --config .php-cs-fixer.php --dry-run", "fix": "php-cs-fixer fix --config .php-cs-fixer.php", "test": "phpunit --debug -c example/phpunit.all.xml" diff --git a/example/pacts/README.md b/example/pacts/README.md deleted file mode 100644 index 0d5d2a0b..00000000 --- a/example/pacts/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Example Pacts - -The json files in this folder are explicitly here for an easy-to-read output of the test examples. These are *not* the actual test results from running all these tests of this project. By default, the pact files of this project's examples are written to example/output. The tests themselves need to generate the appropropriate files as part of the tests. - -To run the tests locally, try `composer test` - - - - - - diff --git a/example/pacts/someconsumer-someprovider.json b/example/pacts/someconsumer-someprovider.json index c502e5ba..b06337fe 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..5fa9b164 100644 --- a/example/phpunit.all.xml +++ b/example/phpunit.all.xml @@ -13,9 +13,6 @@ ./tests/MessageConsumer - - ./tests/MessageProvider - @@ -29,14 +26,9 @@ - - - - - diff --git a/example/phpunit.consumer.xml b/example/phpunit.consumer.xml index 6e88b251..909dfc2a 100644 --- a/example/phpunit.consumer.xml +++ b/example/phpunit.consumer.xml @@ -17,14 +17,10 @@ - - - - diff --git a/example/phpunit.core.xml b/example/phpunit.core.xml index aefb2d26..aaf57cfd 100644 --- a/example/phpunit.core.xml +++ b/example/phpunit.core.xml @@ -17,14 +17,9 @@ - - - - - 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/Service/HttpClientService.php b/example/src/Consumer/Service/HttpClientService.php index 668ba75b..cc728028 100644 --- a/example/src/Consumer/Service/HttpClientService.php +++ b/example/src/Consumer/Service/HttpClientService.php @@ -11,11 +11,8 @@ */ class HttpClientService { - /** @var Client */ - private $httpClient; - - /** @var string */ - private $baseUri; + private Client $httpClient; + private string $baseUri; public function __construct(string $baseUri) { @@ -38,7 +35,7 @@ public function getHelloString(string $name): string $body = $response->getBody(); $object = \json_decode($body); - return $object->message; + return $object->message->data->generate; } /** 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/Provider/public/proxy.php b/example/src/Provider/public/proxy.php new file mode 100644 index 00000000..d4aa1a7a --- /dev/null +++ b/example/src/Provider/public/proxy.php @@ -0,0 +1,70 @@ +addBodyParsingMiddleware(); + +$app->post('/', function (Request $request, Response $response) { + $body = $request->getParsedBody(); + switch ($body['description']) { + case 'an alligator named Mary exists': + $response->getBody()->write(\json_encode([ + 'text' => 'Hello Mary', + ])); + + return $response->withHeader('Content-Type', 'application/json') + ->withHeader('pact_message_metadata', \base64_encode(\json_encode([ + 'queue' => 'wind cries', + 'routing_key' => 'wind cries', + ]))); + case 'footprints dressed in red': + $response->getBody()->write(\json_encode([ + 'song' => 'And the wind whispers Mary', + ])); + + return $response + ->withHeader('Content-Type', 'application/json') + ->withHeader('pact_message_metadata', \base64_encode(\json_encode([ + 'queue' => 'And the clowns have all gone to bed', + 'routing_key' => 'And the clowns have all gone to bed', + ]))); + + default: + break; + } + // What to do with $body['providerStates'] ? + + return $response; +}); + +$app->post('/change-state', function (Request $request, Response $response) { + $body = $request->getParsedBody(); + switch ($body['state']) { + case 'a message': + case 'You can hear happiness staggering on down the street': + if (($body['action'] ?? null) === 'teardown') { + \error_log('Removing fixtures...'); + } else { + \error_log('Creating fixtures...'); + } + + break; + + default: + break; + } + + return $response; +}); + +try { + $app->run(); +} catch (HttpNotFoundException $exception) { + return false; +} diff --git a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php index e5106f55..7a73e5c7 100644 --- a/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceGoodbyeTest.php @@ -2,17 +2,21 @@ namespace Consumer\Service; +use PhpPact\Consumer\Exception\MockServerNotStartedException; use PhpPact\Consumer\InteractionBuilder; use PhpPact\Consumer\Model\ConsumerRequest; use PhpPact\Consumer\Model\ProviderResponse; -use PhpPact\Standalone\Exception\MissingEnvVariableException; +use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; +use PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException; use PhpPact\Standalone\MockService\MockServerEnvConfig; use PHPUnit\Framework\TestCase; class ConsumerServiceGoodbyeTest extends TestCase { /** - * @throws MissingEnvVariableException + * @throws NoDownloaderFoundException + * @throws FileDownloadFailureException + * @throws MockServerNotStartedException */ public function testGetGoodbyeString() { @@ -31,18 +35,20 @@ public function testGetGoodbyeString() ]); $config = new MockServerEnvConfig(); + $config->setProvider('someProvider'); $builder = new InteractionBuilder($config); $builder + ->newInteraction() ->given('Get Goodbye') ->uponReceiving('A get request to /goodbye/{name}') ->with($request) - ->willRespondWith($response); + ->willRespondWith($response) + ->createMockServer(); - $service = new HttpClientService($config->getBaseUri()); + $service = new HttpClientService($builder->getBaseUri()); $result = $service->getGoodbyeString('Bob'); - $builder->verify(); - + $this->assertTrue($builder->verify()); $this->assertEquals('Goodbye, Bob', $result); } } diff --git a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php index 2a80f394..74911f4a 100644 --- a/example/tests/Consumer/Service/ConsumerServiceHelloTest.php +++ b/example/tests/Consumer/Service/ConsumerServiceHelloTest.php @@ -2,10 +2,12 @@ namespace Consumer\Service; +use Exception; use PhpPact\Consumer\InteractionBuilder; use PhpPact\Consumer\Matcher\Matcher; use PhpPact\Consumer\Model\ConsumerRequest; use PhpPact\Consumer\Model\ProviderResponse; +use PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException; use PhpPact\Standalone\MockService\MockServerEnvConfig; use PHPUnit\Framework\TestCase; @@ -14,7 +16,8 @@ class ConsumerServiceHelloTest extends TestCase /** * Example PACT test. * - * @throws \Exception + * @throws NoDownloaderFoundException + * @throws Exception */ public function testGetHelloString() { @@ -38,17 +41,19 @@ public function testGetHelloString() // Create a configuration that reflects the server that was started. You can create a custom MockServerConfigInterface if needed. $config = new MockServerEnvConfig(); + $config->setProvider('someProvider'); $builder = new InteractionBuilder($config); $builder + ->newInteraction() ->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) + ->createMockServer(); - $service = new HttpClientService($config->getBaseUri()); // Pass in the URL to the Mock Server. + $service = new HttpClientService($builder->getBaseUri()); // Pass in the URL to the Mock Server. $result = $service->getHelloString('Bob'); // Make the real API request against the Mock Server. - $builder->verify(); // This will verify that the interactions took place. - + $this->assertTrue($builder->verify()); $this->assertEquals('Hello, Bob', $result); // Make your assertions. } } diff --git a/example/tests/MessageConsumer/ExampleMessageConsumerTest.php b/example/tests/MessageConsumer/ExampleMessageConsumerTest.php index c02006c0..1400213e 100644 --- a/example/tests/MessageConsumer/ExampleMessageConsumerTest.php +++ b/example/tests/MessageConsumer/ExampleMessageConsumerTest.php @@ -2,12 +2,9 @@ namespace MessageConsumer; -require_once __DIR__ . '/../../src/MessageConsumer/ExampleMessageConsumer.php'; - -use PhpPact\Broker\Service\BrokerHttpClient; use PhpPact\Consumer\MessageBuilder; -use PhpPact\Http\GuzzleClient; -use PhpPact\Standalone\PactMessage\PactMessageConfig; +use PhpPact\Standalone\MockService\MockServerConfigInterface; +use PhpPact\Standalone\MockService\MockServerEnvConfig; use PHPUnit\Framework\TestCase; /** @@ -15,30 +12,18 @@ */ class ExampleMessageConsumerTest extends TestCase { - private static $config; + private static MockServerConfigInterface $config; public static function setUpBeforeClass(): void { parent::setUpBeforeClass(); - self::$config = (new PactMessageConfig()) + self::$config = (new MockServerEnvConfig()) ->setConsumer('test_consumer') ->setProvider('test_provider') ->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 */ @@ -62,11 +47,7 @@ public function testProcessText() $callback = [$consumerMessage, 'ProcessText']; $builder->setCallback($callback); - $hasException = false; - - $builder->verify(); - - $this->assertTrue(true, 'Expects to reach this true statement by running verify()'); + $this->assertTrue($builder->verify()); } /** @@ -92,14 +73,6 @@ public function testProcessSong() $callback = [$consumerMessage, 'ProcessSong']; $builder->setCallback($callback); - $hasException = false; - - try { - $builder->verify(); - } catch (\Exception $e) { - $hasException = true; - } - - $this->assertFalse($hasException, 'Expects verification to pass without exceptions being thrown'); + $this->assertTrue($builder->verify()); } } 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 01f3d038..3d491eab 100644 --- a/example/tests/Provider/PactVerifyTest.php +++ b/example/tests/Provider/PactVerifyTest.php @@ -7,8 +7,8 @@ use PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException; use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; use PhpPact\Standalone\ProviderVerifier\Verifier; -use PhpPact\Standalone\Runner\ProcessRunner; use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Process; /** * This is an example on how you could use the included amphp/process wrapper to start your API to run PACT verification against a Provider. @@ -16,19 +16,20 @@ */ class PactVerifyTest extends TestCase { - /** @var ProcessRunner */ - private $processRunner; + private Process $process; /** * Run the PHP build-in web server. */ protected function setUp(): void { - $publicPath = __DIR__ . '/../../src/Provider/public/'; + $publicPath = __DIR__ . '/../../src/Provider/public'; - $this->processRunner = new ProcessRunner('php', ['-S', 'localhost:7202', '-t', $publicPath]); - - $this->processRunner->run(); + $this->process = new Process(['php', '-S', 'localhost:7202', '-t', $publicPath, $publicPath . '/proxy.php']); + $this->process->start(); + $this->process->waitUntil(function ($type, $output) { + return false !== \strpos($output, 'Development Server (http://localhost:7202) started'); + }); } /** @@ -36,14 +37,14 @@ protected function setUp(): void */ protected function tearDown(): void { - $this->processRunner->stop(); + $this->process->stop(); } /** * This test will run after the web server is started. * - * @throws FileDownloadFailureException * @throws NoDownloaderFoundException + * @throws FileDownloadFailureException */ public function testPactVerifyConsumer() { @@ -52,14 +53,16 @@ public function testPactVerifyConsumer() ->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. + ->setHost('localhost') + ->setPort(7202) + ->setStateChangeUrl(new Uri('http://localhost:7202/change-state')) ; // 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 Verifier($config); - $verifier->verifyFiles([__DIR__ . '/../../pacts/someconsumer-someprovider.json']); + $verifier = new Verifier(); + $verifier->newHandle($config); + $verifier->addDirectory(__DIR__ . '/../../pacts/'); - // This will not be reached if the PACT verifier throws an error, otherwise it was successful. - $this->assertTrue(true, 'Pact Verification has failed.'); + $this->assertTrue($verifier->verify()); } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..1ad85666 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,6 @@ +parameters: + excludePaths: + - src/PhpPact/Consumer/InteractionBuilder.php + - src/PhpPact/Consumer/MessageBuilder.php + - src/PhpPact/Consumer/AbstractBuilder.php + - src/PhpPact/Standalone/ProviderVerifier/Verifier.php diff --git a/phpunit.xml b/phpunit.xml index 35d425c2..f6e0c073 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -18,14 +18,10 @@ - - - - diff --git a/src/PhpPact/Broker/Service/BrokerHttpClient.php b/src/PhpPact/Broker/Service/BrokerHttpClient.php deleted file mode 100644 index e65b46ce..00000000 --- a/src/PhpPact/Broker/Service/BrokerHttpClient.php +++ /dev/null @@ -1,112 +0,0 @@ -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) - { - $array = \json_decode($json, true); - $consumer = $array['consumer']['name']; - $provider = $array['provider']['name']; - - /** @var UriInterface $uri */ - $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) - { - /** @var UriInterface $uri */ - $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); - - $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); - - $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 05fb303e..00000000 --- a/src/PhpPact/Broker/Service/BrokerHttpClientInterface.php +++ /dev/null @@ -1,61 +0,0 @@ - 1, + '1.1.0' => 2, + '2.0.0' => 3, + '3.0.0' => 4, + '4.0.0' => 5, + ]; + public const UNKNOWN_PACT_SPECIFICATION_VERSION = 0; + + protected FFI $ffi; + protected MockServerConfigInterface $config; + protected Scripts $scripts; + + /** + * @param MockServerConfigInterface $config + * + * @throws FileDownloadFailureException + * @throws NoDownloaderFoundException + */ + public function __construct(MockServerConfigInterface $config) + { + $this->config = $config; + $this->scripts = (new InstallManager())->install(); + $this->ffi = FFI::cdef(\file_get_contents($this->scripts->getCode()), $this->scripts->getLibrary()); + $this->ffi->pactffi_init('PACT_LOGLEVEL'); + } + + /** + * @return int + */ + protected function getPactSpecificationVersion(): int + { + return static::SUPPORTED_PACT_SPECIFICATION_VERSIONS[$this->config->getPactSpecificationVersion()] ?? static::UNKNOWN_PACT_SPECIFICATION_VERSION; + } +} diff --git a/src/PhpPact/Consumer/BuilderInterface.php b/src/PhpPact/Consumer/BuilderInterface.php index 0b58c219..406acd26 100644 --- a/src/PhpPact/Consumer/BuilderInterface.php +++ b/src/PhpPact/Consumer/BuilderInterface.php @@ -12,11 +12,4 @@ interface BuilderInterface * Verify that the interactions are valid. */ public function verify(): bool; - - /** - * Write the Pact without deleting the interactions. - * - * @return bool - */ - public function writePact(): bool; } diff --git a/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php b/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php new file mode 100644 index 00000000..d107876d --- /dev/null +++ b/src/PhpPact/Consumer/Exception/MockServerNotStartedException.php @@ -0,0 +1,17 @@ +mockServerConfig = new MockServerEnvConfig(); - } - - /** - * @throws AssertionFailedError - * @throws RuntimeException - */ - public function executeAfterLastTest(): void - { - try { - $this->getMockServerService()->verifyInteractions(); - } catch (Exception $e) { - throw new AssertionFailedError('Pact interaction verification failed', 0, $e); - } - - try { - \file_put_contents($this->getPactFilename(), $this->getPactJson()); - } catch (Exception $e) { - throw new RuntimeException('Pact contract generation failed', 0, $e); - } - } - - private function getMockServerService(): MockServerHttpService - { - return new MockServerHttpService( - $this->getClient(), - $this->mockServerConfig - ); - } - - private function getClient(): ClientInterface - { - if (!$this->client) { - $this->client = new GuzzleClient(); - } - - return $this->client; - } - - private function getPactFilename(): string - { - return $this->mockServerConfig->getPactDir() - . DIRECTORY_SEPARATOR - . $this->mockServerConfig->getConsumer() - . '-' - . $this->mockServerConfig->getProvider() . '.json'; - } - - private function getPactJson(): string - { - $uri = $this->mockServerConfig->getBaseUri()->withPath('/pact'); - $response = $this->getClient()->post( - $uri, - [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - 'body' => \json_encode([ - 'consumer' => ['name' => $this->mockServerConfig->getConsumer()], - 'provider' => ['name' => $this->mockServerConfig->getProvider()] - ]) - ] - ); - - return \json_encode(\json_decode($response->getBody()->getContents())); - } -} diff --git a/src/PhpPact/Consumer/InteractionBuilder.php b/src/PhpPact/Consumer/InteractionBuilder.php index 93ec41d6..edfd83f8 100644 --- a/src/PhpPact/Consumer/InteractionBuilder.php +++ b/src/PhpPact/Consumer/InteractionBuilder.php @@ -2,37 +2,43 @@ namespace PhpPact\Consumer; +use PhpPact\Consumer\Exception\MockServerNotStartedException; 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 + * Build an interaction and send it to the Pact Rust FFI * Class InteractionBuilder. */ -class InteractionBuilder implements BuilderInterface +class InteractionBuilder extends AbstractBuilder { - /** @var MockServerHttpService */ - protected $mockServerHttpService; - - /** @var MockServerConfigInterface */ - protected $config; - /** @var Interaction */ - private $interaction; + protected int $pact; + protected int $interaction; + protected ?int $port = null; /** * InteractionBuilder constructor. * - * @param MockServerConfigInterface $config + * {@inheritdoc} */ public function __construct(MockServerConfigInterface $config) { - $this->config = $config; - $this->mockServerHttpService = new MockServerHttpService(new GuzzleClient(), $config); - $this->interaction = new Interaction(); + parent::__construct($config); + $this->pact = $this->ffi->pactffi_new_pact($config->getConsumer(), $config->getProvider()); + $this->ffi->pactffi_with_specification($this->pact, $this->getPactSpecificationVersion()); + } + + /** + * @param string $description what is received when the request is made + * + * @return InteractionBuilder + */ + public function newInteraction(string $description = ''): self + { + $this->interaction = $this->ffi->pactffi_new_interaction($this->pact, $description); + + return $this; } /** @@ -42,7 +48,7 @@ public function __construct(MockServerConfigInterface $config) */ public function given(string $providerState): self { - $this->interaction->setProviderState($providerState); + $this->ffi->pactffi_given($this->interaction, $providerState); return $this; } @@ -54,7 +60,7 @@ public function given(string $providerState): self */ public function uponReceiving(string $description): self { - $this->interaction->setDescription($description); + $this->ffi->pactffi_upon_receiving($this->interaction, $description); return $this; } @@ -66,55 +72,84 @@ public function uponReceiving(string $description): self */ public function with(ConsumerRequest $request): self { - $this->interaction->setRequest($request); + $this->ffi->pactffi_with_request($this->interaction, $request->getMethod(), $request->getPath()); + foreach ($request->getHeaders() as $header => $values) { + foreach ($values as $index => $value) { + $this->ffi->pactffi_with_header($this->interaction, $this->ffi->InteractionPart_Request, $header, $index, $value); + } + } + foreach ($request->getQuery() as $key => $values) { + $values = \is_array($values) ? $values : [$values]; + foreach ($values as $index => $value) { + $this->ffi->pactffi_with_query_parameter($this->interaction, $key, $index, $value); + } + } + if (!\is_null($request->getBody())) { + $this->ffi->pactffi_with_body($this->interaction, $this->ffi->InteractionPart_Request, null, $request->getBody()); + } return $this; } /** - * 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 + * @return InteractionBuilder */ - public function willRespondWith(ProviderResponse $response): bool + public function willRespondWith(ProviderResponse $response): self { - $this->interaction->setResponse($response); + $this->ffi->pactffi_response_status($this->interaction, $response->getStatus()); + foreach ($response->getHeaders() as $header => $values) { + foreach ($values as $index => $value) { + $this->ffi->pactffi_with_header($this->interaction, $this->ffi->InteractionPart_Response, $header, $index, $value); + } + } + if (!\is_null($response->getBody())) { + $this->ffi->pactffi_with_body($this->interaction, $this->ffi->InteractionPart_Response, null, $response->getBody()); + } - return $this->mockServerHttpService->registerInteraction($this->interaction); + return $this; } - /** - * {@inheritdoc} - */ - public function verify(): bool + public function createMockServer(): void { - return $this->mockServerHttpService->verifyInteractions(); + $this->port = $this->ffi->pactffi_create_mock_server_for_pact($this->pact, '127.0.0.1:0', false); } /** - * Writes the file to disk and deletes interactions from mock server. + * {@inheritdoc} + * + * @throws MockServerNotStartedException */ - public function finalize(): bool + public function verify(): bool { - // Write the pact file to disk. - $this->mockServerHttpService->getPactJson(); + if ($this->port === null) { + throw new MockServerNotStartedException('Mock server is not started.'); + } + + $result = $this->ffi->pactffi_mock_server_matched($this->port); - // Delete the interactions. - $this->mockServerHttpService->deleteAllInteractions(); + if ($result) { + $this->ffi->pactffi_write_pact_file($this->port, $this->config->getPactDir(), true); + } - return true; + $this->ffi->pactffi_cleanup_mock_server($this->port); + $this->port = null; + + return $result; } /** - * {@inheritdoc} + * @throws MockServerNotStartedException + * + * @return string */ - public function writePact(): bool + public function getBaseUri(): string { - // Write the pact file to disk. - $this->mockServerHttpService->getPactJson(); + if ($this->port === null) { + throw new MockServerNotStartedException('Mock server is not started.'); + } - return true; + return \sprintf('http://localhost:%d', $this->port); } } diff --git a/src/PhpPact/Consumer/Listener/PactTestListener.php b/src/PhpPact/Consumer/Listener/PactTestListener.php index 5d88ef18..94d77e80 100644 --- a/src/PhpPact/Consumer/Listener/PactTestListener.php +++ b/src/PhpPact/Consumer/Listener/PactTestListener.php @@ -2,13 +2,11 @@ namespace PhpPact\Consumer\Listener; +use Exception; use GuzzleHttp\Psr7\Uri; -use PhpPact\Broker\Service\BrokerHttpClient; -use PhpPact\Http\GuzzleClient; -use PhpPact\Standalone\Exception\MissingEnvVariableException; -use PhpPact\Standalone\MockService\MockServer; +use PhpPact\Standalone\Broker\Broker; +use PhpPact\Standalone\Broker\BrokerConfig; use PhpPact\Standalone\MockService\MockServerEnvConfig; -use PhpPact\Standalone\MockService\Service\MockServerHttpService; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestListener; @@ -23,46 +21,25 @@ class PactTestListener implements TestListener { use TestListenerDefaultImplementation; - /** @var MockServer */ - private $server; - /** * Name of the test suite configured in your phpunit config. * * @var string[] */ - private $testSuiteNames; - - /** @var MockServerEnvConfig */ - private $mockServerConfig; + private array $testSuiteNames; - /** @var bool */ - private $failed; + private MockServerEnvConfig $builderConfig; + private bool $failed = false; /** * PactTestListener constructor. * * @param string[] $testSuiteNames test suite names that need evaluated with the listener - * - * @throws MissingEnvVariableException */ public function __construct(array $testSuiteNames) { - $this->testSuiteNames = $testSuiteNames; - $this->mockServerConfig = new MockServerEnvConfig(); - } - - /** - * @param TestSuite $suite - * - * @throws \Exception - */ - public function startTestSuite(TestSuite $suite): void - { - if (\in_array($suite->getName(), $this->testSuiteNames)) { - $this->server = new MockServer($this->mockServerConfig); - $this->server->start(); - } + $this->testSuiteNames = $testSuiteNames; + $this->builderConfig = new MockServerEnvConfig(); } public function addError(Test $test, \Throwable $t, float $time): void @@ -79,19 +56,12 @@ public function addFailure(Test $test, AssertionFailedError $e, float $time): vo * Publish JSON results to PACT Broker and stop the Mock Server. * * @param TestSuite $suite + * + * @throws Exception */ public function endTestSuite(TestSuite $suite): void { if (\in_array($suite->getName(), $this->testSuiteNames)) { - try { - $httpService = new MockServerHttpService(new GuzzleClient(), $this->mockServerConfig); - $httpService->verifyInteractions(); - - $json = $httpService->getPactJson(); - } finally { - $this->server->stop(); - } - if ($this->failed === true) { print 'A unit test has failed. Skipping PACT file upload.'; } elseif (!($pactBrokerUri = \getenv('PACT_BROKER_URI'))) { @@ -101,29 +71,26 @@ 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->builderConfig->getConsumer()); + $brokerConfig->setPactLocations($this->builderConfig->getPactDir()); + $brokerConfig->setBrokerUri(new Uri($pactBrokerUri)); + $brokerConfig->setConsumerVersion($consumerVersion); + $brokerConfig->setTag($tag); if (($user = \getenv('PACT_BROKER_HTTP_AUTH_USER')) && ($pass = \getenv('PACT_BROKER_HTTP_AUTH_PASS')) ) { - $clientConfig = [ - 'auth' => [$user, $pass], - ]; - } - - if (($sslVerify = \getenv('PACT_BROKER_SSL_VERIFY'))) { - $clientConfig['verify'] = $sslVerify !== 'no'; + $brokerConfig->setBrokerUsername($user); + $brokerConfig->setBrokerPassword($pass); } - $headers = []; if ($bearerToken = \getenv('PACT_BROKER_BEARER_TOKEN')) { - $headers['Authorization'] = 'Bearer ' . $bearerToken; + $brokerConfig->setBrokerToken($bearerToken); } - $client = new GuzzleClient($clientConfig); - - $brokerHttpService = new BrokerHttpClient($client, new Uri($pactBrokerUri), $headers); - $brokerHttpService->tag($this->mockServerConfig->getConsumer(), $consumerVersion, $tag); - $brokerHttpService->publishJson($consumerVersion, $json); + $broker = new Broker($brokerConfig); + $broker->createVersionTag(); + $broker->publish(); print 'Pact file has been uploaded to the Broker successfully.'; } } diff --git a/src/PhpPact/Consumer/MessageBuilder.php b/src/PhpPact/Consumer/MessageBuilder.php index dc195f3f..bdb9cd4b 100644 --- a/src/PhpPact/Consumer/MessageBuilder.php +++ b/src/PhpPact/Consumer/MessageBuilder.php @@ -2,36 +2,30 @@ namespace PhpPact\Consumer; -use PhpPact\Consumer\Model\Message; -use PhpPact\Standalone\PactConfigInterface; -use PhpPact\Standalone\PactMessage\PactMessage; +use Exception; +use PhpPact\Ffi\Helper; +use PhpPact\Standalone\MockService\MockServerConfigInterface; /** - * Build a message and send it to the Ruby Standalone Mock Service + * Build a message and send it to the Pact Rust FFI * Class MessageBuilder. */ -class MessageBuilder implements BuilderInterface +class MessageBuilder extends AbstractBuilder { - /** @var PactMessage */ - protected $pactMessage; - - /** @var PactConfigInterface */ - protected $config; - - /** @var array callable */ - protected $callback; - - /** @var Message */ - private $message; + protected array $callback; + protected int $messagePact; + protected int $message; /** - * @param PactConfigInterface $config + * MessageBuilder constructor. + * + * {@inheritdoc} */ - public function __construct(PactConfigInterface $config) + public function __construct(MockServerConfigInterface $config) { - $this->config = $config; - $this->message = new Message(); - $this->pactMessage = new PactMessage(); + parent::__construct($config); + $this->messagePact = $this->ffi->pactffi_new_message_pact($config->getConsumer(), $config->getProvider()); + $this->message = $this->ffi->pactffi_new_message($this->messagePact, ''); } /** @@ -54,15 +48,20 @@ public function setCallback(callable $callback, $description = false): self } /** - * @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 + * @param string $description what is given to the request + * @param array $params for that request * * @return MessageBuilder */ - public function given(string $name, array $params = [], $overwrite = false): self + public function given(string $description, array $params = []): self { - $this->message->setProviderState($name, $params, $overwrite); + if (\count($params) > 0) { + foreach ($params as $name => $value) { + $this->ffi->pactffi_message_given_with_param($this->message, $description, (string) $name, $value); + } + } else { + $this->ffi->pactffi_message_given($this->message, $description); + } return $this; } @@ -74,19 +73,21 @@ public function given(string $name, array $params = [], $overwrite = false): sel */ public function expectsToReceive(string $description): self { - $this->message->setDescription($description); + $this->ffi->pactffi_message_expects_to_receive($this->message, $description); return $this; } /** - * @param mixed $metadata what is the additional metadata of the message + * @param array $metadata what is the additional metadata of the message * * @return MessageBuilder */ - public function withMetadata($metadata): self + public function withMetadata(array $metadata): self { - $this->message->setMetadata($metadata); + foreach ($metadata as $key => $value) { + $this->ffi->pactffi_message_with_metadata($this->message, $key, $value); + } return $this; } @@ -100,7 +101,15 @@ public function withMetadata($metadata): self */ public function withContent($contents): self { - $this->message->setContents($contents); + if (\is_string($contents)) { + $contentType = 'text/plain'; + } else { + $contents = \json_encode($contents); + $contentType = 'application/json'; + } + + $contents = Helper::getString($contents); + $this->ffi->pactffi_message_with_contents($this->message, $contentType, $contents->getValue(), $contents->getSize()); return $this; } @@ -108,14 +117,11 @@ public function withContent($contents): self /** * Run reify to create an example pact from the message (i.e. create messages from matchers) * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - * * @return string */ public function reify(): string { - return $this->pactMessage->reify($this->message); + return $this->ffi->pactffi_message_reify($this->message); } /** @@ -124,7 +130,7 @@ public function reify(): string * @param callable $callback * @param false|string $description description of the pact and thus callback * - * @throws \Exception + * @throws Exception * * @return bool */ @@ -132,53 +138,35 @@ public function verifyMessage(callable $callback, $description = false): bool { $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 false|string $description description of the pact and thus callback - * - * @throws \Exception if callback is not set + * @throws Exception if callback is not set * * @return bool */ - public function verify($description = false): bool + public function verify(): bool { if (\count($this->callback) < 1) { throw new \Exception('Callbacks need to exist to run verify.'); } - $pactJson = $this->reify(); + $contents = $this->reify(); // call the function to actually run the logic try { foreach ($this->callback as $callback) { //@todo .. what do with the providerState - \call_user_func($callback, $pactJson); + \call_user_func($callback, $contents); } - return $this->writePact(); + return !$this->ffi->pactffi_write_message_pact_file($this->messagePact, $this->config->getPactDir(), true); } catch (\Exception $e) { return false; } } - - /** - * Write the Pact without deleting the interactions. - * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - * - * @return bool - */ - public function writePact(): bool - { - // you do not want to save the reified json - $pactJson = \json_encode($this->message); - - 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 8b0f86e2..cde4af1a 100644 --- a/src/PhpPact/Consumer/Model/ConsumerRequest.php +++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php @@ -6,32 +6,13 @@ * Request initiated by the consumer. * Class ConsumerRequest. */ -class ConsumerRequest implements \JsonSerializable +class ConsumerRequest { - /** - * @var string - */ - private $method; - - /** - * @var string - */ - private $path; - - /** - * @var string[] - */ - private $headers; - - /** - * @var mixed - */ - private $body; - - /** - * @var string - */ - private $query; + private string $method; + private string $path; + private ?string $body = null; + private array $headers = []; + private array $query = []; /** * @return string @@ -56,7 +37,7 @@ public function setMethod(string $method): self /** * @return string */ - public function getPath() + public function getPath(): string { return $this->path; } @@ -74,42 +55,52 @@ public function setPath(string $path): self } /** - * @return string[] + * @return array */ - public function getHeaders() + public function getHeaders(): array { return $this->headers; } /** - * @param string[] $headers + * @param array $headers * * @return ConsumerRequest */ public function setHeaders(array $headers): self { - $this->headers = $headers; + $this->headers = []; + foreach ($headers as $header => $values) { + $this->addHeader($header, $values); + } return $this; } /** * @param string $header - * @param array|string $value + * @param array|string $values * * @return ConsumerRequest */ - public function addHeader(string $header, $value): self + public function addHeader(string $header, $values): self { - $this->headers[$header] = $value; + $this->headers[$header] = []; + if (\is_array($values)) { + foreach ($values as $value) { + $this->addHeaderValue($header, $value); + } + } else { + $this->addHeaderValue($header, $values); + } return $this; } /** - * @return mixed + * @return null|string */ - public function getBody() + public function getBody(): ?string { return $this->body; } @@ -119,17 +110,24 @@ public function getBody() * * @return ConsumerRequest */ - public function setBody($body) + public function setBody($body): self { - $this->body = $body; + if (\is_string($body)) { + $this->body = $body; + } elseif (!\is_null($body)) { + $this->body = \json_encode($body); + $this->addHeader('Content-Type', 'application/json'); + } else { + $this->body = null; + } return $this; } /** - * @return null|string + * @return array */ - public function getQuery() + public function getQuery(): array { return $this->query; } @@ -141,7 +139,9 @@ public function getQuery() */ public function setQuery(string $query): self { - $this->query = $query; + foreach (\explode('&', $query) as $parameter) { + $this->addQueryParameter(...\explode('=', $parameter)); + } return $this; } @@ -154,41 +154,17 @@ public function setQuery(string $query): self */ public function addQueryParameter(string $key, string $value): self { - if ($this->query === null) { - $this->query = "{$key}={$value}"; - } else { - $this->query = "{$this->query}&{$key}={$value}"; - } + $this->query[$key][] = $value; return $this; } /** - * {@inheritdoc} + * @param string $header + * @param string $value */ - #[\ReturnTypeWillChange] - public function jsonSerialize() + private function addHeaderValue(string $header, string $value): void { - $results = []; - - $results['method'] = $this->getMethod(); - - if ($this->getHeaders() !== null) { - $results['headers'] = $this->getHeaders(); - } - - if ($this->getPath() !== null) { - $results['path'] = $this->getPath(); - } - - if ($this->getBody() !== null) { - $results['body'] = $this->getBody(); - } - - if ($this->getQuery() !== null) { - $results['query'] = $this->getQuery(); - } - - return $results; + $this->headers[$header][] = $value; } } diff --git a/src/PhpPact/Consumer/Model/Interaction.php b/src/PhpPact/Consumer/Model/Interaction.php deleted file mode 100644 index 06577c06..00000000 --- a/src/PhpPact/Consumer/Model/Interaction.php +++ /dev/null @@ -1,132 +0,0 @@ -description; - } - - /** - * @param string $description - * - * @return Interaction - */ - public function setDescription(string $description): self - { - $this->description = $description; - - return $this; - } - - /** - * @return null|string - */ - public function getProviderState(): ?string - { - return $this->providerState; - } - - /** - * @param string $providerState - * - * @return Interaction - */ - public function setProviderState(string $providerState): self - { - $this->providerState = $providerState; - - return $this; - } - - /** - * @return ConsumerRequest - */ - public function getRequest(): ConsumerRequest - { - return $this->request; - } - - /** - * @param ConsumerRequest $request - * - * @return Interaction - */ - public function setRequest(ConsumerRequest $request): self - { - $this->request = $request; - - return $this; - } - - /** - * @return ProviderResponse - */ - public function getResponse(): ProviderResponse - { - return $this->response; - } - - /** - * @param ProviderResponse $response - * - * @return Interaction - */ - public function setResponse(ProviderResponse $response): self - { - $this->response = $response; - - return $this; - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - 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 deleted file mode 100644 index dbb1679b..00000000 --- a/src/PhpPact/Consumer/Model/Message.php +++ /dev/null @@ -1,156 +0,0 @@ -description; - } - - /** - * @param string $description - * - * @return Message - */ - public function setDescription(string $description): self - { - $this->description = $description; - - return $this; - } - - /** - * @return array - */ - public function getProviderStates(): array - { - return $this->providerStates; - } - - /** - * @param mixed $overwrite - * - * @return array - */ - public function setProviderState(string $name, array $params = [], $overwrite = true): array - { - $this->addProviderState($name, $params, $overwrite); - - return $this->providerStates; - } - - /** - * @param string $name - * @param array $params - * @param bool $overwrite - if true reset the entire state - * - * @return Message - */ - public function addProviderState(string $name, array $params, $overwrite = false): self - { - $providerState = new \stdClass(); - $providerState->name = $name; - $providerState->params = $params; - - if ($overwrite === true) { - $this->providerStates = []; - } - - $this->providerStates[] = $providerState; - - return $this; - } - - /** - * @return array - */ - public function getMetadata(): array - { - return $this->metadata; - } - - /** - * @param array $metadata - * - * @return Message - */ - public function setMetadata(array $metadata): self - { - $this->metadata = $metadata; - - return $this; - } - - /** - * @return mixed - */ - public function getContents() - { - return $this->contents; - } - - /** - * @param mixed $contents - * - * @return Message - */ - public function setContents($contents) - { - $this->contents = $contents; - - return $this; - } - - /** - * {@inheritdoc} - */ - #[\ReturnTypeWillChange] - public function jsonSerialize() - { - $out = []; - $out['description'] = $this->getDescription(); - - if (\count($this->providerStates) > 0) { - $out['providerStates'] = $this->getProviderStates(); - } - - if ($this->metadata) { - $out['metadata'] = $this->getMetadata(); - } - - if ($this->contents) { - $out['contents'] = $this->getContents(); - } - - return $out; - } -} diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php index d138f555..dfb97754 100644 --- a/src/PhpPact/Consumer/Model/ProviderResponse.php +++ b/src/PhpPact/Consumer/Model/ProviderResponse.php @@ -6,22 +6,11 @@ * Response expectation that would be in response to a Consumer request from the Provider. * Class ProviderResponse. */ -class ProviderResponse implements \JsonSerializable +class ProviderResponse { - /** - * @var int - */ - private $status; - - /** - * @var null|string[] - */ - private $headers; - - /** - * @var null|array - */ - private $body; + private int $status; + private ?string $body = null; + private array $headers = []; /** * @return int @@ -44,76 +33,81 @@ public function setStatus(int $status): self } /** - * @return null|string[] + * @return array */ - public function getHeaders() + public function getHeaders(): array { return $this->headers; } /** - * @param string[] $headers + * @param array $headers * * @return ProviderResponse */ public function setHeaders(array $headers): self { - $this->headers = $headers; + $this->headers = []; + foreach ($headers as $header => $values) { + $this->addHeader($header, $values); + } return $this; } /** * @param string $header - * @param array|string $value + * @param array|string $values * * @return ProviderResponse */ - public function addHeader(string $header, $value): self + public function addHeader(string $header, $values): self { - $this->headers[$header] = $value; + $this->headers[$header] = []; + if (\is_array($values)) { + foreach ($values as $value) { + $this->addHeaderValue($header, $value); + } + } else { + $this->addHeaderValue($header, $values); + } return $this; } /** - * @return mixed + * @return null|string */ - public function getBody() + public function getBody(): ?string { return $this->body; } /** - * @param iterable $body + * @param mixed $body * * @return ProviderResponse */ public function setBody($body): self { - $this->body = $body; + if (\is_string($body)) { + $this->body = $body; + } elseif (!\is_null($body)) { + $this->body = \json_encode($body); + $this->addHeader('Content-Type', 'application/json'); + } else { + $this->body = null; + } return $this; } /** - * {@inheritdoc} + * @param string $header + * @param string $value */ - #[\ReturnTypeWillChange] - public function jsonSerialize() + private function addHeaderValue(string $header, string $value): void { - $results = [ - 'status' => $this->getStatus(), - ]; - - if ($this->getHeaders() !== null) { - $results['headers'] = $this->getHeaders(); - } - - if ($this->getBody() !== null) { - $results['body'] = $this->getBody(); - } - - return $results; + $this->headers[$header][] = $value; } } diff --git a/src/PhpPact/Ffi/Data.php b/src/PhpPact/Ffi/Data.php new file mode 100644 index 00000000..749a6459 --- /dev/null +++ b/src/PhpPact/Ffi/Data.php @@ -0,0 +1,27 @@ +value = $value; + $this->size = $size; + } + + public function getValue(): ?CData + { + return $this->value; + } + + public function getSize(): ?int + { + return $this->size; + } +} diff --git a/src/PhpPact/Ffi/Helper.php b/src/PhpPact/Ffi/Helper.php new file mode 100644 index 00000000..5fef2aca --- /dev/null +++ b/src/PhpPact/Ffi/Helper.php @@ -0,0 +1,50 @@ + $item) { + $length = \strlen($item); + $itemSize = $length + 1; + $cDataItem = FFI::new("char[{$itemSize}]", false); + FFI::memcpy($cDataItem, $item, $length); + $cDataItems[$index] = $cDataItem; + } + + return new Data($cDataItems, $itemsSize); + } +} diff --git a/src/PhpPact/Provider/MessageVerifier.php b/src/PhpPact/Provider/MessageVerifier.php deleted file mode 100644 index 751d83e6..00000000 --- a/src/PhpPact/Provider/MessageVerifier.php +++ /dev/null @@ -1,245 +0,0 @@ -callbacks = []; - - $baseUrl = @$this->config->getProviderBaseUrl(); - if (!$baseUrl) { - $config->setProviderBaseUrl(new Uri("http://{$this->defaultProxyHost}:{$this->defaultProxyPort}")); - } - - // default verification delay - $this->setVerificationDelaySec(\floor($config->getProcessIdleTimeout() / $this->defaultDelayFactor)); - } - - /** - * @param array $callbacks - * - * @return self - */ - public function setCallbacks(array $callbacks): self - { - $this->callbacks = $callbacks; - - return $this; - } - - /** - * Add an individual call back - * - * @param string $key - * @param callable $callback - * - * @throws \Exception - * - * @return MessageVerifier - */ - public function addCallback(string $key, callable $callback): self - { - if (!isset($this->callbacks[$key])) { - $this->callbacks[$key] = $callback; - } else { - throw new \Exception("Callback with key ($key) already exists"); - } - - return $this; - } - - /** - * @param float $verificationDelaySec - * - * @return MessageVerifier - */ - public function setVerificationDelaySec(float $verificationDelaySec): self - { - $this->verificationDelaySec = $verificationDelaySec; - - return $this; - } - - /** - * @param Logger $logger - * - * @return MessageVerifier - */ - public function setLogger(Logger $logger): self - { - $this->logger = $logger; - - return $this; - } - - /** - * @param array $arguments - * - * @throws \Exception - */ - protected function verifyAction(array $arguments) - { - if (\count($this->callbacks) < 1) { - throw new \Exception('Callback needs to bet set when using message pacts'); - } - - $callbacks = $this->callbacks; - $uri = $this->config->getProviderBaseUrl(); - - $scripts = $this->installManager->install(); - $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); - } - - /** - * @return Logger - */ - private function getLogger() - { - 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/Broker.php b/src/PhpPact/Standalone/Broker/Broker.php index a8b8ba53..576df6a4 100644 --- a/src/PhpPact/Standalone/Broker/Broker.php +++ b/src/PhpPact/Standalone/Broker/Broker.php @@ -6,18 +6,28 @@ use Amp\Log\ConsoleFormatter; use Amp\Log\StreamHandler; use Monolog\Logger; +use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; +use PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException; use PhpPact\Standalone\Installer\InstallManager; use PhpPact\Standalone\Runner\ProcessRunner; +/** + * Class Broker. + */ class Broker { - /** @var Logger */ - private $logger; - /** @var BrokerConfig */ - private $config; - /** @var string */ - private $command; + private Logger $logger; + private BrokerConfig $config; + private string $command; + /** + * Broker constructor. + * + * @param BrokerConfig $config + * + * @throws FileDownloadFailureException + * @throws NoDownloaderFoundException + */ public function __construct(BrokerConfig $config) { $this->config = $config; diff --git a/src/PhpPact/Standalone/Broker/BrokerConfig.php b/src/PhpPact/Standalone/Broker/BrokerConfig.php index e54f668e..336b5604 100644 --- a/src/PhpPact/Standalone/Broker/BrokerConfig.php +++ b/src/PhpPact/Standalone/Broker/BrokerConfig.php @@ -4,58 +4,33 @@ use Psr\Http\Message\UriInterface; +/** + * Class BrokerConfig. + */ class BrokerConfig { - /** @var null|UriInterface */ - private $brokerUri; - - /** @var null|string */ - private $brokerToken; - - /** @var null|string */ - private $brokerUsername; - - /** @var null|string */ - private $brokerPassword; - - /** @var bool */ - private $verbose = false; - - /** @var null|string */ - private $pacticipant; - - /** @var null|string */ - private $request; - /** @var null|string */ - private $header; - /** @var null|string */ - private $data; - /** @var null|string */ - private $user; - /** @var null|string */ - private $consumer; - /** @var null|string */ - private $provider; - /** @var null|string */ - private $description; - /** @var null|string */ - private $uuid; - /** @var null|string */ - private $version; - /** @var null|string */ - private $branch = null; - /** @var null|string */ - private $tag = null; - /** @var null|string */ - private $name; - /** @var null|string */ - private $repositoryUrl; - /** @var null|string */ - private $url; - /** @var null|string */ - private $consumerVersion; - /** @var null|string */ - private $pactLocations; + private ?UriInterface $brokerUri = null; + private ?string $brokerToken = null; + private ?string $brokerUsername = null; + private ?string $brokerPassword = null; + private bool $verbose = false; + private ?string $pacticipant = null; + private ?string $request = null; + private ?string $header = null; + private ?string $data = null; + private ?string $user = null; + private ?string $consumer = null; + private ?string $provider = null; + private ?string $description = null; + private ?string $uuid = null; + private ?string $version = null; + private ?string $branch = null; + private ?string $tag = null; + private ?string $name = null; + private ?string $repositoryUrl = null; + private ?string $url = null; + private ?string $consumerVersion = null; + private ?string $pactLocations = null; /** * @return null|string @@ -337,7 +312,10 @@ public function setUuid(?string $uuid): self return $this; } - public function isVerbose() + /** + * @return bool + */ + public function isVerbose(): bool { return $this->verbose; } @@ -352,6 +330,8 @@ public function getBrokerUri(): ?UriInterface /** * @param null|UriInterface $brokerUri + * + * @return BrokerConfig */ public function setBrokerUri(?UriInterface $brokerUri): self { @@ -370,6 +350,8 @@ public function getBrokerToken(): ?string /** * @param null|string $brokerToken + * + * @return BrokerConfig */ public function setBrokerToken(?string $brokerToken): self { @@ -388,6 +370,8 @@ public function getBrokerUsername(): ?string /** * @param null|string $brokerUsername + * + * @return BrokerConfig */ public function setBrokerUsername(?string $brokerUsername): self { @@ -406,6 +390,8 @@ public function getBrokerPassword(): ?string /** * @param null|string $brokerPassword + * + * @return BrokerConfig */ public function setBrokerPassword(?string $brokerPassword): self { @@ -414,13 +400,18 @@ public function setBrokerPassword(?string $brokerPassword): self return $this; } - public function getPacticipant() + /** + * @return null|string + */ + public function getPacticipant(): ?string { return $this->pacticipant; } /** * @param null|string $pacticipant + * + * @return BrokerConfig */ public function setPacticipant(?string $pacticipant): self { diff --git a/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php b/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php deleted file mode 100644 index 665ad684..00000000 --- a/src/PhpPact/Standalone/Exception/HealthCheckFailedException.php +++ /dev/null @@ -1,17 +0,0 @@ -registerInstaller(new InstallerWindows()) ->registerInstaller(new InstallerMac()) - ->registerInstaller(new InstallerLinux()) - ->registerInstaller(new InstallerPosixPreinstalled()); + ->registerInstaller(new InstallerLinux()); } /** @@ -64,7 +62,7 @@ public function setInstallers(array $installers): self } /** - * @throws Exception\FileDownloadFailureException + * @throws FileDownloadFailureException * @throws NoDownloaderFoundException * * @return Scripts @@ -79,70 +77,10 @@ public function install(): Scripts /** * Uninstall. */ - public static function uninstall() - { - $pactInstallPath = self::$destinationDir . DIRECTORY_SEPARATOR . 'pact'; - if (\file_exists($pactInstallPath)) { - self::rmdir($pactInstallPath); - } - } - - /** - * Modified copy of Symphony filesystem remove - * - * - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is furnished - to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - * @param mixed $files - * - * @throws \Exception - */ - public static function rmdir($files) + public static function uninstall(): void { - if ($files instanceof \Traversable) { - $files = \iterator_to_array($files, false); - } elseif (!\is_array($files)) { - $files = [$files]; - } - $files = \array_reverse($files); - foreach ($files as $file) { - if (\is_link($file)) { - // See https://bugs.php.net/52176 - \unlink($file); - - if ('\\' !== \DIRECTORY_SEPARATOR || \file_exists($file)) { - throw new \Exception(\sprintf('Failed to remove symlink "%s"', $file)); - } - } elseif (\is_dir($file)) { - self::rmdir(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS)); - \rmdir($file); - - if (\file_exists($file)) { - throw new \Exception(\sprintf('Failed to remove directory "%s"', $file)); - } - } else { - \unlink($file); - - if (\file_exists($file)) { - throw new \Exception(\sprintf('Failed to remove file "%s"', $file)); - } - } + if (\file_exists(self::$destinationDir)) { + (new Filesystem())->remove(self::$destinationDir); } } diff --git a/src/PhpPact/Standalone/Installer/Model/Scripts.php b/src/PhpPact/Standalone/Installer/Model/Scripts.php index aae076b3..8724dbc2 100644 --- a/src/PhpPact/Standalone/Installer/Model/Scripts.php +++ b/src/PhpPact/Standalone/Installer/Model/Scripts.php @@ -3,87 +3,64 @@ namespace PhpPact\Standalone\Installer\Model; /** - * Represents locations of Ruby Standalone full path and scripts. - * Class BinaryScripts. + * Class Scripts. */ class Scripts { /** - * Path to PhpPact Mock Service. - * - * @var string + * Path to Pact FFI C Header Code. */ - private $mockService; + private string $code; /** - * Path to the PhpPact Stub Service. - * - * @var string + * Path to Pact FFI Dynamic Library. */ - private $stubService; + private string $library; /** - * Path to the PhpPact Pact Message. + * Path to the PhpPact Stub Service. * * @var string */ - private $pactMessage; + private string $stubService; /** - * Path to the PhpPact Provider Verifier. + * Path to the Ruby Standalone Broker. * * @var string */ - private $providerVerifier; - - /** - * @var string - */ - private $broker; + private string $broker; - public function __construct(string $mockService, string $stubService, string $providerVerifier, string $pactMessage, string $broker) + public function __construct(string $code, string $library, string $stubService, string $broker) { - $this->mockService = $mockService; - $this->stubService = $stubService; - $this->providerVerifier = $providerVerifier; - $this->pactMessage = $pactMessage; - $this->broker = $broker; + $this->code = $code; + $this->library = $library; + $this->stubService = $stubService; + $this->broker = $broker; } /** * @return string */ - public function getMockService(): string + public function getCode(): string { - return $this->mockService; + return $this->code; } /** * @return string */ - public function getStubService(): string + public function getLibrary(): string { - return $this->stubService; + return $this->library; } /** * @return string */ - public function getProviderVerifier(): string - { - return $this->providerVerifier; - } - - /** - * @param string $providerVerifier - * - * @return Scripts - */ - public function setProviderVerifier(string $providerVerifier): self + public function getStubService(): string { - $this->providerVerifier = $providerVerifier; - - return $this; + return $this->stubService; } /** @@ -93,12 +70,4 @@ public function getBroker(): string { return $this->broker; } - - /** - * @return string - */ - public function getPactMessage(): string - { - return $this->pactMessage; - } } diff --git a/src/PhpPact/Standalone/Installer/Service/AbstractInstaller.php b/src/PhpPact/Standalone/Installer/Service/AbstractInstaller.php new file mode 100644 index 00000000..ffe5c8ad --- /dev/null +++ b/src/PhpPact/Standalone/Installer/Service/AbstractInstaller.php @@ -0,0 +1,175 @@ + 'pact-reference', + 'filename' => 'pact.h', + 'version' => self::PACT_FFI_VERSION, + 'versionPrefix' => 'libpact_ffi-v', + 'extract' => false, + ], + ]; + + /** + * {@inheritdoc} + */ + public function install(string $destinationDir): Scripts + { + if (\file_exists(($destinationDir)) === false) { + \mkdir($destinationDir); + + foreach (static::FILES as $file) { + $uri = \sprintf( + 'https://github.com/pact-foundation/%s/releases/download/%s%s/%s', + $file['repo'], + $file['versionPrefix'], + $file['version'], + $file['filename'] + ); + $tempFilePath = $destinationDir . DIRECTORY_SEPARATOR . $file['filename']; + + $this->download($uri, $tempFilePath); + if ($file['extract']) { + $this + ->extract($tempFilePath, $destinationDir, $file['extractTo'] ?? null, $file['executable'] ?? false) + ->deleteCompressed($tempFilePath); + } + } + } + + return $this->getScripts($destinationDir); + } + + /** + * @param string $destinationDir + * + * @return Scripts + */ + abstract protected function getScripts(string $destinationDir): Scripts; + + /** + * Download the binaries. + * + * @param string $uri uri of the file to be downloaded + * @param string $tempFilePath location to download the file + * + * @throws FileDownloadFailureException + * + * @return AbstractInstaller + */ + private function download(string $uri, string $tempFilePath): self + { + $data = \file_get_contents($uri); + + if ($data === false) { + throw new FileDownloadFailureException('Failed to download binary from Github for Ruby Standalone!'); + } + + $result = \file_put_contents($tempFilePath, $data); + + if ($result === false) { + throw new FileDownloadFailureException('Failed to save binaries for Ruby Standalone!'); + } + + return $this; + } + + /** + * Uncompress the temp file and install the binaries in the destination directory. + * + * @param string $sourceFile + * @param string $destinationDir + * @param null|string $fileName Required for gz file + * @param bool $executable Required for gz file + * + * @return AbstractInstaller + */ + private function extract(string $sourceFile, string $destinationDir, ?string $fileName, bool $executable): self + { + if (\substr($sourceFile, -7) === '.tar.gz') { + $this->extractTarGz($sourceFile, $destinationDir); + } elseif (\substr($sourceFile, -4) === '.zip') { + $this->extractZip($sourceFile, $destinationDir); + } else { + $this->extractGz($sourceFile, $fileName, $executable); + } + + return $this; + } + + /** + * @param string $sourceFile + * @param string $destinationDir + */ + private function extractTarGz(string $sourceFile, string $destinationDir): void + { + $p = new \PharData($sourceFile); + $p->extractTo($destinationDir); + } + + /** + * @param string $sourceFile + * @param string $destinationDir + */ + private function extractZip(string $sourceFile, string $destinationDir): void + { + $zip = new ZipArchive(); + + if ($zip->open($sourceFile)) { + $zip->extractTo($destinationDir); + $zip->close(); + } + } + + /** + * https://gist.github.com/bastiankoetsier/99827fa4754207d860ac + * + * @param string $sourceFile + * @param string $fileName + * @param bool $executable + */ + private function extractGz(string $sourceFile, string $fileName, bool $executable): void + { + $bufferSize = 4096; + $destFile = \pathinfo($sourceFile, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR . $fileName; + $file = \gzopen($sourceFile, 'rb'); + $outFile = \fopen($destFile, 'wb'); + while (!\gzeof($file)) { + \fwrite($outFile, \gzread($file, $bufferSize)); + } + \fclose($outFile); + \gzclose($file); + if ($executable) { + \chmod($destFile, 0744); + } + } + + /** + * Delete the temp file. + * + * @param string $filePath + * + * @return AbstractInstaller + */ + private function deleteCompressed(string $filePath): self + { + \unlink($filePath); + + return $this; + } +} diff --git a/src/PhpPact/Standalone/Installer/Service/InstallerLinux.php b/src/PhpPact/Standalone/Installer/Service/InstallerLinux.php index 4880278b..823891e9 100644 --- a/src/PhpPact/Standalone/Installer/Service/InstallerLinux.php +++ b/src/PhpPact/Standalone/Installer/Service/InstallerLinux.php @@ -2,12 +2,40 @@ namespace PhpPact\Standalone\Installer\Service; -use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; use PhpPact\Standalone\Installer\Model\Scripts; -class InstallerLinux implements InstallerInterface +/** + * Class InstallerLinux. + */ +class InstallerLinux extends AbstractInstaller { - public const VERSION = '1.88.83'; + public const FILES = [ + [ + 'repo' => 'pact-ruby-standalone', + 'filename' => 'pact-' . self::PACT_RUBY_STANDALONE_VERSION . '-linux-x86_64.tar.gz', + 'version' => self::PACT_RUBY_STANDALONE_VERSION, + 'versionPrefix' => 'v', + 'extract' => true, + ], + [ + 'repo' => 'pact-stub-server', + 'filename' => 'pact-stub-server-linux-x86_64.gz', + 'version' => self::PACT_STUB_SERVER_VERSION, + 'versionPrefix' => 'v', + 'extract' => true, + 'extractTo' => 'pact-stub-server', + 'executable' => true, + ], + [ + 'repo' => 'pact-reference', + 'filename' => 'libpact_ffi-linux-x86_64.so.gz', + 'version' => self::PACT_FFI_VERSION, + 'versionPrefix' => 'libpact_ffi-v', + 'extract' => true, + 'extractTo' => 'libpact_ffi.so', + ], + ...parent::FILES, + ]; /** * {@inheritdoc} @@ -20,85 +48,16 @@ public function isEligible(): bool /** * {@inheritdoc} */ - public function install(string $destinationDir): Scripts + protected function getScripts(string $destinationDir): Scripts { - if (\file_exists($destinationDir . DIRECTORY_SEPARATOR . 'pact') === false) { - $fileName = 'pact-' . self::VERSION . '-linux-x86_64.tar.gz'; - $tempFilePath = \sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; - - $this - ->download($fileName, $tempFilePath) - ->extract($tempFilePath, $destinationDir) - ->deleteCompressed($tempFilePath); - } - - $scripts = new Scripts( - "{$destinationDir}/pact/bin/pact-mock-service", - "{$destinationDir}/pact/bin/pact-stub-service", - "{$destinationDir}/pact/bin/pact-provider-verifier", - "{$destinationDir}/pact/bin/pact-message", - "{$destinationDir}/pact/bin/pact-broker" + $destinationDir = $destinationDir . DIRECTORY_SEPARATOR; + $binDir = $destinationDir . 'pact' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR; + + return new Scripts( + $destinationDir . 'pact.h', + $destinationDir . 'libpact_ffi.so', + $destinationDir . 'pact-stub-server', + $binDir . 'pact-broker' ); - - return $scripts; - } - - /** - * Download the binaries. - * - * @param string $fileName name of the file to be downloaded - * @param string $tempFilePath location to download the file - * - * @throws FileDownloadFailureException - * - * @return InstallerLinux - */ - private function download(string $fileName, string $tempFilePath): self - { - $uri = 'https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v' . self::VERSION . "/{$fileName}"; - - $data = \file_get_contents($uri); - - if ($data === false) { - throw new FileDownloadFailureException('Failed to download binary from Github for Ruby Standalone!'); - } - - $result = \file_put_contents($tempFilePath, $data); - - if ($result === false) { - throw new FileDownloadFailureException('Failed to save binaries for Ruby Standalone!'); - } - - return $this; - } - - /** - * Uncompress the temp file and install the binaries in the destination directory. - * - * @param string $sourceFile - * @param string $destinationDir - * - * @return InstallerLinux - */ - private function extract(string $sourceFile, string $destinationDir): self - { - $p = new \PharData($sourceFile); - $p->extractTo($destinationDir); - - return $this; - } - - /** - * Delete the temp file. - * - * @param string $filePath - * - * @return InstallerLinux - */ - private function deleteCompressed(string $filePath): self - { - \unlink($filePath); - - return $this; } } diff --git a/src/PhpPact/Standalone/Installer/Service/InstallerMac.php b/src/PhpPact/Standalone/Installer/Service/InstallerMac.php index 5a953bf6..f67189f0 100644 --- a/src/PhpPact/Standalone/Installer/Service/InstallerMac.php +++ b/src/PhpPact/Standalone/Installer/Service/InstallerMac.php @@ -2,12 +2,40 @@ namespace PhpPact\Standalone\Installer\Service; -use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; use PhpPact\Standalone\Installer\Model\Scripts; -class InstallerMac implements InstallerInterface +/** + * Class InstallerMac. + */ +class InstallerMac extends AbstractInstaller { - public const VERSION = '1.88.83'; + public const FILES = [ + [ + 'repo' => 'pact-ruby-standalone', + 'filename' => 'pact-' . self::PACT_RUBY_STANDALONE_VERSION . '-osx.tar.gz', + 'version' => self::PACT_RUBY_STANDALONE_VERSION, + 'versionPrefix' => 'v', + 'extract' => true, + ], + [ + 'repo' => 'pact-stub-server', + 'filename' => 'pact-stub-server-osx-x86_64.gz', + 'version' => self::PACT_STUB_SERVER_VERSION, + 'versionPrefix' => 'v', + 'extract' => true, + 'extractTo' => 'pact-stub-server', + 'executable' => true, + ], + [ + 'repo' => 'pact-reference', + 'filename' => 'libpact_ffi-osx-x86_64.dylib.gz', + 'version' => self::PACT_FFI_VERSION, + 'versionPrefix' => 'libpact_ffi-v', + 'extract' => true, + 'extractTo' => 'libpact_ffi.dylib', + ], + ...parent::FILES, + ]; /** * {@inheritdoc} @@ -20,85 +48,16 @@ public function isEligible(): bool /** * {@inheritdoc} */ - public function install(string $destinationDir): Scripts + protected function getScripts(string $destinationDir): Scripts { - if (\file_exists($destinationDir . DIRECTORY_SEPARATOR . 'pact') === false) { - $fileName = 'pact-' . self::VERSION . '-osx.tar.gz'; - $tempFilePath = \sys_get_temp_dir() . DIRECTORY_SEPARATOR . $fileName; - - $this - ->download($fileName, $tempFilePath) - ->extract($tempFilePath, $destinationDir) - ->deleteCompressed($tempFilePath); - } - - $scripts = new Scripts( - "{$destinationDir}/pact/bin/pact-mock-service", - "{$destinationDir}/pact/bin/pact-stub-service", - "{$destinationDir}/pact/bin/pact-provider-verifier", - "{$destinationDir}/pact/bin/pact-message", - "{$destinationDir}/pact/bin/pact-broker" + $destinationDir = $destinationDir . DIRECTORY_SEPARATOR; + $binDir = $destinationDir . 'pact' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR; + + return new Scripts( + $destinationDir . 'pact.h', + $destinationDir . 'libpact_ffi.dylib', + $destinationDir . 'pact-stub-server', + $binDir . 'pact-broker' ); - - return $scripts; - } - - /** - * Download the binaries. - * - * @param string $fileName name of the file to be downloaded - * @param string $tempFilePath location to download the file - * - * @throws FileDownloadFailureException - * - * @return InstallerMac - */ - private function download(string $fileName, string $tempFilePath): self - { - $uri = 'https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v' . self::VERSION . "/{$fileName}"; - - $data = \file_get_contents($uri); - - if ($data === false) { - throw new FileDownloadFailureException('Failed to download binary from Github for Ruby Standalone!'); - } - - $result = \file_put_contents($tempFilePath, $data); - - if ($result === false) { - throw new FileDownloadFailureException('Failed to save binaries for Ruby Standalone!'); - } - - return $this; - } - - /** - * Uncompress the temp file and install the binaries in the destination directory. - * - * @param string $sourceFile - * @param string $destinationDir - * - * @return InstallerMac - */ - private function extract(string $sourceFile, string $destinationDir): self - { - $p = new \PharData($sourceFile); - $p->extractTo($destinationDir); - - return $this; - } - - /** - * Delete the temp file. - * - * @param string $filePath - * - * @return InstallerMac - */ - private function deleteCompressed(string $filePath): self - { - \unlink($filePath); - - return $this; } } diff --git a/src/PhpPact/Standalone/Installer/Service/InstallerPosixPreinstalled.php b/src/PhpPact/Standalone/Installer/Service/InstallerPosixPreinstalled.php deleted file mode 100644 index fc8b48c4..00000000 --- a/src/PhpPact/Standalone/Installer/Service/InstallerPosixPreinstalled.php +++ /dev/null @@ -1,37 +0,0 @@ -getBinaryPath('pact-provider-verifier')); - } - - /** - * {@inheritdoc} - */ - public function install(string $destinationDir): Scripts - { - $scripts = new Scripts( - 'pact-mock-service', - 'pact-stub-service', - 'pact-provider-verifier', - 'pact-message', - 'pact-broker' - ); - - return $scripts; - } - - private function getBinaryPath(string $binary): string - { - return trim((string) shell_exec('command -v ' . escapeshellarg($binary))); - } -} diff --git a/src/PhpPact/Standalone/Installer/Service/InstallerWindows.php b/src/PhpPact/Standalone/Installer/Service/InstallerWindows.php index 3f379f93..e16bb111 100644 --- a/src/PhpPact/Standalone/Installer/Service/InstallerWindows.php +++ b/src/PhpPact/Standalone/Installer/Service/InstallerWindows.php @@ -2,17 +2,40 @@ namespace PhpPact\Standalone\Installer\Service; -use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; use PhpPact\Standalone\Installer\Model\Scripts; -use ZipArchive; /** - * Download the Ruby Standalone binaries for Windows. - * Class BinaryDownloaderWindows. + * Class InstallerWindows. */ -class InstallerWindows implements InstallerInterface +class InstallerWindows extends AbstractInstaller { - public const VERSION = '1.88.83'; + public const FILES = [ + [ + 'repo' => 'pact-ruby-standalone', + 'filename' => 'pact-' . self::PACT_RUBY_STANDALONE_VERSION . '-win32.zip', + 'version' => self::PACT_RUBY_STANDALONE_VERSION, + 'versionPrefix' => 'v', + 'extract' => true, + ], + [ + 'repo' => 'pact-stub-server', + 'filename' => 'pact-stub-server-windows-x86_64.exe.gz', + 'version' => self::PACT_STUB_SERVER_VERSION, + 'versionPrefix' => 'v', + 'extract' => true, + 'extractTo' => 'pact-stub-server.exe', + 'executable' => true, + ], + [ + 'repo' => 'pact-reference', + 'filename' => 'pact_ffi-windows-x86_64.dll.gz', + 'version' => self::PACT_FFI_VERSION, + 'versionPrefix' => 'libpact_ffi-v', + 'extract' => true, + 'extractTo' => 'pact_ffi.dll', + ], + ...parent::FILES, + ]; /** * {@inheritdoc} @@ -25,90 +48,16 @@ public function isEligible(): bool /** * {@inheritdoc} */ - public function install(string $destinationDir): Scripts + protected function getScripts(string $destinationDir): Scripts { - if (\file_exists($destinationDir . DIRECTORY_SEPARATOR . 'pact') === false) { - $fileName = 'pact-' . self::VERSION . '-win32.zip'; - $tempFilePath = __DIR__ . DIRECTORY_SEPARATOR . $fileName; + $destinationDir = $destinationDir . DIRECTORY_SEPARATOR; + $binDir = $destinationDir . 'pact' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR; - $this - ->download($fileName, $tempFilePath) - ->extract($tempFilePath, $destinationDir) - ->deleteCompressed($tempFilePath); - } - - $binDir = $destinationDir . DIRECTORY_SEPARATOR . 'pact' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR; - $scripts = new Scripts( - $binDir . 'pact-mock-service.bat', - $binDir . 'pact-stub-service.bat', - $binDir . 'pact-provider-verifier.bat', - $binDir . 'pact-message.bat', + return new Scripts( + $destinationDir . 'pact.h', + $destinationDir . 'pact_ffi.dll', + $destinationDir . 'pact-stub-server.exe', $binDir . 'pact-broker.bat' ); - - return $scripts; - } - - /** - * Download the binaries. - * - * @param string $fileName name of the file to be downloaded - * @param string $tempFilePath location to download the file - * - * @throws FileDownloadFailureException - * - * @return InstallerWindows - */ - private function download(string $fileName, string $tempFilePath): self - { - $uri = 'https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v' . self::VERSION . "/{$fileName}"; - - $data = \file_get_contents($uri); - - if ($data === false) { - throw new FileDownloadFailureException('Failed to download binary from Github for Ruby Standalone!'); - } - - $result = \file_put_contents($tempFilePath, $data); - - if ($result === false) { - throw new FileDownloadFailureException('Failed to save binaries for Ruby Standalone!'); - } - - return $this; - } - - /** - * Uncompress the temp file and install the binaries in the destination directory. - * - * @param string $sourceFile - * @param string $destinationDir - * - * @return InstallerWindows - */ - private function extract(string $sourceFile, string $destinationDir): self - { - $zip = new ZipArchive(); - - if ($zip->open($sourceFile)) { - $zip->extractTo($destinationDir); - $zip->close(); - } - - return $this; - } - - /** - * Delete the temp file. - * - * @param string $filePath - * - * @return InstallerWindows - */ - private function deleteCompressed(string $filePath): self - { - \unlink($filePath); - - return $this; } } diff --git a/src/PhpPact/Standalone/MockService/MockServer.php b/src/PhpPact/Standalone/MockService/MockServer.php deleted file mode 100644 index 39e0e33d..00000000 --- a/src/PhpPact/Standalone/MockService/MockServer.php +++ /dev/null @@ -1,170 +0,0 @@ -config = $config; - $this->installManager = new InstallManager(); - - if (!$httpService) { - $this->httpService = new MockServerHttpService(new GuzzleClient(), $this->config); - } else { - $this->httpService = $httpService; - } - } - - /** - * Start the Mock Server. Verify that it is running. - * - * @throws Exception - * - * @return int process ID of the started Mock Server - */ - public function start(): int - { - $scripts = $this->installManager->install(); - - $this->processRunner = new ProcessRunner($scripts->getMockService(), $this->getArguments()); - - $processId = $this->processRunner->run(); - - $result = $this->verifyHealthCheck(); - if ($result) { - $retrySec = $this->config->getHealthCheckRetrySec(); - \sleep($retrySec); - } - - return $processId; - } - - /** - * Stop the Mock Server process. - * - * @return bool Was stopping successful? - */ - public function stop(): bool - { - return $this->processRunner->stop(); - } - - /** - * Wrapper to add a custom installer. - * - * @param InstallerInterface $installer - * - * @return self - */ - public function registerInstaller(InstallerInterface $installer): self - { - $this->installManager->registerInstaller($installer); - - return $this; - } - - /** - * Build an array of command arguments. - * - * @return array - */ - private function getArguments(): array - { - $results = []; - - $logLevel = $this->config->getLogLevel(); - $consumer = \escapeshellarg($this->config->getConsumer()); - $provider = \escapeshellarg($this->config->getProvider()); - $pactDir = \escapeshellarg($this->config->getPactDir()); - - $results[] = 'service'; - $results[] = "--consumer={$consumer}"; - $results[] = "--provider={$provider}"; - $results[] = "--pact-dir={$pactDir}"; - $results[] = "--pact-file-write-mode={$this->config->getPactFileWriteMode()}"; - $results[] = "--host={$this->config->getHost()}"; - $results[] = "--port={$this->config->getPort()}"; - - if ($logLevel) { - $results[] = \sprintf('--log-level=%s', \escapeshellarg($logLevel)); - } - - if ($this->config->hasCors()) { - $results[] = '--cors=true'; - } - - if ($this->config->getPactSpecificationVersion() !== null) { - $results[] = "--pact-specification-version={$this->config->getPactSpecificationVersion()}"; - } - - if (!empty($this->config->getLog())) { - $log = \escapeshellarg($this->config->getLog()); - $results[] = \sprintf('--log=%s', $log); - } - - return $results; - } - - /** - * Make sure the server starts as expected. - * - * @throws Exception - * - * @return bool - */ - private function verifyHealthCheck(): bool - { - $service = $this->httpService; - - // Verify that the service is up. - $tries = 0; - $maxTries = $this->config->getHealthCheckTimeout(); - $retrySec = $this->config->getHealthCheckRetrySec(); - do { - ++$tries; - - try { - $status = $service->healthCheck(); - - return $status; - } catch (ConnectException $e) { - \sleep($retrySec); - } - } while ($tries <= $maxTries); - - // @phpstan-ignore-next-line - throw new HealthCheckFailedException("Failed to make connection to Mock Server in {$maxTries} attempts."); - } -} diff --git a/src/PhpPact/Standalone/MockService/MockServerConfig.php b/src/PhpPact/Standalone/MockService/MockServerConfig.php index 3b5c7621..fd37e4ca 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfig.php @@ -2,161 +2,37 @@ namespace PhpPact\Standalone\MockService; -use Composer\Semver\VersionParser; -use GuzzleHttp\Psr7\Uri; -use PhpPact\Standalone\PactConfigInterface; -use Psr\Http\Message\UriInterface; - /** - * Configuration defining the default PhpPact Ruby Standalone server. - * Class MockServerConfig. + * Class ConsumerConfig. */ -class MockServerConfig implements MockServerConfigInterface, PactConfigInterface +class MockServerConfig implements MockServerConfigInterface { - /** - * Host on which to bind the service. - * - * @var string - */ - private $host = 'localhost'; - - /** - * Port on which to run the service. - * - * @var int - */ - private $port = 7200; - - /** - * @var bool - */ - private $secure = false; - /** * Consumer name. - * - * @var string */ - private $consumer; + private string $consumer; /** * Provider name. - * - * @var string */ - private $provider; + private string $provider; /** * Directory to which the pacts will be written. - * - * @var string - */ - private $pactDir; - - /** - * `overwrite` or `merge`. Use `merge` when running multiple mock service - * instances in parallel for the same consumer/provider pair. Ensure the - * pact file is deleted before running tests when using this option so that - * interactions deleted from the code are not maintained in the file. - * - * @var string - */ - private $pactFileWriteMode = 'overwrite'; - - /** - * The pact specification version to use when writing the pact. Note that only versions 1 and 2 are currently supported. - * - * @var string - */ - private $pactSpecificationVersion; - - /** - * File to which to log output. - * - * @var string - */ - private $log; - - /** @var bool */ - private $cors = false; - - /** - * The max allowed attempts the mock server has to be available in. Otherwise it is considered as sick. - * - * @var int - */ - private $healthCheckTimeout; - - /** - * The seconds between health checks of mock server - * - * @var int - */ - private $healthCheckRetrySec; - private $logLevel; - - /** - * {@inheritdoc} - */ - public function getHost(): string - { - return $this->host; - } - - /** - * {@inheritdoc} - */ - public function setHost(string $host): MockServerConfigInterface - { - $this->host = $host; - - return $this; - } - - /** - * {@inheritdoc} */ - public function getPort(): int - { - return $this->port; - } + private string $pactDir; /** - * {@inheritdoc} + * The pact specification version to use when writing the pact. */ - public function setPort(int $port): MockServerConfigInterface - { - $this->port = $port; - - return $this; - } + private string $pactSpecificationVersion; /** - * {@inheritdoc} + * BuilderConfig constructor. */ - public function isSecure(): bool + public function __construct() { - return $this->secure; - } - - /** - * {@inheritdoc} - */ - public function setSecure(bool $secure): MockServerConfigInterface - { - $this->secure = $secure; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getBaseUri(): UriInterface - { - $protocol = $this->secure ? 'https' : 'http'; - - return new Uri("{$protocol}://{$this->getHost()}:{$this->getPort()}"); + $this->pactDir = \sys_get_temp_dir(); } /** @@ -170,7 +46,7 @@ public function getConsumer(): string /** * {@inheritdoc} */ - public function setConsumer(string $consumer): PactConfigInterface + public function setConsumer(string $consumer): MockServerConfigInterface { $this->consumer = $consumer; @@ -188,7 +64,7 @@ public function getProvider(): string /** * {@inheritdoc} */ - public function setProvider(string $provider): PactConfigInterface + public function setProvider(string $provider): MockServerConfigInterface { $this->provider = $provider; @@ -198,24 +74,16 @@ public function setProvider(string $provider): PactConfigInterface /** * {@inheritdoc} */ - public function getPactDir() + public function getPactDir(): string { - if ($this->pactDir === null) { - return \sys_get_temp_dir(); - } - return $this->pactDir; } /** * {@inheritdoc} */ - public function setPactDir($pactDir): PactConfigInterface + public function setPactDir(string $pactDir): MockServerConfigInterface { - if ($pactDir === null) { - return $this; - } - if ('\\' !== \DIRECTORY_SEPARATOR) { $pactDir = \str_replace('\\', \DIRECTORY_SEPARATOR, $pactDir); } @@ -228,33 +96,7 @@ public function setPactDir($pactDir): PactConfigInterface /** * {@inheritdoc} */ - public function getPactFileWriteMode(): string - { - return $this->pactFileWriteMode; - } - - /** - * {@inheritdoc} - */ - public function setPactFileWriteMode(string $pactFileWriteMode): MockServerConfigInterface - { - $options = ['overwrite', 'merge']; - - if (!\in_array($pactFileWriteMode, $options)) { - $implodedOptions = \implode(', ', $options); - - throw new \InvalidArgumentException("Invalid PhpPact File Write Mode, value must be one of the following: {$implodedOptions}."); - } - - $this->pactFileWriteMode = $pactFileWriteMode; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactSpecificationVersion() + public function getPactSpecificationVersion(): string { return $this->pactSpecificationVersion; } @@ -262,110 +104,10 @@ public function getPactSpecificationVersion() /** * {@inheritdoc} */ - public function setPactSpecificationVersion($pactSpecificationVersion): PactConfigInterface + public function setPactSpecificationVersion(string $pactSpecificationVersion): MockServerConfigInterface { - /* - * Parse the version but do not assign it. If it is an invalid version, an exception is thrown - */ - $parser = new VersionParser(); - $parser->normalize($pactSpecificationVersion); - $this->pactSpecificationVersion = $pactSpecificationVersion; return $this; } - - /** - * {@inheritdoc} - */ - public function getLog() - { - return $this->log; - } - - /** - * {@inheritdoc} - */ - public function setLog(string $log): PactConfigInterface - { - $this->log = $log; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getLogLevel() - { - return $this->logLevel; - } - - /** - * {@inheritdoc} - */ - public function setLogLevel(string $logLevel): PactConfigInterface - { - $logLevel = \strtoupper($logLevel); - if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { - throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); - } - $this->logLevel = $logLevel; - - return $this; - } - - public function hasCors(): bool - { - return $this->cors; - } - - public function setCors($flag): MockServerConfigInterface - { - if ($flag === 'true') { - $this->cors = true; - } elseif ($flag === 'false') { - $this->cors = false; - } else { - $this->cors = (bool) $flag; - } - - return $this; - } - - /** - * {@inheritdoc} - */ - public function setHealthCheckTimeout($timeout): MockServerConfigInterface - { - $this->healthCheckTimeout = $timeout; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getHealthCheckTimeout(): int - { - return $this->healthCheckTimeout; - } - - /** - * {@inheritdoc} - */ - public function setHealthCheckRetrySec($seconds): MockServerConfigInterface - { - $this->healthCheckRetrySec = $seconds; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getHealthCheckRetrySec(): int - { - return $this->healthCheckRetrySec; - } } diff --git a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php index a1066ab3..e0c6f75e 100644 --- a/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php +++ b/src/PhpPact/Standalone/MockService/MockServerConfigInterface.php @@ -2,8 +2,6 @@ namespace PhpPact\Standalone\MockService; -use Psr\Http\Message\UriInterface; - /** * Mock Server configuration interface to allow for simple overrides that are reusable. * Interface MockServerConfigInterface. @@ -11,91 +9,50 @@ interface MockServerConfigInterface { /** - * @return string the host of the mock service - */ - public function getHost(): string; - - /** - * @param string $host The host of the mock service - * - * @return MockServerConfigInterface - */ - public function setHost(string $host): self; - - /** - * @return int the port of the mock service - */ - public function getPort(): int; - - /** - * @param int $port the port of the mock service - * - * @return MockServerConfigInterface - */ - public function setPort(int $port): self; - - /** - * @return bool true if https + * @return string */ - public function isSecure(): bool; + public function getConsumer(): string; /** - * @param bool $secure set to true for https + * @param string $consumer consumers name * * @return MockServerConfigInterface */ - public function setSecure(bool $secure): self; + public function setConsumer(string $consumer): self; /** - * @return UriInterface + * @return string */ - public function getBaseUri(): UriInterface; + public function getProvider(): string; /** - * @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 + * @param string $provider providers name * * @return MockServerConfigInterface */ - public function setPactFileWriteMode(string $pactFileWriteMode): self; - - /** - * @return bool - */ - public function hasCors(): bool; + public function setProvider(string $provider): self; /** - * @param bool|string $flag - * - * @return MockServerConfigInterface + * @return string url to place the pact files when written to disk */ - public function setCors($flag): self; + public function getPactDir(): string; /** - * @param int $timeout + * @param string $pactDir url to place the pact files when written to disk * * @return MockServerConfigInterface */ - public function setHealthCheckTimeout($timeout): self; + public function setPactDir(string $pactDir): self; /** - * @return int + * @return string pact version */ - public function getHealthCheckTimeout(): int; + public function getPactSpecificationVersion(): string; /** - * @param int $seconds + * @param string $pactSpecificationVersion pact specification version * * @return MockServerConfigInterface */ - public function setHealthCheckRetrySec($seconds): self; - - /** - * @return int - */ - public function getHealthCheckRetrySec(): int; + public function setPactSpecificationVersion(string $pactSpecificationVersion): self; } diff --git a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php index 0edc8251..f21e3a25 100644 --- a/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php +++ b/src/PhpPact/Standalone/MockService/MockServerEnvConfig.php @@ -10,7 +10,7 @@ */ class MockServerEnvConfig extends MockServerConfig { - public const DEFAULT_SPECIFICATION_VERSION = '2.0.0'; + public const DEFAULT_SPECIFICATION_VERSION = '3.0.0'; /** * MockServerEnvConfig constructor. @@ -19,32 +19,9 @@ class MockServerEnvConfig extends MockServerConfig */ public function __construct() { - $this->setHost($this->parseEnv('PACT_MOCK_SERVER_HOST')); - $this->setPort((int) $this->parseEnv('PACT_MOCK_SERVER_PORT')); + parent::__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); - } - - if ($logLevel = $this->parseEnv('PACT_LOGLEVEL', false)) { - $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); + $this->setPactDir($this->parseEnv('PACT_OUTPUT_DIR')); $version = $this->parseEnv('PACT_SPECIFICATION_VERSION', false); if (!$version) { diff --git a/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php b/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php deleted file mode 100644 index 3d2ac348..00000000 --- a/src/PhpPact/Standalone/MockService/Service/MockServerHttpService.php +++ /dev/null @@ -1,173 +0,0 @@ -client = $client; - $this->config = $config; - } - - /** - * {@inheritdoc} - */ - public function healthCheck(): bool - { - $uri = $this->config->getBaseUri()->withPath('/'); - - $response = $this->client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - $body = $response->getBody()->getContents(); - - if ($response->getStatusCode() !== 200 - || $body !== "Mock service running\n") { - throw new ConnectionException('Failed to receive a successful response from the Mock Server.'); - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function deleteAllInteractions(): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $response = $this->client->delete($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - if ($response->getStatusCode() !== 200) { - return false; - } - - return true; - } - - /** - * {@inheritdoc} - */ - public function registerInteraction(Interaction $interaction): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $body = \json_encode($interaction->jsonSerialize()); - - $this->client->post($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - 'body' => $body, - ]); - - return true; - } - - /** - * Separate function for messages, instead of interactions, as I am unsure what to do with the Ruby Standalone at the moment - * - * @param Message $message - * - * @return bool - */ - public function registerMessage(Message $message): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions'); - - $body = \json_encode($message->jsonSerialize()); - - $this->client->post($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - 'body' => $body, - ]); - - return true; - } - - /** - * {@inheritdoc} - */ - public function verifyInteractions(): bool - { - $uri = $this->config->getBaseUri()->withPath('/interactions/verification'); - - $this->client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - return true; - } - - /** - * {@inheritdoc} - */ - public function getPactJson(): string - { - $uri = $this->config->getBaseUri()->withPath('/pact'); - $response = $this->client->post($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - 'X-Pact-Mock-Service' => true, - ], - ]); - - return \json_encode(\json_decode($response->getBody()->getContents())); - } - - /** - * Wrapper for getPactJson to force the Ruby server to write the pact file to disk - * - * If the Pact-PHP does not gracefully kill the Ruby Server, it will not write the - * file to disk. This enables a work around. - */ - public function writePact(): string - { - return $this->getPactJson(); - } -} diff --git a/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php b/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php deleted file mode 100644 index e3bfbe62..00000000 --- a/src/PhpPact/Standalone/MockService/Service/MockServerHttpServiceInterface.php +++ /dev/null @@ -1,51 +0,0 @@ -installManager = new InstallManager(); - } - - /** - * Build an example from the data structure back into its generated form - * i.e. strip out all of the matchers etc - * - * @param Message $pact - * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - * - * @return string - */ - public function reify(Message $pact): string - { - $scripts = $this->installManager->install(); - - $json = \json_encode($pact); - $process = new ProcessRunner($scripts->getPactMessage(), ['reify', "'" . $json . "'"]); - - $process->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. - * - * @param string $pactJson - * @param string $consumer - * @param string $provider - * @param string $pactDir - * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - * - * @return bool - */ - public function update(string $pactJson, string $consumer, string $provider, string $pactDir): bool - { - $scripts = $this->installManager->install(); - - $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 deleted file mode 100644 index 2874e772..00000000 --- a/src/PhpPact/Standalone/PactMessage/PactMessageConfig.php +++ /dev/null @@ -1,169 +0,0 @@ -consumer; - } - - /** - * {@inheritdoc} - */ - public function setConsumer(string $consumer): PactConfigInterface - { - $this->consumer = $consumer; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getProvider(): string - { - return $this->provider; - } - - /** - * {@inheritdoc} - */ - public function setProvider(string $provider): PactConfigInterface - { - $this->provider = $provider; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactDir() - { - if ($this->pactDir === null) { - return \sys_get_temp_dir(); - } - - return $this->pactDir; - } - - /** - * {@inheritdoc} - */ - public function setPactDir($pactDir): PactConfigInterface - { - $this->pactDir = $pactDir; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getPactSpecificationVersion() - { - return $this->pactSpecificationVersion; - } - - /** - * {@inheritdoc} - * - * @throws \UnexpectedValueException - */ - public function setPactSpecificationVersion($pactSpecificationVersion): PactConfigInterface - { - /* - * Parse the version but do not assign it. If it is an invalid version, an exception is thrown - */ - $parser = new VersionParser(); - $parser->normalize($pactSpecificationVersion); - - $this->pactSpecificationVersion = $pactSpecificationVersion; - - return $this; - } - - /** - * {@inheritdoc} - */ - public function getLog() - { - return $this->log; - } - - /** - * {@inheritdoc} - */ - public function setLog(string $log): PactConfigInterface - { - $this->log = $log; - - return $this; - } - - public function getLogLevel() - { - return $this->logLevel; - } - - public function setLogLevel(string $logLevel): PactConfigInterface - { - $logLevel = \strtoupper($logLevel); - if (!\in_array($logLevel, ['DEBUG', 'INFO', 'WARN', 'ERROR'])) { - throw new \InvalidArgumentException('LogLevel ' . $logLevel . ' not supported.'); - } - $this->logLevel = $logLevel; - - return $this; - } -} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/ConsumerVersionSelectors.php b/src/PhpPact/Standalone/ProviderVerifier/Model/ConsumerVersionSelectors.php index f4fc88b3..234663fd 100644 --- a/src/PhpPact/Standalone/ProviderVerifier/Model/ConsumerVersionSelectors.php +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/ConsumerVersionSelectors.php @@ -7,11 +7,10 @@ class ConsumerVersionSelectors implements Iterator, Countable { - /** @var int */ - private $position = 0; + private int $position = 0; /** @var string[] */ - private $selectors = []; + private array $selectors = []; /** * @param string[] $selectors @@ -28,38 +27,32 @@ 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() + public function key(): int { return $this->position; } - #[\ReturnTypeWillChange] - public function valid() + 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() + 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..09016fb3 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/Broker.php @@ -0,0 +1,131 @@ +consumerVersionSelectors = new ConsumerVersionSelectors(); + } + + /** + * {@inheritdoc} + */ + public function isEnablePending(): bool + { + return $this->enablePending; + } + + /** + * {@inheritdoc} + */ + public function setEnablePending(bool $enablePending): BrokerInterface + { + $this->enablePending = $enablePending; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setIncludeWipPactSince(string $date): BrokerInterface + { + $this->wipPactSince = $date; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getIncludeWipPactSince(): ?string + { + return $this->wipPactSince; + } + + /** + * {@inheritdoc} + */ + public function getProviderTags(): array + { + return $this->providerTags; + } + + /** + * {@inheritdoc} + */ + public function setProviderTags(array $providerTags): BrokerInterface + { + $this->providerTags = $providerTags; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getProviderBranch(): string + { + return $this->providerBranch; + } + + /** + * {@inheritdoc} + */ + public function setProviderBranch(string $providerBranch): BrokerInterface + { + $this->providerBranch = $providerBranch; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConsumerVersionSelectors(): ConsumerVersionSelectors + { + return $this->consumerVersionSelectors; + } + + /** + * {@inheritdoc} + */ + public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): BrokerInterface + { + $this->consumerVersionSelectors = $selectors; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getConsumerVersionTags(): array + { + return $this->consumerVersionTags; + } + + /** + * {@inheritdoc} + */ + public function setConsumerVersionTags(array $consumerVersionTags): BrokerInterface + { + $this->consumerVersionTags = $consumerVersionTags; + + 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..c5181ca9 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/BrokerInterface.php @@ -0,0 +1,84 @@ +url; + } + + /** + * {@inheritdoc} + */ + public function setUrl(UriInterface $url): UrlInterface + { + $this->url = $url; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getToken(): ?string + { + return $this->token; + } + + /** + * {@inheritdoc} + */ + public function setToken(?string $token): UrlInterface + { + $this->token = $token; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getUsername(): ?string + { + return $this->username; + } + + /** + * {@inheritdoc} + */ + public function setUsername(string $username): UrlInterface + { + $this->username = $username; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getPassword(): ?string + { + return $this->password; + } + + /** + * {@inheritdoc} + */ + public function setPassword(string $password): UrlInterface + { + $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..c9a7b380 --- /dev/null +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/Source/UrlInterface.php @@ -0,0 +1,59 @@ +consumerVersionSelectors = new ConsumerVersionSelectors(); - } + private bool $publishResults = false; + private bool $disableSslVerification = false; + private bool $stateChangeAsQuery = false; + private bool $stateChangeTeardown = false; /** * {@inheritdoc} */ - public function getProviderBaseUrl() + public function getBasePath(): ?string { - return $this->providerBaseUrl; + return $this->basePath; } /** * {@inheritdoc} */ - public function setProviderBaseUrl(UriInterface $providerBaseUrl): VerifierConfigInterface + public function setBasePath(string $basePath): VerifierConfigInterface { - $this->providerBaseUrl = $providerBaseUrl; + $this->basePath = $basePath; return $this; } @@ -101,17 +53,17 @@ public function setProviderBaseUrl(UriInterface $providerBaseUrl): VerifierConfi /** * {@inheritdoc} */ - public function getProviderStatesSetupUrl() + public function getStateChangeUrl(): ?UriInterface { - return $this->providerStatesSetupUrl; + return $this->stateChangeUrl; } /** * {@inheritdoc} */ - public function setProviderStatesSetupUrl(string $providerStatesSetupUrl): VerifierConfigInterface + public function setStateChangeUrl(UriInterface $stateChangeUrl): VerifierConfigInterface { - $this->providerStatesSetupUrl = $providerStatesSetupUrl; + $this->stateChangeUrl = $stateChangeUrl; return $this; } @@ -119,9 +71,9 @@ public function setProviderStatesSetupUrl(string $providerStatesSetupUrl): Verif /** * {@inheritdoc} */ - public function getProviderName(): string + public function getProviderName(): ?string { - return (string) $this->providerName; + return $this->providerName; } /** @@ -137,7 +89,7 @@ public function setProviderName(string $providerName): VerifierConfigInterface /** * {@inheritdoc} */ - public function getProviderVersion() + public function getProviderVersion(): string { return $this->providerVersion; } @@ -155,33 +107,17 @@ public function setProviderVersion(string $providerVersion): VerifierConfigInter /** * {@inheritdoc} */ - public function getProviderVersionTag() - { - return $this->providerVersionTag; - } - - /** - * {@inheritdoc} - */ - public function setProviderVersionTag(string $providerVersionTag): VerifierConfigInterface - { - return $this->addProviderVersionTag($providerVersionTag); - } - - /** - * {@inheritdoc} - */ - public function getConsumerVersionTag() + public function getProviderTags(): array { - return $this->consumerVersionTag; + return $this->providerTags; } /** * {@inheritdoc} */ - public function addConsumerVersionTag(string $consumerVersionTag): VerifierConfigInterface + public function setProviderTags(array $providerTags): VerifierConfigInterface { - $this->consumerVersionTag[] = $consumerVersionTag; + $this->providerTags = $providerTags; return $this; } @@ -189,31 +125,17 @@ public function addConsumerVersionTag(string $consumerVersionTag): VerifierConfi /** * {@inheritdoc} */ - public function addProviderVersionTag(string $providerVersionTag): VerifierConfigInterface + public function getProviderBranch(): ?string { - $this->providerVersionTag[] = $providerVersionTag; - - return $this; + return $this->providerBranch; } /** - * @param string $consumerVersionTag - * - * @return VerifierConfigInterface + * {@inheritdoc} */ - public function setConsumerVersionTag(string $consumerVersionTag): VerifierConfigInterface - { - return $this->addConsumerVersionTag($consumerVersionTag); - } - - public function getConsumerVersionSelectors(): ConsumerVersionSelectors - { - return $this->consumerVersionSelectors; - } - - public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): VerifierConfigInterface + public function setProviderBranch(string $providerBranch): VerifierConfigInterface { - $this->consumerVersionSelectors = $selectors; + $this->providerBranch = $providerBranch; return $this; } @@ -239,89 +161,75 @@ public function setPublishResults(bool $publishResults): VerifierConfigInterface /** * {@inheritdoc} */ - public function getBrokerUri() + public function isDisableSslVerification(): bool { - return $this->brokerUri; + return $this->disableSslVerification; } /** * {@inheritdoc} */ - public function setBrokerUri(UriInterface $brokerUri): VerifierConfigInterface + public function setDisableSslVerification(bool $disableSslVerification): VerifierConfigInterface { - $this->brokerUri = $brokerUri; + $this->disableSslVerification = $disableSslVerification; return $this; } /** - * {@inheritdoc}} - */ - public function getBrokerToken(): ?string - { - return $this->brokerToken; - } - - /** - * {@inheritdoc } + * @param bool $stateChangeAsQuery + * + * @return VerifierConfigInterface */ - public function setBrokerToken(?string $brokerToken): VerifierConfigInterface + public function setStateChangeAsQuery(bool $stateChangeAsQuery): VerifierConfigInterface { - $this->brokerToken = $brokerToken; + $this->stateChangeAsQuery = $stateChangeAsQuery; return $this; } /** - * {@inheritdoc} + * @return bool */ - public function getBrokerUsername() + public function isStateChangeAsQuery(): bool { - return $this->brokerUsername; + return $this->stateChangeAsQuery; } /** - * {@inheritdoc} + * @param bool $stateChangeTeardown + * + * @return VerifierConfigInterface */ - public function setBrokerUsername(string $brokerUsername) + public function setStateChangeTeardown(bool $stateChangeTeardown): VerifierConfigInterface { - $this->brokerUsername = $brokerUsername; + $this->stateChangeTeardown = $stateChangeTeardown; return $this; } /** - * {@inheritdoc} - */ - public function getBrokerPassword() - { - return $this->brokerPassword; - } - - /** - * {@inheritdoc} + * @return bool */ - public function setBrokerPassword(string $brokerPassword) + public function isStateChangeTeardown(): bool { - $this->brokerPassword = $brokerPassword; - - return $this; + return $this->stateChangeTeardown; } /** * {@inheritdoc} */ - public function getCustomProviderHeaders() + public function getBuildUrl(): ?UriInterface { - return $this->customProviderHeaders; + return $this->buildUrl; } /** * {@inheritdoc} */ - public function setCustomProviderHeaders(array $customProviderHeaders): VerifierConfigInterface + public function setBuildUrl(UriInterface $buildUrl): VerifierConfigInterface { - $this->customProviderHeaders = $customProviderHeaders; + $this->buildUrl = $buildUrl; return $this; } @@ -329,9 +237,9 @@ public function setCustomProviderHeaders(array $customProviderHeaders): Verifier /** * {@inheritdoc} */ - public function addCustomProviderHeader(string $name, string $value): VerifierConfigInterface + public function setRequestTimeout(int $requestTimeout): VerifierConfigInterface { - $this->customProviderHeaders[] = "{$name}: {$value}"; + $this->requestTimeout = $requestTimeout; return $this; } @@ -339,17 +247,17 @@ public function addCustomProviderHeader(string $name, string $value): VerifierCo /** * {@inheritdoc} */ - public function isVerbose(): bool + public function getRequestTimeout(): int { - return $this->verbose; + return $this->requestTimeout; } /** * {@inheritdoc} */ - public function setVerbose(bool $verbose): VerifierConfigInterface + public function setFilterConsumerNames(string ...$filterConsumerNames): VerifierConfigInterface { - $this->verbose = $verbose; + $this->filterConsumerNames = $filterConsumerNames; return $this; } @@ -357,93 +265,79 @@ public function setVerbose(bool $verbose): VerifierConfigInterface /** * {@inheritdoc} */ - public function getLogDirectory() + public function getFilterConsumerNames(): array { - return $this->logDirectory; + return $this->filterConsumerNames; } /** * {@inheritdoc} */ - public function setLogDirectory(string $log): VerifierConfigInterface + public function getFilterDescription(): ?string { - $this->logDirectory = $log; - - return $this; + return $this->filterDescription; } /** * {@inheritdoc} */ - public function getFormat() + public function setFilterDescription(string $filterDescription): VerifierConfigInterface { - return $this->format; - } - - /** - * {@inheritdoc} - */ - public function setFormat(string $format): VerifierConfigInterface - { - $this->format = $format; + $this->filterDescription = $filterDescription; return $this; } /** - * @param int $timeout - * - * @return VerifierConfigInterface + * {@inheritdoc} */ - public function setProcessTimeout(int $timeout): VerifierConfigInterface + public function getFilterNoState(): bool { - $this->processTimeout = $timeout; - - return $this; + return $this->filterNoState; } /** - * @param int $timeout - * - * @return VerifierConfigInterface + * {@inheritdoc} */ - public function setProcessIdleTimeout(int $timeout): VerifierConfigInterface + public function setFilterNoState(bool $filterNoState): VerifierConfigInterface { - $this->processIdleTimeout = $timeout; + $this->filterNoState = $filterNoState; return $this; } /** - * @return int + * {@inheritdoc} */ - public function getProcessTimeout(): int + public function getFilterState(): ?string { - return $this->processTimeout; + return $this->filterState; } /** - * @return int + * {@inheritdoc} */ - public function getProcessIdleTimeout(): int + public function setFilterState(string $filterState): VerifierConfigInterface { - return $this->processIdleTimeout; + $this->filterState = $filterState; + + return $this; } /** * {@inheritdoc} */ - public function isEnablePending(): bool + public function getScheme(): ?string { - return $this->enablePending; + return $this->scheme; } /** * {@inheritdoc} */ - public function setEnablePending(bool $pending): VerifierConfigInterface + public function setScheme(string $scheme): VerifierConfigInterface { - $this->enablePending = $pending; + $this->scheme = $scheme; return $this; } @@ -451,29 +345,17 @@ public function setEnablePending(bool $pending): VerifierConfigInterface /** * {@inheritdoc} */ - public function setIncludeWipPactSince(string $date): VerifierConfigInterface + public function getHost(): ?string { - $this->wipPactSince = $date; - - return $this; + return $this->host; } /** * {@inheritdoc} */ - public function getIncludeWipPactSince() - { - return $this->wipPactSince; - } - - public function getRequestFilter(): ?callable + public function setHost(string $host): VerifierConfigInterface { - return $this->requestFilter; - } - - public function setRequestFilter(callable $requestFilter): VerifierConfigInterface - { - $this->requestFilter = $requestFilter; + $this->host = $host; return $this; } @@ -481,18 +363,18 @@ public function setRequestFilter(callable $requestFilter): VerifierConfigInterfa /** * {@inheritdoc} */ - public function setProviderBranch(string $providerBranch): VerifierConfigInterface + public function getPort(): ?int { - $this->providerBranch = $providerBranch; - - return $this; + return $this->port; } /** * {@inheritdoc} */ - public function getProviderBranch(): ?string + public function setPort(int $port): VerifierConfigInterface { - return $this->providerBranch; + $this->port = $port; + + return $this; } } diff --git a/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php b/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php index b1acbdf6..1c9a6333 100644 --- a/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php +++ b/src/PhpPact/Standalone/ProviderVerifier/Model/VerifierConfigInterface.php @@ -5,288 +5,235 @@ use Psr\Http\Message\UriInterface; /** - * Configuration to use with the verifier server. * Interface VerifierServerConfigInterface. */ interface VerifierConfigInterface { /** - * @return null|UriInterface providers base url + * @return null|string */ - public function getProviderBaseUrl(); + public function getBasePath(): ?string; /** - * @param UriInterface $providerBaseUrl providers base url + * @param string $basePath * - * @return VerifierConfigInterface - */ - public function setProviderBaseUrl(UriInterface $providerBaseUrl): self; - - /** - * @return null|string Base URL to setup the provider states at - */ - public function getProviderStatesSetupUrl(); - - /** - * @param string $providerStatesSetupUrl Base URL to setup the provider states at - * - * @return VerifierConfigInterface + * @return $this */ - public function setProviderStatesSetupUrl(string $providerStatesSetupUrl): self; + public function setBasePath(string $basePath): self; /** - * @return null|string name of the provider + * @return null|UriInterface */ - public function getProviderName(); + public function getStateChangeUrl(): ?UriInterface; /** - * @param string $name Name of the provider + * @param UriInterface $stateChangeUrl * - * @return VerifierConfigInterface - */ - public function setProviderName(string $name): self; - - /** - * @return null|string providers version + * @return $this */ - public function getProviderVersion(); + public function setStateChangeUrl(UriInterface $stateChangeUrl): self; /** - * @param string $providerAppVersion providers version - * - * @return VerifierConfigInterface + * @return null|string */ - public function setProviderVersion(string $providerAppVersion): self; + public function getProviderName(): ?string; /** - * @param string $providerBranch providers branch name + * @param string $providerName * - * @return VerifierConfigInterface + * @return $this */ - public function setProviderBranch(string $providerBranch): self; + public function setProviderName(string $providerName): self; /** - * @return array providers version tag + * @return string */ - public function getProviderVersionTag(); + public function getProviderVersion(): string; /** - * @return null|string providers branch name - */ - public function getProviderBranch(): ?string; - - /** - * @param string $providerVersionTag providers version tag + * @param string $providerVersion * - * @return VerifierConfigInterface - */ - public function setProviderVersionTag(string $providerVersionTag): self; - - /** - * @return array consumers version tag + * @return $this */ - public function getConsumerVersionTag(); + public function setProviderVersion(string $providerVersion): self; /** - * @param string $consumerVersionTag consumers version tag - * - * @return VerifierConfigInterface + * @return array */ - public function addConsumerVersionTag(string $consumerVersionTag): self; + public function getProviderTags(): array; /** - * @param string $providerVersionTag provider version tag + * @param array|string[] $providerTags * - * @return VerifierConfigInterface + * @return $this */ - public function addProviderVersionTag(string $providerVersionTag): self; + public function setProviderTags(array $providerTags): self; /** - * @return ConsumerVersionSelectors + * @return string|null */ - public function getConsumerVersionSelectors(): ConsumerVersionSelectors; + public function getProviderBranch(): ?string; /** - * @param ConsumerVersionSelectors $selectors Consumer version selectors + * @param string $providerBranch * * @return $this */ - public function setConsumerVersionSelectors(ConsumerVersionSelectors $selectors): self; + public function setProviderBranch(string $providerBranch): VerifierConfigInterface; /** - * @return bool are results going to be published + * @return bool */ public function isPublishResults(): bool; /** - * @param bool $publishResults flag to publish results + * @param bool $publishResults * - * @return VerifierConfigInterface + * @return $this */ public function setPublishResults(bool $publishResults): self; /** - * @return null|UriInterface url to the pact broker + * @return bool */ - public function getBrokerUri(); + public function isDisableSslVerification(): bool; /** - * @param UriInterface $brokerUri uri to the pact broker + * @param bool $disableSslVerification * - * @return VerifierConfigInterface - */ - public function setBrokerUri(UriInterface $brokerUri): self; - - /** - * @return null|string token for the pact broker + * @return $this */ - public function getBrokerToken(): ?string; + public function setDisableSslVerification(bool $disableSslVerification): self; /** - * @param null|string $brokerToken token for the pact broker + * @param bool $stateChangeAsQuery * - * @return VerifierConfigInterface + * @return $this */ - public function setBrokerToken(?string $brokerToken): self; + public function setStateChangeAsQuery(bool $stateChangeAsQuery): self; /** - * @return null|string username for the pact broker if secured + * @return bool */ - public function getBrokerUsername(); + public function isStateChangeAsQuery(): bool; /** - * @param string $brokerUsername username for the pact broker if secured + * @param bool $stateChangeTeardown * - * @return VerifierConfigInterface - */ - public function setBrokerUsername(string $brokerUsername); - - /** - * @return null|string password for the pact broker if secured + * @return $this */ - public function getBrokerPassword(); + public function setStateChangeTeardown(bool $stateChangeTeardown): self; /** - * @param string $brokerPassword password for the pact broker if secured - * - * @return VerifierConfigInterface + * @return bool */ - public function setBrokerPassword(string $brokerPassword); + public function isStateChangeTeardown(): bool; /** - * @return null|string[] custom headers for the request to the provider such as authorization + * @return null|UriInterface */ - public function getCustomProviderHeaders(); + public function getBuildUrl(): ?UriInterface; /** - * @param string[] $customProviderHeaders custom headers for the requests to the provider such as authorization + * @param UriInterface $buildUrl * - * @return VerifierConfigInterface + * @return $this */ - public function setCustomProviderHeaders(array $customProviderHeaders): self; + public function setBuildUrl(UriInterface $buildUrl): self; /** - * @param string $name - * @param string $value + * @param int $requestTimeout * - * @return VerifierConfigInterface + * @return $this */ - public function addCustomProviderHeader(string $name, string $value): self; + public function setRequestTimeout(int $requestTimeout): self; /** - * @return bool is verbosity level increased + * @return int */ - public function isVerbose(): bool; + public function getRequestTimeout(): int; /** - * @param bool $verbose increase verbosity level + * @param string ...$filterConsumerNames * - * @return VerifierConfigInterface + * @return $this */ - public function setVerbose(bool $verbose): self; + public function setFilterConsumerNames(string ...$filterConsumerNames): self; /** - * @return null|string set the directory for the pact.log file + * @return array */ - public function getLogDirectory(); + public function getFilterConsumerNames(): array; /** - * @param string $log set the directory for the pact.log file + * @param string $filterDescription * - * @return VerifierConfigInterface + * @return $this */ - public function setLogDirectory(string $log): self; + public function setFilterDescription(string $filterDescription): self; /** - * @return null|string RSpec formatter. Defaults to custom Pact formatter. json and RspecJunitFormatter may also be used + * @return null|string */ - public function getFormat(); + public function getFilterDescription(): ?string; /** - * @param string $format RSpec formatter. Defaults to custom Pact formatter. json and RspecJunitFormatter may also be used + * @param bool $filterNoState * - * @return VerifierConfigInterface + * @return $this */ - public function setFormat(string $format): self; + public function setFilterNoState(bool $filterNoState): self; /** - * @param int $timeout - * - * @return VerifierConfigInterface + * @return bool */ - public function setProcessTimeout(int $timeout): self; + public function getFilterNoState(): bool; /** - * @param int $timeout + * @param string $filterState * - * @return VerifierConfigInterface + * @return $this */ - public function setProcessIdleTimeout(int $timeout): self; + public function setFilterState(string $filterState): self; /** - * @return int + * @return null|string */ - public function getProcessTimeout(): int; + public function getFilterState(): ?string; /** - * @return int + * @return null|string */ - public function getProcessIdleTimeout(): int; + public function getScheme(): ?string; /** - * @param bool $pending allow pacts which are in pending state to be verified without causing the overall task to fail + * @param string $scheme * - * @return VerifierConfigInterface + * @return $this */ - public function setEnablePending(bool $pending): self; + public function setScheme(string $scheme): self; /** - * @return bool is enabled pending pacts + * @return null|string */ - public function isEnablePending(): bool; + public function getHost(): ?string; /** - * @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) + * @param string $host * - * @return VerifierConfigInterface - */ - public function setIncludeWipPactSince(string $date): self; - - /** - * @return null|string get start date of included WIP Pacts + * @return $this */ - public function getIncludeWipPactSince(); + public function setHost(string $host): self; /** - * @return null|callable + * @return null|int */ - public function getRequestFilter(): ?callable; + public function getPort(): ?int; /** - * @param callable $requestFilter + * @param int $port * * @return $this */ - public function setRequestFilter(callable $requestFilter): self; + public function setPort(int $port): self; } diff --git a/src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php b/src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php deleted file mode 100644 index 60f73750..00000000 --- a/src/PhpPact/Standalone/ProviderVerifier/ProcessRunnerFactory.php +++ /dev/null @@ -1,25 +0,0 @@ -setLogger($logger); - } - - return $processRunner; - } -} diff --git a/src/PhpPact/Standalone/ProviderVerifier/Verifier.php b/src/PhpPact/Standalone/ProviderVerifier/Verifier.php index cfa02b5a..8343ca3f 100644 --- a/src/PhpPact/Standalone/ProviderVerifier/Verifier.php +++ b/src/PhpPact/Standalone/ProviderVerifier/Verifier.php @@ -2,305 +2,173 @@ 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; +use FFI\CData; +use PhpPact\Ffi\Helper; use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; use PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException; use PhpPact\Standalone\Installer\InstallManager; -use PhpPact\Standalone\Installer\Service\InstallerInterface; +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 VerifierServer. + * Class Verifier. */ class Verifier { - /** @var int */ - protected $processTimeout = 60; - - /** @var int */ - protected $processIdleTimeout = 10; - - /** @var VerifierConfigInterface */ - protected $config; - - /** @var null|BrokerHttpClientInterface */ - protected $brokerHttpClient; - - /** @var InstallManager */ - protected $installManager; - - /** @var null|VerifierProcess */ - protected $verifierProcess; - - public function __construct( - VerifierConfigInterface $config, - InstallManager $installManager = null, - VerifierProcess $verifierProcess = null, - BrokerHttpClient $brokerHttpClient = null - ) { - $this->config = $config; - $this->installManager = $installManager ?: new InstallManager(); - $this->verifierProcess = $verifierProcess ?: new VerifierProcess($this->installManager); - $this->processTimeout = $config->getProcessTimeout(); - $this->processIdleTimeout = $config->getProcessIdleTimeout(); - - if ($brokerHttpClient) { - $this->brokerHttpClient = $brokerHttpClient; - } - } + protected FFI $ffi; + protected CData $handle; /** - * @throws \Exception + * Verifier constructor. * - * @return array parameters to be passed into the process + * @throws FileDownloadFailureException + * @throws NoDownloaderFoundException */ - public function getArguments(): array + public function __construct() { - $parameters = []; - - if (!empty($this->config->getProviderName())) { - $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}"; - } - } - - if (\count($this->config->getConsumerVersionSelectors()) > 0) { - foreach ($this->config->getConsumerVersionSelectors() as $selector) { - $parameters[] = "--consumer-version-selector='{$selector}'"; - } - } - - if (\count($this->config->getProviderVersionTag()) > 0) { - foreach ($this->config->getProviderVersionTag() as $tag) { - $parameters[] = "--provider-version-tag={$tag}"; - } - } - - if ($this->config->getProviderStatesSetupUrl() !== null) { - $parameters[] = "--provider-states-setup-url={$this->config->getProviderStatesSetupUrl()}"; - } - - if ($this->config->isPublishResults() === true) { - $parameters[] = '--publish-verification-results'; - } - - if ($this->config->getBrokerToken() !== null) { - $parameters[] = "--broker-token={$this->config->getBrokerToken()}"; - } - - if ($this->config->getBrokerUsername() !== null) { - $parameters[] = "--broker-username={$this->config->getBrokerUsername()}"; - } - - if ($this->config->getBrokerPassword() !== null) { - $parameters[] = "--broker-password={$this->config->getBrokerPassword()}"; - } - - if ($this->config->getCustomProviderHeaders() !== null) { - foreach ($this->config->getCustomProviderHeaders() as $customProviderHeader) { - $parameters[] = "--custom-provider-header=\"{$customProviderHeader}\""; - } - } - - if ($this->config->isVerbose() === true) { - $parameters[] = '--verbose'; - } - - if ($this->config->getLogDirectory() !== null) { - $parameters[] = "--log={$this->config->getLogDirectory()}"; - } - - if ($this->config->getFormat() !== null) { - $parameters[] = "--format={$this->config->getFormat()}"; - } - - if ($this->config->isEnablePending() === true) { - $parameters[] = '--enable-pending'; - } - - if ($this->config->getIncludeWipPactSince() !== null) { - $parameters[] = "--include-wip-pacts-since={$this->config->getIncludeWipPactSince()}"; - } - - if ($this->config->getBrokerUri() !== null) { - $parameters[] = "--pact-broker-base-url={$this->config->getBrokerUri()->__toString()}"; - } - - return $parameters; + $scripts = (new InstallManager())->install(); + $this->ffi = FFI::cdef(\file_get_contents($scripts->getCode()), $scripts->getLibrary()); + $this->ffi->pactffi_init('PACT_LOGLEVEL'); } /** - * 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 + * @param VerifierConfigInterface $config * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - * - * @return Verifier + * @return $this */ - public function verify(string $consumerName, string $tag = null, string $consumerVersion = null): self + public function newHandle(VerifierConfigInterface $config): self { - $path = "/pacts/provider/{$this->config->getProviderName()}/consumer/{$consumerName}/"; - - if ($tag) { - $path .= "latest/{$tag}/"; - } elseif ($consumerVersion) { - $path .= "version/{$consumerVersion}/"; - } else { - $path .= 'latest/'; - } - - $uri = $this->config->getBrokerUri()->withPath($path); - - $arguments = \array_merge([$uri->__toString()], $this->getArguments()); - - $this->verifyAction($arguments); + $this->handle = $this->ffi->pactffi_verifier_new(); + $this->ffi->pactffi_verifier_set_provider_info( + $this->handle, + $config->getProviderName(), + $config->getScheme(), + $config->getHost(), + $config->getPort(), + $config->getBasePath() + ); + $this->ffi->pactffi_verifier_set_filter_info( + $this->handle, + $config->getFilterDescription(), + $config->getFilterState(), + $config->getFilterNoState() + ); + $this->ffi->pactffi_verifier_set_provider_state( + $this->handle, + $config->getStateChangeUrl() ? (string) $config->getStateChangeUrl() : null, + $config->isStateChangeTeardown(), + !$config->isStateChangeAsQuery() + ); + $providerTags = Helper::getArray($config->getProviderTags()); + $this->ffi->pactffi_verifier_set_verification_options( + $this->handle, + $config->isDisableSslVerification(), + $config->getRequestTimeout() + ); + if ($config->isPublishResults()) { + $this->ffi->pactffi_verifier_set_publish_options( + $this->handle, + $config->getProviderVersion(), + $config->getBuildUrl(), + $providerTags->getValue(), + $providerTags->getSize(), + $config->getProviderBranch() + ); + } + $filterConsumerNames = Helper::getArray($config->getFilterConsumerNames()); + $this->ffi->pactffi_verifier_set_consumer_filters( + $this->handle, + $filterConsumerNames->getValue(), + $filterConsumerNames->getSize() + ); return $this; } /** - * Provides a way to validate local Pact JSON files. + * @param string $file * - * @param array $files paths to pact json files - * - * @throws FileDownloadFailureException - * @throws NoDownloaderFoundException - * - * @return Verifier + * @return $this */ - public function verifyFiles(array $files): self + public function addFile(string $file): self { - $arguments = \array_merge($files, $this->getArguments()); - - $this->verifyAction($arguments); + $this->ffi->pactffi_verifier_add_file_source($this->handle, $file); return $this; } /** - * Verify all Pacts from the Pact Broker are valid for the Provider. + * @param string $directory * - * @throws FileDownloadFailureException - * @throws NoDownloaderFoundException + * @return $this */ - public function verifyAll() + public function addDirectory(string $directory): self { - $arguments = $this->getBrokerHttpClient()->getAllConsumerUrls($this->config->getProviderName()); - - $arguments = \array_merge($arguments, $this->getArguments()); + $this->ffi->pactffi_verifier_add_directory_source($this->handle, $directory); - $this->verifyAction($arguments); + return $this; } /** - * Verify all PACTs for a given tag. + * @param UrlInterface $url * - * @param string $tag - * - * @throws FileDownloadFailureException - * @throws NoDownloaderFoundException + * @return $this */ - public function verifyAllForTag(string $tag) + public function addUrl(UrlInterface $url): self { - $arguments = $this->getBrokerHttpClient()->getAllConsumerUrlsForTag($this->config->getProviderName(), $tag); - - $arguments = \array_merge($arguments, $this->getArguments()); - - $this->verifyAction($arguments); - } + $this->ffi->pactffi_verifier_url_source( + $this->handle, + $url->getUrl(), + $url->getUsername(), + $url->getPassword(), + $url->getToken() + ); - /** - * Verify all PACTs that match the VerifierConfig - * - * @throws FileDownloadFailureException - * @throws NoDownloaderFoundException - */ - public function verifyFromConfig() - { - $this->verifyAction($this->getArguments()); + return $this; } /** - * Wrapper to add a custom installer. + * @param BrokerInterface $broker * - * @param InstallerInterface $installer - * - * @return self + * @return $this */ - public function registerInstaller(InstallerInterface $installer): self + public function addBroker(BrokerInterface $broker): self { - $this->installManager->registerInstaller($installer); + $providerTags = Helper::getArray($broker->getProviderTags()); + $consumerVersionSelectors = Helper::getArray($broker->getConsumerVersionSelectors()); + $consumerVersionTags = Helper::getArray($broker->getConsumerVersionTags()); + $this->ffi->pactffi_verifier_broker_source_with_selectors( + $this->handle, + $broker->getUrl(), + $broker->getUsername(), + $broker->getPassword(), + $broker->getToken(), + $broker->isEnablePending(), + $broker->getIncludeWipPactSince(), + $providerTags->getValue(), + $providerTags->getSize(), + $broker->getProviderBranch(), + $consumerVersionSelectors->getValue(), + $consumerVersionSelectors->getSize(), + $consumerVersionTags->getValue(), + $consumerVersionTags->getSize() + ); return $this; } - public function getTimeoutValues(): array - { - return ['process_timeout' => $this->processTimeout, 'process_idle_timeout' => $this->processIdleTimeout]; - } - /** - * Trigger execution of the Pact Verifier Service. - * - * @param array $arguments + * Verifier a provider. * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException + * @return bool */ - protected function verifyAction(array $arguments) + public function verify(): bool { - $this->verifierProcess->run($arguments, $this->processTimeout, $this->processIdleTimeout); - } - - protected function getBrokerHttpClient(): BrokerHttpClientInterface - { - 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; - } - $client = new GuzzleClient($config); - - $this->brokerHttpClient = new BrokerHttpClient($client, $this->config->getBrokerUri()); - } + $result = $this->ffi->pactffi_verifier_execute($this->handle); + $this->ffi->pactffi_verifier_shutdown($this->handle); - return $this->brokerHttpClient; + return !$result; } } diff --git a/src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php b/src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php deleted file mode 100644 index 6963bf90..00000000 --- a/src/PhpPact/Standalone/ProviderVerifier/VerifierProcess.php +++ /dev/null @@ -1,103 +0,0 @@ -installManager = $installManager; - $this->processRunnerFactory = $processRunnerFactory ?: new ProcessRunnerFactory(); - } - - /** - * @param LoggerInterface $logger - * - * @return VerifierProcess - */ - public function setLogger(LoggerInterface $logger): self - { - $this->logger = $logger; - - return $this; - } - - /** - * Execute the Pact Verifier Service. - * - * @param array $arguments - * @param int $processTimeout - * @param int $processIdleTimeout - * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - */ - public function run(array $arguments, $processTimeout, $processIdleTimeout) - { - $scripts = $this->installManager->install(); - - $logger = $this->getLogger(); - $processRunner = $this->processRunnerFactory->createRunner( - $scripts->getProviderVerifier(), - $arguments, - $logger - ); - - $logger->info("Verifying PACT with script:\n{$processRunner->getCommand()}\n\n"); - - try { - $processRunner->runBlocking(); - - $logger->info('out > ' . $processRunner->getOutput()); - $logger->error('err > ' . $processRunner->getStderr()); - } catch (\Exception $e) { - $logger->info('out > ' . $processRunner->getOutput()); - $logger->error('err > ' . $processRunner->getStderr()); - - throw $e; - } - } - - /** - * @return LoggerInterface - */ - private function getLogger() - { - 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/Runner/ProcessRunner.php b/src/PhpPact/Standalone/Runner/ProcessRunner.php index bcd8325f..d922f933 100644 --- a/src/PhpPact/Standalone/Runner/ProcessRunner.php +++ b/src/PhpPact/Standalone/Runner/ProcessRunner.php @@ -11,28 +11,18 @@ use Amp\Process\ProcessException; use Monolog\Logger; use Psr\Log\LoggerInterface; +use Throwable; /** * Wrapper around Process with Amp */ class ProcessRunner { - /** @var Process */ - private $process; - - /** @var string command output */ - private $output; - - /** @var int command exit code */ - private $exitCode; - - /** @var string */ - private $stderr; - - /** - * @var LoggerInterface - */ - private $logger; + private Process $process; + private ?string $output = null; + private int $exitCode; + private ?string $stderr = null; + private ?LoggerInterface $logger = null; /** * @param string $command @@ -154,10 +144,10 @@ public function runNonBlocking(): int $pid = yield $this->process->start(); - $this->process->getStdout()->read()->onResolve(function (\Throwable $reason = null, $value) { + $this->process->getStdout()->read()->onResolve(function (Throwable $reason = null, $value) { $this->output .= $value; }); - $this->process->getStderr()->read()->onResolve(function (\Throwable $reason = null, $value) { + $this->process->getStderr()->read()->onResolve(function (Throwable $reason = null, $value) { $this->output .= $value; }); @@ -178,7 +168,7 @@ public function runNonBlocking(): int * * @return int Process Id */ - public function run($blocking = false): int + public function run(bool $blocking = false): int { return $blocking ? $this->runBlocking() diff --git a/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php index b3b98fed..33b0293a 100644 --- a/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php +++ b/src/PhpPact/Standalone/StubService/Service/StubServerHttpService.php @@ -2,9 +2,11 @@ namespace PhpPact\Standalone\StubService\Service; +use GuzzleHttp\Psr7\Uri; use PhpPact\Exception\ConnectionException; use PhpPact\Http\ClientInterface; use PhpPact\Standalone\StubService\StubServerConfigInterface; +use Psr\Http\Message\UriInterface; /** * Http Service that interacts with the Ruby Standalone Stub Server. @@ -14,15 +16,8 @@ */ class StubServerHttpService implements StubServerHttpServiceInterface { - /** - * @var ClientInterface - */ - private $client; - - /** - * @var StubServerConfigInterface - */ - private $config; + private ClientInterface $client; + private StubServerConfigInterface $config; /** * StubServerHttpService constructor. diff --git a/src/PhpPact/Standalone/StubService/StubServer.php b/src/PhpPact/Standalone/StubService/StubServer.php index 0701fce1..ea009740 100644 --- a/src/PhpPact/Standalone/StubService/StubServer.php +++ b/src/PhpPact/Standalone/StubService/StubServer.php @@ -2,6 +2,7 @@ namespace PhpPact\Standalone\StubService; +use Amp\Process\ProcessException; use Exception; use PhpPact\Standalone\Installer\InstallManager; use PhpPact\Standalone\Installer\Service\InstallerInterface; @@ -13,14 +14,9 @@ */ class StubServer { - /** @var StubServerConfigInterface */ - private $config; - - /** @var InstallManager */ - private $installManager; - - /** @var ProcessRunner */ - private $processRunner; + private StubServerConfigInterface $config; + private InstallManager $installManager; + private ProcessRunner $processRunner; public function __construct(StubServerConfigInterface $config) { @@ -37,11 +33,9 @@ public function __construct(StubServerConfigInterface $config) * * @return int process ID of the started Stub Server */ - public function start($wait = 1): int + public function start(int $wait = 1): int { - $scripts = $this->installManager->install(); - - $this->processRunner = new ProcessRunner($scripts->getStubService(), $this->getArguments()); + $this->processRunner = new ProcessRunner($this->installManager->install()->getStubService(), $this->getArguments()); $processId = $this->processRunner->run(); \sleep($wait); // wait for server to start @@ -52,6 +46,8 @@ public function start($wait = 1): int /** * Stop the Stub Server process. * + * @throws ProcessException + * * @return bool Was stopping successful? */ public function stop(): bool @@ -82,12 +78,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 0b179366..1a21a590 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfig.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfig.php @@ -11,49 +11,103 @@ */ class StubServerConfig implements StubServerConfigInterface { + private ?UriInterface $brokerUrl = null; + private array $dirs = []; + private ?string $extension = null; + private array $files = []; + private ?string $logLevel = null; + private ?int $port = null; + private ?string $providerState = null; + private ?string $providerStateHeaderName = null; + private ?string $token = null; + private array $urls = []; + private ?string $user = null; + + private bool $cors = false; + private bool $corsReferer = false; + private bool $emptyProviderState = false; + private bool $insecureTls = false; + + private string $endpoint; + + /** + * {@inheritdoc} + */ + public function getBrokerUrl(): ?UriInterface + { + return $this->brokerUrl; + } + + /** + * {@inheritdoc} + */ + public function setBrokerUrl(UriInterface $brokerUrl): StubServerConfigInterface + { + $this->brokerUrl = $brokerUrl; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function setDirs(string ...$dirs): StubServerConfigInterface + { + $this->dirs = $dirs; + + return $this; + } + /** - * Host on which to bind the service. - * - * @var string + * {@inheritdoc} */ - private $host = 'localhost'; + public function getDirs(): array + { + return $this->dirs; + } /** - * Port on which to run the service. - * - * @var int + * {@inheritdoc} */ - private $port = 7201; + public function getExtension(): ?string + { + return $this->extension; + } /** - * @var bool + * {@inheritdoc} */ - private $secure = false; + public function setExtension(string $extension): StubServerConfigInterface + { + $this->extension = $extension; + + return $this; + } /** - * File to which to log output. - * - * @var string + * {@inheritdoc} */ - private $log; + public function setFiles(string ...$files): StubServerConfigInterface + { + $this->files = $files; - private $pactLocation; - private $endpoint; + return $this; + } /** * {@inheritdoc} */ - public function getHost(): string + public function getFiles(): array { - return $this->host; + return $this->files; } /** * {@inheritdoc} */ - public function setHost(string $host): StubServerConfigInterface + public function setLogLevel(string $logLevel): StubServerConfigInterface { - $this->host = $host; + $this->logLevel = $logLevel; return $this; } @@ -61,7 +115,15 @@ public function setHost(string $host): StubServerConfigInterface /** * {@inheritdoc} */ - public function getPort(): int + public function getLogLevel(): ?string + { + return $this->logLevel; + } + + /** + * {@inheritdoc} + */ + public function getPort(): ?int { return $this->port; } @@ -79,17 +141,17 @@ public function setPort(int $port): StubServerConfigInterface /** * {@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; } @@ -97,49 +159,167 @@ public function setSecure(bool $secure): StubServerConfigInterface /** * {@inheritdoc} */ - public function getBaseUri(): UriInterface + public function getProviderStateHeaderName(): ?string { - $protocol = $this->secure ? 'https' : 'http'; + return $this->providerStateHeaderName; + } + + /** + * {@inheritdoc} + */ + public function setProviderStateHeaderName(string $providerStateHeaderName): StubServerConfigInterface + { + $this->providerStateHeaderName = $providerStateHeaderName; + + return $this; + } - return new Uri("{$protocol}://{$this->getHost()}:{$this->getPort()}"); + /** + * {@inheritdoc} + */ + public function getToken(): ?string + { + return $this->token; } /** * {@inheritdoc} */ - public function getLog() + public function setToken(?string $token): StubServerConfigInterface { - return $this->log; + $this->token = $token; + + return $this; } /** * {@inheritdoc} */ - public function setLog(string $log): StubServerConfigInterface + public function setUrls(string ...$urls): StubServerConfigInterface { - $this->log = $log; + $this->urls = $urls; return $this; } - public function getPactLocation(): string + /** + * {@inheritdoc} + */ + public function getUrls(): array { - return $this->pactLocation; + return $this->urls; } - public function setPactLocation(string $location) + /** + * {@inheritdoc} + */ + public function getUser(): ?string { - $this->pactLocation = $location; + return $this->user; + } + + /** + * {@inheritdoc} + */ + public function setUser(string $user): StubServerConfigInterface + { + $this->user = $user; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isCors(): bool + { + return $this->cors; + } + + /** + * {@inheritdoc} + */ + public function setCors(bool $cors): StubServerConfigInterface + { + $this->cors = $cors; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isCorsReferer(): bool + { + return $this->corsReferer; + } + + /** + * {@inheritdoc} + */ + public function setCorsReferer(bool $corsReferer): StubServerConfigInterface + { + $this->corsReferer = $corsReferer; return $this; } + /** + * {@inheritdoc} + */ + public function isEmptyProviderState(): bool + { + return $this->emptyProviderState; + } + + /** + * {@inheritdoc} + */ + public function setEmptyProviderState(bool $emptyProviderState): StubServerConfigInterface + { + $this->emptyProviderState = $emptyProviderState; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function isInsecureTls(): bool + { + return $this->insecureTls; + } + + /** + * {@inheritdoc} + */ + public function setInsecureTls(bool $insecureTls): StubServerConfigInterface + { + $this->insecureTls = $insecureTls; + + return $this; + } + + /** + * {@inheritdoc} + */ + public function getBaseUri(): UriInterface + { + return new Uri("http://localhost:{$this->getPort()}"); + } + + /** + * {@inheritdoc} + */ public function getEndpoint(): string { return $this->endpoint; } - public function setEndpoint(string $endpoint) + /** + * {@inheritdoc} + */ + public function setEndpoint(string $endpoint): StubServerConfigInterface { $this->endpoint = $endpoint; diff --git a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php index 2585fd88..ca82dec1 100644 --- a/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php +++ b/src/PhpPact/Standalone/StubService/StubServerConfigInterface.php @@ -11,63 +11,201 @@ 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 + * URL of the pact broker to fetch pacts from + * + * @param UriInterface $brokerUrl * * @return StubServerConfigInterface */ - public function setHost(string $host): self; + public function setBrokerUrl(UriInterface $brokerUrl): self; + + /** + * @param string ...$dirs Directory of pact files to load + * + * @return StubServerConfigInterface + */ + public function setDirs(string ...$dirs): self; + + /** + * @return array + */ + public function getDirs(): array; + + /** + * @return null|string + */ + public function getExtension(): ?string; + + /** + * @param string $extension File extension to use when loading from a directory (default is json) + * + * @return StubServerConfigInterface + */ + public function setExtension(string $extension): self; + + /** + * @param string ...$files Pact file to load + * + * @return StubServerConfigInterface + */ + public function setFiles(string ...$files): self; + + /** + * @return array + */ + public function getFiles(): array; /** - * @return int the port of the stub service + * @return null|string */ - public function getPort(): int; + public function getLogLevel(): ?string; /** - * @param int $port the port of the stub service + * @param string $logLevel Log level (defaults to info) [possible values: error, warn, info, debug, trace, none] + * + * @return StubServerConfigInterface + */ + 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) * * @return StubServerConfigInterface */ 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 * * @return StubServerConfigInterface */ - 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 + * + * @return StubServerConfigInterface + */ + 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 + * + * @return StubServerConfigInterface */ - public function getLog(); + public function setToken(?string $token): self; /** - * @param string $log directory for log output + * @param string ...$urls URL of pact file to fetch * * @return StubServerConfigInterface */ - public function setLog(string $log): self; + public function setUrls(string ...$urls): self; - public function getPactLocation(): string; + /** + * @return array + */ + public function getUrls(): array; - public function setPactLocation(string $location); + /** + * @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 + * + * @return StubServerConfigInterface + */ + public function setUser(string $user): self; + + /** + * @return bool + */ + public function isCors(): bool; + + /** + * @param bool $cors + * + * @return StubServerConfigInterface + */ + public function setCors(bool $cors): self; + + /** + * @return bool + */ + public function isCorsReferer(): bool; + + /** + * @param bool $corsReferer + * + * @return StubServerConfigInterface + */ + public function setCorsReferer(bool $corsReferer): self; + + /** + * @return bool + */ + public function isEmptyProviderState(): bool; + + /** + * @param bool $emptyProviderState + * + * @return StubServerConfigInterface + */ + public function setEmptyProviderState(bool $emptyProviderState): self; + + /** + * @return bool + */ + public function isInsecureTls(): bool; + + /** + * @param bool $insecureTls + * + * @return StubServerConfigInterface + */ + public function setInsecureTls(bool $insecureTls): self; + + /** + * @return UriInterface + */ + public function getBaseUri(): UriInterface; + + /** + * @return string + */ public function getEndpoint(): string; - public function setEndpoint(string $location); + /** + * @param string $endpoint + * + * @return StubServerConfigInterface + */ + public function setEndpoint(string $endpoint): self; } 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/Consumer/InteractionBuilderTest.php b/tests/PhpPact/Consumer/InteractionBuilderTest.php index 06567a76..ffeea585 100644 --- a/tests/PhpPact/Consumer/InteractionBuilderTest.php +++ b/tests/PhpPact/Consumer/InteractionBuilderTest.php @@ -2,47 +2,44 @@ namespace PhpPact\Consumer; +use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use GuzzleHttp\Psr7\Uri; +use PhpPact\Consumer\Exception\MockServerNotStartedException; 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 PhpPact\Standalone\MockService\MockServerConfig; use PHPUnit\Framework\TestCase; class InteractionBuilderTest extends TestCase { - /** @var MockServerHttpServiceInterface */ - private $service; - - /** @var MockServer */ - private $mockServer; + private InteractionBuilder $builder; + private string $consumer = 'test-consumer'; + private string $provider = 'test-provider'; + private string $dir = __DIR__ . '/../../_output'; + private Client $httpClient; /** - * @throws MissingEnvVariableException - * @throws \Exception + * @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(); + $config = new MockServerConfig(); + $config->setProvider($this->provider); + $config->setConsumer($this->consumer); + $config->setPactDir($this->dir); + $config->setPactSpecificationVersion('3.0.0'); + $this->builder = new InteractionBuilder($config); + $this->httpClient = new Client(); } /** - * @throws MissingEnvVariableException - * @throws \Exception + * @throws Exception + * @throws GuzzleException */ - public function testSimpleGet() + public function testMultipleInteractions(): void { $matcher = new Matcher(); @@ -61,21 +58,14 @@ public function testSimpleGet() ]) ->addHeader('Content-Type', 'application/json'); - $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder - ->given('A test request.') - ->uponReceiving('A test response.') + // Simple get request. + $this->builder + ->newInteraction() + ->given('A single item.') + ->uponReceiving('A simple get request.') ->with($request) ->willRespondWith($response); - $this->assertTrue($result); - } - - /** - * @throws MissingEnvVariableException - */ - public function testPostWithBody() - { $request = new ConsumerRequest(); $request ->setPath('/something') @@ -93,26 +83,43 @@ public function testPostWithBody() $response = new ProviderResponse(); $response - ->setStatus(200) + ->setStatus(201) ->addHeader('Content-Type', 'application/json') ->setBody([ 'message' => 'Hello, world!', ]); - $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder - ->given('A test request.') - ->uponReceiving('A test response.') + // Post request with body. + $this->builder + ->newInteraction() + ->given('It does not matter.') + ->uponReceiving('A post request with body.') ->with($request) - ->willRespondWith($response); + ->willRespondWith($response) + ->createMockServer(); + + $this->httpClient->post(new Uri("{$this->builder->getBaseUri()}/something"), [ + 'headers' => ['Content-Type' => 'application/json'], + 'json' => [ + 'someStuff' => 'someThingElse', + 'someNumber' => 11, + 'anArray' => [ + 'some words', + 'some other words here', + 99.99, + ], + ], + 'http_errors' => false, + ]); - $this->assertTrue($result); + $this->assertFalse($this->builder->verify()); } /** - * @throws MissingEnvVariableException + * @throws Exception + * @throws GuzzleException */ - public function testBuildWithEachLikeMatcher() + public function testSingleInteraction(): void { $matcher = new Matcher(); @@ -120,7 +127,10 @@ public function testBuildWithEachLikeMatcher() $request ->setPath('/something') ->setMethod('GET') - ->addHeader('Content-Type', 'application/json'); + ->addHeader('Content-Type', 'application/json') + ->addQueryParameter('test', 1) + ->addQueryParameter('another[]', 2) + ->addQueryParameter('another[]', 33); $response = new ProviderResponse(); $response @@ -133,13 +143,51 @@ public function testBuildWithEachLikeMatcher() ]), ]); - $builder = new InteractionBuilder(new MockServerEnvConfig()); - $result = $builder - ->given('A test request.') - ->uponReceiving('A test response.') + $this->builder + ->newInteraction() + ->given('A list of items.') + ->uponReceiving('A get request with list in body.') ->with($request) - ->willRespondWith($response); + ->willRespondWith($response) + ->createMockServer(); + + $this->httpClient->get(new Uri("{$this->builder->getBaseUri()}/something?test=1&another[]=2&another[]=33"), [ + 'headers' => ['Content-Type' => 'application/json'], + 'http_errors' => false, + ]); + + $this->assertTrue($this->builder->verify()); + } + + public function testGetBaseUriWhenMockServerNotStarted(): void + { + $this->expectException(MockServerNotStartedException::class); + $this->expectExceptionMessage('Mock server is not started.'); + $this->builder->getBaseUri(); + } - $this->assertTrue($result); + public function testVerifyWhenMockServerNotStarted(): void + { + $this->expectException(MockServerNotStartedException::class); + $this->expectExceptionMessage('Mock server is not started.'); + $this->builder->verify(); + } + + public function testGetBaseUriWhenMockServerStopped(): void + { + $this->builder->createMockServer(); + $this->builder->verify(); + $this->expectException(MockServerNotStartedException::class); + $this->expectExceptionMessage('Mock server is not started.'); + $this->builder->getBaseUri(); + } + + public function testVerifyWhenMockServerStopped(): void + { + $this->builder->createMockServer(); + $this->builder->verify(); + $this->expectException(MockServerNotStartedException::class); + $this->expectExceptionMessage('Mock server is not started.'); + $this->builder->verify(); } } diff --git a/tests/PhpPact/Consumer/Matcher/MatcherTest.php b/tests/PhpPact/Consumer/Matcher/MatcherTest.php index 6db70501..16bfda80 100644 --- a/tests/PhpPact/Consumer/Matcher/MatcherTest.php +++ b/tests/PhpPact/Consumer/Matcher/MatcherTest.php @@ -7,8 +7,7 @@ class MatcherTest extends TestCase { - /** @var Matcher */ - private $matcher; + private Matcher $matcher; protected function setUp(): void { diff --git a/tests/PhpPact/Consumer/MessageBuilderTest.php b/tests/PhpPact/Consumer/MessageBuilderTest.php new file mode 100644 index 00000000..bdd0a59d --- /dev/null +++ b/tests/PhpPact/Consumer/MessageBuilderTest.php @@ -0,0 +1,52 @@ +setProvider($this->provider); + $config->setConsumer($this->consumer); + $config->setPactDir($this->dir); + $config->setPactSpecificationVersion('3.0.0'); + $this->builder = new MessageBuilder($config); + } + + /** + * @throws Exception + */ + public function testMessage() + { + $contents = new \stdClass(); + $contents->song = 'And the wind whispers Mary'; + + $metadata = ['queue' => 'And the clowns have all gone to bed', 'routing_key' => 'And the clowns have all gone to bed']; + + $this->builder + ->given('You can hear happiness staggering on down the street') + ->expectsToReceive('footprints dressed in red') + ->withMetadata($metadata) + ->withContent($contents); + + $this->builder->setCallback(function (string $message) use ($contents) { + $obj = \json_decode($message); + $this->assertEquals($contents, $obj->contents); + }); + + $this->assertTrue($this->builder->verify()); + } +} diff --git a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php index 71ce76e3..d866ec91 100644 --- a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php +++ b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php @@ -17,11 +17,9 @@ public function testSerializing() 'currentCity' => 'Austin', ]); - $data = \json_decode(\json_encode($model->jsonSerialize()), true); - - $this->assertEquals('PUT', $data['method']); - $this->assertEquals('application/json', $data['headers']['Content-Type']); - $this->assertEquals('/somepath', $data['path']); - $this->assertEquals('Austin', $data['body']['currentCity']); + $this->assertEquals('PUT', $model->getMethod()); + $this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders()); + $this->assertEquals('/somepath', $model->getPath()); + $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } } diff --git a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php index 17291378..69918480 100644 --- a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php +++ b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php @@ -16,10 +16,8 @@ public function testSerializing() 'currentCity' => 'Austin', ]); - $data = \json_decode(\json_encode($model->jsonSerialize()), true); - - $this->assertEquals(200, $data['status']); - $this->assertEquals('application/json', $data['headers']['Content-Type']); - $this->assertEquals('Austin', $data['body']['currentCity']); + $this->assertEquals(200, $model->getStatus()); + $this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders()); + $this->assertEquals('{"currentCity":"Austin"}', $model->getBody()); } } diff --git a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php b/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php deleted file mode 100644 index 18e27893..00000000 --- a/tests/PhpPact/Standalone/Broker/BrokerConfigTest.php +++ /dev/null @@ -1,43 +0,0 @@ -setHost($host) - ->setPort($port) - ->setProvider($provider) - ->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()); - 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()); - } -} diff --git a/tests/PhpPact/Standalone/Broker/BrokerTest.php b/tests/PhpPact/Standalone/Broker/BrokerTest.php index fd55efec..913697a2 100644 --- a/tests/PhpPact/Standalone/Broker/BrokerTest.php +++ b/tests/PhpPact/Standalone/Broker/BrokerTest.php @@ -50,7 +50,7 @@ public function describeVersion(): void $this->assertArrayHasKey('number', (new Broker( (new BrokerConfig()) ->setPacticipant(\rawurlencode('Animal Profile Service')) - ->setBrokerUri(new Uri('https://test.pact.dius.com.au')) + ->setBrokerUri(new Uri('https://test.pactflow.io')) ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1') ))->describeVersion()); @@ -65,7 +65,7 @@ public function listLatestPactVersions(): void { $this->assertArrayHasKey('pacts', (new Broker( (new BrokerConfig()) - ->setBrokerUri(new Uri('https://test.pact.dius.com.au')) + ->setBrokerUri(new Uri('https://test.pactflow.io')) ->setBrokerUsername('dXfltyFMgNOFZAxr8io9wJ37iUpY42M') ->setBrokerPassword('O5AIZWxelWbLvqMd8PkAVycBJh2Psyg1') ))->listLatestPactVersions()); diff --git a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php index 91efbfae..daf251d5 100644 --- a/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php +++ b/tests/PhpPact/Standalone/MockServer/MockServerConfigTest.php @@ -1,6 +1,6 @@ setHost($host) - ->setPort($port) ->setProvider($provider) ->setConsumer($consumer) ->setPactDir($pactDir) - ->setPactFileWriteMode($pactFileWriteMode) - ->setLog($log) - ->setPactSpecificationVersion($pactSpecificationVersion) - ->setCors($cors); + ->setPactSpecificationVersion($pactSpecificationVersion); - static::assertSame($host, $subject->getHost()); - static::assertSame($port, $subject->getPort()); static::assertSame($provider, $subject->getProvider()); 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()); } } diff --git a/tests/PhpPact/Standalone/MockServer/MockServerTest.php b/tests/PhpPact/Standalone/MockServer/MockServerTest.php deleted file mode 100644 index fe63811a..00000000 --- a/tests/PhpPact/Standalone/MockServer/MockServerTest.php +++ /dev/null @@ -1,68 +0,0 @@ -start(); - $this->assertTrue(\is_int($pid)); - } finally { - $result = $mockServer->stop(); - $this->assertTrue($result); - } - } - - /** - * @throws MissingEnvVariableException - * @throws \Exception - */ - public function testStartAndStopWithRecognizedTimeout() - { - // the mock server actually takes more than one second to be ready - // we use this fact to test the timeout - $orig = \getenv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT'); - \putenv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT=1'); - - $httpService = $this->getMockBuilder(MockServerHttpService::class) - ->disableOriginalConstructor() - ->getMock(); - - $connectionException = $this->getMockBuilder(ConnectException::class) - ->disableOriginalConstructor() - ->getMock(); - - // take sth lower than the default value - $httpService->expects($this->atMost(5)) - ->method('healthCheck') - ->will($this->returnCallback(function () use ($connectionException) { - throw $connectionException; - })); - - try { - $mockServer = new MockServer(new MockServerEnvConfig(), $httpService); - $mockServer->start(); - $this->fail('MockServer should not pass defined health check.'); - } catch (HealthCheckFailedException $e) { - $this->assertTrue(true); - } finally { - $mockServer->stop(); - \putenv('PACT_MOCK_SERVER_HEALTH_CHECK_TIMEOUT=' . $orig); - } - } -} diff --git a/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php b/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php deleted file mode 100644 index cfb86df6..00000000 --- a/tests/PhpPact/Standalone/MockServer/Service/MockServerHttpServiceTest.php +++ /dev/null @@ -1,210 +0,0 @@ -config = new MockServerEnvConfig(); - $this->mockServer = new MockServer($this->config); - $this->mockServer->start(); - $this->service = new MockServerHttpService(new GuzzleClient(), $this->config); - } - - protected function tearDown(): void - { - $this->mockServer->stop(); - } - - /** - * @throws ConnectionException - */ - public function testHealthCheck() - { - $result = $this->service->healthCheck(); - $this->assertTrue($result); - } - - public function testRegisterInteraction() - { - $request = new ConsumerRequest(); - $request - ->setPath('/example') - ->setMethod('GET'); - $response = new ProviderResponse(); - $response->setStatus(200); - - $interaction = new Interaction(); - $interaction - ->setDescription('Fake description') - ->setProviderState('Fake provider state') - ->setRequest($request) - ->setResponse($response); - - $result = $this->service->registerInteraction($interaction); - - $this->assertTrue($result); - } - - public function testDeleteAllInteractions() - { - $result = $this->service->deleteAllInteractions(); - $this->assertTrue($result); - } - - public function testVerifyInteractions() - { - $result = $this->service->verifyInteractions(); - $this->assertTrue($result); - } - - public function testVerifyInteractionsFailure() - { - $request = new ConsumerRequest(); - $request - ->setPath('/example') - ->setMethod('GET'); - - $response = new ProviderResponse(); - $response->setStatus(200); - - $interaction = new Interaction(); - $interaction - ->setDescription('Some description') - ->setProviderState('Some state') - ->setRequest($request) - ->setResponse($response); - $this->service->registerInteraction($interaction); - - $this->expectException(ServerException::class); - $result = $this->service->verifyInteractions(); - $this->assertFalse($result); - } - - public function testGetPactJson() - { - $result = $this->service->getPactJson(); - $this->assertEquals('{"consumer":{"name":"someConsumer"},"provider":{"name":"someProvider"},"interactions":[],"metadata":{"pactSpecification":{"version":"2.0.0"}}}', $result); - } - - public function testFullGetInteraction() - { - $request = new ConsumerRequest(); - $request - ->setPath('/example') - ->setMethod('GET') - ->setQuery('enabled=true') - ->addQueryParameter('order', 'asc') - ->addQueryParameter('value', '12') - ->addHeader('Content-Type', 'application/json'); - - $expectedResponseBody = [ - 'message' => 'Hello, world!', - ]; - $response = new ProviderResponse(); - $response - ->setStatus(200) - ->setBody($expectedResponseBody) - ->addHeader('Content-Type', 'application/json'); - - $interaction = new Interaction(); - $interaction - ->setDescription('Fake description') - ->setProviderState('Fake provider state') - ->setRequest($request) - ->setResponse($response); - - $result = $this->service->registerInteraction($interaction); - - $this->assertTrue($result); - - $client = new GuzzleClient(); - $uri = $this->config->getBaseUri()->withPath('/example')->withQuery('enabled=true&order=asc&value=12'); - $response = $client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - $body = $response->getBody()->getContents(); - $this->assertEquals(\json_encode($expectedResponseBody), $body); - $this->assertEquals($response->getHeaderLine('Access-Control-Allow-Origin'), '*', 'CORS flag not set properly'); - $this->assertEquals(200, $response->getStatusCode()); - } - - /** - * @throws MissingEnvVariableException - * @throws \Exception - */ - public function testMatcherWithMockServer() - { - $matcher = new Matcher(); - - $category = new stdClass(); - $category->name = $matcher->term('Games', '[gbBG]'); - - $request = new ConsumerRequest(); - $request - ->setPath('/test') - ->setMethod('GET'); - - $response = new ProviderResponse(); - $response - ->setStatus(200) - ->addHeader('Content-Type', 'application/json') - ->setBody([ - 'results' => $matcher->eachLike($category), - ]); - - $config = new MockServerEnvConfig(); - $interaction = new InteractionBuilder($config); - $interaction - ->given('Something') - ->uponReceiving('Stuff') - ->with($request) - ->willRespondWith($response); - - $client = new GuzzleClient(); - $uri = $this->config->getBaseUri()->withPath('/test'); - $client->get($uri, [ - 'headers' => [ - 'Content-Type' => 'application/json', - ], - ]); - - $httpClient = new MockServerHttpService(new GuzzleClient(), $config); - - $pact = \json_decode($httpClient->getPactJson(), true); - - $this->assertArrayHasKey('$.body.results[*].name', $pact['interactions'][0]['response']['matchingRules']); - } -} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php b/tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php deleted file mode 100644 index 6531e098..00000000 --- a/tests/PhpPact/Standalone/ProviderVerifier/VerifierProcessTest.php +++ /dev/null @@ -1,112 +0,0 @@ - 'bar']; - - $scripts = $this->createMock(Scripts::class); - $scripts->expects($this->once()) - ->method('getProviderVerifier') - ->will($this->returnValue($verifier)); - - $logger = $this->createMock(LoggerInterface::class); - - $processRunner = $this->createMock(ProcessRunner::class); - - $installManager = $this->createMock(InstallManager::class); - $installManager->expects($this->once()) - ->method('install') - ->will($this->returnValue($scripts)); - - $processRunnerFactory = $this->createMock(ProcessRunnerFactory::class); - $processRunnerFactory->expects($this->once()) - ->method('createRunner') - ->with($this->equalTo($verifier), $this->equalTo($arguments), $this->equalTo($logger)) - ->will($this->returnValue($processRunner)); - - $process = new VerifierProcess($installManager, $processRunnerFactory); - $process->setLogger($logger); - $process->run($arguments, 42, 23); - } - - public function testRunWithDefaultLogger() - { - $verifier = 'foo'; - $arguments = ['foo' => 'bar']; - - $scripts = $this->createMock(Scripts::class); - $scripts->expects($this->once()) - ->method('getProviderVerifier') - ->will($this->returnValue($verifier)); - - $processRunner = $this->createMock(ProcessRunner::class); - - $installManager = $this->createMock(InstallManager::class); - $installManager->expects($this->once()) - ->method('install') - ->will($this->returnValue($scripts)); - - $processRunnerFactory = $this->createMock(ProcessRunnerFactory::class); - $processRunnerFactory->expects($this->once()) - ->method('createRunner') - ->with($this->equalTo($verifier), $this->equalTo($arguments)) - ->will($this->returnValue($processRunner)); - - $process = new VerifierProcess($installManager, $processRunnerFactory); - $process->run($arguments, 42, 23); - } - - public function testRunForwardsException() - { - $this->expectExceptionMessage('foo'); - $this->expectException(\RuntimeException::class); - - $verifier = 'foo'; - $arguments = ['foo' => 'bar']; - - $expectedException = new \RuntimeException('foo'); - - $scripts = $this->createMock(Scripts::class); - $scripts->expects($this->once()) - ->method('getProviderVerifier') - ->will($this->returnValue($verifier)); - - $processRunner = $this->createMock(ProcessRunner::class); - $processRunner->expects($this->once()) - ->method('runBlocking') - ->will( - $this->returnCallback( - function () use ($expectedException) { - throw $expectedException; - } - ) - ); - - $installManager = $this->createMock(InstallManager::class); - $installManager->expects($this->once()) - ->method('install') - ->will($this->returnValue($scripts)); - - $processRunnerFactory = $this->createMock(ProcessRunnerFactory::class); - $processRunnerFactory->expects($this->once()) - ->method('createRunner') - ->with($this->equalTo($verifier), $this->equalTo($arguments)) - ->will($this->returnValue($processRunner)); - - $process = new VerifierProcess($installManager, $processRunnerFactory); - $process->run($arguments, 42, 23); - } -} diff --git a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php index c3f79de4..450fdd44 100644 --- a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php +++ b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php @@ -2,250 +2,37 @@ namespace PhpPact\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\Installer\InstallManager; -use PhpPact\Standalone\Installer\Model\Scripts; -use PhpPact\Standalone\ProviderVerifier\Model\ConsumerVersionSelectors; +use PhpPact\Standalone\Installer\Exception\FileDownloadFailureException; +use PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException; use PhpPact\Standalone\ProviderVerifier\Model\VerifierConfig; use PHPUnit\Framework\TestCase; -use Psr\Http\Message\RequestInterface; +use Symfony\Component\Process\Process; 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', $arguments); - $this->assertContains('--log=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); - } - /** - * Strip spaces for Windows CMD + * @throws NoDownloaderFoundException + * @throws FileDownloadFailureException */ - private function stripSpaces($arr) + public function testVerify(): void { - $newArr = []; - foreach ($arr as $str) { - $newArr[] = str_ireplace(' ', '', $str); - } - return $newArr; - } - - public function testGetArgumentsEmptyConfig() - { - $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 - * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - */ - 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); - - $installerMock = $this->createMock(InstallManager::class); - - $verifierProcessMock = $this->createMock(VerifierProcess::class); - $verifierProcessMock->expects($this->once()) - ->method('run') - ->with( - $this->callback(function ($args) use ($expectedUrltoBroker) { - return \in_array($expectedUrltoBroker, $args); - }) - ); + $provider = new Process(['php', '-S', 'localhost:8000', '-t', __DIR__ . '/../../../_public']); + $provider->start(); + $provider->waitUntil(function ($type, $output) { + return false !== \strpos($output, 'Development Server (http://localhost:8000) started'); + }); $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, $installerMock, $verifierProcessMock); - - $verifier->verify($consumerName, $tag, $version); - } - - public function dataProviderForBrokerPathTest() - { - $consumerName = 'someProviderName'; - $providerName = 'someProviderName'; - $tag = '1.0.0'; - $version = '11111'; - - 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/"], - ]; - } - - /** - * @dataProvider provideDataForVerifyAll - * - * @param string $providerName - * @param string $providerVersion - * @param bool $forceLatest - * @param mixed $expectedProviderVersion - * - * @throws \PhpPact\Standalone\Installer\Exception\FileDownloadFailureException - * @throws \PhpPact\Standalone\Installer\Exception\NoDownloaderFoundException - */ - public function testIfDataForVerifyAllIsConvertedCorrectly($providerName, $providerVersion) - { - $expectedUrl1 = 'expectedUrl1'; - $expectedUrl2 = 'expectedUrl2'; - $expectedPactUrls = [$expectedUrl1, $expectedUrl2]; - - $installerMock = $this->createMock(InstallManager::class); - - $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, $installerMock, $verifierProcessMock, $brokerHttpClient); - $verifier->verifyAll(); - } - - public function provideDataForVerifyAll() - { - 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'; - } - - $scriptsMock = $this->createMock(Scripts::class); - $scriptsMock->method('getProviderVerifier')->willReturn($cmd); - - $installerMock = $this->createMock(InstallManager::class); - $installerMock->method('install')->willReturn($scriptsMock); - - $process = new VerifierProcess($installerMock); - - $logger = new Logger('console', [$handler = new TestHandler()]); - $process->setLogger($logger); - - try { - $exception = null; - $process->run([], 60, 10); - } catch (\Exception $e) { - $exception = $e; - } - - $logMessages = $handler->getRecords(); + $config + ->setPort(8000) + ->setProviderName('someProvider') + ->setProviderVersion('1.0.0') + ->setProviderBranch('main'); - $this->assertGreaterThan(2, \count($logMessages)); - $this->assertStringContainsString('first line', $logMessages[\count($logMessages) - 2]['message']); - $this->assertStringContainsString('second line', $logMessages[\count($logMessages) - 1]['message']); + $verifier = new Verifier(); + $verifier->newHandle($config); + $verifier->addDirectory(__DIR__ . '/../../../_resources'); - $this->assertNotNull($exception); + $this->assertTrue($verifier->verify()); } } 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/StubServerConfigTest.php b/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php deleted file mode 100644 index 53613800..00000000 --- a/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php +++ /dev/null @@ -1,28 +0,0 @@ -setPactLocation($pactLocation) - ->setHost($host) - ->setPort($port) - ->setLog($log); - - static::assertSame($pactLocation, $subject->getPactLocation()); - static::assertSame($host, $subject->getHost()); - static::assertSame($port, $subject->getPort()); - static::assertSame($log, $subject->getLog()); - } -} diff --git a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php b/tests/PhpPact/Standalone/StubService/Service/StubServerHttpServiceTest.php similarity index 78% rename from tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php rename to tests/PhpPact/Standalone/StubService/Service/StubServerHttpServiceTest.php index 4e029ece..b55c87da 100644 --- a/tests/PhpPact/Standalone/StubServer/Service/StubServerHttpServiceTest.php +++ b/tests/PhpPact/Standalone/StubService/Service/StubServerHttpServiceTest.php @@ -11,14 +11,9 @@ class StubServerHttpServiceTest extends TestCase { - /** @var StubServerHttpServiceInterface */ - private $service; - - /** @var StubServer */ - private $stubServer; - - /** @var StubServerConfigInterface */ - private $config; + private StubServerHttpServiceInterface $service; + private StubServer $stubServer; + private StubServerConfigInterface $config; /** * @throws MissingEnvVariableException @@ -27,18 +22,16 @@ class StubServerHttpServiceTest extends TestCase protected function setUp(): void { $pactLocation = __DIR__ . '/../../../../_resources/someconsumer-someprovider.json'; - $host = 'localhost'; $port = 7201; $endpoint = 'test'; $this->config = (new StubServerConfig()) - ->setPactLocation($pactLocation) - ->setHost($host) + ->setFiles($pactLocation) ->setPort($port) ->setEndpoint($endpoint); $this->stubServer = new StubServer($this->config); - $this->stubServer->start(10); + $this->stubServer->start(); $this->service = new StubServerHttpService(new GuzzleClient(), $this->config); } diff --git a/tests/PhpPact/Standalone/StubService/StubServerConfigTest.php b/tests/PhpPact/Standalone/StubService/StubServerConfigTest.php new file mode 100644 index 00000000..2becb556 --- /dev/null +++ b/tests/PhpPact/Standalone/StubService/StubServerConfigTest.php @@ -0,0 +1,24 @@ +setDirs($pactLocation) + ->setPort($port) + ->setLogLevel($logLevel); + + static::assertSame([$pactLocation], $subject->getDirs()); + static::assertSame($port, $subject->getPort()); + static::assertSame($logLevel, $subject->getLogLevel()); + } +} diff --git a/tests/PhpPact/Standalone/StubServer/StubServerTest.php b/tests/PhpPact/Standalone/StubService/StubServerTest.php similarity index 63% rename from tests/PhpPact/Standalone/StubServer/StubServerTest.php rename to tests/PhpPact/Standalone/StubService/StubServerTest.php index cf12606d..21db9c29 100644 --- a/tests/PhpPact/Standalone/StubServer/StubServerTest.php +++ b/tests/PhpPact/Standalone/StubService/StubServerTest.php @@ -1,9 +1,7 @@ setPactLocation($pactLocation) - ->setHost($host) - ->setPort($port) - ->setEndpoint($endpoint); + ->setFiles($pactLocation) + ->setPort($port); $stubServer = new StubServer($subject); $pid = $stubServer->start(); diff --git a/tests/PhpPact/php.ini b/tests/PhpPact/php.ini index 243c4ea6..a62e7fec 100644 --- a/tests/PhpPact/php.ini +++ b/tests/PhpPact/php.ini @@ -51,6 +51,7 @@ extension=php_openssl.dll extension=fileinfo extension=intl extension=php_sockets.dll +extension=php_ffi.dll [CLI Server] cli_server.color = On diff --git a/tests/_output/.gitignore b/tests/_output/.gitignore new file mode 100644 index 00000000..a6c57f5f --- /dev/null +++ b/tests/_output/.gitignore @@ -0,0 +1 @@ +*.json 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', + ], + ], +]);