Skip to content

Commit

Permalink
Merge pull request #333 from tienvx/multipart
Browse files Browse the repository at this point in the history
Use Rust FFI #13: Multipart
  • Loading branch information
YOU54F committed Sep 12, 2023
2 parents 19e95a1 + 7fde6a4 commit d87ffec
Show file tree
Hide file tree
Showing 23 changed files with 558 additions and 21 deletions.
10 changes: 7 additions & 3 deletions 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 All @@ -69,12 +73,12 @@
"extra": {
"downloads": {
"pact-ffi-headers": {
"version": "0.4.7",
"version": "0.4.8",
"url": "https://github.com/pact-foundation/pact-reference/releases/download/libpact_ffi-v{$version}/pact.h",
"path": "bin/pact-ffi-headers/pact.h"
},
"pact-ffi-lib": {
"version": "0.4.7",
"version": "0.4.8",
"variables": {
"{$prefix}": "PHP_OS_FAMILY === 'Windows' ? 'pact_ffi' : 'libpact_ffi'",
"{$os}": "PHP_OS === 'Darwin' ? 'osx' : strtolower(PHP_OS_FAMILY)",
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>
57 changes: 57 additions & 0 deletions example/multipart/consumer/src/Service/HttpClientService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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',
'filename' => 'full_name.txt',
'headers' => [
'Content-Type' => 'application/octet-stream',
],
],
[
'name' => 'profile_image',
'contents' => file_get_contents(__DIR__ . '/../_resource/image.jpg'),
'filename' => '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', in_array(php_uname('m'), ['AMD64', 'arm64']) ? 'application/octet-stream' : '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.
129 changes: 129 additions & 0 deletions example/multipart/pacts/multipartConsumer-multipartProvider.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
{
"consumer": {
"name": "multipartConsumer"
},
"interactions": [
{
"description": "A put request to /user-profile",
"providerStates": [
{
"name": "User exists"
}
],
"request": {
"body": "LS1rdEptZVlIYmtUU2ExanhEDQpDb250ZW50LURpc3Bvc2l0aW9uOiBmb3JtLWRhdGE7IG5hbWU9ImZ1bGxfbmFtZSI7IGZpbGVuYW1lPSJwYWN0dmZhU1JiIg0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0NCg0KQ29sdGVuIFppZW1hbm4NCi0ta3RKbWVZSGJrVFNhMWp4RA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJwcm9maWxlX2ltYWdlIjsgZmlsZW5hbWU9ImltYWdlLmpwZyINCkNvbnRlbnQtVHlwZTogaW1hZ2UvanBlZw0KDQr/2P/gABBKRklGAAEBAAABAAEAAP/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoMDwwKCw4LCQkNEQ0ODxAQERAKDBITEhATDxAQEP/AAAsIAAwADAEBEQD/xAAWAAEBAQAAAAAAAAAAAAAAAAAEAAX/xAAiEAACAQMDBQEAAAAAAAAAAAABAwIEERIABSEGEyIjQRT/2gAIAQEAAD8AUa1jdqVVShOmTKYY4qW1MC0syis3jITyjExBiBjYXHkDq209MlLCjqqe3HuEMp5TknGYsCR7QJA2BysOSR80euY9O3L3oOymKamrAkqWFjOEGFfERLtgyIAyvb7fnWAmoqnMqR+6tV26lsPRWOTGXmTfGEhEHn4AOBxr/9kNCi0ta3RKbWVZSGJrVFNhMWp4RA0KQ29udGVudC1EaXNwb3NpdGlvbjogZm9ybS1kYXRhOyBuYW1lPSJwZXJzb25hbF9ub3RlIjsgZmlsZW5hbWU9InBhY3Q3OU1jT1QiDQpDb250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL29jdGV0LXN0cmVhbQ0KDQp0ZXN0aW5nDQotLWt0Sm1lWUhia1RTYTFqeEQtLQ0K",
"headers": {
"Accept": "application/json",
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIXVCJ9",
"Content-Type": "multipart/form-data; boundary=ktJmeYHbkTSa1jxD"
},
"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=.*"
}
]
}
}
},
"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.8",
"mockserver": "1.2.4",
"models": "1.1.11"
},
"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 d87ffec

Please sign in to comment.