From 255231494296e3ffebcb9e1f687fb87040a679c5 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Wed, 9 Mar 2022 12:31:25 +0800 Subject: [PATCH 1/5] tests compatible for phpstan@^1.4.5 --- tests/Util/MediaUtilTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Util/MediaUtilTest.php b/tests/Util/MediaUtilTest.php index dd023b1..a2f5677 100644 --- a/tests/Util/MediaUtilTest.php +++ b/tests/Util/MediaUtilTest.php @@ -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', @@ -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 */ ], ]; } From 9745acf5b65aba48b019ad42d05056e276547acc Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Thu, 10 Mar 2022 18:21:09 +0800 Subject: [PATCH 2/5] `assertNotEquals` onto the complaint file downloading --- .../MerchantService/Images/DownloadTest.php | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php diff --git a/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php b/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php new file mode 100644 index 0000000..f3db13b --- /dev/null +++ b/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php @@ -0,0 +1,147 @@ +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 $config + * @return array{\WeChatPay\BuilderChainable,HandlerStack} + */ + private function newInstance(array $config): 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) { + // Note here: because the `$target` is used onto the `signature`, **IT IS NOT SAME TO** the original URI. + // And **NO IDEA** about the platform HOW TO VERIFY the `media_slot_url` while there contains the double pct-encoded characters. + $target = $request->getRequestTarget(); + self::assertNotEquals('/v3/merchant-service/images/' . self::MEDIA_ID, $target); + }, /* after */static function (RequestInterface $request) { + $target = $request->getRequestTarget(); + self::assertNotEquals('/v3/merchant-service/images/' . self::MEDIA_ID, $target); + })); + + return [$instance, $stack]; + } + + /** + * @return array> + */ + 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 $config + * @param string $slot + * @param ResponseInterface $respondor + */ + public function testGet(array $config, string $slot, ResponseInterface $respondor): void + { + [$endpoint, $stack] = $this->newInstance($config); + + $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 $config + * @param string $slot + * @param ResponseInterface $respondor + */ + public function testGetAsync(array $config, string $slot, ResponseInterface $respondor): void + { + [$endpoint, $stack] = $this->newInstance($config); + + $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(); + } +} From 0383b25592351e556f3389e442ef0ca4936b7413 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Fri, 11 Mar 2022 10:17:05 +0800 Subject: [PATCH 3/5] tests on `HOWTO` download the complaint images, also cover onto guzzle/uri-template#18 --- .../MerchantService/Images/DownloadTest.php | 87 +++++++++++++++++-- 1 file changed, 78 insertions(+), 9 deletions(-) diff --git a/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php b/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php index f3db13b..e32eccf 100644 --- a/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php +++ b/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php @@ -11,6 +11,7 @@ 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; @@ -36,9 +37,10 @@ private function guzzleMockStack(): HandlerStack /** * @param array $config + * @param string $assertMethod * @return array{\WeChatPay\BuilderChainable,HandlerStack} */ - private function newInstance(array $config): array + private function newInstance(array $config, string $assertMethod): array { $instance = Builder::factory($config + ['handler' => $this->guzzleMockStack(),]); @@ -47,14 +49,18 @@ private function newInstance(array $config): array $stack = clone $stack; $stack->remove('verifier'); - $stack->push(Middleware::tap(/* before */static function (RequestInterface $request) { - // Note here: because the `$target` is used onto the `signature`, **IT IS NOT SAME TO** the original URI. - // And **NO IDEA** about the platform HOW TO VERIFY the `media_slot_url` while there contains the double pct-encoded characters. + $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::assertNotEquals('/v3/merchant-service/images/' . self::MEDIA_ID, $target); - }, /* after */static function (RequestInterface $request) { + 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::assertNotEquals('/v3/merchant-service/images/' . self::MEDIA_ID, $target); + self::{$assertMethod}('/v3/merchant-service/images/' . self::MEDIA_ID, $target); })); return [$instance, $stack]; @@ -88,7 +94,11 @@ public function mockDataProvider(): array */ public function testGet(array $config, string $slot, ResponseInterface $respondor): void { - [$endpoint, $stack] = $this->newInstance($config); + // 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); @@ -124,7 +134,11 @@ private static function responseAssertion(ResponseInterface $response): void */ public function testGetAsync(array $config, string $slot, ResponseInterface $respondor): void { - [$endpoint, $stack] = $this->newInstance($config); + // 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 `media_slot_url` while there contains the double pct-encoded characters. + [$endpoint, $stack] = $this->newInstance($config, 'assertNotEquals'); $this->mock->reset(); $this->mock->append($respondor); @@ -144,4 +158,59 @@ public function testGetAsync(array $config, string $slot, ResponseInterface $res self::responseAssertion($response); })->wait(); } + + /** + * @dataProvider mockDataProvider + * @param array $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 signature `get` method */ + $response = $apiv3Client->get($relativeUrl, ['handler' => $stack]); + self::responseAssertion($response); + + $this->mock->append($respondor); + /** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no signature `get` method */ + $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 signature `getAsync` method */ + $response = $apiv3Client->getAsync($fullUri, ['handler' => $stack])->then($asyncAssertion)->wait(); + + $this->mock->append($respondor); + /** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no signature `getAsync` method */ + $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(); + } } From df56edd8bce48f889ac4c794db98ea3874fb9f12 Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Fri, 11 Mar 2022 10:23:42 +0800 Subject: [PATCH 4/5] refine words --- .../OpenAPI/V3/MerchantService/Images/DownloadTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php b/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php index e32eccf..b65791f 100644 --- a/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php +++ b/tests/OpenAPI/V3/MerchantService/Images/DownloadTest.php @@ -137,7 +137,7 @@ public function testGetAsync(array $config, string $slot, ResponseInterface $res // 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 `media_slot_url` while there contains the double pct-encoded characters. + // 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(); @@ -186,12 +186,12 @@ public function testUseStandardGuzzleHttpClient(array $config, string $slot, Res self::responseAssertion($response); $this->mock->append($respondor); - /** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no signature `get` method */ + /** @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 signature `get` method */ + /** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no `get` method signature */ $response = $apiv3Client->get($fullUri, ['handler' => $stack]); self::responseAssertion($response); @@ -200,11 +200,11 @@ public function testUseStandardGuzzleHttpClient(array $config, string $slot, Res }; $this->mock->append($respondor); - /** @phpstan-ignore-next-line because of \GuzzleHttp\ClientInterface no signature `getAsync` method */ + /** @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 signature `getAsync` method */ + /** @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); From d89e2345cc9da6fb279f6ebfd9fa0ce6e400a4ad Mon Sep 17 00:00:00 2001 From: James ZHNAG Date: Fri, 11 Mar 2022 15:00:11 +0800 Subject: [PATCH 5/5] uploading the complaint feedback image sample --- .../V3/MerchantService/Images/UploadTest.php | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 tests/OpenAPI/V3/MerchantService/Images/UploadTest.php diff --git a/tests/OpenAPI/V3/MerchantService/Images/UploadTest.php b/tests/OpenAPI/V3/MerchantService/Images/UploadTest.php new file mode 100644 index 0000000..9919588 --- /dev/null +++ b/tests/OpenAPI/V3/MerchantService/Images/UploadTest.php @@ -0,0 +1,144 @@ +mock = new MockHandler(); + + return HandlerStack::create($this->mock); + } + + /** + * @param array $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> + */ + 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 $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 $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(); + } +}