diff --git a/composer.json b/composer.json
index 48837478c..8a329e551 100644
--- a/composer.json
+++ b/composer.json
@@ -33,6 +33,7 @@
"php-amqplib/php-amqplib": "^3.0",
"phpstan/phpstan": "^1.9",
"phpunit/phpunit": ">=8.5.23 <10",
+ "tienvx/pact-php-xml": "dev-main",
"guzzlehttp/guzzle": "^7.8"
},
"autoload": {
@@ -55,6 +56,10 @@
"BinaryConsumer\\Tests\\": "example/binary/consumer/tests",
"BinaryProvider\\": "example/binary/provider/src",
"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",
"MultipartConsumer\\": "example/multipart/consumer/src",
"MultipartConsumer\\Tests\\": "example/multipart/consumer/tests",
"MultipartProvider\\": "example/multipart/provider/src",
diff --git a/example/xml/consumer/phpunit.xml b/example/xml/consumer/phpunit.xml
new file mode 100644
index 000000000..62a9eb007
--- /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 000000000..a2193e03a
--- /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 000000000..853eb11e6
--- /dev/null
+++ b/example/xml/consumer/tests/Service/HttpClientServiceTest.php
@@ -0,0 +1,125 @@
+setMethod('GET')
+ ->setPath('/movies')
+ ->addHeader('Accept', 'text/xml; charset=UTF8');
+
+ $xmlBuilder = new XmlBuilder('1.0', 'UTF-8');
+ $xmlBuilder
+ ->root(
+ $xmlBuilder->name('movies'),
+ $xmlBuilder->add(
+ $xmlBuilder->eachLike(),
+ $xmlBuilder->name('movie'),
+ $xmlBuilder->add(
+ $xmlBuilder->name('title'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike('PHP: Behind the Parser'),
+ ),
+ ),
+ $xmlBuilder->add(
+ $xmlBuilder->name('characters'),
+ $xmlBuilder->add(
+ $xmlBuilder->eachLike(examples: 2),
+ $xmlBuilder->name('character'),
+ $xmlBuilder->add(
+ $xmlBuilder->name('name'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike('Ms. Coder'),
+ ),
+ ),
+ $xmlBuilder->add(
+ $xmlBuilder->name('actor'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike('Onlivia Actora'),
+ ),
+ ),
+ ),
+ ),
+ $xmlBuilder->add(
+ $xmlBuilder->name('plot'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike(
+ <<add(
+ $xmlBuilder->name('great-lines'),
+ $xmlBuilder->add(
+ $xmlBuilder->eachLike(),
+ $xmlBuilder->name('line'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike('PHP solves all my web problems'),
+ ),
+ ),
+ ),
+ $xmlBuilder->add(
+ $xmlBuilder->name('rating'),
+ $xmlBuilder->attribute('type', 'thumbs'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike(7),
+ ),
+ ),
+ $xmlBuilder->add(
+ $xmlBuilder->name('rating'),
+ $xmlBuilder->attribute('type', 'stars'),
+ $xmlBuilder->text(
+ $xmlBuilder->contentLike(5),
+ ),
+ ),
+ ),
+ );
+
+ $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 000000000..a8e4a8e97
--- /dev/null
+++ b/example/xml/pacts/xmlConsumer-xmlProvider.json
@@ -0,0 +1,125 @@
+{
+ "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",
+ "min": 1
+ }
+ ]
+ },
+ "$.movies.movie.characters.character": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "type",
+ "min": 1
+ }
+ ]
+ },
+ "$.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",
+ "min": 1
+ }
+ ]
+ },
+ "$.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.8",
+ "mockserver": "1.2.4",
+ "models": "1.1.11"
+ },
+ "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 000000000..62a9eb007
--- /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 000000000..c48aec963
--- /dev/null
+++ b/example/xml/provider/public/index.php
@@ -0,0 +1,50 @@
+get('/movies', function (Request $request, Response $response) {
+ $response->getBody()->write(
+ <<
+
+
+ PHP: Behind the Parser
+
+
+ Ms. Coder
+ Onlivia Actora
+
+
+ Mr. Coder
+ El ActÓr
+
+
+
+ So, this language. It's like, a programming language. Or is it a
+ scripting language? All is revealed in this thrilling horror spoof
+ of a documentary.
+
+
+ PHP solves all my web problems
+
+ 7
+ 5
+
+
+ XML
+ );
+
+ 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 000000000..82a64982d
--- /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);
+ }
+}