diff --git a/.github/workflows/compatibility-suite.yml b/.github/workflows/compatibility-suite.yml index de583a99..8cc1ee7d 100644 --- a/.github/workflows/compatibility-suite.yml +++ b/.github/workflows/compatibility-suite.yml @@ -54,3 +54,19 @@ jobs: - name: Run Behat run: vendor/bin/behat compatibility-suite/pact-compatibility-suite/features/V3 --name '/^((?!Kafka|binary body \(negative|Message provider).)*$/' + v4: + 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/V4 diff --git a/behat.yml b/behat.yml index 56830032..e0a02922 100644 --- a/behat.yml +++ b/behat.yml @@ -9,3 +9,11 @@ imports: - 'compatibility-suite/suites/v3/message/provider.yml' - 'compatibility-suite/suites/v3/generators.yml' - 'compatibility-suite/suites/v3/matching-rules.yml' + - 'compatibility-suite/suites/v4/http/consumer.yml' + - 'compatibility-suite/suites/v4/http/provider.yml' + - 'compatibility-suite/suites/v4/message/consumer.yml' + - 'compatibility-suite/suites/v4/message/provider.yml' + - 'compatibility-suite/suites/v4/combined.yml' + - 'compatibility-suite/suites/v4/sync-message/consumer.yml' + - 'compatibility-suite/suites/v4/matching-rules.yml' + - 'compatibility-suite/suites/v4/generators.yml' diff --git a/compatibility-suite/suites/v4/combined.yml b/compatibility-suite/suites/v4/combined.yml new file mode 100644 index 00000000..1873d0b6 --- /dev/null +++ b/compatibility-suite/suites/v4/combined.yml @@ -0,0 +1,14 @@ +default: + suites: + v4_combined: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4/v4.feature' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V4\CombinedContext': + - '@interaction_builder' + - '@interactions_storage' + - '@pact_writer' + - '@message_pact_writer' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/generators.yml b/compatibility-suite/suites/v4/generators.yml new file mode 100644 index 00000000..22fcc247 --- /dev/null +++ b/compatibility-suite/suites/v4/generators.yml @@ -0,0 +1,28 @@ +default: + suites: + v4_generators: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4/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\V4\BodyGeneratorsContext': + - '@body_validator' + - 'PhpPactTest\CompatibilitySuite\Context\V4\ResponseGeneratorsContext': + - '@interaction_builder' + - '@response_generator_builder' + - '@interactions_storage' + - '@server' + - '@client' + - '@body_storage' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/http/consumer.yml b/compatibility-suite/suites/v4/http/consumer.yml new file mode 100644 index 00000000..57b71dea --- /dev/null +++ b/compatibility-suite/suites/v4/http/consumer.yml @@ -0,0 +1,13 @@ +default: + suites: + v4_http_consumer: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4/http_consumer.feature' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V4\Http\ConsumerContext': + - '@interaction_builder' + - '@pact_writer' + - '@interactions_storage' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/http/provider.yml b/compatibility-suite/suites/v4/http/provider.yml new file mode 100644 index 00000000..cdfd7dec --- /dev/null +++ b/compatibility-suite/suites/v4/http/provider.yml @@ -0,0 +1,31 @@ +default: + suites: + v4_http_provider: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4/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\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\V4\Http\ProviderContext': + - '@pact_writer' + - '@provider_verifier' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/matching-rules.yml b/compatibility-suite/suites/v4/matching-rules.yml new file mode 100644 index 00000000..34c4d7dc --- /dev/null +++ b/compatibility-suite/suites/v4/matching-rules.yml @@ -0,0 +1,23 @@ +default: + suites: + v4_matching_rules: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4/matching_rules.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' + - 'PhpPactTest\CompatibilitySuite\Context\V4\ResponseMatchingContext': + - '@interaction_builder' + - '@interactions_storage' + - '@response_matching_rule_builder' + - '@server' + - '@pact_writer' + - '@provider_verifier' + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/message/consumer.yml b/compatibility-suite/suites/v4/message/consumer.yml new file mode 100644 index 00000000..25ae890f --- /dev/null +++ b/compatibility-suite/suites/v4/message/consumer.yml @@ -0,0 +1,14 @@ +default: + suites: + v4_message_consumer: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V4\Message\ConsumerContext': + - '@message_pact_writer' + + filters: + tags: "@consumer&&@message" + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/message/provider.yml b/compatibility-suite/suites/v4/message/provider.yml new file mode 100644 index 00000000..c66c344f --- /dev/null +++ b/compatibility-suite/suites/v4/message/provider.yml @@ -0,0 +1,18 @@ +default: + suites: + v4_message_provider: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V4\Message\ProviderContext': + - '@server' + - '@interaction_builder' + - '@interactions_storage' + - '@message_pact_writer' + - '@provider_verifier' + + filters: + tags: "@provider&&@message" + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/suites/v4/sync-message/consumer.yml b/compatibility-suite/suites/v4/sync-message/consumer.yml new file mode 100644 index 00000000..ea263247 --- /dev/null +++ b/compatibility-suite/suites/v4/sync-message/consumer.yml @@ -0,0 +1,14 @@ +default: + suites: + v4_sync_message_consumer: + paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V4' ] + + contexts: + - 'PhpPactTest\CompatibilitySuite\Context\Shared\Hook\SetUpContext' + - 'PhpPactTest\CompatibilitySuite\Context\V4\SyncMessage\ConsumerContext': + - '@sync_message_pact_writer' + + filters: + tags: "@SynchronousMessage&&@message" + + services: PhpPactTest\CompatibilitySuite\ServiceContainer\V4 diff --git a/compatibility-suite/tests/Context/V4/BodyGeneratorsContext.php b/compatibility-suite/tests/Context/V4/BodyGeneratorsContext.php new file mode 100644 index 00000000..f72012f3 --- /dev/null +++ b/compatibility-suite/tests/Context/V4/BodyGeneratorsContext.php @@ -0,0 +1,22 @@ +validator->validateValue($path, $value); + } +} diff --git a/compatibility-suite/tests/Context/V4/CombinedContext.php b/compatibility-suite/tests/Context/V4/CombinedContext.php new file mode 100644 index 00000000..6a1b0bce --- /dev/null +++ b/compatibility-suite/tests/Context/V4/CombinedContext.php @@ -0,0 +1,64 @@ +builder->build([ + 'description' => 'http interaction', + 'method' => 'GET', + 'path' => '/v4-features', + ]); + $this->storage->add(InteractionsStorageInterface::PACT_WRITER_DOMAIN, $this->id, $interaction); + } + + /** + * @Given a message interaction is being defined for a consumer test + */ + public function aMessageInteractionIsBeingDefinedForAConsumerTest(): void + { + } + + /** + * @When the Pact file for the test is generated + */ + public function thePactFileForTheTestIsGenerated(): void + { + $this->pactWriter->write($this->id, 'c', 'p', PactConfigInterface::MODE_MERGE); + $this->messagePactWriter->write('message interaction', '', 'c', 'p', PactConfigInterface::MODE_MERGE); + } + + /** + * @Then there will be an interaction in the Pact file with a type of :type + */ + public function thereWillBeAnInteractionInThePactFileWithATypeOf(string $type): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $types = array_map(fn (array $interaction) => $interaction['type'], $pact['interactions']); + Assert::assertContains($type, $types); + } +} diff --git a/compatibility-suite/tests/Context/V4/Http/ConsumerContext.php b/compatibility-suite/tests/Context/V4/Http/ConsumerContext.php new file mode 100644 index 00000000..4bab8b17 --- /dev/null +++ b/compatibility-suite/tests/Context/V4/Http/ConsumerContext.php @@ -0,0 +1,87 @@ +interaction = $this->builder->build([ + 'description' => 'interaction for a consumer test', + 'method' => 'GET', + 'path' => '/v4-features', + ]); + $this->storage->add(InteractionsStorageInterface::PACT_WRITER_DOMAIN, $this->id, $this->interaction); + } + + /** + * @Then the first interaction in the Pact file will have a type of :type + */ + public function theFirstInteractionInThePactFileWillHaveATypeOf(string $type): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertSame($type, $pact['interactions'][0]['type']); + } + + /** + * @Given a key of :key is specified for the HTTP interaction + */ + public function aKeyOfIsSpecifiedForTheHttpInteraction(string $key): void + { + throw new PendingException("Can't set interaction's key using FFI call"); + } + + /** + * @Then the first interaction in the Pact file will have :name = :value + */ + public function theFirstInteractionInThePactFileWillHave(string $name, string $value): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertSame($value, $pact['interactions'][0][$name]); + } + + /** + * @Given the HTTP interaction is marked as pending + */ + public function theHttpInteractionIsMarkedAsPending(): void + { + throw new PendingException("Can't set interaction's pending using FFI call"); + } + + /** + * @Given a comment :value is added to the HTTP interaction + */ + public function aCommentIsAddedToTheHttpInteraction(string $value): void + { + throw new PendingException("Can't set interaction's comment using FFI call"); + } + + /** + * @When the Pact file for the test is generated + */ + public function thePactFileForTheTestIsGenerated(): void + { + $this->pactWriter->write($this->id); + } +} diff --git a/compatibility-suite/tests/Context/V4/Http/ProviderContext.php b/compatibility-suite/tests/Context/V4/Http/ProviderContext.php new file mode 100644 index 00000000..4f769e2a --- /dev/null +++ b/compatibility-suite/tests/Context/V4/Http/ProviderContext.php @@ -0,0 +1,106 @@ +pactWriter->write($id); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $pact['interactions'][0]['pending'] = true; + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + } + + /** + * @Then there will be a pending :error error + */ + public function thereWillBeAPendingError(string $error): void + { + $output = json_decode($this->providerVerifier->getVerifyResult()->getOutput(), true); + $errors = array_reduce( + $output['pendingErrors'], + function (array $errors, array $error) { + switch ($error['mismatch']['type']) { + case 'error': + $errors[] = Mismatch::VERIFIER_MISMATCH_ERROR_MAP[$error['mismatch']['message']]; + break; + + case 'mismatches': + foreach ($error['mismatch']['mismatches'] as $mismatch) { + $errors[] = Mismatch::VERIFIER_MISMATCH_TYPE_MAP[$mismatch['type']]; + } + break; + + default: + break; + } + + return $errors; + }, + [] + ); + Assert::assertContains($error, $errors); + } + + /** + * @Given a Pact file for interaction :id is to be verified with the following comments: + */ + public function aPactFileForInteractionIsToBeVerifiedWithTheFollowingComments(int $id, TableNode $table): void + { + $comments = []; + foreach ($table->getHash() as $row) { + switch ($row['type']) { + case 'text': + $comments['text'][] = $row['comment']; + break; + + case 'testname': + $comments['testname'] = $row['comment']; + break; + + default: + # code... + break; + } + } + $this->pactWriter->write($id); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $pact['interactions'][0]['comments'] = $comments; + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + } + + /** + * @Then the comment :comment will have been printed to the console + */ + public function theCommentWillHaveBeenPrintedToTheConsole(string $comment): void + { + Assert::assertStringContainsString($comment, $this->providerVerifier->getVerifyResult()->getOutput()); + } + + /** + * @Then the :name will displayed as the original test name + */ + public function theWillDisplayedAsTheOriginalTestName(string $name): void + { + Assert::assertStringContainsString(sprintf('Test Name: %s', $name), $this->providerVerifier->getVerifyResult()->getOutput()); + } +} diff --git a/compatibility-suite/tests/Context/V4/Message/ConsumerContext.php b/compatibility-suite/tests/Context/V4/Message/ConsumerContext.php new file mode 100644 index 00000000..f5e98b78 --- /dev/null +++ b/compatibility-suite/tests/Context/V4/Message/ConsumerContext.php @@ -0,0 +1,73 @@ +pactWriter->write('a message', ''); + } + + /** + * @Then the first interaction in the Pact file will have a type of :type + */ + public function theFirstInteractionInThePactFileWillHaveATypeOf(string $type): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertSame($type, $pact['interactions'][0]['type']); + } + + /** + * @Given a key of :key is specified for the message interaction + */ + public function aKeyOfIsSpecifiedForTheMessageInteraction(string $key): void + { + throw new PendingException("Can't set message's key using FFI call"); + } + + /** + * @Given the message interaction is marked as pending + */ + public function theMessageInteractionIsMarkedAsPending(): void + { + throw new PendingException("Can't set message's pending using FFI call"); + } + + /** + * @Given a comment :value is added to the message interaction + */ + public function aCommentIsAddedToTheMessageInteraction(string $value): void + { + throw new PendingException("Can't set message's comment using FFI call"); + } + + /** + * @Then the first interaction in the Pact file will have :name = :value + */ + public function theFirstInteractionInThePactFileWillHave(string $name, string $value): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertSame($value, $pact['interactions'][0][$name]); + } +} diff --git a/compatibility-suite/tests/Context/V4/Message/ProviderContext.php b/compatibility-suite/tests/Context/V4/Message/ProviderContext.php new file mode 100644 index 00000000..44550566 --- /dev/null +++ b/compatibility-suite/tests/Context/V4/Message/ProviderContext.php @@ -0,0 +1,159 @@ +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->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, but is marked pending + */ + public function aPactFileForIsToBeVerifiedButIsMarkedPending(string $name, string $fixture): void + { + $this->pactWriter->write($name, $fixture); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $pact['interactions'][0]['pending'] = true; + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + } + + /** + * @Given a Pact file for :name::fixture is to be verified with the following comments: + */ + public function aPactFileForIsToBeVerifiedWithTheFollowingComments(string $name, string $fixture, TableNode $table): void + { + $comments = []; + foreach ($table->getHash() as $row) { + switch ($row['type']) { + case 'text': + $comments['text'][] = $row['comment']; + break; + + case 'testname': + $comments['testname'] = $row['comment']; + break; + + default: + # code... + break; + } + } + $this->pactWriter->write($name, $fixture); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + $pact['interactions'][0]['comments'] = $comments; + file_put_contents($this->pactWriter->getPactPath(), json_encode($pact)); + } + + /** + * @When the verification is run + */ + public function theVerificationIsRun(): void + { + $this->providerVerifier->getConfig()->getProviderInfo()->setPort($this->server->getPort()); + $this->providerVerifier->verify(); + } + + /** + * @Then the verification will be successful + */ + public function theVerificationWillBeSuccessful(): void + { + Assert::assertTrue($this->providerVerifier->getVerifyResult()->isSuccess()); + } + + /** + * @Then there will be a pending :error error + */ + public function thereWillBeAPendingError(string $error): void + { + $output = json_decode($this->providerVerifier->getVerifyResult()->getOutput(), true); + $errors = array_reduce( + $output['pendingErrors'], + function (array $errors, array $error) { + switch ($error['mismatch']['type']) { + case 'error': + $errors[] = Mismatch::VERIFIER_MISMATCH_ERROR_MAP[$error['mismatch']['message']]; + break; + + case 'mismatches': + foreach ($error['mismatch']['mismatches'] as $mismatch) { + $errors[] = Mismatch::VERIFIER_MISMATCH_TYPE_MAP[$mismatch['type']]; + } + break; + + default: + break; + } + + return $errors; + }, + [] + ); + Assert::assertContains($error, $errors); + } + + /** + * @Then the comment :comment will have been printed to the console + */ + public function theCommentWillHaveBeenPrintedToTheConsole(string $comment): void + { + Assert::assertStringContainsString($comment, $this->providerVerifier->getVerifyResult()->getOutput()); + } + + /** + * @Then the :name will displayed as the original test name + */ + public function theWillDisplayedAsTheOriginalTestName(string $name): void + { + Assert::assertStringContainsString(sprintf('Test Name: %s', $name), $this->providerVerifier->getVerifyResult()->getOutput()); + } +} diff --git a/compatibility-suite/tests/Context/V4/ResponseGeneratorsContext.php b/compatibility-suite/tests/Context/V4/ResponseGeneratorsContext.php new file mode 100644 index 00000000..93dcb826 --- /dev/null +++ b/compatibility-suite/tests/Context/V4/ResponseGeneratorsContext.php @@ -0,0 +1,56 @@ +builder->build([ + 'No' => $this->id, + 'method' => 'GET', + 'path' => '/response-generators', + 'response body' => 'file: basic.json', + ]); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->storage->add(InteractionsStorageInterface::CLIENT_DOMAIN, $this->id, $interaction, true); + $this->responseGeneratorBuilder->build($interaction->getResponse(), 'mockserver-generator.json'); + + $this->server->register($this->id); + $this->client->sendRequestToServer($this->id); + + $body = $this->client->getResponse()->getBody()->getContents(); + $href = json_decode($table->getRow(0)[0], true)['href']; + $serverBaseUri = $this->server->getBaseUri(); + $search = [ + (string) $serverBaseUri->withHost('127.0.0.1'), + (string) $serverBaseUri->withHost('::1'), + ]; + $body = str_replace($search, $href, $body); + $this->bodyStorage->setBody($body); + } +} diff --git a/compatibility-suite/tests/Context/V4/ResponseMatchingContext.php b/compatibility-suite/tests/Context/V4/ResponseMatchingContext.php new file mode 100644 index 00000000..d878570c --- /dev/null +++ b/compatibility-suite/tests/Context/V4/ResponseMatchingContext.php @@ -0,0 +1,112 @@ +getHash(); + $row = reset($rows); + $interaction = $this->builder->build([ + 'No' => $this->id, + 'method' => 'GET', + 'path' => '/matching', + ] + $row); + $this->storage->add(InteractionsStorageInterface::SERVER_DOMAIN, $this->id, $interaction); + $this->storage->add(InteractionsStorageInterface::PACT_WRITER_DOMAIN, $this->id, $interaction); + $this->responseMatchingRuleBuilder->build($interaction->getResponse(), $row['matching rules']); + $this->pactWriter->write($this->id); + $this->providerVerifier->addSource($this->pactWriter->getPactPath()); + } + + /** + * @Given a status :status response is received + */ + public function aStatusResponseIsReceived(int $status): void + { + $interaction = $this->storage->get(InteractionsStorageInterface::SERVER_DOMAIN, $this->id); + $interaction->getResponse()->setStatus($status); + $this->server->register($this->id); + } + + /** + * @When the response is compared to the expected one + */ + public function theResponseIsComparedToTheExpectedOne(): void + { + $this->providerVerifier->getConfig()->getProviderInfo()->setPort($this->server->getPort()); + $this->providerVerifier->verify(); + } + + /** + * @Then the response comparison should be OK + */ + public function theResponseComparisonShouldBeOk(): void + { + Assert::assertTrue($this->providerVerifier->getVerifyResult()->isSuccess()); + } + + /** + * @Then the response comparison should NOT be OK + */ + public function theResponseComparisonShouldNotBeOk(): void + { + Assert::assertFalse($this->providerVerifier->getVerifyResult()->isSuccess()); + } + + /** + * @Then the response mismatches will contain a :type mismatch with error :error + */ + public function theResponseMismatchesWillContainAMismatchWithError(string $type, string $error): void + { + $output = json_decode($this->providerVerifier->getVerifyResult()->getOutput(), true); + $errors = array_reduce( + $output['errors'], + function (array $errors, array $error) use ($type) { + switch ($error['mismatch']['type']) { + case 'mismatches': + foreach ($error['mismatch']['mismatches'] as $mismatch) { + if ($mismatch['type'] === Mismatch::MOCK_SERVER_MISMATCH_TYPE_MAP[$type]) { + $errors[] = $mismatch['mismatch']; + } + } + break; + + default: + break; + } + + return $errors; + }, + [] + ); + Assert::assertContains($error, $errors); + } +} diff --git a/compatibility-suite/tests/Context/V4/SyncMessage/ConsumerContext.php b/compatibility-suite/tests/Context/V4/SyncMessage/ConsumerContext.php new file mode 100644 index 00000000..0f86ef1a --- /dev/null +++ b/compatibility-suite/tests/Context/V4/SyncMessage/ConsumerContext.php @@ -0,0 +1,328 @@ +message = new Message(); + $this->message->setDescription('a synchronous message'); + } + + /** + * @When the Pact file for the test is generated + */ + public function thePactFileForTheTestIsGenerated(): void + { + $this->pactWriter->write($this->message); + } + + /** + * @Then the first interaction in the Pact file will have a type of :type + */ + public function theFirstInteractionInThePactFileWillHaveATypeOf(string $type): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertSame($type, $pact['interactions'][0]['type']); + } + + /** + * @Given a key of :key is specified for the synchronous message interaction + */ + public function aKeyOfIsSpecifiedForTheSynchronousMessageInteraction(string $key): void + { + throw new PendingException("Can't set sync message's key using FFI call"); + } + + /** + * @Given the synchronous message interaction is marked as pending + */ + public function theSynchronousMessageInteractionIsMarkedAsPending(): void + { + throw new PendingException("Can't set sync message's pending using FFI call"); + } + + /** + * @Given a comment :value is added to the synchronous message interaction + */ + public function aCommentIsAddedToTheSynchronousMessageInteraction(string $value): void + { + throw new PendingException("Can't set message's comment using FFI call"); + } + + /** + * @Then the first interaction in the Pact file will have :name = :value + */ + public function theFirstInteractionInThePactFileWillHave(string $name, string $value): void + { + $pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + Assert::assertSame($value, $pact['interactions'][0][$name]); + } + + /** + * @Given the message request payload contains the :fixture JSON document + */ + public function theMessageRequestPayloadContainsTheJsonDocument(string $fixture): void + { + throw new PendingException("Can't set sync message's request payload using FFI call"); + } + + /** + * @Given the message response payload contains the :fixture document + */ + public function theMessageResponsePayloadContainsTheDocument(string $fixture): void + { + throw new PendingException("Can't set sync message's response payload using FFI call"); + } + + /** + * @Then the received message payload will contain the :fixture document + */ + public function theReceivedMessagePayloadWillContainTheDocument(string $fixture): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then a Pact file for the message interaction will have been written + */ + public function aPactFileForTheMessageInteractionWillHaveBeenWritten(): void + { + Assert::assertTrue(file_exists($this->pactWriter->getPactPath())); + $this->pact = json_decode(file_get_contents($this->pactWriter->getPactPath()), true); + } + + /** + * @Then the pact file will contain :num interaction + */ + public function thePactFileWillContainInteraction(int $num): void + { + Assert::assertCount($num, $this->pact['interactions']); + } + + /** + * @Then the first interaction in the pact file will contain the :fixture document as the request + */ + public function theFirstInteractionInThePactFileWillContainTheDocumentAsTheRequest(string $fixture): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the first interaction in the pact file request content type will be :contentType + */ + public function theFirstInteractionInThePactFileRequestContentTypeWillBe(string $contentType): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the first interaction in the pact file will contain the :fixture document as a response + */ + public function theFirstInteractionInThePactFileWillContainTheDocumentAsAResponse(string $fixture): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the first interaction in the pact file response content type will be :contentType + */ + public function theFirstInteractionInThePactFileResponseContentTypeWillBe(string $contentType): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the first interaction in the pact file will contain :num response messages + */ + public function theFirstInteractionInThePactFileWillContainResponseMessages(int $num): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the first interaction in the pact file will contain the :fixture document as the first response message + */ + public function theFirstInteractionInThePactFileWillContainTheDocumentAsTheFirstResponseMessage(string $fixture): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the first interaction in the pact file will contain the :fixture document as the second response message + */ + public function theFirstInteractionInThePactFileWillContainTheDocumentAsTheSecondResponseMessage(string $fixture): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Given the message request contains the following metadata: + */ + public function theMessageRequestContainsTheFollowingMetadata(TableNode $table): void + { + throw new PendingException("Can't set sync message's metadata using FFI call"); + } + + /** + * @Then /^the received message request metadata will contain "([^"]+)" == "(.+)"$/ + */ + public function theReceivedMessageRequestMetadataWillContain(string $key, string $value): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then /^the first message in the pact file will contain the request message metadata "([^"]+)" == "(.+)"$/ + */ + public function theFirstMessageInThePactFileWillContainTheRequestMessageMetadata(string $key, string $value): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Given a provider state :state for the synchronous message is specified + */ + public function aProviderStateForTheSynchronousMessageIsSpecified(string $state): void + { + $this->message->addProviderState($state, []); + } + + /** + * @Given a provider state :state for the synchronous message is specified with the following data: + */ + public function aProviderStateForTheSynchronousMessageIsSpecifiedWithTheFollowingData(string $state, TableNode $table): void + { + $rows = $table->getHash(); + $row = reset($rows); + $this->message->addProviderState($state, $row); + } + + /** + * @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['interactions'][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['interactions'][0]['providerStates']); + Assert::assertContains($state, $states); + } + + /** + * @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['interactions'][0]['providerStates']); + } + + /** + * @Given the message request is configured with the following: + */ + public function theMessageRequestIsConfiguredWithTheFollowing(TableNode $table): void + { + throw new PendingException("Can't set sync message's request generators using FFI call"); + } + + /** + * @Given the message response is configured with the following: + */ + public function theMessageResponseIsConfiguredWithTheFollowing(TableNode $table): void + { + throw new PendingException("Can't set sync message's response generators using FFI call"); + } + + /** + * @Then the message request contents for :path will have been replaced with a(n) :type + */ + public function theMessageRequestContentsForWillHaveBeenReplacedWithAn(string $path, string $type): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the message response contents for :path will have been replaced with a(n) :type + */ + public function theMessageResponseContentsForWillHaveBeenReplacedWithAn(string $path, string $type): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the received message request metadata will contain :key replaced with a(n) :type + */ + public function theReceivedMessageRequestMetadataWillContainReplacedWithAn(string $key, string $type): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the received message response metadata will contain :key == :value + */ + public function theReceivedMessageResponseMetadataWillContain(string $key, string $value): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the received message response metadata will contain :key replaced with an :type + */ + public function theReceivedMessageResponseMetadataWillContainReplacedWithAn(string $key, string $type): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @When the message is successfully processed + */ + public function theMessageIsSuccessfullyProcessed(): void + { + $this->thePactFileForTheTestIsGenerated(); // TODO Implement other pending steps first then update this step + } + + /** + * @Then the consumer test will have passed + */ + public function theConsumerTestWillHavePassed(): void + { + throw new PendingException('Implement previous pending step first'); + } + + /** + * @Then the received message content type will be :contentType + */ + public function theReceivedMessageContentTypeWillBe(string $contentType): void + { + throw new PendingException('Implement previous pending step first'); + } +} diff --git a/compatibility-suite/tests/Service/SyncMessagePactWriter.php b/compatibility-suite/tests/Service/SyncMessagePactWriter.php new file mode 100644 index 00000000..d15b0ee7 --- /dev/null +++ b/compatibility-suite/tests/Service/SyncMessagePactWriter.php @@ -0,0 +1,49 @@ +pactPath = "$pactDir/$consumer-$provider.json"; + $config = new MockServerConfig(); + $config + ->setConsumer($consumer) + ->setProvider($provider) + ->setPactDir($pactDir) + ->setPactSpecificationVersion($this->specificationVersion) + ->setPactFileWriteMode($mode); + $client = new Client(); + $pactRegistry = new PactRegistry($client); + $pactDriver = new PactDriver($client, $config, $pactRegistry); + $messageRegistry = new SyncMessageRegistry($client, $pactRegistry); + + $pactDriver->setUp(); + $messageRegistry->registerMessage($message); + $pactDriver->writePact(); + $pactDriver->cleanUp(); + } + + public function getPactPath(): string + { + return $this->pactPath; + } +} diff --git a/compatibility-suite/tests/Service/SyncMessagePactWriterInterface.php b/compatibility-suite/tests/Service/SyncMessagePactWriterInterface.php new file mode 100644 index 00000000..409256dc --- /dev/null +++ b/compatibility-suite/tests/Service/SyncMessagePactWriterInterface.php @@ -0,0 +1,13 @@ +set('sync_message_pact_writer', new SyncMessagePactWriter($this->getSpecification())); + } + + protected function getSpecification(): string + { + return '4.0.0'; + } +}