From 1e22634839fd0458b27ff2fc68aaabccf0c41a6f Mon Sep 17 00:00:00 2001 From: "tien.xuan.vo" Date: Sat, 23 Dec 2023 07:07:02 +0700 Subject: [PATCH] test(compatibility-suite): Implement V3 scenarios --- .github/workflows/compatibility-suite.yml | 16 + behat.yml | 6 + .../public/generators/.gitignore | 2 + .../public/generators/index.php | 33 ++ compatibility-suite/suites/v3/generators.yml | 28 ++ .../suites/v3/http/consumer.yml | 13 + .../suites/v3/http/provider.yml | 34 ++ .../suites/v3/matching-rules.yml | 18 + .../suites/v3/message/consumer.yml | 16 + .../suites/v3/message/provider.yml | 26 ++ .../Context/V3/BodyGeneratorsContext.php | 22 ++ .../tests/Context/V3/Http/ConsumerContext.php | 99 ++++++ .../tests/Context/V3/Http/ProviderContext.php | 57 ++++ .../Context/V3/Message/ConsumerContext.php | 307 ++++++++++++++++++ .../Context/V3/Message/ProviderContext.php | 166 ++++++++++ .../Context/V3/RequestGeneratorsContext.php | 123 +++++++ .../Context/V3/RequestMatchingContext.php | 169 ++++++++++ .../Context/V3/ResponseGeneratorsContext.php | 86 +++++ compatibility-suite/tests/Model/Generator.php | 34 ++ compatibility-suite/tests/Model/Message.php | 42 +++ .../tests/Service/BodyStorage.php | 18 + .../tests/Service/BodyStorageInterface.php | 10 + .../tests/Service/BodyValidator.php | 52 +++ .../tests/Service/BodyValidatorInterface.php | 10 + .../tests/Service/GeneratorConverter.php | 21 ++ .../Service/GeneratorConverterInterface.php | 11 + .../tests/Service/GeneratorParser.php | 52 +++ .../Service/GeneratorParserInterface.php | 13 + .../tests/Service/GeneratorServer.php | 63 ++++ .../Service/GeneratorServerInterface.php | 20 ++ .../tests/Service/MessageGeneratorBuilder.php | 44 +++ .../MessageGeneratorBuilderInterface.php | 10 + .../tests/Service/MessagePactWriter.php | 45 +++ .../Service/MessagePactWriterInterface.php | 12 + compatibility-suite/tests/Service/Parser.php | 29 ++ .../tests/Service/ParserInterface.php | 6 + .../tests/Service/RequestGeneratorBuilder.php | 58 ++++ .../RequestGeneratorBuilderInterface.php | 10 + .../Service/ResponseGeneratorBuilder.php | 48 +++ .../ResponseGeneratorBuilderInterface.php | 10 + .../tests/ServiceContainer/V3.php | 35 ++ 41 files changed, 1874 insertions(+) create mode 100644 compatibility-suite/public/generators/.gitignore create mode 100644 compatibility-suite/public/generators/index.php create mode 100644 compatibility-suite/suites/v3/generators.yml create mode 100644 compatibility-suite/suites/v3/http/consumer.yml create mode 100644 compatibility-suite/suites/v3/http/provider.yml create mode 100644 compatibility-suite/suites/v3/matching-rules.yml create mode 100644 compatibility-suite/suites/v3/message/consumer.yml create mode 100644 compatibility-suite/suites/v3/message/provider.yml create mode 100644 compatibility-suite/tests/Context/V3/BodyGeneratorsContext.php create mode 100644 compatibility-suite/tests/Context/V3/Http/ConsumerContext.php create mode 100644 compatibility-suite/tests/Context/V3/Http/ProviderContext.php create mode 100644 compatibility-suite/tests/Context/V3/Message/ConsumerContext.php create mode 100644 compatibility-suite/tests/Context/V3/Message/ProviderContext.php create mode 100644 compatibility-suite/tests/Context/V3/RequestGeneratorsContext.php create mode 100644 compatibility-suite/tests/Context/V3/RequestMatchingContext.php create mode 100644 compatibility-suite/tests/Context/V3/ResponseGeneratorsContext.php create mode 100644 compatibility-suite/tests/Model/Generator.php create mode 100644 compatibility-suite/tests/Model/Message.php create mode 100644 compatibility-suite/tests/Service/BodyStorage.php create mode 100644 compatibility-suite/tests/Service/BodyStorageInterface.php create mode 100644 compatibility-suite/tests/Service/BodyValidator.php create mode 100644 compatibility-suite/tests/Service/BodyValidatorInterface.php create mode 100644 compatibility-suite/tests/Service/GeneratorConverter.php create mode 100644 compatibility-suite/tests/Service/GeneratorConverterInterface.php create mode 100644 compatibility-suite/tests/Service/GeneratorParser.php create mode 100644 compatibility-suite/tests/Service/GeneratorParserInterface.php create mode 100644 compatibility-suite/tests/Service/GeneratorServer.php create mode 100644 compatibility-suite/tests/Service/GeneratorServerInterface.php create mode 100644 compatibility-suite/tests/Service/MessageGeneratorBuilder.php create mode 100644 compatibility-suite/tests/Service/MessageGeneratorBuilderInterface.php create mode 100644 compatibility-suite/tests/Service/MessagePactWriter.php create mode 100644 compatibility-suite/tests/Service/MessagePactWriterInterface.php create mode 100644 compatibility-suite/tests/Service/RequestGeneratorBuilder.php create mode 100644 compatibility-suite/tests/Service/RequestGeneratorBuilderInterface.php create mode 100644 compatibility-suite/tests/Service/ResponseGeneratorBuilder.php create mode 100644 compatibility-suite/tests/Service/ResponseGeneratorBuilderInterface.php create mode 100644 compatibility-suite/tests/ServiceContainer/V3.php diff --git a/.github/workflows/compatibility-suite.yml b/.github/workflows/compatibility-suite.yml index 38c017ba0..4a44444b7 100644 --- a/.github/workflows/compatibility-suite.yml +++ b/.github/workflows/compatibility-suite.yml @@ -38,3 +38,19 @@ jobs: - name: Run Behat run: vendor/bin/behat compatibility-suite/pact-compatibility-suite/features/V2 + v3: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + coverage: none + + - uses: ramsey/composer-install@v2 + + - name: Run Behat + run: vendor/bin/behat compatibility-suite/pact-compatibility-suite/features/V3 diff --git a/behat.yml b/behat.yml index e2cb9df09..568300321 100644 --- a/behat.yml +++ b/behat.yml @@ -3,3 +3,9 @@ imports: - 'compatibility-suite/suites/v1/http/provider.yml' - 'compatibility-suite/suites/v2/http/consumer.yml' - 'compatibility-suite/suites/v2/http/provider.yml' + - 'compatibility-suite/suites/v3/http/consumer.yml' + - 'compatibility-suite/suites/v3/http/provider.yml' + - 'compatibility-suite/suites/v3/message/consumer.yml' + - 'compatibility-suite/suites/v3/message/provider.yml' + - 'compatibility-suite/suites/v3/generators.yml' + - 'compatibility-suite/suites/v3/matching-rules.yml' diff --git a/compatibility-suite/public/generators/.gitignore b/compatibility-suite/public/generators/.gitignore new file mode 100644 index 000000000..35340fb5b --- /dev/null +++ b/compatibility-suite/public/generators/.gitignore @@ -0,0 +1,2 @@ +*.json +*.txt diff --git a/compatibility-suite/public/generators/index.php b/compatibility-suite/public/generators/index.php new file mode 100644 index 000000000..f74b84e22 --- /dev/null +++ b/compatibility-suite/public/generators/index.php @@ -0,0 +1,33 @@ +put('/request-generators', function (Request $request, Response $response) { + file_put_contents(__DIR__ . '/body.json', $request->getBody()->getContents()); + file_put_contents(__DIR__ . '/headers.json', json_encode($request->getHeaders())); + file_put_contents(__DIR__ . '/queryParams.json', json_encode($request->getQueryParams())); + + return $response; +}); + +$app->post('/return-provider-state-values', function (Request $request, Response $response) { + $values = $request->getQueryParams(); + + $response->getBody()->write(json_encode($values)); + + return $response->withHeader('Content-Type', 'application/json'); +}); + +$app->any('{path:.*}', function ($request, $response, array $args) { + file_put_contents(__DIR__ . '/path.txt', $args['path']); + + return $response; +}); + +$app->run(); diff --git a/compatibility-suite/suites/v3/generators.yml b/compatibility-suite/suites/v3/generators.yml new file mode 100644 index 000000000..bab7c3b30 --- /dev/null +++ b/compatibility-suite/suites/v3/generators.yml @@ -0,0 +1,28 @@ +default: + suites: + v3_generators: + paths: + - '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/generators.feature' + - '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/http_generators.feature' + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V3\BodyGeneratorsContext': + - '@body_validator' + - 'PhpPactTest\CompatibilitySuite\Context\V3\RequestGeneratorsContext': + - '@interaction_builder' + - '@request_generator_builder' + - '@interactions_storage' + - '@pact_writer' + - '@generator_server' + - '@provider_verifier' + - '@body_storage' + - 'PhpPactTest\CompatibilitySuite\Context\V3\ResponseGeneratorsContext': + - '@interaction_builder' + - '@response_generator_builder' + - '@interactions_storage' + - '@server' + - '@client' + - '@body_storage' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V3 diff --git a/compatibility-suite/suites/v3/http/consumer.yml b/compatibility-suite/suites/v3/http/consumer.yml new file mode 100644 index 000000000..e164ed807 --- /dev/null +++ b/compatibility-suite/suites/v3/http/consumer.yml @@ -0,0 +1,13 @@ +default: + suites: + v3_http_consumer: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/http_consumer.feature' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V3\Http\ConsumerContext': + - '@interaction_builder' + - '@pact_writer' + - '@interactions_storage' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V3 diff --git a/compatibility-suite/suites/v3/http/provider.yml b/compatibility-suite/suites/v3/http/provider.yml new file mode 100644 index 000000000..7d8c01118 --- /dev/null +++ b/compatibility-suite/suites/v3/http/provider.yml @@ -0,0 +1,34 @@ +default: + suites: + v3_http_provider: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/http_provider.feature' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\Shared\InteractionsContext': + - '@interactions_storage' + - '@request_matching_rule_builder' + - '@response_matching_rule_builder' + - '@matching_rules_storage' + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Transform\InteractionsContext': + - '@interaction_builder' + - '@matching_rules_storage' + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\ProviderStateContext': + - '@provider_state_server' + - 'PhpPactTest\CompatibilitySuite\Context\Shared\ProviderContext': + - '@server' + - '@provider_verifier' + - '@provider_state_server' + - 'PhpPactTest\CompatibilitySuite\Context\V1\Http\ProviderContext': + - '@server' + - '@pact_writer' + - '@pact_broker' + - '@response_builder' + - '@interactions_storage' + - '@provider_verifier' + - 'PhpPactTest\CompatibilitySuite\Context\V3\Http\ProviderContext': + - '@pact_writer' + - '@provider_state_server' + - '@provider_verifier' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V3 diff --git a/compatibility-suite/suites/v3/matching-rules.yml b/compatibility-suite/suites/v3/matching-rules.yml new file mode 100644 index 000000000..090cb6543 --- /dev/null +++ b/compatibility-suite/suites/v3/matching-rules.yml @@ -0,0 +1,18 @@ +default: + suites: + v3_matching_rules: + paths: + - '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/matching_rules.feature' + - '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/http_matching.feature' + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V3\RequestMatchingContext': + - '@interaction_builder' + - '@server' + - '@client' + - '@interactions_storage' + - '@request_builder' + - '@request_matching_rule_builder' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V3 diff --git a/compatibility-suite/suites/v3/message/consumer.yml b/compatibility-suite/suites/v3/message/consumer.yml new file mode 100644 index 000000000..662dc2aec --- /dev/null +++ b/compatibility-suite/suites/v3/message/consumer.yml @@ -0,0 +1,16 @@ +default: + suites: + v3_message_consumer: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/message_consumer.feature' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V3\Message\ConsumerContext': + - '@specification' + - '@message_generator_builder' + - '@parser' + - '@body_validator' + - '@body_storage' + - '@fixture_loader' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V3 diff --git a/compatibility-suite/suites/v3/message/provider.yml b/compatibility-suite/suites/v3/message/provider.yml new file mode 100644 index 000000000..41dbc7e95 --- /dev/null +++ b/compatibility-suite/suites/v3/message/provider.yml @@ -0,0 +1,26 @@ +default: + suites: + v3_message_provider: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V3/message_provider.feature' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\ProviderStateContext': + - '@provider_state_server' + - 'PhpPactTest\CompatibilitySuite\Context\Shared\ProviderContext': + - '@server' + - '@provider_verifier' + - '@provider_state_server' + - 'PhpPactTest\CompatibilitySuite\Context\V3\Message\ProviderContext': + - '@server' + - '@interaction_builder' + - '@interactions_storage' + - '@message_pact_writer' + - '@provider_verifier' + - '@parser' + - '@fixture_loader' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V3 + + # filters: + # tags: ~@wip diff --git a/compatibility-suite/tests/Context/V3/BodyGeneratorsContext.php b/compatibility-suite/tests/Context/V3/BodyGeneratorsContext.php new file mode 100644 index 000000000..b829e7f3b --- /dev/null +++ b/compatibility-suite/tests/Context/V3/BodyGeneratorsContext.php @@ -0,0 +1,22 @@ +validator->validateType($path, $type); + } +} diff --git a/compatibility-suite/tests/Context/V3/Http/ConsumerContext.php b/compatibility-suite/tests/Context/V3/Http/ConsumerContext.php new file mode 100644 index 000000000..6105d551b --- /dev/null +++ b/compatibility-suite/tests/Context/V3/Http/ConsumerContext.php @@ -0,0 +1,99 @@ +interaction = $this->builder->build([ + 'description' => 'interaction for a consumer test', + 'method' => 'GET', + 'path' => '/provider-states', + ]); + $this->storage->add(InteractionsStorageInterface::PACT_WRITER_DOMAIN, $this->id, $this->interaction); + } + + /** + * @Given a provider state :state is specified + */ + public function aProviderStateIsSpecified(string $state): void + { + $this->interaction->addProviderState($state, []); + } + + /** + * @When the Pact file for the test is generated + */ + public function thePactFileForTheTestIsGenerated(): void + { + $this->pactWriter->write($this->id); + } + + /** + * @Then the interaction in the Pact file will contain :states provider state(s) + */ + public function theInteractionInThePactFileWillContainProviderStates(int $states): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertCount($states, $pact['interactions'][0]['providerStates']); + } + + /** + * @Then the interaction in the Pact file will contain provider state :name + */ + public function theInteractionInThePactFileWillContainProviderState(string $name): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertNotEmpty(array_filter( + $pact['interactions'][0]['providerStates'], + fn (array $providerState) => $providerState['name'] === $name + )); + } + + /** + * @Given a provider state :state is specified with the following data: + */ + public function aProviderStateIsSpecifiedWithTheFollowingData(string $state, TableNode $table): void + { + $rows = $table->getHash(); + $row = reset($rows); + $this->interaction->addProviderState($state, $row); + } + + /** + * @Then the provider state :name in the Pact file will contain the following parameters: + */ + public function theProviderStateInThePactFileWillContainTheFollowingParameters(string $name, TableNode $table): void + { + $rows = $table->getHash(); + $row = reset($rows); + $params = json_decode($row['parameters'], true); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertNotEmpty(array_filter( + $pact['interactions'][0]['providerStates'], + fn (array $providerState) => $providerState['name'] === $name && $providerState['params'] === $params + )); + } +} diff --git a/compatibility-suite/tests/Context/V3/Http/ProviderContext.php b/compatibility-suite/tests/Context/V3/Http/ProviderContext.php new file mode 100644 index 000000000..88b8b475b --- /dev/null +++ b/compatibility-suite/tests/Context/V3/Http/ProviderContext.php @@ -0,0 +1,57 @@ +pactWriter->write($id); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $rows = $table->getHash(); + $pact['interactions'][0]['providerStates'] = array_map(fn (array $row): array => ['name' => $row['State Name'], 'params' => json_decode($row['Parameters'] ?? '{}', true)], $rows); + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + } + + /** + * @Then the provider state callback will receive a setup call with :state and the following parameters: + */ + public function theProviderStateCallbackWillReceiveASetupCallWithAndTheFollowingParameters(string $state, TableNode $table): void + { + $params = $table->getHash()[0]; + foreach ($params as &$value) { + $value = trim($value, '"'); + } + Assert::assertTrue($this->providerStateServer->hasState(ProviderStateServerInterface::ACTION_SETUP, $state, $params)); + } + + /** + * @Then the provider state callback will receive a teardown call :state and the following parameters: + */ + public function theProviderStateCallbackWillReceiveATeardownCallAndTheFollowingParameters(string $state, TableNode $table): void + { + $params = $table->getHash()[0]; + foreach ($params as &$value) { + $value = trim($value, '"'); + } + Assert::assertTrue($this->providerStateServer->hasState(ProviderStateServerInterface::ACTION_TEARDOWN, $state, $params)); + } +} diff --git a/compatibility-suite/tests/Context/V3/Message/ConsumerContext.php b/compatibility-suite/tests/Context/V3/Message/ConsumerContext.php new file mode 100644 index 000000000..cd2bd79f7 --- /dev/null +++ b/compatibility-suite/tests/Context/V3/Message/ConsumerContext.php @@ -0,0 +1,307 @@ +pactPath = "$pactDir/$consumer-$provider.json"; + $config = new PactMessageConfig(); + $config + ->setConsumer($consumer) + ->setProvider($provider) + ->setPactDir($pactDir) + ->setPactSpecificationVersion($specificationVersion) + ->setPactFileWriteMode(PactConfigInterface::MODE_OVERWRITE); + $this->builder = new MessageBuilder($config); + } + + /** + * @Given a message integration is being defined for a consumer test + */ + public function aMessageIntegrationIsBeingDefinedForAConsumerTest(): void + { + $this->builder->expectsToReceive('a message'); + } + + /** + * @Given the message payload contains the :fixture JSON document + */ + public function theMessagePayloadContainsTheJsonDocument(string $fixture): void + { + $this->builder->withContent($this->fixtureLoader->loadJson($fixture . '.json')); + } + + /** + * @When the message is successfully processed + */ + public function theMessageIsSuccessfullyProcessed(): void + { + $this->process([$this, 'storeMessage']); + } + + /** + * @Then the received message payload will contain the :fixture JSON document + */ + public function theReceivedMessagePayloadWillContainTheJsonDocument(string $fixture): void + { + Assert::assertJsonStringEqualsJsonString( + $this->fixtureLoader->load($fixture . '.json'), + json_encode($this->receivedMessage->contents) + ); + } + + /** + * @Then the received message content type will be :contentType + */ + public function theReceivedMessageContentTypeWillBe(string $contentType): void + { + Assert::assertSame($contentType, $this->receivedMessage->metadata->contentType); + } + + /** + * @Then the consumer test will have passed + */ + public function theConsumerTestWillHavePassed(): void + { + Assert::assertTrue($this->verifyResult); + } + + /** + * @Then a Pact file for the message interaction will have been written + */ + public function aPactFileForTheMessageInteractionWillHaveBeenWritten(): void + { + Assert::assertTrue(file_exists($this->pactPath)); + $this->pact = json_decode(file_get_contents($this->pactPath), true); + } + + /** + * @Then the pact file will contain :messages message interaction(s) + */ + public function thePactFileWillContainMessageInteraction(int $messages): void + { + Assert::assertCount($messages, $this->pact['messages'] ?? []); + } + + /** + * @Then the first message in the pact file will contain the :fixture document + */ + public function theFirstMessageInThePactFileWillContainTheDocument(string $fixture): void + { + Assert::assertJsonStringEqualsJsonString( + $this->fixtureLoader->load($fixture), + json_encode($this->pact['messages'][0]['contents'] ?? null) + ); + } + + /** + * @Then the first message in the pact file content type will be :contentType + */ + public function theFirstMessageInThePactFileContentTypeWillBe(string $contentType): void + { + Assert::assertSame($contentType, $this->pact['messages'][0]['metadata']['contentType'] ?? null); + } + + /** + * @When the message is NOT successfully processed with a :error exception + */ + public function theMessageIsNotSuccessfullyProcessedWithAException(string $error): void + { + $this->process(fn () => throw new Exception($error)); + } + + /** + * @Then the consumer test will have failed + */ + public function theConsumerTestWillHaveFailed(): void + { + Assert::assertFalse($this->verifyResult); + } + + /** + * @Then the consumer test error will be :error + */ + public function theConsumerTestErrorWillBe(string $error): void + { + // TODO Modify MessageBuilder code to check this exception? + } + + /** + * @Then a Pact file for the message interaction will NOT have been written + */ + public function aPactFileForTheMessageInteractionWillNotHaveBeenWritten(): void + { + Assert::assertFalse(file_exists($this->pactPath)); + } + + /** + * @Given the message contains the following metadata: + */ + public function theMessageContainsTheFollowingMetadata(TableNode $table): void + { + $this->builder->withMetadata($this->parser->parseMetadataTable($table->getHash())); + } + + /** + * @Then /^the received message metadata will contain "([^"]+)" == "(.+)"$/ + */ + public function theReceivedMessageMetadataWillContain(string $key, string $value): void + { + $actual = $this->receivedMessage->metadata->{$key}; + if (is_string($actual)) { + Assert::assertSame($this->parser->parseMetadataValue($value), $actual); + } else { + Assert::assertJsonStringEqualsJsonString($this->parser->parseMetadataValue($value), json_encode($actual)); + } + } + + /** + * @Then /^the first message in the pact file will contain the message metadata "([^"]+)" == "(.+)"$/ + */ + public function theFirstMessageInThePactFileWillContainTheMessageMetadata(string $key, string $value): void + { + $actual = $this->pact['messages'][0]['metadata'][$key] ?? null; + if (is_string($actual)) { + Assert::assertSame($this->parser->parseMetadataValue($value), $actual); + } else { + Assert::assertJsonStringEqualsJsonString($this->parser->parseMetadataValue($value), json_encode($actual)); + } + } + + /** + * @Given a provider state :state for the message is specified + */ + public function aProviderStateForTheMessageIsSpecified(string $state): void + { + $this->builder->given($state, []); + } + + /** + * @Given a message is defined + */ + public function aMessageIsDefined(): void + { + $this->aMessageIntegrationIsBeingDefinedForAConsumerTest(); + } + + /** + * @Then the first message in the pact file will contain :states provider state(s) + */ + public function theFirstMessageInThePactFileWillContainProviderStates(int $states): void + { + Assert::assertCount($states, $this->pact['messages'][0]['providerStates'] ?? []); + } + + /** + * @Then the first message in the Pact file will contain provider state :state + */ + public function theFirstMessageInThePactFileWillContainProviderState(string $state): void + { + $states = array_map(fn (array $state): string => $state['name'], $this->pact['messages'][0]['providerStates']); + Assert::assertContains($state, $states); + } + + /** + * @Given a provider state :state for the message is specified with the following data: + */ + public function aProviderStateForTheMessageIsSpecifiedWithTheFollowingData(string $state, TableNode $table): void + { + $rows = $table->getHash(); + $row = reset($rows); + $this->builder->given($state, $row); + } + + /** + * @Then the provider state :state for the message will contain the following parameters: + */ + public function theProviderStateForTheMessageWillContainTheFollowingParameters(string $state, TableNode $table): void + { + $params = json_decode($table->getHash()[0]['parameters'], true); + Assert::assertContains([ + 'name' => $state, + 'params' => $params, + ], $this->pact['messages'][0]['providerStates']); + } + + /** + * @Given the message is configured with the following: + */ + public function theMessageIsConfiguredWithTheFollowing(TableNode $table): void + { + $rows = $table->getHash(); + $row = reset($rows); + $message = new Message(); + $message->setBody(isset($row['body']) ? $this->parser->parseBody($row['body']) : null); + $message->setMetadata(isset($row['metadata']) ? json_decode($row['metadata'], true) : null); + $this->messageGeneratorBuilder->build($message, $row['generators']); + if ($message->hasBody()) { + $this->builder->withContent($message->getBody()); + } + if ($message->hasMetadata()) { + $this->builder->withContent('not empty'); // any not empty text, doesn't matter. If empty or not provided, received message will be null. + $this->builder->withMetadata($message->getMetadata()); + } + } + + /** + * @Then the message contents for :path will have been replaced with a(n) :type + */ + public function theMessageContentsForWillHaveBeenReplacedWithAn(string $path, string $type): void + { + $this->bodyStorage->setBody(json_encode($this->receivedMessage->contents)); + $this->validator->validateType($path, $type); + } + + /** + * @Then the received message metadata will contain :key replaced with an :type + */ + public function theReceivedMessageMetadataWillContainReplacedWithAn(string $key, string $type): void + { + $this->bodyStorage->setBody(json_encode($this->receivedMessage->metadata)); + $this->validator->validateType("$.$key", $type); + } + + public function storeMessage(string $message): void + { + $this->receivedMessage = json_decode($message); + } + + private function process(callable $callback): void + { + $this->builder->setCallback($callback); + + $this->verifyResult = $this->builder->verify(); + } +} diff --git a/compatibility-suite/tests/Context/V3/Message/ProviderContext.php b/compatibility-suite/tests/Context/V3/Message/ProviderContext.php new file mode 100644 index 000000000..e62e7bcd7 --- /dev/null +++ b/compatibility-suite/tests/Context/V3/Message/ProviderContext.php @@ -0,0 +1,166 @@ +builder->build([ + 'No' => $this->id, + 'description' => sprintf('Interaction for message %s', $name), + 'method' => 'POST', + 'path' => '/messages', + 'body' => 'JSON: ' . json_encode(['description' => $name]), + 'response body' => $fixture, + ]); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->ids[] = $this->id++; + } + + /** + * @BeforeStep + */ + public function registerInteractions(BeforeStepScope $scope): void + { + if ( + $this->ids + && preg_match('/^a Pact file for .* is to be verified/', $scope->getStep()->getText()) + ) { + $this->server->register(...$this->ids); + $this->ids = []; + $this->providerVerifier + ->getConfig() + ->addProviderTransport( + (new ProviderTransport()) + ->setProtocol(ProviderTransport::MESSAGE_PROTOCOL) + ->setPort($this->server->getPort()) + ->setPath('/messages') + ->setScheme('http') + ); + ; + } + } + + /** + * @Given a Pact file for :name::fixture is to be verified + */ + public function aPactFileForIsToBeVerified(string $name, string $fixture): void + { + $this->pactWriter->write($name, $fixture, "c-$name"); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + } + + /** + * @Given a Pact file for :name::fixture is to be verified with provider state :state + */ + public function aPactFileForIsToBeVerifiedWithProviderState(string $name, string $fixture, string $state): void + { + $this->aPactFileForIsToBeVerified($name, $fixture); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $pact['messages'][0]['providerState'] = $state; + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + } + + /** + * @Given a provider is started that can generate the :name message with :fixture and the following metadata: + */ + public function aProviderIsStartedThatCanGenerateTheMessageWithAndTheFollowingMetadata(string $name, string $fixture, TableNode $table): void + { + $interaction = $this->builder->build([ + 'No' => $this->id, + 'description' => sprintf('Interaction for message %s', $name), + 'method' => 'POST', + 'path' => '/messages', + 'body' => 'JSON: ' . json_encode(['description' => $name]), + 'response body' => $fixture, + 'response headers' => 'Pact-Message-Metadata: ' . base64_encode(json_encode($this->parser->parseMetadataTable($table->getHash()))), + ]); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->server->register($this->id); + $this->providerVerifier + ->getConfig() + ->addProviderTransport( + (new ProviderTransport()) + ->setProtocol(ProviderTransport::MESSAGE_PROTOCOL) + ->setPort($this->server->getPort()) + ->setPath('/messages') + ->setScheme('http') + ); + ; + } + + /** + * @Given a Pact file for :name::fixture is to be verified with the following metadata: + */ + public function aPactFileForIsToBeVerifiedWithTheFollowingMetadata(string $name, string $fixture, TableNode $table): void + { + $this->pactWriter->write($name, $fixture); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $pact['messages'][0]['metaData'] = $this->parser->parseMetadataTable($table->getHash()); + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + } + + /** + * @Given a Pact file for :name is to be verified with the following: + */ + public function aPactFileForIsToBeVerifiedWithTheFollowing(string $name, TableNode $table): void + { + foreach ($table->getRowsHash() as $key => $value) { + switch ($key) { + case 'body': + $body = $value; + break; + + case 'matching rules': + $matchingRules = $this->fixtureLoader->loadJson($value); + break; + + case 'metadata': + $metadata = $value; + break; + + default: + break; + } + } + $this->aPactFileForIsToBeVerified($name, $body); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + if (isset($metadata)) { + $pact['messages'][0]['metadata'] = array_merge($pact['messages'][0]['metadata'], $this->parser->parseMetadataMultiValues($metadata)); + } + $pact['messages'][0]['matchingRules'] = $matchingRules; + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + } +} diff --git a/compatibility-suite/tests/Context/V3/RequestGeneratorsContext.php b/compatibility-suite/tests/Context/V3/RequestGeneratorsContext.php new file mode 100644 index 000000000..65959d64d --- /dev/null +++ b/compatibility-suite/tests/Context/V3/RequestGeneratorsContext.php @@ -0,0 +1,123 @@ +getHash(); + $row = reset($rows); + $interaction = $this->builder->build([ + 'No' => $this->id, + 'method' => 'PUT', + 'path' => '/request-generators', + 'body' => $row['body'] ?? '', + ]); + $this->requestGeneratorBuilder->build($interaction->getRequest(), $row['generators']); + $this->storage->add(InteractionsStorageInterface::PACT_WRITER_DOMAIN, $this->id, $interaction); + } + + /** + * @When the request is prepared for use + */ + public function theRequestIsPreparedForUse(): void + { + $this->generatorServer->start(); + $this->pactWriter->write($this->id); + $this->providerVerifier->getConfig()->getProviderInfo()->setPort($this->generatorServer->getPort()); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + $this->providerVerifier->verify(); + $this->generatorServer->stop(); + $this->bodyStorage->setBody($this->generatorServer->getBody()); + } + + /** + * @Given the generator test mode is set as :mode + */ + public function theGeneratorTestModeIsSetAs(string $mode): void + { + // There is nothing we can do using FFI call. + } + + /** + * @When the request is prepared for use with a "providerState" context: + */ + public function theRequestIsPreparedForUseWithAProviderStateContext(TableNode $table): void + { + $this->generatorServer->start(); + $this->pactWriter->write($this->id); + $port = $this->generatorServer->getPort(); + $this->providerVerifier->getConfig()->getProviderInfo()->setPort($port); + $params = json_decode($table->getRow(0)[0], true); + $this->providerVerifier + ->getConfig() + ->getProviderState() + ->setStateChangeUrl(new Uri("http://localhost:$port/return-provider-state-values?" . http_build_query($params))) + ->setStateChangeTeardown(false); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + $this->providerVerifier->verify(); + $this->generatorServer->stop(); + $this->bodyStorage->setBody($this->generatorServer->getBody()); + } + + /** + * @Then the request :part will be set as :value + */ + public function theRequestWillBeSetAs(string $part, string $value): void + { + switch ($part) { + case 'path': + $path = $this->generatorServer->getPath(); + Assert::assertSame($value, $path); + break; + + default: + break; + } + } + + /** + * @Then the request :part will match :regex + */ + public function theRequestWillMatch(string $part, string $regex): void + { + if ($part === 'path') { + Assert::assertMatchesRegularExpression("/$regex/", $this->generatorServer->getPath()); + } elseif (preg_match('/header\[(.*)\]/', $part, $matches)) { + foreach ($this->generatorServer->getHeader($matches[1]) as $value) { + Assert::assertMatchesRegularExpression("/$regex/", $value); + } + } elseif (preg_match('/queryParameter\[(.*)\]/', $part, $matches)) { + Assert::assertMatchesRegularExpression("/$regex/", $this->generatorServer->getQueryParam($matches[1])); + } + } +} diff --git a/compatibility-suite/tests/Context/V3/RequestMatchingContext.php b/compatibility-suite/tests/Context/V3/RequestMatchingContext.php new file mode 100644 index 000000000..9bf139f7a --- /dev/null +++ b/compatibility-suite/tests/Context/V3/RequestMatchingContext.php @@ -0,0 +1,169 @@ +type = self::HEADER_TYPE; + $interaction = $this->builder->build([ + 'No' => $this->id, + 'method' => 'GET', + 'path' => '/matching', + 'headers' => implode(', ', array_map(fn (string $value) => "'$header: $value'", explode(', ', $value))), + ]); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->storage->add(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id, $interaction, true); + $this->server->register($this->id); + } + + /** + * @Given a request is received with a(n) :header header of :value + */ + public function aRequestIsReceivedWithAHeaderOf(string $header, string $value): void + { + $request = $this->storage->get(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id)->getRequest(); + $request->addHeader($header, $value); + $this->client->sendRequestToServer($this->id); + } + + /** + * @When the request is compared to the expected one + */ + public function theRequestIsComparedToTheExpectedOne(): void + { + $this->server->verify(); + } + + /** + * @Then the comparison should be OK + */ + public function theComparisonShouldBeOk(): void + { + Assert::assertTrue($this->server->getVerifyResult()->isSuccess()); + } + + /** + * @Then the comparison should NOT be OK + */ + public function theComparisonShouldNotBeOk(): void + { + Assert::assertFalse($this->server->getVerifyResult()->isSuccess()); + } + + /** + * @Then /^the mismatches will contain a mismatch with error "([^"]+)" -> "(.+)"$/ + */ + public function theMismatchesWillContainAMismatchWithError(string $path, string $error): void + { + $error = str_replace('\"', '"', $error); + $key = $this->type === self::HEADER_TYPE ? 'key' : 'path'; + $mismatches = json_decode($this->server->getVerifyResult()->getOutput(), true); + $mismatches = array_reduce($mismatches, function (array $results, array $mismatch): array { + Assert::assertSame('request-mismatch', $mismatch['type']); + $results = array_merge($results, array_filter( + $mismatch['mismatches'], + fn (array $mismatch) => $mismatch['type'] === Mismatch::MOCK_SERVER_MISMATCH_TYPE_MAP[$this->type] + )); + + return $results; + }, []); + $mismatches = array_filter( + $mismatches, + fn (array $mismatch) => $mismatch[$key] === $path + && ( + str_contains($mismatch['mismatch'], $error) + || @preg_match("|$error|", $mismatch['mismatch']) + ) + ); + Assert::assertNotEmpty($mismatches); + } + + /** + * @Given an expected request configured with the following: + */ + public function anExpectedRequestConfiguredWithTheFollowing(TableNode $table): void + { + $this->type = self::BODY_TYPE; + $rows = $table->getHash(); + $row = reset($rows); + $interaction = $this->builder->build([ + 'No' => $this->id, + 'method' => 'POST', + 'path' => '/matching', + ] + $row); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->storage->add(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id, $interaction, true); + try { + $this->requestMatchingRuleBuilder->build($interaction->getRequest(), $row['matching rules']); + } catch (IntegrationJsonFormatException $exception) { + throw new PendingException($exception->getMessage()); + } + $this->server->register($this->id); + } + + /** + * @Given a request is received with the following: + */ + public function aRequestIsReceivedWithTheFollowing(TableNode $table): void + { + $rows = $table->getHash(); + $row = reset($rows); + $request = $this->storage->get(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id)->getRequest(); + $this->requestBuilder->build($request, $row); + $this->client->sendRequestToServer($this->id); + } + + /** + * @Given the following requests are received: + */ + public function theFollowingRequestsAreReceived(TableNode $table): void + { + foreach ($table->getHash() as $row) { + $request = $this->storage->get(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id)->getRequest(); + $this->requestBuilder->build($request, $row); + $this->client->sendRequestToServer($this->id); + } + } + + /** + * @When the requests are compared to the expected one + */ + public function theRequestsAreComparedToTheExpectedOne(): void + { + $this->server->verify(); + } +} diff --git a/compatibility-suite/tests/Context/V3/ResponseGeneratorsContext.php b/compatibility-suite/tests/Context/V3/ResponseGeneratorsContext.php new file mode 100644 index 000000000..c2a1ed30b --- /dev/null +++ b/compatibility-suite/tests/Context/V3/ResponseGeneratorsContext.php @@ -0,0 +1,86 @@ +getHash(); + $row = reset($rows); + $interaction = $this->builder->build([ + 'No' => $this->id, + 'method' => 'GET', + 'path' => '/response-generators', + 'response body' => $row['body'] ?? '', + ]); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->storage->add(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id, $interaction, true); + $this->responseGeneratorBuilder->build($interaction->getResponse(), $row['generators']); + } + + /** + * @When the response is prepared for use + */ + public function theResponseIsPreparedForUse(): void + { + $this->server->register($this->id); + $this->client->sendRequestToServer($this->id); + $this->bodyStorage->setBody($this->client->getResponse()->getBody()->getContents()); + } + + /** + * @Then the response :part will not be :value + */ + public function theResponseWillNotBe(string $part, string $value): void + { + switch ($part) { + case 'status': + $code = $this->client->getResponse()->getStatusCode(); + Assert::assertNotEquals($value, $code); + break; + + default: + break; + } + } + + /** + * @Then the response :part will match :regex + */ + public function theResponseWillMatch(string $part, string $regex): void + { + if ($part === 'status') { + Assert::assertMatchesRegularExpression("/$regex/", $this->client->getResponse()->getStatusCode()); + } elseif (preg_match('/header\[(.*)\]/', $part, $matches)) { + foreach ($this->client->getResponse()->getHeader($matches[1]) as $value) { + Assert::assertMatchesRegularExpression("/$regex/", $value); + } + } + } +} diff --git a/compatibility-suite/tests/Model/Generator.php b/compatibility-suite/tests/Model/Generator.php new file mode 100644 index 000000000..3fb70da84 --- /dev/null +++ b/compatibility-suite/tests/Model/Generator.php @@ -0,0 +1,34 @@ +generator; + } + + public function getCategory(): string + { + return $this->category; + } + + public function getSubCategory(): ?string + { + return $this->subCategory; + } + + public function getGeneratorAttributes(): array + { + return $this->generatorAttributes; + } +} diff --git a/compatibility-suite/tests/Model/Message.php b/compatibility-suite/tests/Model/Message.php new file mode 100644 index 000000000..58406380d --- /dev/null +++ b/compatibility-suite/tests/Model/Message.php @@ -0,0 +1,42 @@ +body; + } + + public function setBody(null|Binary|Text $body): void + { + $this->body = $body; + } + + public function hasBody(): bool + { + return null !== $this->body; + } + + public function getMetadata(): ?array + { + return $this->metadata; + } + + public function setMetadata(?array $metadata): void + { + $this->metadata = $metadata; + } + + public function hasMetadata(): bool + { + return null !== $this->metadata; + } +} diff --git a/compatibility-suite/tests/Service/BodyStorage.php b/compatibility-suite/tests/Service/BodyStorage.php new file mode 100644 index 000000000..66900aeee --- /dev/null +++ b/compatibility-suite/tests/Service/BodyStorage.php @@ -0,0 +1,18 @@ +body = $body; + } + + public function getBody(): string + { + return $this->body; + } +} diff --git a/compatibility-suite/tests/Service/BodyStorageInterface.php b/compatibility-suite/tests/Service/BodyStorageInterface.php new file mode 100644 index 000000000..bf7865fa6 --- /dev/null +++ b/compatibility-suite/tests/Service/BodyStorageInterface.php @@ -0,0 +1,10 @@ +getActualValue($path); + Assert::assertTrue((bool) match ($type) { + 'integer' => is_numeric($value) && preg_match(self::INT_REGEX, $value), + 'decimal number' => is_string($value) && preg_match(self::DEC_REGEX, $value), + 'hexadecimal number' => is_string($value) && preg_match(self::HEX_REGEX, $value), + 'random string' => is_string($value), + 'string from the regex' => is_string($value) && preg_match(self::STR_REGEX, $value), + 'date' => is_string($value) && preg_match(self::DATE_REGEX, $value), + 'time' => is_string($value) && preg_match(self::TIME_REGEX, $value), + 'date-time' => is_string($value) && preg_match(self::DATETIME_REGEX, $value), + 'UUID', 'simple UUID', 'lower-case-hyphenated UUID', 'upper-case-hyphenated UUID', 'URN UUID' => Uuid::isValid($value), + 'boolean' => is_bool($value), + default => false, + }); + } + + public function validateValue(string $path, string $value): void + { + Assert::assertSame($value, $this->getActualValue($path)); + } + + private function getActualValue(string $path): mixed + { + $jsonObject = new JsonObject($this->bodyStorage->getBody(), true); + + return $jsonObject->{$path}; + } +} diff --git a/compatibility-suite/tests/Service/BodyValidatorInterface.php b/compatibility-suite/tests/Service/BodyValidatorInterface.php new file mode 100644 index 000000000..805d6bd74 --- /dev/null +++ b/compatibility-suite/tests/Service/BodyValidatorInterface.php @@ -0,0 +1,10 @@ +getGenerator()); + + $matcher = new Integer(); // Doesn't matter. Any matcher doesn't require value and accept generator will be fine. + $matcher->setGenerator(new $class(...$generator->getGeneratorAttributes())); + + return $matcher; + } +} diff --git a/compatibility-suite/tests/Service/GeneratorConverterInterface.php b/compatibility-suite/tests/Service/GeneratorConverterInterface.php new file mode 100644 index 000000000..af71456e0 --- /dev/null +++ b/compatibility-suite/tests/Service/GeneratorConverterInterface.php @@ -0,0 +1,11 @@ +fixtureLoader->loadJson($value); + } + + return $this->loadFromMap($map); + } + + private function loadFromMap(array $map): array + { + $generators = []; + $removeType = fn (array $values): array => array_filter( + $values, + fn (mixed $v, string $k) => $k !== 'type', + ARRAY_FILTER_USE_BOTH + ); + foreach ($map as $category => $values) { + switch ($category) { + case 'path': + case 'method': + case 'status': + $generators[] = new Generator($values['type'], $category, null, $removeType($values)); + break; + + default: + foreach ($values as $subCategory => $value) { + $generators[] = new Generator($value['type'], $category, $subCategory, $removeType($value)); + } + break; + } + } + + return $generators; + } +} diff --git a/compatibility-suite/tests/Service/GeneratorParserInterface.php b/compatibility-suite/tests/Service/GeneratorParserInterface.php new file mode 100644 index 000000000..7b77cd829 --- /dev/null +++ b/compatibility-suite/tests/Service/GeneratorParserInterface.php @@ -0,0 +1,13 @@ + + */ + public function parse(string $value): array; +} diff --git a/compatibility-suite/tests/Service/GeneratorServer.php b/compatibility-suite/tests/Service/GeneratorServer.php new file mode 100644 index 000000000..3a2e3d75e --- /dev/null +++ b/compatibility-suite/tests/Service/GeneratorServer.php @@ -0,0 +1,63 @@ +bodyFile, $this->pathFile, $this->headersFile, $this->queryParamsFile] as $file) { + @unlink($file); + } + $socket = \socket_create_listen($this->port); + \socket_getsockname($socket, $addr, $this->port); + \socket_close($socket); + $this->process = new ProviderProcess(Path::PUBLIC_PATH . '/generators/', $this->port); + $this->process->start(); + } + + public function stop(): void + { + $this->port = 0; + $this->process->stop(); + } + + public function getPort(): int + { + return $this->port; + } + + public function getBody(): string + { + return @file_get_contents($this->bodyFile); + } + + public function getPath(): string + { + return file_get_contents($this->pathFile); + } + + public function getHeader(string $header): array + { + $headers = json_decode(file_get_contents($this->headersFile), true); + + return $headers[$header] ?? []; + } + + public function getQueryParam(string $name): string + { + $queryParams = json_decode(file_get_contents($this->queryParamsFile), true); + + return $queryParams[$name] ?? ''; + } +} diff --git a/compatibility-suite/tests/Service/GeneratorServerInterface.php b/compatibility-suite/tests/Service/GeneratorServerInterface.php new file mode 100644 index 000000000..05ae02afa --- /dev/null +++ b/compatibility-suite/tests/Service/GeneratorServerInterface.php @@ -0,0 +1,20 @@ +parser->parse($value) as $generator) { + switch ($generator->getCategory()) { + case 'metadata': + $metadata = $message->getMetadata() ?? []; + $metadata[$generator->getSubCategory()] = $this->converter->convert($generator); + $message->setMetadata($metadata); + break; + + case 'body': + $body = $message->getBody(); + if ($body instanceof Text && $body->getContentType() === 'application/json') { + $jsonObject = new JsonObject($body->getContents(), true); + $jsonObject->{$generator->getSubCategory()} = $this->converter->convert($generator); + $body->setContents($jsonObject); + } else { + throw new IntegrationJsonFormatException("Integration JSON format doesn't support non-JSON format"); + } + break; + + default: + break; + } + } + } +} diff --git a/compatibility-suite/tests/Service/MessageGeneratorBuilderInterface.php b/compatibility-suite/tests/Service/MessageGeneratorBuilderInterface.php new file mode 100644 index 000000000..45c446d98 --- /dev/null +++ b/compatibility-suite/tests/Service/MessageGeneratorBuilderInterface.php @@ -0,0 +1,10 @@ +pactPath = "$pactDir/$consumer-$provider.json"; + $config = new MockServerConfig(); + $config + ->setConsumer($consumer) + ->setProvider($provider) + ->setPactDir($pactDir) + ->setPactSpecificationVersion($this->specificationVersion) + ->setPactFileWriteMode($mode); + $driver = (new MessageDriverFactory())->create($config); + + $message = new Message(); + $message->setDescription($name); + $message->setContents($this->parser->parseBody($body)); + $driver->registerMessage($message); + $driver->writePactAndCleanUp(); + } + + public function getPactPath(): string + { + return $this->pactPath; + } +} diff --git a/compatibility-suite/tests/Service/MessagePactWriterInterface.php b/compatibility-suite/tests/Service/MessagePactWriterInterface.php new file mode 100644 index 000000000..4cc4ce8e5 --- /dev/null +++ b/compatibility-suite/tests/Service/MessagePactWriterInterface.php @@ -0,0 +1,12 @@ +parser->parse($value) as $generator) { + switch ($generator->getCategory()) { + case 'method': + // Can't set generator to method + break; + + case 'path': + $request->setPath($this->converter->convert($generator)); + break; + + case 'query': + if ($generator->getSubCategory()) { + $request->addQueryParameter($generator->getSubCategory(), $this->converter->convert($generator)); + } + break; + + case 'header': + if ($generator->getSubCategory()) { + $request->addHeader($generator->getSubCategory(), $this->converter->convert($generator)); + } + break; + + case 'body': + $body = $request->getBody(); + if ($body instanceof Text && $body->getContentType() === 'application/json') { + $jsonObject = new JsonObject($body->getContents(), true); + $jsonObject->{$generator->getSubCategory()} = $this->converter->convert($generator); + $body->setContents($jsonObject); + } else { + throw new IntegrationJsonFormatException("Integration JSON format doesn't support non-JSON format"); + } + break; + + default: + break; + } + } + } +} diff --git a/compatibility-suite/tests/Service/RequestGeneratorBuilderInterface.php b/compatibility-suite/tests/Service/RequestGeneratorBuilderInterface.php new file mode 100644 index 000000000..8a409bb7a --- /dev/null +++ b/compatibility-suite/tests/Service/RequestGeneratorBuilderInterface.php @@ -0,0 +1,10 @@ +parser->parse($value) as $generator) { + switch ($generator->getCategory()) { + case 'status': + $response->setStatus($this->converter->convert($generator)); + break; + + case 'header': + if ($generator->getSubCategory()) { + $response->addHeader($generator->getSubCategory(), $this->converter->convert($generator)); + } + break; + + case 'body': + $body = $response->getBody(); + if ($body instanceof Text && $body->getContentType() === 'application/json') { + $jsonObject = new JsonObject($body->getContents(), true); + $jsonObject->{$generator->getSubCategory()} = $this->converter->convert($generator); + $body->setContents($jsonObject); + } else { + throw new IntegrationJsonFormatException("Integration JSON format doesn't support non-JSON format"); + } + break; + + default: + break; + } + } + } +} diff --git a/compatibility-suite/tests/Service/ResponseGeneratorBuilderInterface.php b/compatibility-suite/tests/Service/ResponseGeneratorBuilderInterface.php new file mode 100644 index 000000000..b7f11acaa --- /dev/null +++ b/compatibility-suite/tests/Service/ResponseGeneratorBuilderInterface.php @@ -0,0 +1,10 @@ +set('generator_parser', new GeneratorParser($this->get('fixture_loader'))); + $this->set('generator_converter', new GeneratorConverter()); + $this->set('generator_server', new GeneratorServer()); + $this->set('body_storage', new BodyStorage()); + $this->set('body_validator', new BodyValidator($this->get('body_storage'))); + $this->set('message_pact_writer', new MessagePactWriter($this->get('parser'), $this->getSpecification())); + $this->set('request_generator_builder', new RequestGeneratorBuilder($this->get('generator_parser'), $this->get('generator_converter'))); + $this->set('response_generator_builder', new ResponseGeneratorBuilder($this->get('generator_parser'), $this->get('generator_converter'))); + $this->set('message_generator_builder', new MessageGeneratorBuilder($this->get('generator_parser'), $this->get('generator_converter'))); + } + + protected function getSpecification(): string + { + return '3.0.0'; + } +}