Skip to content

Commit

Permalink
Merge pull request #85 from TheNorthMemory/v1.4
Browse files Browse the repository at this point in the history
增加了两条APIv3的测试用例,示例说明图片下载及上传的特殊用法
  • Loading branch information
xy-peng authored Mar 11, 2022
2 parents d384eba + d89e234 commit 390fcc9
Show file tree
Hide file tree
Showing 3 changed files with 362 additions and 2 deletions.
216 changes: 216 additions & 0 deletions tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<?php declare(strict_types=1);

namespace WeChatPay\Tests\OpenAPI\V3\MerchantService\Images;

use function rtrim;
use function file_get_contents;
use function sprintf;

use WeChatPay\Builder;
use WeChatPay\Crypto\Rsa;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\LazyOpenStream;
use Psr\Http\Message\ResponseInterface;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;

class DownloadTest extends TestCase
{
private const FIXTURES = 'file://' . __DIR__ . '/../../../../fixtures/%s';

/** @var MockHandler $mock */
private $mock;

private function guzzleMockStack(): HandlerStack
{
$this->mock = new MockHandler();

return HandlerStack::create($this->mock);
}

/** @var string The media_slot_url by a community reporting */
private const MEDIA_ID = 'ChsyMDAyMDgwMjAyMjAyMTgxMTA0NDEzMTEwMzASGzMwMDIwMDAyMDIyMDIxODE1MDQ0MTcwOTI5NhgAIO%2FFR1pAGKAMwAjgB';

/**
* @param array<string,mixed> $config
* @param string $assertMethod
* @return array{\WeChatPay\BuilderChainable,HandlerStack}
*/
private function newInstance(array $config, string $assertMethod): array
{
$instance = Builder::factory($config + ['handler' => $this->guzzleMockStack(),]);

/** @var HandlerStack $stack */
$stack = $instance->getDriver()->select()->getConfig('handler');
$stack = clone $stack;
$stack->remove('verifier');

$stack->push(Middleware::tap(/* before */static function (RequestInterface $request) use ($assertMethod) {
self::assertTrue($request->hasHeader('Authorization'));
self::assertStringStartsWith('WECHATPAY2-SHA256-RSA2048', $request->getHeaderLine('Authorization'));

$target = $request->getRequestTarget();
self::{$assertMethod}('/v3/merchant-service/images/' . self::MEDIA_ID, $target);
}, /* after */static function (RequestInterface $request) use ($assertMethod) {
self::assertTrue($request->hasHeader('Authorization'));
self::assertStringStartsWith('WECHATPAY2-SHA256-RSA2048', $request->getHeaderLine('Authorization'));

$target = $request->getRequestTarget();
self::{$assertMethod}('/v3/merchant-service/images/' . self::MEDIA_ID, $target);
}));

return [$instance, $stack];
}

/**
* @return array<string,array<mixed>>
*/
public function mockDataProvider(): array
{
$mchid = '1230000109';
$mchSerial = rtrim(file_get_contents(sprintf(self::FIXTURES, 'mock.serial.txt')) ?: '');
$mchPrivateKey = Rsa::from(sprintf(self::FIXTURES, 'mock.pkcs8.key'));

$stream = new LazyOpenStream(sprintf(self::FIXTURES, 'logo.png'), 'rb');

return [
'PNG image stream with the raw mediaId' => [
['mchid' => $mchid, 'serial' => $mchSerial, 'privateKey' => $mchPrivateKey, 'certs' => ['nop' => null]],
self::MEDIA_ID,
new Response(200, ['Content-Type' => 'image/png'], $stream),
],
];
}

/**
* @dataProvider mockDataProvider
* @param array<string,mixed> $config
* @param string $slot
* @param ResponseInterface $respondor
*/
public function testGet(array $config, string $slot, ResponseInterface $respondor): void
{
// Note here: using the `UriTemplate` may be caused that, **IT IS NOT SAME TO** the original URI,
// because the `$slot` is used onto the `signature` algorithm.
// More @see https://github.com/guzzle/uri-template/issues/18
// And **NO IDEA** about the platform HOW TO VERIFY the `$slot` while there contains the double pct-encoded characters.
[$endpoint, $stack] = $this->newInstance($config, 'assertNotEquals');

$this->mock->reset();
$this->mock->append($respondor);
$this->mock->append($respondor);

$response = $endpoint->chain('v3/merchant-service/images/{media_slot_url}')->get([
'handler' => $stack,
'media_slot_url' => $slot,
]);
self::responseAssertion($response);

$response = $endpoint->chain('v3/merchant-service/images/{+media_slot_url}')->get([
'handler' => $stack,
'media_slot_url' => $slot,
]);
self::responseAssertion($response);
}

/**
* @param ResponseInterface $response
*/
private static function responseAssertion(ResponseInterface $response): void
{
self::assertTrue($response->hasHeader('Content-Type'));
self::assertStringStartsWith('image/', $response->getHeaderLine('Content-Type'));
}

/**
* @dataProvider mockDataProvider
* @param array<string,mixed> $config
* @param string $slot
* @param ResponseInterface $respondor
*/
public function testGetAsync(array $config, string $slot, ResponseInterface $respondor): void
{
// Note here: using the `UriTemplate` may be caused that, **IT IS NOT SAME TO** the original URI,
// because the `$slot` is used onto the `signature` algorithm.
// More @see https://github.com/guzzle/uri-template/issues/18
// And **NO IDEA** about the platform HOW TO VERIFY the `$slot` while there contains the double pct-encoded characters.
[$endpoint, $stack] = $this->newInstance($config, 'assertNotEquals');

$this->mock->reset();
$this->mock->append($respondor);
$this->mock->append($respondor);

$endpoint->chain('v3/merchant-service/images/{media_slot_url}')->getAsync([
'handler' => $stack,
'media_slot_url' => $slot,
])->then(static function(ResponseInterface $response) {
self::responseAssertion($response);
})->wait();

$endpoint->chain('v3/merchant-service/images/{+media_slot_url}')->getAsync([
'handler' => $stack,
'media_slot_url' => $slot,
])->then(static function (ResponseInterface $response) {
self::responseAssertion($response);
})->wait();
}

/**
* @dataProvider mockDataProvider
* @param array<string,mixed> $config
* @param string $slot
* @param ResponseInterface $respondor
*/
public function testUseStandardGuzzleHttpClient(array $config, string $slot, ResponseInterface $respondor): void
{
[$endpoint, $stack] = $this->newInstance($config, 'assertEquals');

$relativeUrl = 'v3/merchant-service/images/' . $slot;
$fullUri = 'https://api.mch.weixin.qq.com/' . $relativeUrl;

$apiv3Client = $endpoint->getDriver()->select();
self::assertInstanceOf(ClientInterface::class, $apiv3Client);

$this->mock->reset();

$this->mock->append($respondor);
$response = $apiv3Client->request('GET', $relativeUrl, ['handler' => $stack]);
self::responseAssertion($response);

$this->mock->append($respondor);
$response = $apiv3Client->request('GET', $fullUri, ['handler' => $stack]);
self::responseAssertion($response);

$this->mock->append($respondor);
/** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no `get` method signature */
$response = $apiv3Client->get($relativeUrl, ['handler' => $stack]);
self::responseAssertion($response);

$this->mock->append($respondor);
/** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no `get` method signature */
$response = $apiv3Client->get($fullUri, ['handler' => $stack]);
self::responseAssertion($response);

$asyncAssertion = static function (ResponseInterface $response) {
self::responseAssertion($response);
};

$this->mock->append($respondor);
/** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no `getAsync` method signature */
$response = $apiv3Client->getAsync($fullUri, ['handler' => $stack])->then($asyncAssertion)->wait();

$this->mock->append($respondor);
/** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no `getAsync` method signature */
$response = $apiv3Client->getAsync($relativeUrl, ['handler' => $stack])->then($asyncAssertion)->wait();

$this->mock->append($respondor);
$response = $apiv3Client->requestAsync('GET', $relativeUrl, ['handler' => $stack])->then($asyncAssertion)->wait();

$this->mock->append($respondor);
$response = $apiv3Client->requestAsync('GET', $fullUri, ['handler' => $stack])->then($asyncAssertion)->wait();
}
}
144 changes: 144 additions & 0 deletions tests/OpenAPI/V3/MerchantService/Images/UploadTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<?php declare(strict_types=1);

namespace WeChatPay\Tests\OpenAPI\V3\MerchantService\Images;

use function rtrim;
use function file_get_contents;
use function sprintf;
use function strtoupper;

use WeChatPay\Builder;
use WeChatPay\BuilderChainable;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Util\MediaUtil;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\Utils;
use Psr\Http\Message\ResponseInterface;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\StreamInterface;
use WeChatPay\Formatter;

class UploadTest extends TestCase
{
private const FIXTURES = 'file://' . __DIR__ . '/../../../../fixtures/%s';
private const MEDIA_JSON = '{"media_id":"BB04A5DEEFEA18D4F2554C1EDD3B610B.bmp"}';

/** @var MockHandler $mock */
private $mock;

private function guzzleMockStack(): HandlerStack
{
$this->mock = new MockHandler();

return HandlerStack::create($this->mock);
}

/**
* @param array<string,mixed> $config
*/
private function newInstance(array $config): BuilderChainable
{
$check = static function (RequestInterface $request) {
self::assertTrue($request->hasHeader('Authorization'));
self::assertStringStartsWith('WECHATPAY2-SHA256-RSA2048', $request->getHeaderLine('Authorization'));
self::assertEquals('/v3/merchant-service/images/upload', $request->getRequestTarget());
self::assertTrue($request->hasHeader('Content-Type'));
self::assertStringStartsWith('multipart/form-data; boundary=', $request->getHeaderLine('Content-Type'));
self::assertInstanceOf(FnStream::class, $request->getBody());
};

$instance = Builder::factory($config + ['handler' => $this->guzzleMockStack(),]);

/** @var HandlerStack $stack */
$stack = $instance->getDriver()->select()->getConfig('handler');
$stack->push(Middleware::tap(/* before */$check, /* after */$check));

return $instance;
}

/**
* @return array<string,array<mixed>>
*/
public function mockDataProvider(): array
{
$mchid = '1230000109';
$mchSerial = rtrim(file_get_contents(sprintf(self::FIXTURES, 'mock.serial.txt')) ?: '');
$mchPrivateKey = Rsa::from(sprintf(self::FIXTURES, 'mock.pkcs1.key'));

$platPrivateKey = Rsa::from(sprintf(self::FIXTURES, 'mock.pkcs8.key'));
$platPublicKey = Rsa::from(sprintf(self::FIXTURES, 'mock.spki.pem'), Rsa::KEY_TYPE_PUBLIC);
$platSerial = strtoupper(Formatter::nonce(40));

return [
'image upload' => [
['mchid' => $mchid, 'serial' => $mchSerial, 'privateKey' => $mchPrivateKey, 'certs' => [$platSerial => $platPublicKey]],
sprintf(self::FIXTURES, 'logo.png'),
new Response(200, [
'Content-Type' => 'application/json',
'Wechatpay-Serial' => $platSerial,
'Wechatpay-Nonce' => $nonce = Formatter::nonce(),
'Wechatpay-Timestamp' => $timestamp = (string) Formatter::timestamp(),
'Wechatpay-Signature' => Rsa::sign(Formatter::response($timestamp, $nonce, self::MEDIA_JSON), $platPrivateKey),
], self::MEDIA_JSON),
],
];
}

/**
* @dataProvider mockDataProvider
* @param array<string,mixed> $config
* @param string $file
* @param ResponseInterface $respondor
*/
public function testPost(array $config, string $file, ResponseInterface $respondor): void
{
$endpoint = $this->newInstance($config);
$media = new MediaUtil($file);

$this->mock->reset();
$this->mock->append($respondor);

$response = $endpoint->chain('v3/merchant-service/images/upload')->post([
'body' => $media->getStream(),
'headers' => ['Content-Type' => $media->getContentType()],
]);
self::responseAssertion($response);
}

/**
* @param ResponseInterface $response
*/
private static function responseAssertion(ResponseInterface $response): void
{
self::assertTrue($response->hasHeader('Content-Type'));
self::assertStringStartsWith('application/json', $response->getHeaderLine('Content-Type'));
self::assertEquals(self::MEDIA_JSON, (string) $response->getBody());
}

/**
* @dataProvider mockDataProvider
* @param array<string,mixed> $config
* @param string $file
* @param ResponseInterface $respondor
*/
public function testPostAsync(array $config, string $file, ResponseInterface $respondor): void
{
$endpoint = $this->newInstance($config);
$media = new MediaUtil($file);

$this->mock->reset();
$this->mock->append($respondor);

$endpoint->chain('v3/merchant-service/images/upload')->postAsync([
'body' => $media->getStream(),
'headers' => ['Content-Type' => $media->getContentType()],
])->then(static function(ResponseInterface $response) {
self::responseAssertion($response);
})->wait();
}
}
4 changes: 2 additions & 2 deletions tests/Util/MediaUtilTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function fileDataProvider(): array
self::FOPEN_MODE_BINARYREAD
),
'transparent.gif',
hash(self::ALGO_SHA256, base64_decode($data)) ?: '',
hash(self::ALGO_SHA256, base64_decode($data)) ?: '', /** @phpstan-ignore-line compatible for PHP7 */
],
'data://text/csv;base64 string' => [//RFC2397
'active_user_batch_tasks_001.csv',
Expand All @@ -59,7 +59,7 @@ public function fileDataProvider(): array
self::FOPEN_MODE_BINARYREAD
),
'active_user_batch_tasks_001.csv',
hash(self::ALGO_SHA256, $data) ?: '',
hash(self::ALGO_SHA256, $data) ?: '', /** @phpstan-ignore-line compatible for PHP7 */
],
];
}
Expand Down

0 comments on commit 390fcc9

Please sign in to comment.