diff --git a/composer.json b/composer.json index b1c13c5b..9183bff3 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,10 @@ "XmlConsumer\\": "example/xml/consumer/src", "XmlConsumer\\Tests\\": "example/xml/consumer/tests", "XmlProvider\\": "example/xml/provider/src", - "XmlProvider\\Tests\\": "example/xml/provider/tests" + "XmlProvider\\Tests\\": "example/xml/provider/tests", + "MatchersConsumer\\": "example/matchers/consumer/src", + "MatchersConsumer\\Tests\\": "example/matchers/consumer/tests", + "MatchersProvider\\Tests\\": "example/matchers/provider/tests" } }, "scripts": { diff --git a/example/matchers/consumer/phpunit.xml b/example/matchers/consumer/phpunit.xml new file mode 100644 index 00000000..62a9eb00 --- /dev/null +++ b/example/matchers/consumer/phpunit.xml @@ -0,0 +1,11 @@ + + + + + ./tests + + + + + + diff --git a/example/matchers/consumer/src/Service/HttpClientService.php b/example/matchers/consumer/src/Service/HttpClientService.php new file mode 100644 index 00000000..9ffff90f --- /dev/null +++ b/example/matchers/consumer/src/Service/HttpClientService.php @@ -0,0 +1,28 @@ +httpClient = new Client(); + $this->baseUri = $baseUri; + } + + public function getMatchers(): array + { + $response = $this->httpClient->get("{$this->baseUri}/matchers", [ + 'headers' => ['Accept' => 'application/json'], + 'query' => ['ignore' => 'statusCode'], + ]); + + return \json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR); + } +} diff --git a/example/matchers/consumer/tests/Service/MatchersTest.php b/example/matchers/consumer/tests/Service/MatchersTest.php new file mode 100644 index 00000000..275cc2ca --- /dev/null +++ b/example/matchers/consumer/tests/Service/MatchersTest.php @@ -0,0 +1,140 @@ +matcher = new Matcher(); + } + + public function testGetMatchers() + { + $request = new ConsumerRequest(); + $request + ->setMethod('GET') + ->setPath($this->matcher->regex('/matchers', '^\/matchers$')) + ->setQuery([ + 'ignore' => 'statusCode', + ]) + ->addHeader('Accept', 'application/json'); + + $response = new ProviderResponse(); + $response + ->setStatus(200) + ->addHeader('Content-Type', 'application/json') + ->setBody([ + 'like' => $this->matcher->like(['key' => 'value']), + 'eachLike' => $this->matcher->eachLike('item'), + 'atLeastLike' => $this->matcher->atLeastLike(1, 5), + 'atMostLike' => $this->matcher->atMostLike(1, 3), + 'constrainedArrayLike' => $this->matcher->constrainedArrayLike('item', 2, 4), + 'regex' => $this->matcher->regex('500 miles', '^\d+ (miles|kilometers)$'), + 'dateISO8601' => $this->matcher->dateISO8601(), + 'timeISO8601' => $this->matcher->timeISO8601(), + 'dateTimeISO8601' => $this->matcher->dateTimeISO8601(), + 'dateTimeWithMillisISO8601' => $this->matcher->dateTimeWithMillisISO8601(), + 'timestampRFC3339' => $this->matcher->timestampRFC3339(), + 'likeBool' => $this->matcher->boolean(), + 'likeInt' => $this->matcher->integer(), + 'likeDecimal' => $this->matcher->decimal(), + 'boolean' => $this->matcher->booleanV3(false), + 'integer' => $this->matcher->integerV3(9), + 'decimal' => $this->matcher->decimalV3(79.01), + 'hexadecimal' => $this->matcher->hexadecimal('F7A16'), + 'uuid' => $this->matcher->uuid('52c9585e-f345-4964-aa28-a45c64b2b2eb'), + 'ipv4Address' => $this->matcher->ipv4Address(), + 'ipv6Address' => $this->matcher->ipv6Address(), + 'email' => $this->matcher->email(), + 'nullValue' => $this->matcher->nullValue(), + 'date' => $this->matcher->date('yyyy-MM-dd', '2015-05-16'), + 'time' => $this->matcher->time('HH:mm::ss', '23:59::58'), + 'datetime' => $this->matcher->datetime("YYYY-mm-DD'T'HH:mm:ss", '2000-10-31T01:30:00'), + 'likeString' => $this->matcher->string('some string'), + 'equal' => $this->matcher->equal('exact this value'), + 'includes' => $this->matcher->includes('lazy dog'), + 'number' => $this->matcher->number(123), + 'arrayContaining' => $this->matcher->arrayContaining([ + $this->matcher->string('some text'), + $this->matcher->number(111), + $this->matcher->uuid('2fbd41cc-4bbc-44ea-a419-67f767691407'), + ]), + 'notEmpty' => $this->matcher->notEmpty(['1','2','3']), + 'semver' => $this->matcher->semver('10.0.0-alpha4'), + 'contentType' => $this->matcher->contentType('text/html'), + ]); + + $config = new MockServerConfig(); + $config + ->setConsumer('matchersConsumer') + ->setProvider('matchersProvider') + ->setPactDir(__DIR__.'/../../../pacts') + ->setPactSpecificationVersion('4.0.0'); + if ($logLevel = \getenv('PACT_LOGLEVEL')) { + $config->setLogLevel($logLevel); + } + $builder = new InteractionBuilder($config); + $builder + ->given('Get Matchers') + ->uponReceiving('A get request to /matchers') + ->with($request) + ->willRespondWith($response); + + $service = new HttpClientService($config->getBaseUri()); + $matchersResult = $service->getMatchers(); + $verifyResult = $builder->verify(); + + $this->assertTrue($verifyResult); + $this->assertEquals([ + 'like' => ['key' => 'value'], + 'eachLike' => ['item'], + 'atLeastLike' => [1, 1, 1, 1, 1], + 'atMostLike' => [1], + 'constrainedArrayLike' => ['item', 'item'], + 'regex' => '500 miles', + 'dateISO8601' => '2013-02-01', + 'timeISO8601' => 'T22:44:30.652Z', + 'dateTimeISO8601' => '2015-08-06T16:53:10+01:00', + 'dateTimeWithMillisISO8601' => '2015-08-06T16:53:10.123+01:00', + 'timestampRFC3339' => 'Mon, 31 Oct 2016 15:21:41 -0400', + 'likeBool' => true, + 'likeInt' => 13, + 'likeDecimal' => 13.01, + 'boolean' => false, + 'integer' => 9, + 'decimal' => 79.01, + 'hexadecimal' => 'F7A16', + 'uuid' => '52c9585e-f345-4964-aa28-a45c64b2b2eb', + 'ipv4Address' => '127.0.0.13', + 'ipv6Address' => '::ffff:192.0.2.128', + 'email' => 'hello@pact.io', + 'nullValue' => null, + 'date' => '2015-05-16', + 'time' => '23:59::58', + 'datetime' => '2000-10-31T01:30:00', + 'likeString' => 'some string', + 'equal' => 'exact this value', + 'includes' => 'lazy dog', + 'number' => 123, + 'arrayContaining' => [ + 'some text', + 111, + '2fbd41cc-4bbc-44ea-a419-67f767691407', + ], + 'notEmpty' => ['1', '2', '3'], + 'semver' => '10.0.0-alpha4', + 'contentType' => 'text/html', + ], $matchersResult); + } +} diff --git a/example/matchers/pacts/matchersConsumer-matchersProvider.json b/example/matchers/pacts/matchersConsumer-matchersProvider.json new file mode 100644 index 00000000..e5ad5469 --- /dev/null +++ b/example/matchers/pacts/matchersConsumer-matchersProvider.json @@ -0,0 +1,469 @@ +{ + "consumer": { + "name": "matchersConsumer" + }, + "interactions": [ + { + "description": "A get request to /matchers", + "pending": false, + "providerStates": [ + { + "name": "Get Matchers" + } + ], + "request": { + "headers": { + "Accept": [ + "application/json" + ] + }, + "matchingRules": { + "header": {}, + "path": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^\\/matchers$" + } + ] + }, + "query": {} + }, + "method": "GET", + "path": "/matchers", + "query": { + "ignore": [ + "statusCode" + ] + } + }, + "response": { + "body": { + "content": { + "arrayContaining": [ + "some text", + 111, + "2fbd41cc-4bbc-44ea-a419-67f767691407" + ], + "atLeastLike": [ + 1, + 1, + 1, + 1, + 1 + ], + "atMostLike": [ + 1 + ], + "boolean": false, + "constrainedArrayLike": [ + "item", + "item" + ], + "contentType": "text/html", + "date": "2015-05-16", + "dateISO8601": "2013-02-01", + "dateTimeISO8601": "2015-08-06T16:53:10+01:00", + "dateTimeWithMillisISO8601": "2015-08-06T16:53:10.123+01:00", + "datetime": "2000-10-31T01:30:00", + "decimal": 79.01, + "eachLike": [ + "item" + ], + "email": "hello@pact.io", + "equal": "exact this value", + "hexadecimal": "F7A16", + "includes": "lazy dog", + "integer": 9, + "ipv4Address": "127.0.0.13", + "ipv6Address": "::ffff:192.0.2.128", + "like": { + "key": "value" + }, + "likeBool": true, + "likeDecimal": 13.01, + "likeInt": 13, + "likeString": "some string", + "notEmpty": [ + "1", + "2", + "3" + ], + "nullValue": null, + "number": 123, + "regex": "500 miles", + "semver": "10.0.0-alpha4", + "time": "23:59::58", + "timeISO8601": "T22:44:30.652Z", + "timestampRFC3339": "Mon, 31 Oct 2016 15:21:41 -0400", + "uuid": "52c9585e-f345-4964-aa28-a45c64b2b2eb" + }, + "contentType": "application/json", + "encoded": false + }, + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "matchingRules": { + "body": { + "$.arrayContaining": { + "combine": "AND", + "matchers": [ + { + "match": "arrayContains", + "variants": [ + { + "index": 0, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + } + }, + { + "index": 1, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + } + } + }, + { + "index": 2, + "rules": { + "$": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" + } + ] + } + } + } + ] + } + ] + }, + "$.atLeastLike": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 5 + } + ] + }, + "$.atMostLike": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "max": 3 + } + ] + }, + "$.boolean": { + "combine": "AND", + "matchers": [ + { + "match": "boolean" + } + ] + }, + "$.constrainedArrayLike": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "max": 4, + "min": 2 + } + ] + }, + "$.contentType": { + "combine": "AND", + "matchers": [ + { + "match": "contentType", + "value": "text/html" + } + ] + }, + "$.date": { + "combine": "AND", + "matchers": [ + { + "format": "yyyy-MM-dd", + "match": "date" + } + ] + }, + "$.dateISO8601": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))?)$" + } + ] + }, + "$.dateTimeISO8601": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$" + } + ] + }, + "$.dateTimeWithMillisISO8601": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d{3}([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?$" + } + ] + }, + "$.datetime": { + "combine": "AND", + "matchers": [ + { + "format": "YYYY-mm-DD'T'HH:mm:ss", + "match": "datetime" + } + ] + }, + "$.decimal": { + "combine": "AND", + "matchers": [ + { + "match": "decimal" + } + ] + }, + "$.eachLike": { + "combine": "AND", + "matchers": [ + { + "match": "type", + "min": 1 + } + ] + }, + "$.email": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$" + } + ] + }, + "$.equal": { + "combine": "AND", + "matchers": [ + { + "match": "equality" + } + ] + }, + "$.hexadecimal": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^[0-9a-fA-F]+$" + } + ] + }, + "$.includes": { + "combine": "AND", + "matchers": [ + { + "match": "include", + "value": "lazy dog" + } + ] + }, + "$.integer": { + "combine": "AND", + "matchers": [ + { + "match": "integer" + } + ] + }, + "$.ipv4Address": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^(\\d{1,3}\\.)+\\d{1,3}$" + } + ] + }, + "$.ipv6Address": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$" + } + ] + }, + "$.like": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.likeBool": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.likeDecimal": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.likeInt": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.likeString": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.notEmpty": { + "combine": "AND", + "matchers": [ + { + "match": "notEmpty" + } + ] + }, + "$.nullValue": { + "combine": "AND", + "matchers": [ + { + "match": "null" + } + ] + }, + "$.number": { + "combine": "AND", + "matchers": [ + { + "match": "number" + } + ] + }, + "$.regex": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^\\d+ (miles|kilometers)$" + } + ] + }, + "$.semver": { + "combine": "AND", + "matchers": [ + { + "match": "semver" + } + ] + }, + "$.time": { + "combine": "AND", + "matchers": [ + { + "format": "HH:mm::ss", + "match": "time" + } + ] + }, + "$.timeISO8601": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^(T\\d\\d:\\d\\d(:\\d\\d)?(\\.\\d+)?([+-][0-2]\\d(?:|:?[0-5]\\d)|Z)?)$" + } + ] + }, + "$.timestampRFC3339": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\\s\\d{2}\\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{4}\\s\\d{2}:\\d{2}:\\d{2}\\s(\\+|-)\\d{4}$" + } + ] + }, + "$.uuid": { + "combine": "AND", + "matchers": [ + { + "match": "regex", + "regex": "^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$" + } + ] + } + }, + "header": {} + }, + "status": 200 + }, + "transport": "http", + "type": "Synchronous/HTTP" + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.10", + "mockserver": "1.2.4", + "models": "1.1.11" + }, + "pactSpecification": { + "version": "4.0" + } + }, + "provider": { + "name": "matchersProvider" + } +} \ No newline at end of file diff --git a/example/matchers/provider/phpunit.xml b/example/matchers/provider/phpunit.xml new file mode 100644 index 00000000..62a9eb00 --- /dev/null +++ b/example/matchers/provider/phpunit.xml @@ -0,0 +1,11 @@ + + + + + ./tests + + + + + + diff --git a/example/matchers/provider/public/index.php b/example/matchers/provider/public/index.php new file mode 100644 index 00000000..bf6961e6 --- /dev/null +++ b/example/matchers/provider/public/index.php @@ -0,0 +1,75 @@ +addBodyParsingMiddleware(); + +$app->get('/matchers', function (Request $request, Response $response) { + $response->getBody()->write(\json_encode([ + 'like' => ['key' => 'another value'], + 'eachLike' => ['item 1', 'item 2'], + 'atLeastLike' => [1, 2, 3, 4, 5, 6], + 'atMostLike' => [1, 2], + 'constrainedArrayLike' => ['item 1', 'item 2', 'item 3'], + 'regex' => '800 kilometers', + 'dateISO8601' => '2001-11-21', + 'timeISO8601' => 'T11:22:15.153Z', + 'dateTimeISO8601' => '2004-02-12T15:19:21+00:00', + 'dateTimeWithMillisISO8601' => '2018-11-07T00:25:00.073+01:00', + 'timestampRFC3339' => 'Thu, 01 Dec 1994 16:00:00 +0700', + 'likeBool' => false, + 'likeInt' => 34, + 'likeDecimal' => 24.12, + 'boolean' => true, + 'integer' => 11, + 'decimal' => 25.1, + 'hexadecimal' => '20AC', + 'uuid' => 'e9d2f3a5-6ecc-4bff-8935-84bb6141325a', + 'ipv4Address' => '192.168.1.1', + 'ipv6Address' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', + 'email' => 'pact@example.com', + 'nullValue' => null, + 'date' => '1997-12-11', + 'time' => '11:01::02', + 'datetime' => '1997-07-16T19:20:30', + 'likeString' => 'another string', + 'equal' => 'exact this value', + 'includes' => 'The quick brown fox jumps over the lazy dog', + 'number' => 112.3, + 'arrayContaining' => [ + 102.3, + 'eb375cad-48cc-4f7f-981b-ea4f1af90bf2', + ], + 'notEmpty' => [111], + 'semver' => '0.27.1-beta2', + 'contentType' => + << + + + +

My First Heading

+

My first paragraph.

+ + + + HTML, + ])); + + return $response->withHeader('Content-Type', 'application/json'); +}); + +$app->post('/pact-change-state', function (Request $request, Response $response) { + $body = $request->getParsedBody(); + + printf('%s provider state %s with params: %s', $body['action'], $body['state'], json_encode($body['params'])); + + return $response; +}); + +$app->run(); diff --git a/example/matchers/provider/tests/PactVerifyTest.php b/example/matchers/provider/tests/PactVerifyTest.php new file mode 100644 index 00000000..8a823f5d --- /dev/null +++ b/example/matchers/provider/tests/PactVerifyTest.php @@ -0,0 +1,47 @@ +process = new ProviderProcess(__DIR__ . '/../public/'); + $this->process->start(); + } + + protected function tearDown(): void + { + $this->process->stop(); + } + + public function testPactVerifyConsumer() + { + $config = new VerifierConfig(); + $config->getProviderInfo() + ->setName('matchersProvider') + ->setHost('localhost') + ->setPort(7202); + $config->getProviderState() + ->setStateChangeUrl(new Uri('http://localhost:7202/pact-change-state')) + ; + if ($level = \getenv('PACT_LOGLEVEL')) { + $config->setLogLevel($level); + } + + $verifier = new Verifier($config); + $verifier->addFile(__DIR__ . '/../../pacts/matchersConsumer-matchersProvider.json'); + + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); + } +} diff --git a/phpunit.xml b/phpunit.xml index f82b56e4..96a878b0 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -37,6 +37,12 @@ ./example/message/provider/tests + + ./example/matchers/consumer/tests + + + ./example/matchers/provider/tests +