diff --git a/.cirrus.yml b/.cirrus.yml
index dde4e49c..80abe231 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -27,7 +27,7 @@ linux_arm64_task:
container:
image: php:$VERSION
pre_req_script:
- - apt update --yes && apt install --yes zip unzip git libffi-dev
+ - apt update --yes && apt install --yes zip unzip git libffi-dev shared-mime-info
- curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php
- php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer
- docker-php-ext-install sockets
@@ -46,7 +46,7 @@ macos_arm64_task:
macos_instance:
image: ghcr.io/cirruslabs/macos-ventura-base:latest
pre_req_script:
- - brew install php@$VERSION composer
+ - brew install php@$VERSION composer shared-mime-info
version_check_script:
- php --version
<< : *BUILD_TEST_TASK_TEMPLATE
diff --git a/composer.json b/composer.json
index f506240c..e558f038 100644
--- a/composer.json
+++ b/composer.json
@@ -49,7 +49,11 @@
"MessageConsumer\\": "example/message/consumer/src",
"MessageConsumer\\Tests\\": "example/message/consumer/tests",
"MessageProvider\\": "example/message/provider/src",
- "MessageProvider\\Tests\\": "example/message/provider/tests"
+ "MessageProvider\\Tests\\": "example/message/provider/tests",
+ "BinaryConsumer\\": "example/binary/consumer/src",
+ "BinaryConsumer\\Tests\\": "example/binary/consumer/tests",
+ "BinaryProvider\\": "example/binary/provider/src",
+ "BinaryProvider\\Tests\\": "example/binary/provider/tests"
}
},
"scripts": {
diff --git a/example/binary/consumer/phpunit.xml b/example/binary/consumer/phpunit.xml
new file mode 100644
index 00000000..62a9eb00
--- /dev/null
+++ b/example/binary/consumer/phpunit.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ ./tests
+
+
+
+
+
+
diff --git a/example/binary/consumer/src/Service/HttpClientService.php b/example/binary/consumer/src/Service/HttpClientService.php
new file mode 100644
index 00000000..3f8d22a7
--- /dev/null
+++ b/example/binary/consumer/src/Service/HttpClientService.php
@@ -0,0 +1,31 @@
+httpClient = new Client();
+ $this->baseUri = $baseUri;
+ }
+
+ public function getImageContent(): string
+ {
+ $response = $this->httpClient->get(new Uri("{$this->baseUri}/image.jpg"), [
+ 'headers' => ['Accept' => 'image/jpeg']
+ ]);
+
+ return $response->getBody();
+ }
+}
diff --git a/example/binary/consumer/tests/Service/HttpClientServiceTest.php b/example/binary/consumer/tests/Service/HttpClientServiceTest.php
new file mode 100644
index 00000000..42fe6b82
--- /dev/null
+++ b/example/binary/consumer/tests/Service/HttpClientServiceTest.php
@@ -0,0 +1,53 @@
+setMethod('GET')
+ ->setPath('/image.jpg')
+ ->addHeader('Accept', 'image/jpeg');
+
+ $response = new ProviderResponse();
+ $response
+ ->setStatus(200)
+ ->addHeader('Content-Type', 'image/jpeg')
+ ->setBody(new Binary($imageContent, in_array(php_uname('m'), ['AMD64', 'arm64']) ? 'application/octet-stream' : 'image/jpeg'));
+
+ $config = new MockServerConfig();
+ $config
+ ->setConsumer('binaryConsumer')
+ ->setProvider('binaryProvider')
+ ->setPactDir(__DIR__.'/../../../pacts');
+ if ($logLevel = \getenv('PACT_LOGLEVEL')) {
+ $config->setLogLevel($logLevel);
+ }
+ $builder = new InteractionBuilder($config);
+ $builder
+ ->given('Image file image.jpg exists')
+ ->uponReceiving('A get request to /image.jpg')
+ ->with($request)
+ ->willRespondWith($response);
+
+ $service = new HttpClientService($config->getBaseUri());
+ $imageContentResult = $service->getImageContent();
+ $verifyResult = $builder->verify();
+
+ $this->assertTrue($verifyResult);
+ $this->assertEquals($imageContent, $imageContentResult);
+ }
+}
diff --git a/example/binary/consumer/tests/_resource/image.jpg b/example/binary/consumer/tests/_resource/image.jpg
new file mode 100644
index 00000000..6e93db1c
Binary files /dev/null and b/example/binary/consumer/tests/_resource/image.jpg differ
diff --git a/example/binary/pacts/binaryConsumer-binaryProvider.json b/example/binary/pacts/binaryConsumer-binaryProvider.json
new file mode 100644
index 00000000..7ecf427f
--- /dev/null
+++ b/example/binary/pacts/binaryConsumer-binaryProvider.json
@@ -0,0 +1,56 @@
+{
+ "consumer": {
+ "name": "binaryConsumer"
+ },
+ "interactions": [
+ {
+ "description": "A get request to /image.jpg",
+ "providerStates": [
+ {
+ "name": "Image file image.jpg exists"
+ }
+ ],
+ "request": {
+ "headers": {
+ "Accept": "image/jpeg"
+ },
+ "method": "GET",
+ "path": "/image.jpg"
+ },
+ "response": {
+ "body": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/wAALCAAPAA8BAREA/8QAFgABAQEAAAAAAAAAAAAAAAAAAgME/8QAIBAAAgIDAAIDAQAAAAAAAAAAAQIDBAUREhMhACIxUf/aAAgBAQAAPwBNlclNW6S0s7wbvv4K6iNI2HJcp9Rrnk+T0ByNofwZIjdnyYx0stWrYWAP5y03SBjt+GQ/YnmHbaUkfoA107Br40Wa+DrRLNXhjchECDwyjjlXPpdFSo3Gw5CbDFdmUVOrHcfEU4JYb2PWSJCzbVPUEmh7PQaOZHOyD0x/hHz/2Q==",
+ "headers": {
+ "Content-Type": "image/jpeg"
+ },
+ "matchingRules": {
+ "body": {
+ "$": {
+ "combine": "AND",
+ "matchers": [
+ {
+ "match": "contentType",
+ "value": "image/jpeg"
+ }
+ ]
+ }
+ },
+ "header": {}
+ },
+ "status": 200
+ }
+ }
+ ],
+ "metadata": {
+ "pactRust": {
+ "ffi": "0.4.7",
+ "mockserver": "1.2.3",
+ "models": "1.1.9"
+ },
+ "pactSpecification": {
+ "version": "3.0.0"
+ }
+ },
+ "provider": {
+ "name": "binaryProvider"
+ }
+}
\ No newline at end of file
diff --git a/example/binary/provider/phpunit.xml b/example/binary/provider/phpunit.xml
new file mode 100644
index 00000000..62a9eb00
--- /dev/null
+++ b/example/binary/provider/phpunit.xml
@@ -0,0 +1,11 @@
+
+
+
+
+ ./tests
+
+
+
+
+
+
diff --git a/example/binary/provider/public/image.jpg b/example/binary/provider/public/image.jpg
new file mode 100644
index 00000000..5477a6da
Binary files /dev/null and b/example/binary/provider/public/image.jpg differ
diff --git a/example/binary/provider/tests/PactVerifyTest.php b/example/binary/provider/tests/PactVerifyTest.php
new file mode 100644
index 00000000..cc30e108
--- /dev/null
+++ b/example/binary/provider/tests/PactVerifyTest.php
@@ -0,0 +1,62 @@
+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('binaryProvider') // Providers name to fetch.
+ ->setHost('localhost')
+ ->setPort(7202);
+ if ($level = \getenv('PACT_LOGLEVEL')) {
+ $config->setLogLevel($level);
+ }
+
+ $verifier = new Verifier($config);
+ $verifier->addFile(__DIR__ . '/../../pacts/binaryConsumer-binaryProvider.json');
+
+ $verifyResult = $verifier->verify();
+
+ $this->assertTrue($verifyResult);
+ }
+}
diff --git a/example/json/provider/tests/PactVerifyTest.php b/example/json/provider/tests/PactVerifyTest.php
index 73dd84c9..408dade1 100644
--- a/example/json/provider/tests/PactVerifyTest.php
+++ b/example/json/provider/tests/PactVerifyTest.php
@@ -17,9 +17,7 @@ class PactVerifyTest extends TestCase
*/
protected function setUp(): void
{
- $publicPath = __DIR__ . '/../public/';
-
- $this->process = new Process(['php', '-S', '127.0.0.1:7202', '-t', $publicPath]);
+ $this->process = new Process(['php', '-S', '127.0.0.1:7202', '-t', __DIR__ . '/../public/']);
$this->process->start();
$this->process->waitUntil(function (): bool {
diff --git a/phpunit.xml b/phpunit.xml
index ff6fe126..3c2344a0 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -19,6 +19,12 @@
./example/json/provider/tests
+
+ ./example/binary/consumer/tests
+
+
+ ./example/binary/provider/tests
+
./example/message/consumer/tests
diff --git a/src/PhpPact/Consumer/AbstractMessageBuilder.php b/src/PhpPact/Consumer/AbstractMessageBuilder.php
index d25a743c..71a23275 100644
--- a/src/PhpPact/Consumer/AbstractMessageBuilder.php
+++ b/src/PhpPact/Consumer/AbstractMessageBuilder.php
@@ -56,11 +56,4 @@ public function withContent(mixed $contents): self
return $this;
}
-
- public function withContentType(?string $contentType): self
- {
- $this->message->setContentType($contentType);
-
- return $this;
- }
}
diff --git a/src/PhpPact/Consumer/Exception/MessageContentsNotAddedException.php b/src/PhpPact/Consumer/Exception/MessageContentsNotAddedException.php
new file mode 100644
index 00000000..4cd40881
--- /dev/null
+++ b/src/PhpPact/Consumer/Exception/MessageContentsNotAddedException.php
@@ -0,0 +1,9 @@
+setContents(StringData::createFrom($contents, false));
+ $this->setContentType($contentType);
+ }
+
+ public function getContents(): StringData
+ {
+ return $this->contents;
+ }
+
+ public function setContents(StringData $contents): self
+ {
+ $this->contents = $contents;
+
+ return $this;
+ }
+}
diff --git a/src/PhpPact/Consumer/Model/Body/ContentTypeTrait.php b/src/PhpPact/Consumer/Model/Body/ContentTypeTrait.php
new file mode 100644
index 00000000..41acc2cf
--- /dev/null
+++ b/src/PhpPact/Consumer/Model/Body/ContentTypeTrait.php
@@ -0,0 +1,20 @@
+contentType;
+ }
+
+ public function setContentType(string $contentType): self
+ {
+ $this->contentType = $contentType;
+
+ return $this;
+ }
+}
diff --git a/src/PhpPact/Consumer/Model/Body/Text.php b/src/PhpPact/Consumer/Model/Body/Text.php
new file mode 100644
index 00000000..1d2e54f7
--- /dev/null
+++ b/src/PhpPact/Consumer/Model/Body/Text.php
@@ -0,0 +1,28 @@
+setContents($contents);
+ $this->setContentType($contentType);
+ }
+
+ public function getContents(): string
+ {
+ return $this->contents;
+ }
+
+ public function setContents(string $contents): self
+ {
+ $this->contents = $contents;
+
+ return $this;
+ }
+}
diff --git a/src/PhpPact/Consumer/Model/ConsumerRequest.php b/src/PhpPact/Consumer/Model/ConsumerRequest.php
index 58d98740..1c0b4c08 100644
--- a/src/PhpPact/Consumer/Model/ConsumerRequest.php
+++ b/src/PhpPact/Consumer/Model/ConsumerRequest.php
@@ -3,7 +3,6 @@
namespace PhpPact\Consumer\Model;
use PhpPact\Consumer\Model\Interaction\BodyTrait;
-use PhpPact\Consumer\Model\Interaction\ContentTypeTrait;
use PhpPact\Consumer\Model\Interaction\HeadersTrait;
use PhpPact\Consumer\Model\Interaction\MethodTrait;
use PhpPact\Consumer\Model\Interaction\PathTrait;
@@ -16,7 +15,6 @@ class ConsumerRequest
{
use HeadersTrait;
use BodyTrait;
- use ContentTypeTrait;
use MethodTrait;
use PathTrait;
use QueryTrait;
diff --git a/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php b/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php
index 3a7c6b37..3cb31e7f 100644
--- a/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php
+++ b/src/PhpPact/Consumer/Model/Interaction/BodyTrait.php
@@ -3,32 +3,29 @@
namespace PhpPact\Consumer\Model\Interaction;
use JsonException;
+use PhpPact\Consumer\Model\Body\Binary;
+use PhpPact\Consumer\Model\Body\Text;
trait BodyTrait
{
- use ContentTypeTrait;
+ private Text|Binary|null $body = null;
- private ?string $body = null;
-
- public function getBody(): ?string
+ public function getBody(): Text|Binary|null
{
return $this->body;
}
/**
- * @param array|string|null $body
- *
* @throws JsonException
*/
- public function setBody(array|string|null $body): self
+ public function setBody(mixed $body): self
{
- if (\is_string($body) || \is_null($body)) {
+ if (\is_string($body)) {
+ $this->body = new Text($body, 'text/plain');
+ } elseif (\is_null($body) || $body instanceof Text || $body instanceof Binary) {
$this->body = $body;
} else {
- $this->body = \json_encode($body, JSON_THROW_ON_ERROR);
- if (!isset($this->contentType)) {
- $this->setContentType('application/json');
- }
+ $this->body = new Text(\json_encode($body, JSON_THROW_ON_ERROR), 'application/json');
}
return $this;
diff --git a/src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php b/src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php
deleted file mode 100644
index 257f6cbe..00000000
--- a/src/PhpPact/Consumer/Model/Interaction/ContentTypeTrait.php
+++ /dev/null
@@ -1,20 +0,0 @@
-contentType;
- }
-
- public function setContentType(?string $contentType): self
- {
- $this->contentType = $contentType;
-
- return $this;
- }
-}
diff --git a/src/PhpPact/Consumer/Model/Message.php b/src/PhpPact/Consumer/Model/Message.php
index 796a683f..8d2d2a4e 100644
--- a/src/PhpPact/Consumer/Model/Message.php
+++ b/src/PhpPact/Consumer/Model/Message.php
@@ -3,7 +3,8 @@
namespace PhpPact\Consumer\Model;
use JsonException;
-use PhpPact\Consumer\Model\Interaction\ContentTypeTrait;
+use PhpPact\Consumer\Model\Body\Binary;
+use PhpPact\Consumer\Model\Body\Text;
/**
* Message metadata and contents to be posted to the Mock Server for PACT tests.
@@ -11,7 +12,6 @@
class Message
{
use ProviderStates;
- use ContentTypeTrait;
private string $description;
@@ -20,7 +20,7 @@ class Message
*/
private array $metadata = [];
- private ?string $contents = null;
+ private Text|Binary|null $contents = null;
public function getDescription(): string
{
@@ -60,7 +60,7 @@ private function setMetadataValue(string $key, string $value): void
$this->metadata[$key] = $value;
}
- public function getContents(): ?string
+ public function getContents(): Text|Binary|null
{
return $this->contents;
}
@@ -70,13 +70,12 @@ public function getContents(): ?string
*/
public function setContents(mixed $contents): self
{
- if (\is_string($contents) || \is_null($contents)) {
+ if (\is_string($contents)) {
+ $this->contents = new Text($contents, 'text/plain');
+ } elseif (\is_null($contents) || $contents instanceof Text || $contents instanceof Binary) {
$this->contents = $contents;
} else {
- $this->contents = \json_encode($contents, JSON_THROW_ON_ERROR);
- if (!isset($this->contentType)) {
- $this->setContentType('application/json');
- }
+ $this->contents = new Text(\json_encode($contents, JSON_THROW_ON_ERROR), 'application/json');
}
return $this;
diff --git a/src/PhpPact/Consumer/Model/ProviderResponse.php b/src/PhpPact/Consumer/Model/ProviderResponse.php
index 3201811f..2abbe24b 100644
--- a/src/PhpPact/Consumer/Model/ProviderResponse.php
+++ b/src/PhpPact/Consumer/Model/ProviderResponse.php
@@ -3,7 +3,6 @@
namespace PhpPact\Consumer\Model;
use PhpPact\Consumer\Model\Interaction\BodyTrait;
-use PhpPact\Consumer\Model\Interaction\ContentTypeTrait;
use PhpPact\Consumer\Model\Interaction\HeadersTrait;
use PhpPact\Consumer\Model\Interaction\StatusTrait;
@@ -14,6 +13,5 @@ class ProviderResponse
{
use HeadersTrait;
use BodyTrait;
- use ContentTypeTrait;
use StatusTrait;
}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Body/AbstractBodyRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Body/AbstractBodyRegistry.php
new file mode 100644
index 00000000..aa3760d9
--- /dev/null
+++ b/src/PhpPact/Consumer/Registry/Interaction/Body/AbstractBodyRegistry.php
@@ -0,0 +1,32 @@
+client->call('pactffi_with_binary_file', $this->interactionRegistry->getId(), $this->getPart(), $body->getContentType(), $body->getContents()->getValue(), $body->getContents()->getSize());
+ } else {
+ $success = $this->client->call('pactffi_with_body', $this->interactionRegistry->getId(), $this->getPart(), $body->getContentType(), $body->getContents());
+ }
+ if (!$success) {
+ throw new InteractionBodyNotAddedException();
+ }
+ }
+
+ abstract protected function getPart(): int;
+}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Body/BodyRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Body/BodyRegistryInterface.php
new file mode 100644
index 00000000..d7f69d3e
--- /dev/null
+++ b/src/PhpPact/Consumer/Registry/Interaction/Body/BodyRegistryInterface.php
@@ -0,0 +1,11 @@
+client->call('pactffi_with_binary_file', $this->messageRegistry->getId(), $this->getPart(), $body->getContentType(), $body->getContents()->getValue(), $body->getContents()->getSize());
+ } else {
+ $success = $this->client->call('pactffi_with_body', $this->messageRegistry->getId(), $this->getPart(), $body->getContentType(), $body->getContents());
+ }
+ if (!$success) {
+ throw new MessageContentsNotAddedException();
+ }
+ }
+}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Body/RequestBodyRegistry.php
similarity index 73%
rename from src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php
rename to src/PhpPact/Consumer/Registry/Interaction/Body/RequestBodyRegistry.php
index 1a4ed86e..f1266404 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/Contents/RequestBodyRegistry.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/Body/RequestBodyRegistry.php
@@ -1,6 +1,6 @@
client->call('pactffi_with_body', $this->interactionRegistry->getId(), $this->getPart(), $contentType, $body);
- if (!$success) {
- throw new InteractionBodyNotAddedException();
- }
- }
-
- abstract protected function getPart(): int;
-}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php
deleted file mode 100644
index 3e3394ff..00000000
--- a/src/PhpPact/Consumer/Registry/Interaction/Contents/ContentsRegistryInterface.php
+++ /dev/null
@@ -1,8 +0,0 @@
-client->call('pactffi_message_with_contents', $this->messageRegistry->getId(), $contentType, $data->getValue(), $data->getSize());
- }
-}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php
index 7a917416..70a4b083 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/InteractionRegistry.php
@@ -76,7 +76,7 @@ private function with(ConsumerRequest $request): self
->withRequest($request->getMethod(), $request->getPath())
->withQueryParameters($request->getQuery())
->withHeaders($request->getHeaders())
- ->withBody($request->getContentType(), $request->getBody());
+ ->withBody($request->getBody());
return $this;
}
@@ -86,7 +86,7 @@ private function willRespondWith(ProviderResponse $response): self
$this->responseRegistry
->withResponse($response->getStatus())
->withHeaders($response->getHeaders())
- ->withBody($response->getContentType(), $response->getBody());
+ ->withBody($response->getBody());
return $this;
}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php
index 2da4e9b5..607b9bdf 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/MessageRegistry.php
@@ -2,21 +2,23 @@
namespace PhpPact\Consumer\Registry\Interaction;
+use PhpPact\Consumer\Model\Body\Binary;
+use PhpPact\Consumer\Model\Body\Text;
use PhpPact\Consumer\Model\Message;
use PhpPact\Consumer\Model\ProviderState;
-use PhpPact\Consumer\Registry\Interaction\Contents\ContentsRegistryInterface;
-use PhpPact\Consumer\Registry\Interaction\Contents\MessageContentsRegistry;
+use PhpPact\Consumer\Registry\Interaction\Body\BodyRegistryInterface;
+use PhpPact\Consumer\Registry\Interaction\Body\MessageContentsRegistry;
use PhpPact\Consumer\Registry\Pact\PactRegistryInterface;
use PhpPact\FFI\ClientInterface;
class MessageRegistry extends AbstractRegistry implements MessageRegistryInterface
{
- private ContentsRegistryInterface $messageContentsRegistry;
+ private BodyRegistryInterface $messageContentsRegistry;
public function __construct(
ClientInterface $client,
PactRegistryInterface $pactRegistry,
- ?ContentsRegistryInterface $messageContentsRegistry = null
+ ?BodyRegistryInterface $messageContentsRegistry = null
) {
parent::__construct($client, $pactRegistry);
$this->messageContentsRegistry = $messageContentsRegistry ?? new MessageContentsRegistry($client, $this);
@@ -30,7 +32,7 @@ public function registerMessage(Message $message): void
->given($message->getProviderStates())
->expectsToReceive($message->getDescription())
->withMetadata($message->getMetadata())
- ->withContents($message->getContentType(), $message->getContents());
+ ->withContents($message->getContents());
}
protected function newInteraction(string $description): self
@@ -40,9 +42,11 @@ protected function newInteraction(string $description): self
return $this;
}
- private function withContents(?string $contentType = null, ?string $contents = null): self
+ private function withContents(Text|Binary|null $contents): self
{
- $this->messageContentsRegistry->withContents($contentType, $contents);
+ if ($contents) {
+ $this->messageContentsRegistry->withBody($contents);
+ }
return $this;
}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/AbstractPartRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Part/AbstractPartRegistry.php
index 9531a07c..787d71dc 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/Part/AbstractPartRegistry.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/Part/AbstractPartRegistry.php
@@ -2,7 +2,9 @@
namespace PhpPact\Consumer\Registry\Interaction\Part;
-use PhpPact\Consumer\Registry\Interaction\Contents\ContentsRegistryInterface;
+use PhpPact\Consumer\Model\Body\Binary;
+use PhpPact\Consumer\Model\Body\Text;
+use PhpPact\Consumer\Registry\Interaction\Body\BodyRegistryInterface;
use PhpPact\Consumer\Registry\Interaction\InteractionRegistryInterface;
use PhpPact\FFI\ClientInterface;
@@ -11,13 +13,15 @@ abstract class AbstractPartRegistry implements PartRegistryInterface
public function __construct(
protected ClientInterface $client,
protected InteractionRegistryInterface $interactionRegistry,
- private ContentsRegistryInterface $contentsRegistry
+ private BodyRegistryInterface $bodyRegistry
) {
}
- public function withBody(?string $contentType = null, ?string $body = null): self
+ public function withBody(Text|Binary|null $body): self
{
- $this->contentsRegistry->withContents($contentType, $body);
+ if ($body) {
+ $this->bodyRegistry->withBody($body);
+ }
return $this;
}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php b/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php
index 90a6516b..5f7f59f8 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/Part/PartRegistryInterface.php
@@ -2,9 +2,12 @@
namespace PhpPact\Consumer\Registry\Interaction\Part;
+use PhpPact\Consumer\Model\Body\Binary;
+use PhpPact\Consumer\Model\Body\Text;
+
interface PartRegistryInterface
{
- public function withBody(?string $contentType = null, ?string $body = null): self;
+ public function withBody(Text|Binary|null $body): self;
/**
* @param array $headers
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php
index 026dc57c..890ad9b7 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/Part/RequestRegistry.php
@@ -2,8 +2,8 @@
namespace PhpPact\Consumer\Registry\Interaction\Part;
-use PhpPact\Consumer\Registry\Interaction\Contents\ContentsRegistryInterface;
-use PhpPact\Consumer\Registry\Interaction\Contents\RequestBodyRegistry;
+use PhpPact\Consumer\Registry\Interaction\Body\BodyRegistryInterface;
+use PhpPact\Consumer\Registry\Interaction\Body\RequestBodyRegistry;
use PhpPact\Consumer\Registry\Interaction\InteractionRegistryInterface;
use PhpPact\FFI\ClientInterface;
@@ -14,7 +14,7 @@ class RequestRegistry extends AbstractPartRegistry implements RequestRegistryInt
public function __construct(
ClientInterface $client,
InteractionRegistryInterface $interactionRegistry,
- ?ContentsRegistryInterface $requestBodyRegistry = null
+ ?BodyRegistryInterface $requestBodyRegistry = null
) {
parent::__construct($client, $interactionRegistry, $requestBodyRegistry ?? new RequestBodyRegistry($client, $interactionRegistry));
}
diff --git a/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php
index 3649a408..b74c0078 100644
--- a/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php
+++ b/src/PhpPact/Consumer/Registry/Interaction/Part/ResponseRegistry.php
@@ -2,8 +2,8 @@
namespace PhpPact\Consumer\Registry\Interaction\Part;
-use PhpPact\Consumer\Registry\Interaction\Contents\ContentsRegistryInterface;
-use PhpPact\Consumer\Registry\Interaction\Contents\ResponseBodyRegistry;
+use PhpPact\Consumer\Registry\Interaction\Body\BodyRegistryInterface;
+use PhpPact\Consumer\Registry\Interaction\Body\ResponseBodyRegistry;
use PhpPact\Consumer\Registry\Interaction\InteractionRegistryInterface;
use PhpPact\FFI\ClientInterface;
@@ -14,7 +14,7 @@ class ResponseRegistry extends AbstractPartRegistry implements ResponseRegistryI
public function __construct(
ClientInterface $client,
InteractionRegistryInterface $interactionRegistry,
- ?ContentsRegistryInterface $responseBodyRegistry = null
+ ?BodyRegistryInterface $responseBodyRegistry = null
) {
parent::__construct($client, $interactionRegistry, $responseBodyRegistry ?? new ResponseBodyRegistry($client, $interactionRegistry));
}
diff --git a/src/PhpPact/FFI/Model/StringData.php b/src/PhpPact/FFI/Model/StringData.php
index 622f457a..82e8fe57 100644
--- a/src/PhpPact/FFI/Model/StringData.php
+++ b/src/PhpPact/FFI/Model/StringData.php
@@ -23,10 +23,10 @@ public function getSize(): int
return $this->size;
}
- public static function createFrom(string $value): ?self
+ public static function createFrom(string $value, bool $nullTerminated = true): self
{
$length = \strlen($value);
- $size = $length + 1;
+ $size = $length + ($nullTerminated ? 1 : 0);
$cData = FFI::new("uint8_t[{$size}]");
FFI::memcpy($cData, $value, $length);
diff --git a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php
index f30aeca7..6ddcb9d7 100644
--- a/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php
+++ b/tests/PhpPact/Consumer/Model/ConsumerRequestTest.php
@@ -3,6 +3,7 @@
namespace PhpPactTest\Consumer\Model;
use PhpPact\Consumer\Matcher\Matcher;
+use PhpPact\Consumer\Model\Body\Text;
use PhpPact\Consumer\Model\ConsumerRequest;
use PHPUnit\Framework\TestCase;
@@ -24,7 +25,11 @@ public function testSerializing()
$this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders());
$this->assertEquals(['fruit' => ['apple', 'banana']], $model->getQuery());
$this->assertEquals('/somepath', $model->getPath());
- $this->assertEquals('{"currentCity":"Austin"}', $model->getBody());
+
+ $body = $model->getBody();
+ $this->assertInstanceOf(Text::class, $body);
+ $this->assertEquals('{"currentCity":"Austin"}', $body->getContents());
+ $this->assertEquals('application/json', $body->getContentType());
}
public function testSerializingWhenPathUsingMatcher()
@@ -45,6 +50,10 @@ public function testSerializingWhenPathUsingMatcher()
$this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders());
$this->assertEquals(['food' => ['milk']], $model->getQuery());
$this->assertEquals('{"value":"\/somepath\/474d610b-c6e3-45bd-9f70-529e7ad21df0\/status","regex":"\\\\\\/somepath\\\\\\/[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\\\\\\/status","pact:matcher:type":"regex"}', $model->getPath());
- $this->assertEquals('{"status":"finished"}', $model->getBody());
+
+ $body = $model->getBody();
+ $this->assertInstanceOf(Text::class, $body);
+ $this->assertEquals('{"status":"finished"}', $body->getContents());
+ $this->assertEquals('application/json', $body->getContentType());
}
}
diff --git a/tests/PhpPact/Consumer/Model/MessageTest.php b/tests/PhpPact/Consumer/Model/MessageTest.php
index 165ed8a8..8182706d 100644
--- a/tests/PhpPact/Consumer/Model/MessageTest.php
+++ b/tests/PhpPact/Consumer/Model/MessageTest.php
@@ -2,6 +2,7 @@
namespace PhpPactTest\Consumer\Model;
+use PhpPact\Consumer\Model\Body\Text;
use PhpPact\Consumer\Model\Message;
use PhpPact\Consumer\Model\ProviderState;
use PHPUnit\Framework\TestCase;
@@ -29,6 +30,10 @@ public function testSetters()
static::assertEquals($providerStateName, $providerStates[0]->getName());
static::assertEquals($providerStateParams, $providerStates[0]->getParams());
static::assertSame($metadata, $subject->getMetadata());
- static::assertSame($contents, $subject->getContents());
+
+ $messageContents = $subject->getContents();
+ $this->assertInstanceOf(Text::class, $messageContents);
+ $this->assertEquals($contents, $messageContents->getContents());
+ $this->assertEquals('text/plain', $messageContents->getContentType());
}
}
diff --git a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php
index 2da664be..315322e7 100644
--- a/tests/PhpPact/Consumer/Model/ProviderResponseTest.php
+++ b/tests/PhpPact/Consumer/Model/ProviderResponseTest.php
@@ -2,6 +2,7 @@
namespace PhpPactTest\Consumer\Model;
+use PhpPact\Consumer\Model\Body\Text;
use PhpPact\Consumer\Model\ProviderResponse;
use PHPUnit\Framework\TestCase;
@@ -19,6 +20,10 @@ public function testSerializing()
$this->assertEquals(200, $model->getStatus());
$this->assertEquals(['Content-Type' => ['application/json']], $model->getHeaders());
- $this->assertEquals('{"currentCity":"Austin"}', $model->getBody());
+
+ $body = $model->getBody();
+ $this->assertInstanceOf(Text::class, $body);
+ $this->assertEquals('{"currentCity":"Austin"}', $body->getContents());
+ $this->assertEquals('application/json', $body->getContentType());
}
}
diff --git a/tests/PhpPact/FFI/Model/StringDataTest.php b/tests/PhpPact/FFI/Model/StringDataTest.php
new file mode 100644
index 00000000..075399b6
--- /dev/null
+++ b/tests/PhpPact/FFI/Model/StringDataTest.php
@@ -0,0 +1,43 @@
+getValue();
+ $size = \strlen($value) + 1;
+
+ $this->assertSame($size, FFI::sizeof($cData));
+ $this->assertSame($value, $this->cDataToString($cData, $size - 1)); // ignore null
+ }
+
+ public function testCreateBinaryString()
+ {
+ $value = file_get_contents(__DIR__ . '/../../../_resources/image.jpg');
+ $stringData = StringData::createFrom($value, false);
+ $cData = $stringData->getValue();
+ $size = \strlen($value);
+
+ $this->assertSame($size, FFI::sizeof($cData));
+ $this->assertEquals($value, $this->cDataToString($cData, $size));
+ }
+
+ private function cDataToString(CData $cData, int $size): string
+ {
+ $result = '';
+ for ($index = 0; $index < $size; $index++) {
+ $result .= chr($cData[$index]); // @phpstan-ignore-line
+ }
+
+ return $result;
+ }
+}
diff --git a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php
index 245d60a5..cf302a7c 100644
--- a/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php
+++ b/tests/PhpPact/Standalone/ProviderVerifier/VerifierTest.php
@@ -55,7 +55,7 @@ public function testVerify(): void
}
$verifier = new Verifier($config);
- $verifier->addDirectory(__DIR__ . '/../../../_resources');
+ $verifier->addFile(__DIR__ . '/../../../_resources/someconsumer-someprovider.json');
$verifyResult = $verifier->verify();
diff --git a/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php b/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php
index 6ae7c400..1e0e77f3 100644
--- a/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php
+++ b/tests/PhpPact/Standalone/StubServer/StubServerConfigTest.php
@@ -18,7 +18,7 @@ public function testSetters()
$providerStateHeaderName = 'header';
$token = 'token';
$user = 'user:password';
- $dirs = [__DIR__ . '/../../../_resources'];
+ $dirs = ['/path/to/pacts'];
$files = ['/path/to/pact.json'];
$urls = ['http://example.com/path/to/file.json'];
$consumerNames = ['consumer-1', 'consumer-2'];
diff --git a/tests/_resources/image.jpg b/tests/_resources/image.jpg
new file mode 100644
index 00000000..8c1702a1
Binary files /dev/null and b/tests/_resources/image.jpg differ