diff --git a/lib/formatter.js b/lib/formatter.js index 2cf9dff..6df7b14 100644 --- a/lib/formatter.js +++ b/lib/formatter.js @@ -89,7 +89,7 @@ class Formatter { * @returns {string} - The content for `Rsa.sign` */ static request(method, uri, timestamp, nonce, body = '') { - return `${method}\n${uri}\n${timestamp}\n${nonce}\n${body}\n` + return this.joinedByLineFeed(method, uri, timestamp, nonce, body) } /** @@ -102,7 +102,40 @@ class Formatter { * @returns {string} - The content for `Rsa.verify` */ static response(timestamp, nonce, body = '') { - return `${timestamp}\n${nonce}\n${body}\n` + return this.joinedByLineFeed(timestamp, nonce, body) + } + + /** + * Joined this inputs by for `Line Feed`(LF) char. + * + * @param {string[]} pieces - The string(s) joined by line feed. + * + * @returns {string} - The joined string. + */ + static joinedByLineFeed(...pieces) { + return [...pieces, ``].join(`\n`) + } + + /** + * Sorts an Object by key. + * + * @param {object} thing - The input object. + * + * @returns {object} - The sorted object. + */ + static ksort(thing) { + return Object.keys(thing).sort().reduce((des, key) => (des[key] = thing[key], des), {}) + } + + /** + * Like `queryString` does but without the `sign` and `empty value` entities. + * + * @param {object} thing - The input object. + * + * @returns {string} - The sorted object. + */ + static queryStringLike(thing) { + return Object.entries(thing).filter(([k, v]) => k != `sign` && v !== void 0 && v !== ``).map(i => i.join(`=`)).join(`&`) } } diff --git a/lib/wechatpay.js b/lib/wechatpay.js index 7c11e28..371f8f2 100644 --- a/lib/wechatpay.js +++ b/lib/wechatpay.js @@ -1,6 +1,9 @@ const axios = require('axios') const interceptor = require('./interceptor') +/*eslint-disable-next-line*/ +const CLIENT = Symbol('CLIENT') + /** * A Wechatpay APIv3's amazing client. * @@ -46,14 +49,14 @@ class Wechatpay { /** * @property {AxiosInstance} client - The axios instance */ - static get client() { return Wechatpay[CLIENT] } - static set client(target) { Wechatpay[CLIENT] = target } + static get client() { return this[CLIENT] } + static set client(target) { this[CLIENT] = target } /** - * @property {RegExp} URI_ENTITY - The URI entity which's split by slash ask `uri_template` + * @property {RegExp} REGEXP_URI_ENTITY - The URI entity which's split by slash ask `uri_template` */ /*eslint-disable-next-line*/ - static get URI_ENTITY() { return /^\{([^\}]+)\}$/ } + static get REGEXP_URI_ENTITY() { return /^\{([^\}]+)\}$/ } /** * Compose the `URL`.pathname based on the container's entities @@ -76,13 +79,13 @@ class Wechatpay { // `camelCase` to `camel-case` .replace(/[A-Z]/g, w => `-${w.toLowerCase()}`) // `$dynamic_variable$` to `{dynamic_variable}` - .replace(/^\$/, `{`).replace(/\$$/, `}`) + .replace(/^\$(.*)\$$/, `{$1}`) } /** * @property {object} container - Client side the URIs' entity mapper */ - static get container() { return { + static get container() { const that = this; return { /** * @property {string[]} entities - The URI entities */ @@ -95,8 +98,8 @@ class Wechatpay { */ withEntities: function(list) { this.entities.forEach((one, index, src) => { - if (Wechatpay.URI_ENTITY.test(one)) { - const sign = one.replace(Wechatpay.URI_ENTITY, `$1`) + if (that.REGEXP_URI_ENTITY.test(one)) { + const sign = one.replace(that.REGEXP_URI_ENTITY, `$1`) src[index] = list[sign] ? list[sign] : one } }) @@ -110,7 +113,7 @@ class Wechatpay { * @returns {PromiseLike} - The `AxiosPromise` */ get: async function(...arg) { - return Wechatpay.client.get(Wechatpay.pathname(this.entities), ...arg) + return that.client.get(that.pathname(this.entities), ...arg) }, /** @@ -119,7 +122,7 @@ class Wechatpay { * @returns {PromiseLike} - The `AxiosPromise` */ post: async function(...arg) { - return Wechatpay.client.post(Wechatpay.pathname(this.entities), ...arg) + return that.client.post(that.pathname(this.entities), ...arg) }, /** @@ -128,7 +131,7 @@ class Wechatpay { * @returns {PromiseLike} - The `AxiosPromise` */ put: async function(...arg) { - return Wechatpay.client.put(Wechatpay.pathname(this.entities), ...arg) + return that.client.put(that.pathname(this.entities), ...arg) }, /** @@ -137,7 +140,7 @@ class Wechatpay { * @returns {PromiseLike} - The `AxiosPromise` */ patch: async function(...arg) { - return Wechatpay.client.patch(Wechatpay.pathname(this.entities), ...arg) + return that.client.patch(that.pathname(this.entities), ...arg) }, /** @@ -146,7 +149,7 @@ class Wechatpay { * @returns {PromiseLike} - The `AxiosPromise` */ delete: async function(...arg) { - return Wechatpay.client.delete(Wechatpay.pathname(this.entities), ...arg) + return that.client.delete(that.pathname(this.entities), ...arg) }, } } @@ -166,9 +169,9 @@ class Wechatpay { } if (!(property in target)) { /*eslint-disable-next-line*/ - target[property] = new Proxy({...Wechatpay.container}, Wechatpay.handler) + target[property] = new Proxy({...this.container}, this.handler) if (`entities` in target) { - target[property].entities = [...target.entities, Wechatpay.normalize(property)] + target[property].entities = [...target.entities, this.normalize(property)] } } @@ -184,15 +187,12 @@ class Wechatpay { * @returns {Proxy} - The magic APIv3 container */ constructor(wxpayConfig = {}, axiosConfig = {baseURL: 'https://api.mch.weixin.qq.com'}) { - Wechatpay.client = Wechatpay.client || interceptor(axios.create(axiosConfig), wxpayConfig) + this.constructor.client = this.constructor.client || interceptor(axios.create(axiosConfig), wxpayConfig) /*eslint-disable-next-line*/ - return new Proxy({...Wechatpay.container}, Wechatpay.handler) + return new Proxy({...this.constructor.container}, this.constructor.handler) } } -/*eslint-disable-next-line*/ -const CLIENT = Symbol('CLIENT') - module.exports = Wechatpay module.exports.default = Wechatpay diff --git a/tests/lib/formatter.test.js b/tests/lib/formatter.test.js index f151ab7..94d2927 100644 --- a/tests/lib/formatter.test.js +++ b/tests/lib/formatter.test.js @@ -219,4 +219,48 @@ describe('lib/formatter', () => { fmt.response(9,8).substr(-2).should.equal(`\n\n`) }) }) + + describe('Formatter::joinedByLineFeed', () => { + it('method `joinedByLineFeed` should be static', () => { + should(fmt.joinedByLineFeed).be.a.Function() + should((new fmt).joinedByLineFeed).is.Undefined() + }) + + it('method `joinedByLineFeed` should returns a string which is end with LF(ASCII 10)', () => { + fmt.joinedByLineFeed('').should.be.a.String().endWith(`\n`) + fmt.joinedByLineFeed('').substr(-1).charCodeAt().should.equal(10) + }) + + it('method `joinedByLineFeed(a,b,c,d,e,f,g)` should returns a string, and filled as `aLFbLFcLFdLFeLFfLFgLF`', () => { + fmt.joinedByLineFeed('a', 'b', 'c', 'd', 'e', 'f', 'g').should.be.a.String() + /*eslint-disable-next-line quotes*/ + .equal(`a\nb\nc\nd\ne\nf\ng\n`) + .and.startWith('a') + .and.endWith(`\n`) + }) + }) + + describe('Formatter::ksort', () => { + it('method `ksort` should be static', () => { + should(fmt.ksort).be.a.Function() + should((new fmt).ksort).is.Undefined() + }) + + it('method `ksort({b: 1, a: 0})` should returns an object and deepEqual to {a: 0, b: 1}', () => { + fmt.ksort({b: 1, a: 0}).should.be.an.instanceOf(Object).and.have.properties([`a`, `b`]) + fmt.ksort({b: 1, a: 0}).should.deepEqual({a: 0, b: 1}) + }) + }) + + describe('Formatter::queryStringLike', () => { + it('method `queryStringLike` should be static', () => { + should(fmt.queryStringLike).be.a.Function() + should((new fmt).queryStringLike).is.Undefined() + }) + + it('method `queryStringLike({appid: 3, mchid: 2, openid: \'\', sign: 0})` should returns a string and equal to `appid=3&mchid=2`', () => { + fmt.queryStringLike({appid: 3, mchid: 2, openid: '', sign: 0}).should.be.a.String() + fmt.queryStringLike({appid: 3, mchid: 2, openid: '', sign: 0}).should.equal(`appid=3&mchid=2`) + }) + }) }) diff --git a/tests/lib/rsa.test.js b/tests/lib/rsa.test.js index 6e6b85f..7ef5650 100644 --- a/tests/lib/rsa.test.js +++ b/tests/lib/rsa.test.js @@ -20,8 +20,6 @@ describe('lib/rsa', () => { rsa.encrypt() }).throw(TypeError, { code: 'ERR_INVALID_ARG_TYPE', - message: 'The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined', - stack: /at Function\.from \(buffer\.js/ }) }) @@ -30,8 +28,6 @@ describe('lib/rsa', () => { rsa.encrypt('') }).throw(TypeError, { code: 'ERR_INVALID_ARG_TYPE', - message: 'The "key" argument must be of type string or an instance of Buffer, TypedArray, DataView, or KeyObject. Received an instance of Object', - stack: /at prepareAsymmetricKey \(internal\/crypto\/keys\.js/ }) }) @@ -72,8 +68,6 @@ describe('lib/rsa', () => { rsa.decrypt() }).throw(TypeError, { code: 'ERR_INVALID_ARG_TYPE', - message: 'The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined', - stack: /at Function\.from \(buffer\.js/ }) }) @@ -82,8 +76,6 @@ describe('lib/rsa', () => { rsa.decrypt('') }).throw(TypeError, { code: 'ERR_INVALID_ARG_TYPE', - message: 'The "key" argument must be of type string or an instance of Buffer, TypedArray, DataView, or KeyObject. Received an instance of Object', - stack: /at prepareAsymmetricKey \(internal\/crypto\/keys\.js/ }) })