Skip to content

Commit

Permalink
feat: Support http request multipart body
Browse files Browse the repository at this point in the history
  • Loading branch information
tienvx committed Aug 21, 2023
1 parent 9c30bfb commit 72b725e
Show file tree
Hide file tree
Showing 23 changed files with 559 additions and 19 deletions.
6 changes: 5 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,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",
"MultipartConsumer\\": "example/multipart/consumer/src",
"MultipartConsumer\\Tests\\": "example/multipart/consumer/tests",
"MultipartProvider\\": "example/multipart/provider/src",
"MultipartProvider\\Tests\\": "example/multipart/provider/tests"
}
},
"scripts": {
Expand Down
11 changes: 11 additions & 0 deletions example/multipart/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>
52 changes: 52 additions & 0 deletions example/multipart/consumer/src/Service/HttpClientService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace MultipartConsumer\Service;

use GuzzleHttp\Client;

/**
* Example HTTP Service
*/
class HttpClientService
{
private Client $httpClient;

private string $baseUri;

public function __construct(string $baseUri)
{
$this->httpClient = new Client();
$this->baseUri = $baseUri;
}

public function updateUserProfile(): string
{
$response = $this->httpClient->post("{$this->baseUri}/user-profile", [
'multipart' => [
[
'name' => 'full_name',
'contents' => 'Zoey Turcotte'
],
[
'name' => 'profile_image',
'contents' => file_get_contents(__DIR__ . '/../_resource/image.jpg')
],
[
'name' => 'personal_note',
'contents' => 'testing',
'filename' => 'note.txt',
'headers' => [
'X-Foo' => 'this is a note',
'Content-Type' => 'application/octet-stream',
],
],
],
'headers' => [
'Accept' => 'application/json',
'Authorization' => 'Bearer ZmluLWFwaTphcGktc2VjcmV0',
],
]);

return $response->getBody();
}
}
Binary file added example/multipart/consumer/src/_resource/image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions example/multipart/consumer/tests/Service/HttpClientServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace MultipartConsumer\Tests\Service;

use MultipartConsumer\Service\HttpClientService;
use PhpPact\Consumer\InteractionBuilder;
use PhpPact\Consumer\Matcher\Matcher;
use PhpPact\Consumer\Model\Body\Multipart;
use PhpPact\Consumer\Model\Body\Part;
use PhpPact\Consumer\Model\ConsumerRequest;
use PhpPact\Consumer\Model\ProviderResponse;
use PhpPact\Standalone\MockService\MockServerConfig;
use PHPUnit\Framework\TestCase;

class HttpClientServiceTest extends TestCase
{
public const URL_FORMAT = '^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()!@:%_\+.~#?&\/\/=]*)';

public function testUpdateUserProfile()
{
$matcher = new Matcher();
$fullName = 'Colten Ziemann';
$profileImageUrl = 'http://example.test/profile-image.jpg';
$personalNote = 'testing';

$request = new ConsumerRequest();
$request
->setMethod('POST')
->setPath('/user-profile')
->setHeaders([
'Accept' => 'application/json',
'Authorization' => [
\json_encode($matcher->like('Bearer eyJhbGciOiJIUzI1NiIXVCJ9'))
],
])
->setBody(new Multipart([
new Part($fullNameTempFile = $this->createTempFile($fullName), 'full_name', 'text/plain'),
new Part(__DIR__ . '/../_resource/image.jpg', 'profile_image', 'image/jpeg'),
new Part($personalNoteTempFile = $this->createTempFile($personalNote), 'personal_note', 'text/plain'),
]));

$response = new ProviderResponse();
$response
->setStatus(200)
->addHeader('Content-Type', 'application/json')
->setBody([
'full_name' => $matcher->like($fullName),
'profile_image' => $matcher->regex($profileImageUrl, self::URL_FORMAT),
'personal_note' => $matcher->like($personalNote),
]);

$config = new MockServerConfig();
$config
->setConsumer('multipartConsumer')
->setProvider('multipartProvider')
->setPactDir(__DIR__.'/../../../pacts');
if ($logLevel = \getenv('PACT_LOGLEVEL')) {
$config->setLogLevel($logLevel);
}
$builder = new InteractionBuilder($config);
$builder
->given('User exists')
->uponReceiving('A put request to /user-profile')
->with($request)
->willRespondWith($response);

$service = new HttpClientService($config->getBaseUri());
$userProfileResponse = $service->updateUserProfile();
$verifyResult = $builder->verify();

unlink($fullNameTempFile);
unlink($personalNoteTempFile);

$this->assertTrue($verifyResult);
$this->assertEquals([
'full_name' => $fullName,
'profile_image' => $profileImageUrl,
'personal_note' => $personalNote,
], \json_decode($userProfileResponse, true, 512, JSON_THROW_ON_ERROR));
}

private function createTempFile(string $contents): string
{
$path = tempnam(sys_get_temp_dir(), 'pact');
//$newPath = "$path.txt";
//rename($path, $newPath);
$newPath = $path;

$handle = fopen($newPath, 'w');
fwrite($handle, $contents);
fclose($handle);

return $newPath;
}
}
Binary file added example/multipart/consumer/tests/_resource/image.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
137 changes: 137 additions & 0 deletions example/multipart/pacts/multipartConsumer-multipartProvider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
{
"consumer": {
"name": "multipartConsumer"
},
"interactions": [
{
"description": "A put request to /user-profile",
"providerStates": [
{
"name": "User exists"
}
],
"request": {
"body": "--MxafNCX4RAVZ1d2c\r\nContent-Disposition: form-data; name=\"personal_note\"; filename=\"pactmk99lD\"\r\nContent-Type: application/octet-stream\r\n\r\ntesting\r\n--MxafNCX4RAVZ1d2c--\r\n",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIXVCJ9",
"Content-Type": "multipart/form-data; boundary=MxafNCX4RAVZ1d2c"
},
"matchingRules": {
"body": {
"$.full_name": {
"combine": "AND",
"matchers": [
{
"match": "contentType",
"value": "text/plain"
}
]
},
"$.personal_note": {
"combine": "AND",
"matchers": [
{
"match": "contentType",
"value": "text/plain"
}
]
},
"$.profile_image": {
"combine": "AND",
"matchers": [
{
"match": "contentType",
"value": "image/jpeg"
}
]
}
},
"header": {
"$.Authorization[0]": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
"Content-Type": {
"combine": "AND",
"matchers": [
{
"match": "regex",
"regex": "multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*"
},
{
"match": "regex",
"regex": "multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*"
},
{
"match": "regex",
"regex": "multipart/form-data;(\\s*charset=[^;]*;)?\\s*boundary=.*"
}
]
}
}
},
"method": "POST",
"path": "/user-profile"
},
"response": {
"body": {
"full_name": "Colten Ziemann",
"personal_note": "testing",
"profile_image": "http://example.test/profile-image.jpg"
},
"headers": {
"Content-Type": "application/json"
},
"matchingRules": {
"body": {
"$.full_name": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
"$.personal_note": {
"combine": "AND",
"matchers": [
{
"match": "type"
}
]
},
"$.profile_image": {
"combine": "AND",
"matchers": [
{
"match": "regex",
"regex": "^https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()!@:%_\\+.~#?&\\/\\/=]*)"
}
]
}
},
"header": {}
},
"status": 200
}
}
],
"metadata": {
"pactRust": {
"ffi": "0.4.7",
"mockserver": "1.2.3",
"models": "1.1.9"
},
"pactSpecification": {
"version": "3.0.0"
}
},
"provider": {
"name": "multipartProvider"
}
}
11 changes: 11 additions & 0 deletions example/multipart/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>
23 changes: 23 additions & 0 deletions example/multipart/provider/public/index.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?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->addBodyParsingMiddleware();

$app->post('/user-profile', function (Request $request, Response $response) {
$fileName = (string)$request->getUploadedFiles()['profile_image']->getClientFilename();
$response->getBody()->write(\json_encode([
'full_name' => (string)$request->getUploadedFiles()['full_name']->getStream(),
'profile_image' => "http://example.test/$fileName",
'personal_note' => (string)$request->getUploadedFiles()['personal_note']->getStream(),
]));

return $response->withHeader('Content-Type', 'application/json');
});

$app->run();
Loading

0 comments on commit 72b725e

Please sign in to comment.