Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Add form urlencoded example #630

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 13 additions & 15 deletions compatibility-suite/tests/Service/BodyValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@

final class BodyValidator implements BodyValidatorInterface
{
public const INT_REGEX = '/\d+/';
public const DEC_REGEX = '/\d+\.\d+/';
public const HEX_REGEX = '/[a-fA-F0-9]+/';
public const STR_REGEX = '/\d{1,8}/';
public const DATE_REGEX = '/\d{4}-\d{2}-\d{2}/';
Expand All @@ -23,19 +21,19 @@ public function __construct(private BodyStorageInterface $bodyStorage)
public function validateType(string $path, string $type): void
{
$value = $this->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,
});
match ($type) {
'integer' => Assert::assertIsInt($value),
'decimal number' => Assert::assertIsFloat($value),
'hexadecimal number' => Assert::assertIsString($value) && Assert::assertMatchesRegularExpression(self::HEX_REGEX, $value),
'random string' => Assert::assertIsString($value),
'string from the regex' => Assert::assertIsString($value) && Assert::assertMatchesRegularExpression(self::STR_REGEX, $value),
'date' => Assert::assertIsString($value) && Assert::assertMatchesRegularExpression(self::DATE_REGEX, $value),
'time' => Assert::assertIsString($value) && Assert::assertMatchesRegularExpression(self::TIME_REGEX, $value),
'date-time' => Assert::assertIsString($value) && Assert::assertMatchesRegularExpression(self::DATETIME_REGEX, $value),
'UUID', 'simple UUID', 'lower-case-hyphenated UUID', 'upper-case-hyphenated UUID', 'URN UUID' => Assert::assertTrue(Uuid::isValid($value)),
'boolean' => Assert::assertIsBool($value),
default => null,
};
}

public function validateValue(string $path, string $value): void
Expand Down
52 changes: 29 additions & 23 deletions compatibility-suite/tests/Service/MatchingRuleConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace PhpPactTest\CompatibilitySuite\Service;

use PhpPact\Consumer\Matcher\Enum\HttpStatus;
use PhpPact\Consumer\Matcher\Matchers\ArrayContains;
use PhpPact\Consumer\Matcher\Matchers\Boolean;
use PhpPact\Consumer\Matcher\Matchers\ContentType;
Expand Down Expand Up @@ -55,10 +56,10 @@ public function convert(MatchingRule $rule, mixed $value): ?MatcherInterface
return new Number($this->getNumber($value));

case 'integer':
return new Integer($this->getNumber($value));
return new Integer($this->getInteger($value));

case 'decimal':
return new Decimal($this->getNumber($value));
return new Decimal($this->getDecimal($value));

case 'null':
return new NullValue();
Expand Down Expand Up @@ -92,43 +93,48 @@ public function convert(MatchingRule $rule, mixed $value): ?MatcherInterface

case 'regex':
$regex = $rule->getMatcherAttribute('regex');
return new Regex($regex, $this->ignoreInvalidValue($regex, $value));
return new Regex($regex, $value ?? '');

case 'statusCode':
return new StatusCode($rule->getMatcherAttribute('status'));
return new StatusCode($this->getHttpStatus($rule));

default:
return null;
}
}

private function getNumber(mixed $value): int|float|null
private function getNumber(mixed $value): int|float
{
if (is_numeric($value)) {
$value = $value + 0;
} else {
$value = null;
return $value + 0;
}

return $value;
// @todo Fix this compatibility-suite's mistake: there is no number in `basic.json`
return 1;
}

private function ignoreInvalidValue(string $regex, mixed $value): string|array|null
private function getInteger(mixed $value): int
{
if (is_string($value)) {
if (!preg_match("/$regex/", $value)) {
$value = null;
}
} elseif (is_array($value)) {
foreach (array_keys($value) as $key) {
if (!preg_match("/$regex/", $value[$key])) {
$value[$key] = null;
}
}
} else {
$value = null;
if (is_numeric($value)) {
return $value + 0;
}

return $value;
// @todo Fix this compatibility-suite's mistake: there is no integer in `basic.json`
return 1;
}

private function getDecimal(mixed $value): float
{
if (is_numeric($value)) {
return $value + 0;
}

// @todo Fix this compatibility-suite's mistake: there is no decimal in `basic.json`
return 1.1;
}

private function getHttpStatus(MatchingRule $rule): HttpStatus
{
return HttpStatus::from($rule->getMatcherAttribute('status') ?? '');
}
}
8 changes: 7 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@
"composer run-example:protobuf-sync-message",
"composer run-example:stub-server",
"composer run-example:xml",
"composer run-example:graphql"
"composer run-example:graphql",
"composer run-example:form-urlencoded"
],
"run-example:binary": [
"rm -f example/binary/pacts/binaryConsumer-binaryProvider.json",
Expand Down Expand Up @@ -137,6 +138,11 @@
"rm -f example/graphql/pacts/graphqlConsumer-graphqlProvider.json",
"cd example/graphql/consumer && phpunit",
"cd example/graphql/provider && phpunit"
],
"run-example:form-urlencoded": [
"rm -f example/graphql/pacts/formUrlEncodedConsumer-formUrlEncodedProvider.json",
"cd example/form-urlencoded/consumer && phpunit",
"cd example/form-urlencoded/provider && phpunit"
]
},
"extra": {
Expand Down
5 changes: 5 additions & 0 deletions example/form-urlencoded/consumer/autoload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

$loader = require __DIR__ . '/../../../vendor/autoload.php';
$loader->addPsr4('FormUrlEncodedConsumer\\', __DIR__ . '/src');
$loader->addPsr4('FormUrlEncodedConsumer\\Tests\\', __DIR__ . '/tests');
11 changes: 11 additions & 0 deletions example/form-urlencoded/consumer/phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./autoload.php" colors="true">
<testsuites>
<testsuite name="PhpPact Example Tests">
<directory>./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="PACT_LOGLEVEL" value="DEBUG"/>
</php>
</phpunit>
50 changes: 50 additions & 0 deletions example/form-urlencoded/consumer/src/Service/HttpClientService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

namespace FormUrlEncodedConsumer\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Uri;

class HttpClientService
{
private Client $httpClient;

private string $baseUri;

public function __construct(string $baseUri)
{
$this->httpClient = new Client();
$this->baseUri = $baseUri;
}

public function createUser(): string
{
$response = $this->httpClient->post(new Uri("{$this->baseUri}/users"), [
'body' => http_build_query([
'empty' => '',
'agree' => 'true',
'fullname' => 'First Last Name',
'email' => '[email protected]',
'password' => 'very@secure&password123',
'age' => 41,
'ampersand' => '&',
'slash' => '/',
'question-mark' => '?',
'equals-sign' => '=',
'&' => 'ampersand',
'/' => 'slash',
'?' => 'question-mark',
'=' => 'equals-sign',
]) .
'&=first&=second&=third' .
'&roles[]=User&roles[]=Manager' .
'&orders[]=&orders[]=ASC&orders[]=DESC',
'headers' => [
'Accept' => 'application/x-www-form-urlencoded',
'Content-Type' => 'application/x-www-form-urlencoded',
],
]);

return $response->getBody();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace FormUrlEncodedConsumer\Tests\Service;

use PhpPact\Consumer\Matcher\Generators\RandomInt;
use PhpPact\Consumer\Matcher\Matcher;
use FormUrlEncodedConsumer\Service\HttpClientService;
use PhpPact\Consumer\InteractionBuilder;
use PhpPact\Consumer\Model\Body\Text;
use PhpPact\Consumer\Model\ConsumerRequest;
use PhpPact\Consumer\Model\ProviderResponse;
use PhpPact\Standalone\MockService\MockServerConfig;
use PHPUnit\Framework\TestCase;

class HttpClientServiceTest extends TestCase
{
public function testGetMovies()
{
$matcher = new Matcher();

$request = new ConsumerRequest();
$request
->setMethod('POST')
->setPath('/users')
->addHeader('Content-Type', 'application/x-www-form-urlencoded')
->addHeader('Accept', 'application/x-www-form-urlencoded')
->setBody(
new Text(
json_encode([
'empty' => $matcher->equal(''),
'agree' => $matcher->regex('false', 'true|false'),
'fullname' => $matcher->string('User name'),
'email' => $matcher->email('[email protected]'),
'password' => $matcher->regex('user@password111', '^[\w\d@$!%*#?&^_-]{8,}$'),
'age' => $matcher->number(27),
'roles[]' => $matcher->eachValue(['User'], [$matcher->regex('User', 'Admin|User|Manager')]),
'orders[]' => $matcher->arrayContaining([
$matcher->equal('DESC'),
$matcher->equal('ASC'),
$matcher->equal(''),
]),
// Empty string keys are supported
'' => ['first', 'second', 'third'],
// Null, boolean and object values are not supported, so the values and matchers will be ignored
'null' => $matcher->nullValue(),
'boolean' => $matcher->booleanV3(true),
'object' => $matcher->like([
'key' => $matcher->string('value')
]),
// special characters are encoded
'ampersand' => $matcher->equal('&'),
'slash' => '/',
'question-mark' => '?',
'equals-sign' => '=',
'&' => 'ampersand',
'/' => 'slash',
'?' => 'question-mark',
'=' => 'equals-sign',
]),
'application/x-www-form-urlencoded'
)
)
;

$response = new ProviderResponse();
$response
->setStatus(201)
->addHeader('Content-Type', 'application/x-www-form-urlencoded')
->setBody(
new Text(
json_encode([
'id' => $matcher->uuid(),
'age' => $matcher->integerV3()->withGenerator(new RandomInt(0, 130)),
'name[]' => [
$matcher->regex(null, $gender = 'Mr\.|Mrs\.|Miss|Ms\.'),
$matcher->string(),
$matcher->string(),
$matcher->string(),
],
]),
'application/x-www-form-urlencoded'
)
);

$config = new MockServerConfig();
$config
->setConsumer('formUrlEncodedConsumer')
->setProvider('formUrlEncodedProvider')
->setPactDir(__DIR__.'/../../../pacts');
if ($logLevel = \getenv('PACT_LOGLEVEL')) {
$config->setLogLevel($logLevel);
}
$builder = new InteractionBuilder($config);
$builder
->given('Endpoint is protected')
->uponReceiving('A post request to /users')
->with($request)
->willRespondWith($response);

$service = new HttpClientService($config->getBaseUri());
parse_str($service->createUser(), $params);
$verifyResult = $builder->verify();

$this->assertTrue(condition: $verifyResult);
$this->assertArrayHasKey('id', $params);
$pattern = Matcher::UUID_V4_FORMAT;
$this->assertMatchesRegularExpression("/{$pattern}/", $params['id']);
$this->assertArrayHasKey('age', $params);
$this->assertLessThanOrEqual(130, $params['age']);
$this->assertGreaterThanOrEqual(0, $params['age']);
$this->assertArrayHasKey('name', $params);
$this->assertIsArray($params['name']);
$this->assertCount(4, $params['name']);
$this->assertMatchesRegularExpression("/{$gender}/", $params['name'][0]);
}
}
Loading
Loading