Skip to content
Closed
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
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions example/xml/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="../../../vendor/autoload.php" colors="true">
<testsuites>
<testsuite name="PhpPact Example Tests">
<directory>./tests</directory>
</testsuite>
</testsuites>
<php>
<env name="PACT_LOGLEVEL" value="DEBUG"/>
</php>
</phpunit>
28 changes: 28 additions & 0 deletions example/xml/consumer/src/Service/HttpClientService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace XmlConsumer\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 getMovies(): string
{
$response = $this->httpClient->get(new Uri("{$this->baseUri}/movies"), [
'headers' => ['Accept' => 'text/xml; charset=UTF8']
]);

return $response->getBody();
}
}
125 changes: 125 additions & 0 deletions example/xml/consumer/tests/Service/HttpClientServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
<?php

namespace XmlConsumer\Tests\Service;

use Tienvx\PactPhpXml\Model\Options;
use Tienvx\PactPhpXml\XmlBuilder;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will probably want to document that this comes from a seperate-repo that would need to be added, into a readme of the changes.

https://github.com/tienvx/pact-php-xml

ps, what is the advantage of publishing these are separate packages?

Is it useful for

  • users of the library?
  • testing of the library?

or are there other advantages, outside of the possibly maintainence sprawl of having lots of different projects.

It might be that it works for PHP and is what users expect, but equally leads to more places to maintain, and more things to convey to the user, that they need, conditionally, depending on their particular use case

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I put some code outside of this PR, into separate project pact-php-xml because:

  • I just want to make this PR small so it easier to review
  • The code of that library is highly opinionated
  • That library is optional, can easily replace by raw json

What do you think if I move the code back to this PR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure on a definitive answer, but good question and counter points. I've tagged a few other parties who may be offer to offer their thoughts.

On the fact its optional and highly opinionated, would you see this kind of library either coming under the pact-foundation, or us having a list of them in the repo, so that others can create their own and point to them (many ways to skin a cat and all that!)

Hey team, just wondering if you can cast your eyes on the above thread. 🙏🏾

@Greg0 @WaylandAce @Lewiscowles1986 @mmoll @cfmack

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reading now, but feel it would be better for Pact to have this repo under their umbrella with a contributor to review (there might be a lot I'd miss as I dip in and out)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the specifics, it's a value-object using builder; it's a strange mix. Why this vs templating? Unless I'm mistaken (in which case I apologise); but it looks like a special string builder. XML is not strictly speaking a string.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it to be a special string builder, as we need to store the XML in the pact json file, so its stored in a string (along with any matchers that have been encoded as part of the test)

This is unpacked at the provider side, and reads the content type of application/xml and sends the content appropriately to the provider

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the input dude 👍🏾

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

json_encode($iContainXML) should handle escaping etc though right?

I guess we need to know if pact needs UTF-8 escaped, but XML does not and JSON spec does not, so I'm guessing not. 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the builder to use the Functional Options pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • About UTF-8 encoding:
    • According to this safety note, I think yes, pact (in this case pactffi_with_body) need UTF-8 encoding.
    • According to this doc I think json_encode also need the value to be UTF-8 encoded.
  • About UTF-8 encoding vs escaping: I'm not good at this. I don't think I handle these things at all in the code. I think we need to add special test cases and then update the code to handle these cases.

use XmlConsumer\Service\HttpClientService;
use PhpPact\Consumer\InteractionBuilder;
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()
{
$request = new ConsumerRequest();
$request
->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(
<<<EOF
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.
EOF
),
),
),
$xmlBuilder->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);
}
}
125 changes: 125 additions & 0 deletions example/xml/pacts/xmlConsumer-xmlProvider.json
Original file line number Diff line number Diff line change
@@ -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": "<?xml version='1.0'?><movies><movie><title>PHP: Behind the Parser</title><characters><character><name>Ms. Coder</name><actor>Onlivia Actora</actor></character><character><name/><actor/></character></characters><plot>So, 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.</plot><great-lines><line>PHP solves all my web problems</line></great-lines><rating type='thumbs'>7</rating><rating type='stars'>5</rating></movie></movies>",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

"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"
}
}
11 changes: 11 additions & 0 deletions example/xml/provider/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="../../../vendor/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/xml/provider/public/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?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->get('/movies', function (Request $request, Response $response) {
$response->getBody()->write(
<<<XML
<?xml version='1.0' standalone='yes'?>
<movies>
<movie>
<title>PHP: Behind the Parser</title>
<characters>
<character>
<name>Ms. Coder</name>
<actor>Onlivia Actora</actor>
</character>
<character>
<name>Mr. Coder</name>
<actor>El Act&#211;r</actor>
</character>
</characters>
<plot>
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.
</plot>
<great-lines>
<line>PHP solves all my web problems</line>
</great-lines>
<rating type="thumbs">7</rating>
<rating type="stars">5</rating>
</movie>
</movies>
XML
);

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

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

$app->run();
Loading