Skip to content

Commit c831bd3

Browse files
feat!: support the v3/global/merchant-service/images/{media_id} download behavior
1 parent 6704263 commit c831bd3

File tree

3 files changed

+224
-2
lines changed

3 files changed

+224
-2
lines changed

lib/decorator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ const EV3_RES_HEADER_TIMESTAMP_OFFSET = 'It\'s allowed time offset in ± %s seco
3030
const EV3_RES_HEADER_PLATFORM_SERIAL = 'Cannot found the serial(`%s`)\'s configuration, which\'s from the response(header:%s), your\'s %O.';
3131
const EV3_RES_HEADER_SIGNATURE_DIGEST = 'Verify the response\'s data with: timestamp=%s, nonce=%s, signature=%s, cert={%s: ...} failed.';
3232

33-
const COMPLAINT = /\/v3\/merchant-service\/images\/(?!upload).{6,}/;
33+
const COMPLAINT = /\/v3\/(?:global\/)?merchant-service\/images\/(?!upload).{6,}/;
3434
const DOWNLOADS = [
3535
'/v3/new-tax-control-fapiao/download',
3636
'/v3/transferdownload/elecvoucherfile',
@@ -162,7 +162,7 @@ class Decorator {
162162
*/
163163
static responseVerifier(certs = {}) {
164164
return function verifier(data, headers, status) {
165-
/** @since v0.9.0 only detect the basis pathname which may contains `/hk` or behind of the `reserved-proxy-url` */
165+
/** @since v0.9.0 detect the basis pathname */
166166
const pathname = utils.isString(this.url) && utils.absPath(this.url);
167167
if (DOWNLOADS.includes(pathname) || COMPLAINT.test(pathname)) { return data; }
168168

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const { join } = require('path');
2+
const fs = require('fs');
3+
const should = require('should');
4+
const nock = require('nock');
5+
const {
6+
Wechatpay, Rsa, Formatter, Hash,
7+
} = require('../..');
8+
9+
const privateKey = Rsa.from(`file://${join(__dirname, '../fixtures/apiclient_key.pem')}`, Rsa.KEY_TYPE_PRIVATE);
10+
const mockPubkey = Rsa.from(`file://${join(__dirname, '../fixtures/apiserver_cert.pem')}`, Rsa.KEY_TYPE_PUBLIC);
11+
12+
describe('v3/global/merchant-service/images/[upload|{media_id}]', () => {
13+
let scope;
14+
let instance;
15+
16+
function basisServerSideBehavior(headers = {}) {
17+
should(headers).be.an.Object().and.have.keys('accept', 'authorization', 'content-type', 'user-agent');
18+
should(headers.accept).be.a.String().and.match(/application\/json/);
19+
should(headers.authorization).be.a.String().and.match(/^WECHATPAY2-SHA256-RSA2048\s\w+/);
20+
should(headers['user-agent']).be.a.String().and.match(/\w+/);
21+
}
22+
const mockUploadingBehavior = () => {
23+
const msg = '{"media_id":"file752398_7983424"}';
24+
const nonce = Formatter.nonce();
25+
const timestamp = Formatter.timestamp();
26+
27+
return [
28+
function mockServerSideBehavior(requestUri, requestBody) {
29+
requestUri.should.be.String().and.eql('/v3/global/merchant-service/images/upload');
30+
31+
basisServerSideBehavior(this.req.headers);
32+
should(this.req.headers['content-type']).be.a.String().and.match(/^multipart\/form-data;\s?\w+/);
33+
34+
const boundary = this.req.headers['content-type'].slice(this.req.headers['content-type'].indexOf('boundary=') + 10);
35+
should(requestBody).be.a.String().and.match(new RegExp(boundary));
36+
37+
return msg;
38+
},
39+
{
40+
'Wechatpay-Nonce': nonce,
41+
'Wechatpay-Serial': 'BE2A2344B984167B',
42+
'Wechatpay-Timestamp': timestamp,
43+
'Wechatpay-Signature': Rsa.sign(Formatter.response(timestamp, nonce, msg), privateKey),
44+
'Content-Type': 'application/json',
45+
},
46+
];
47+
};
48+
const mockDownloadingBehavior = () => [
49+
function mockServerSideDownloadingBehavior(requestUri) {
50+
requestUri.should.be.String().and.match(/^\/v3\/global\/merchant-service\/images\/(?!upload){6,}/);
51+
52+
basisServerSideBehavior(this.req.headers);
53+
54+
return Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64');
55+
},
56+
{
57+
'Content-Type': 'image/gif',
58+
},
59+
];
60+
61+
beforeEach(() => {
62+
instance = new Wechatpay({
63+
baseURL: 'https://apihk.mch.weixin.qq.com/',
64+
mchid: '101',
65+
serial: '898DBAD30F416EC7',
66+
privateKey,
67+
certs: { BE2A2344B984167B: mockPubkey },
68+
});
69+
70+
scope = nock('https://apihk.mch.weixin.qq.com/')
71+
.defaultReplyHeaders({
72+
server: 'Nginx',
73+
})
74+
.replyDate();
75+
});
76+
77+
describe('POST `v3/global/merchant-service/images/upload`', () => {
78+
it('POST the `v3/global/merchant-service/images/upload` should be a `200` response', async () => {
79+
scope.post((uri) => uri.startsWith('/v3/global/merchant-service/images/upload')).reply(200, ...mockUploadingBehavior());
80+
81+
const filename = 'README.md';
82+
const filepath = join(__dirname, '../fixtures/README.md');
83+
const sha256 = Hash.sha256(fs.readFileSync(filepath));
84+
85+
await instance.chain('v3/global/merchant-service/images/upload').post({
86+
meta: JSON.stringify({ filename, sha256 }),
87+
file: fs.createReadStream(filepath),
88+
}, {
89+
meta: { filename, sha256 },
90+
headers: { 'Content-Type': 'multipart/form-data' },
91+
}).then((res) => {
92+
res.should.be.an.Object().and.have.keys('headers', 'data');
93+
res.data.should.be.an.Object().and.have.keys('media_id');
94+
});
95+
});
96+
});
97+
98+
describe('GET `v3/global/merchant-service/images/{media_id}`', () => {
99+
it('GET the `v3/global/merchant-service/images/{media_id}` should be a `200` response', async () => {
100+
scope.get((uri) => uri.match(/^\/v3\/global\/merchant-service\/images\/(?!upload){6,}/)).reply(200, ...mockDownloadingBehavior());
101+
102+
await instance.chain('v3/global/merchant-service/images/{media_id}').get({
103+
media_id: 'ChsyMDAyMDA1MjAyMTA3MjIxNzAwMDAxMzIwNzIYACD%2B9I6IBigBMAE4AQ==',
104+
params: {
105+
complaint_id: '200200520210722170000132072',
106+
},
107+
}).then((res) => {
108+
res.should.be.an.Object().and.have.keys('headers', 'data');
109+
res.headers['content-type'].should.be.a.String().and.match(/image\/(?:png|gif|jpe?g)/);
110+
});
111+
});
112+
});
113+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
const { join } = require('path');
2+
const fs = require('fs');
3+
const should = require('should');
4+
const nock = require('nock');
5+
const {
6+
Wechatpay, Rsa, Formatter, Hash,
7+
} = require('../..');
8+
9+
const privateKey = Rsa.from(`file://${join(__dirname, '../fixtures/apiclient_key.pem')}`, Rsa.KEY_TYPE_PRIVATE);
10+
const mockPubkey = Rsa.from(`file://${join(__dirname, '../fixtures/apiserver_cert.pem')}`, Rsa.KEY_TYPE_PUBLIC);
11+
12+
describe('v3/merchant-service/images/[upload|{media_id}]', () => {
13+
let scope;
14+
let instance;
15+
16+
function basisServerSideBehavior(headers = {}) {
17+
should(headers).be.an.Object().and.have.keys('accept', 'authorization', 'content-type', 'user-agent');
18+
should(headers.accept).be.a.String().and.match(/application\/json/);
19+
should(headers.authorization).be.a.String().and.match(/^WECHATPAY2-SHA256-RSA2048\s\w+/);
20+
should(headers['user-agent']).be.a.String().and.match(/\w+/);
21+
}
22+
const mockUploadingBehavior = () => {
23+
const msg = '{"media_id":"file752398_7983424"}';
24+
const nonce = Formatter.nonce();
25+
const timestamp = Formatter.timestamp();
26+
27+
return [
28+
function mockServerSideUploadingBehavior(requestUri, requestBody) {
29+
requestUri.should.be.String().and.eql('/v3/merchant-service/images/upload');
30+
31+
basisServerSideBehavior(this.req.headers);
32+
should(this.req.headers['content-type']).be.a.String().and.match(/^multipart\/form-data;\s?\w+/);
33+
34+
const boundary = this.req.headers['content-type'].slice(this.req.headers['content-type'].indexOf('boundary=') + 10);
35+
should(requestBody).be.a.String().and.match(new RegExp(boundary));
36+
37+
return msg;
38+
},
39+
{
40+
'Wechatpay-Nonce': nonce,
41+
'Wechatpay-Serial': 'BE2A2344B984167B',
42+
'Wechatpay-Timestamp': timestamp,
43+
'Wechatpay-Signature': Rsa.sign(Formatter.response(timestamp, nonce, msg), privateKey),
44+
'Content-Type': 'application/json',
45+
},
46+
];
47+
};
48+
const mockDownloadingBehavior = () => [
49+
function mockServerSideDownloadingBehavior(requestUri) {
50+
requestUri.should.be.String().and.match(/^\/v3\/merchant-service\/images\/(?!upload){6,}/);
51+
52+
basisServerSideBehavior(this.req.headers);
53+
54+
return Buffer.from('R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==', 'base64');
55+
},
56+
{
57+
'Content-Type': 'image/gif',
58+
},
59+
];
60+
61+
beforeEach(() => {
62+
instance = new Wechatpay({
63+
mchid: '101',
64+
serial: '898DBAD30F416EC7',
65+
privateKey,
66+
certs: { BE2A2344B984167B: mockPubkey },
67+
});
68+
69+
scope = nock('https://api.mch.weixin.qq.com/')
70+
.defaultReplyHeaders({
71+
server: 'Nginx',
72+
})
73+
.replyDate();
74+
});
75+
76+
describe('POST `v3/merchant-service/images/upload`', () => {
77+
it('POST the `v3/merchant-service/images/upload` should be a `200` response', async () => {
78+
scope.post((uri) => uri.startsWith('/v3/merchant-service/images/upload')).reply(200, ...mockUploadingBehavior());
79+
80+
const filename = 'README.md';
81+
const filepath = join(__dirname, '../fixtures/README.md');
82+
const sha256 = Hash.sha256(fs.readFileSync(filepath));
83+
84+
await instance.chain('v3/merchant-service/images/upload').post({
85+
meta: JSON.stringify({ filename, sha256 }),
86+
file: fs.createReadStream(filepath),
87+
}, {
88+
meta: { filename, sha256 },
89+
headers: { 'Content-Type': 'multipart/form-data' },
90+
}).then((res) => {
91+
res.should.be.an.Object().and.have.keys('headers', 'data');
92+
res.data.should.be.an.Object().and.have.keys('media_id');
93+
});
94+
});
95+
});
96+
97+
describe('GET `v3/merchant-service/images/{media_id}`', () => {
98+
it('GET the `v3/merchant-service/images/{media_id}` should be a `200` response', async () => {
99+
scope.get((uri) => uri.match(/^\/v3\/merchant-service\/images\/(?!upload){6,}/)).reply(200, ...mockDownloadingBehavior());
100+
101+
await instance.chain('v3/merchant-service/images/{media_id}').get({
102+
media_id: 'ChsyMDAyMDA1MjAyMTA3MjIxNzAwMDAxMzIwNzIYACD%2B9I6IBigBMAE4AQ==',
103+
}).then((res) => {
104+
res.should.be.an.Object().and.have.keys('headers', 'data');
105+
res.headers['content-type'].should.be.a.String().and.match(/image\/(?:png|gif|jpe?g)/);
106+
});
107+
});
108+
});
109+
});

0 commit comments

Comments
 (0)