diff --git a/composer.json b/composer.json index e558f038..59ac76a8 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,8 @@ "friendsofphp/php-cs-fixer": "^3.0", "php-amqplib/php-amqplib": "^3.0", "phpstan/phpstan": "^1.9", - "phpunit/phpunit": ">=8.5.23 <10" + "phpunit/phpunit": ">=8.5.23 <10", + "tienvx/pact-php-xml": "dev-main" }, "autoload": { "psr-4": { @@ -53,7 +54,11 @@ "BinaryConsumer\\": "example/binary/consumer/src", "BinaryConsumer\\Tests\\": "example/binary/consumer/tests", "BinaryProvider\\": "example/binary/provider/src", - "BinaryProvider\\Tests\\": "example/binary/provider/tests" + "BinaryProvider\\Tests\\": "example/binary/provider/tests", + "XmlConsumer\\": "example/xml/consumer/src", + "XmlConsumer\\Tests\\": "example/xml/consumer/tests", + "XmlProvider\\": "example/xml/provider/src", + "XmlProvider\\Tests\\": "example/xml/provider/tests" } }, "scripts": { diff --git a/example/xml/consumer/phpunit.xml b/example/xml/consumer/phpunit.xml new file mode 100644 index 00000000..62a9eb00 --- /dev/null +++ b/example/xml/consumer/phpunit.xml @@ -0,0 +1,11 @@ + + + + + ./tests + + + + + + diff --git a/example/xml/consumer/src/Service/HttpClientService.php b/example/xml/consumer/src/Service/HttpClientService.php new file mode 100644 index 00000000..a2193e03 --- /dev/null +++ b/example/xml/consumer/src/Service/HttpClientService.php @@ -0,0 +1,28 @@ +httpClient = new Client(); + $this->baseUri = $baseUri; + } + + public function getMovies(): string + { + $response = $this->httpClient->get(new Uri("{$this->baseUri}/movies"), [ + 'headers' => ['Accept' => 'text/xml; charset=UTF8'] + ]); + + return $response->getBody(); + } +} diff --git a/example/xml/consumer/tests/Service/HttpClientServiceTest.php b/example/xml/consumer/tests/Service/HttpClientServiceTest.php new file mode 100644 index 00000000..8e33abf9 --- /dev/null +++ b/example/xml/consumer/tests/Service/HttpClientServiceTest.php @@ -0,0 +1,94 @@ +setMethod('GET') + ->setPath('/movies') + ->addHeader('Accept', 'text/xml; charset=UTF8'); + + $xmlBuilder = new XmlBuilder('1.0', 'UTF-8'); + $xmlBuilder + ->start('movies') + ->eachLike('movie') + ->start('title') + ->contentLike('PHP: Behind the Parser') + ->end() + ->start('characters') + ->eachLike('character', [], new Options(examples: 2)) + ->start('name') + ->contentLike('Ms. Coder') + ->end() + ->start('actor') + ->contentLike('Onlivia Actora') + ->end() + ->end() + ->end() + ->start('plot') + ->contentLike( + <<end() + ->start('great-lines') + ->eachLike('line') + ->contentLike('PHP solves all my web problems') + ->end() + ->end() + ->start('rating', ['type' => 'thumbs']) + ->contentLike(7) + ->end() + ->start('rating', ['type' => 'stars']) + ->contentLike(5) + ->end() + ->end() + ->end(); + + $response = new ProviderResponse(); + $response + ->setStatus(200) + ->addHeader('Content-Type', 'text/xml') + ->setBody( + json_encode($xmlBuilder->getArray()) + ); + + $config = new MockServerConfig(); + $config + ->setConsumer('xmlConsumer') + ->setProvider('xmlProvider') + ->setPactDir(__DIR__.'/../../../pacts'); + if ($logLevel = \getenv('PACT_LOGLEVEL')) { + $config->setLogLevel($logLevel); + } + $builder = new InteractionBuilder($config); + $builder + ->given('Movies exist') + ->uponReceiving('A get request to /movies') + ->with($request) + ->willRespondWith($response); + + $service = new HttpClientService($config->getBaseUri()); + $moviesResult = new \SimpleXMLElement($service->getMovies()); + $verifyResult = $builder->verify(); + + $this->assertTrue($verifyResult); + $this->assertCount(1, $moviesResult); + } +} diff --git a/example/xml/pacts/xmlConsumer-xmlProvider.json b/example/xml/pacts/xmlConsumer-xmlProvider.json new file mode 100644 index 00000000..8ead9538 --- /dev/null +++ b/example/xml/pacts/xmlConsumer-xmlProvider.json @@ -0,0 +1,122 @@ +{ + "consumer": { + "name": "xmlConsumer" + }, + "interactions": [ + { + "description": "A get request to /movies", + "providerStates": [ + { + "name": "Movies exist" + } + ], + "request": { + "headers": { + "Accept": "text/xml; charset=UTF8" + }, + "method": "GET", + "path": "/movies" + }, + "response": { + "body": "PHP: Behind the ParserMs. CoderOnlivia ActoraSo, this language. It's like, a programming language. Or is it a\nscripting language? All is revealed in this thrilling horror spoof\nof a documentary.PHP solves all my web problems75", + "headers": { + "Content-Type": "text/xml" + }, + "matchingRules": { + "body": { + "$.movies.movie": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.characters.character": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.characters.character.actor.#text": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.characters.character.name.#text": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.great-lines.line": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.great-lines.line.#text": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.plot.#text": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + }, + "$.movies.movie.rating.#text": { + "combine": "AND", + "matchers": [ + { + "match": "type" + }, + { + "match": "type" + } + ] + }, + "$.movies.movie.title.#text": { + "combine": "AND", + "matchers": [ + { + "match": "type" + } + ] + } + }, + "header": {} + }, + "status": 200 + } + } + ], + "metadata": { + "pactRust": { + "ffi": "0.4.7", + "mockserver": "1.2.3", + "models": "1.1.9" + }, + "pactSpecification": { + "version": "3.0.0" + } + }, + "provider": { + "name": "xmlProvider" + } +} \ No newline at end of file diff --git a/example/xml/provider/phpunit.xml b/example/xml/provider/phpunit.xml new file mode 100644 index 00000000..62a9eb00 --- /dev/null +++ b/example/xml/provider/phpunit.xml @@ -0,0 +1,11 @@ + + + + + ./tests + + + + + + diff --git a/example/xml/provider/public/index.php b/example/xml/provider/public/index.php new file mode 100644 index 00000000..458ec3e1 --- /dev/null +++ b/example/xml/provider/public/index.php @@ -0,0 +1,53 @@ +createXMLArray() + ->start('movies') + ->start('movie') + ->add('title', 'PHP: Behind the Parser') + ->start('characters') + ->start('character') + ->add('name', 'Ms. Coder') + ->add('actor', 'Onlivia Actora') + ->end() + ->start('character') + ->add('name', 'Mr. Coder') + ->add('actor', 'El ActÓr') + ->end() + ->end() + ->add('plot', <<start('great-lines') + ->add('line', 'PHP solves all my web problems') + ->end() + ->add('rating', 7, ['type' => 'thumbs']) + ->add('rating', 5, ['type' => 'stars']) + ->end() + ->end(); + +$app = AppFactory::create(); + +$app->get('/movies', function (Request $request, Response $response) use ($xmlBuilder) { + $response->getBody()->write($xmlBuilder->getXml()); + + return $response->withHeader('Content-Type', 'text/xml'); +}); + +$app->post('/pact-change-state', function (Request $request, Response $response) { + return $response; +}); + +$app->run(); diff --git a/example/xml/provider/tests/PactVerifyTest.php b/example/xml/provider/tests/PactVerifyTest.php new file mode 100644 index 00000000..82a64982 --- /dev/null +++ b/example/xml/provider/tests/PactVerifyTest.php @@ -0,0 +1,66 @@ +process = new Process(['php', '-S', '127.0.0.1:7202', '-t', __DIR__ . '/../public/']); + + $this->process->start(); + $this->process->waitUntil(function (): bool { + $fp = @fsockopen('127.0.0.1', 7202); + $isOpen = is_resource($fp); + if ($isOpen) { + fclose($fp); + } + + return $isOpen; + }); + } + + /** + * Stop the web server process once complete. + */ + protected function tearDown(): void + { + $this->process->stop(); + } + + /** + * This test will run after the web server is started. + */ + public function testPactVerifyConsumer() + { + $config = new VerifierConfig(); + $config->getProviderInfo() + ->setName('xmlProvider') // Providers name to fetch. + ->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/xmlConsumer-xmlProvider.json'); + + $verifyResult = $verifier->verify(); + + $this->assertTrue($verifyResult); + } +}