Skip to content

Commit

Permalink
Merge pull request #429 from tienvx/v1-provider
Browse files Browse the repository at this point in the history
test(compatibility-suite): Implement V1 Provider scenarios
  • Loading branch information
tienvx committed Dec 22, 2023
2 parents a6fca91 + 3b57f20 commit c07b72a
Show file tree
Hide file tree
Showing 25 changed files with 988 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: pact-php
name: Pact-PHP Code Analysis & Test

on:
push:
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/compatibility-suite.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Pact-PHP Compatibility Suite

on: [push, pull_request]

env:
pact_do_not_track: true

jobs:
v1:
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/V1
3 changes: 3 additions & 0 deletions behat.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
imports:
- 'compatibility-suite/suites/v1/http/consumer.yml'
- 'compatibility-suite/suites/v1/http/provider.yml'
1 change: 1 addition & 0 deletions compatibility-suite/public/provider-states/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.json
68 changes: 68 additions & 0 deletions compatibility-suite/public/provider-states/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;

require __DIR__ . '/../../../vendor/autoload.php';

$app = AppFactory::create();
$app->addBodyParsingMiddleware();

$path = __DIR__ . '/provider-states.json';
$get = fn (): array => json_decode(file_get_contents($path), true);
$set = fn (array $providerStates) => file_put_contents($path, json_encode($providerStates));

if (!file_exists($path)) {
$set([]);
}

$stateChangeHandler = function (Request $request, Response $response) use ($get, $set) {
$body = $request->getParsedBody();

$providerStates = $get();
$providerStates[] = $body;
$set($providerStates);

return $response;
};

$app->get('/has-action', function (Request $request, Response $response) use ($get) {
$action = $request->getQueryParams()['action'];
$hasAction = !empty(array_filter(
$get(),
fn (array $providerState) => $providerState['action'] === $action
));

$response->getBody()->write((string) $hasAction);

return $response->withHeader('Content-Type', 'text/plain');
});

$app->get('/has-state', function (Request $request, Response $response) use ($get) {
$params = $request->getQueryParams();
$action = $params['action'];
$state = $params['state'];
unset($params['action'], $params['state']);
$hasState = !empty(array_filter(
$get(),
fn (array $providerState) =>
$providerState['action'] === $action
&& $providerState['state'] === $state
&& $providerState['params'] == $params
));

$response->getBody()->write((string) $hasState);

return $response->withHeader('Content-Type', 'text/plain');
});

$app->post('/pact-change-state', $stateChangeHandler);

$app->post('/failed-pact-change-state', function (Request $request, Response $response) use ($stateChangeHandler) {
$stateChangeHandler($request, $response);

throw new \Exception('Cant do it');
});

$app->run();
23 changes: 23 additions & 0 deletions compatibility-suite/suites/v1/http/consumer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
default:
suites:
v1_http_consumer:
paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V1/http_consumer.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\V1\Http\ConsumerContext':
- '@server'
- '@request_builder'
- '@client'
- '@interactions_storage'
- '@fixture_loader'

services: PhpPactTest\CompatibilitySuite\ServiceContainer\V1
32 changes: 32 additions & 0 deletions compatibility-suite/suites/v1/http/provider.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
default:
suites:
v1_http_provider:
paths: [ '%paths.base%/compatibility-suite/pact-compatibility-suite/features/V1/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\PactBrokerContext':
- '@pact_broker'
- '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'

services: PhpPactTest\CompatibilitySuite\ServiceContainer\V1
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace PhpPactTest\CompatibilitySuite\Context\Shared\Hook;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use PhpPactTest\CompatibilitySuite\Service\PactBrokerInterface;

final class PactBrokerContext implements Context
{
public function __construct(
private PactBrokerInterface $pactBroker
) {
}

/**
* @BeforeScenario
*/
public function startPactBroker(BeforeScenarioScope $scope): void
{
if (str_contains($scope->getScenario()->getTitle(), 'via a Pact broker')) {
$this->pactBroker->start();
}
}

/**
* @AfterScenario
*/
public function stopPactBroker(AfterScenarioScope $scope): void
{
if (str_contains($scope->getScenario()->getTitle(), 'via a Pact broker')) {
$this->pactBroker->stop();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace PhpPactTest\CompatibilitySuite\Context\Shared\Hook;

use Behat\Behat\Context\Context;
use Behat\Behat\Hook\Scope\AfterScenarioScope;
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
use PhpPactTest\CompatibilitySuite\Service\ProviderStateServerInterface;

final class ProviderStateContext implements Context
{
public function __construct(
private ProviderStateServerInterface $providerStateServer
) {
}

/**
* @BeforeScenario
*/
public function startProviderState(BeforeScenarioScope $scope): void
{
if (preg_match('/^Verifying .* provider state/', $scope->getScenario()->getTitle())) {
$this->providerStateServer->start();
}
}

/**
* @AfterScenario
*/
public function stopProviderState(AfterScenarioScope $scope): void
{
if (preg_match('/^Verifying .* provider state/', $scope->getScenario()->getTitle())) {
$this->providerStateServer->stop();
}
}
}
145 changes: 145 additions & 0 deletions compatibility-suite/tests/Context/Shared/ProviderContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

namespace PhpPactTest\CompatibilitySuite\Context\Shared;

use Behat\Behat\Context\Context;
use GuzzleHttp\Psr7\Uri;
use PhpPactTest\CompatibilitySuite\Constant\Mismatch;
use PhpPactTest\CompatibilitySuite\Service\ProviderStateServerInterface;
use PhpPactTest\CompatibilitySuite\Service\ProviderVerifierInterface;
use PhpPactTest\CompatibilitySuite\Service\ServerInterface;
use PHPUnit\Framework\Assert;

final class ProviderContext implements Context
{
public function __construct(
private ServerInterface $server,
private ProviderVerifierInterface $providerVerifier,
private ProviderStateServerInterface $providerStateServer,
) {
}

/**
* @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 the verification will NOT be successful
*/
public function theVerificationWillNotBeSuccessful(): void
{
Assert::assertFalse($this->providerVerifier->getVerifyResult()->isSuccess());
}

/**
* @Given a provider state callback is configured
*/
public function aProviderStateCallbackIsConfigured(): void
{
$port = $this->providerStateServer->getPort();
$this->providerVerifier
->getConfig()
->getProviderState()
->setStateChangeUrl(new Uri("http://localhost:$port/pact-change-state"))
->setStateChangeTeardown(true);
;
}

/**
* @Then the provider state callback will be called before the verification is run
*/
public function theProviderStateCallbackWillBeCalledBeforeTheVerificationIsRun(): void
{
Assert::assertTrue($this->providerStateServer->hasAction(ProviderStateServerInterface::ACTION_SETUP));
}

/**
* @Then the provider state callback will receive a setup call with :state as the provider state parameter
*/
public function theProviderStateCallbackWillReceiveASetupCallWithAsTheProviderStateParameter(string $state): void
{
Assert::assertTrue($this->providerStateServer->hasState(ProviderStateServerInterface::ACTION_SETUP, $state));
}

/**
* @Then the provider state callback will be called after the verification is run
*/
public function theProviderStateCallbackWillBeCalledAfterTheVerificationIsRun(): void
{
Assert::assertTrue($this->providerStateServer->hasAction(ProviderStateServerInterface::ACTION_TEARDOWN));
}

/**
* @Then the provider state callback will receive a teardown call :state as the provider state parameter
*/
public function theProviderStateCallbackWillReceiveATeardownCallAsTheProviderStateParameter(string $state): void
{
Assert::assertTrue($this->providerStateServer->hasState(ProviderStateServerInterface::ACTION_TEARDOWN, $state));
}

/**
* @Given a provider state callback is configured, but will return a failure
*/
public function aProviderStateCallbackIsConfiguredButWillReturnAFailure(): void
{
$port = $this->providerStateServer->getPort();
$this->providerVerifier
->getConfig()
->getProviderState()
->setStateChangeUrl(new Uri("http://localhost:$port/failed-pact-change-state"))
->setStateChangeTeardown(true);
;
}

/**
* @Then the provider state callback will NOT receive a teardown call
*/
public function theProviderStateCallbackWillNotReceiveATeardownCall(): void
{
Assert::assertFalse($this->providerStateServer->hasAction(ProviderStateServerInterface::ACTION_TEARDOWN));
}

/**
* @Then the verification results will contain a :error error
*/
public function theVerificationResultsWillContainAError(string $error): void
{
$output = json_decode($this->providerVerifier->getVerifyResult()->getOutput(), true);
$errors = array_reduce(
$output['errors'],
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);
}
}
Loading

0 comments on commit c07b72a

Please sign in to comment.