├── .eslintignore
├── .eslintrc
├── .github
└── workflows
│ ├── codeql-analysis.yml
│ └── node.js.yml
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── README.md
├── index.js
├── lib
├── helper.js
├── roa.js
├── rpc.d.ts
└── rpc.js
├── package.json
└── test
├── roa.integration.js
├── roa.test.js
├── rpc.integration.js
└── rpc.test.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.debug.js
2 | *.min.js
3 | node_modules/*
4 | assets/scripts/lib/*
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "indent": [2, 2],
4 | "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
5 | "linebreak-style": [2, "unix"],
6 | "semi": [2, "always"],
7 | "strict": [2, "global"],
8 | "curly": 2,
9 | "eqeqeq": 2,
10 | "no-eval": 2,
11 | "guard-for-in": 2,
12 | "no-caller": 2,
13 | "no-else-return": 2,
14 | "no-eq-null": 2,
15 | "no-extend-native": 2,
16 | "no-extra-bind": 2,
17 | "no-floating-decimal": 2,
18 | "no-implied-eval": 2,
19 | "no-labels": 2,
20 | "no-with": 2,
21 | "no-loop-func": 1,
22 | "no-native-reassign": 2,
23 | "no-redeclare": [2, {"builtinGlobals": true}],
24 | "no-delete-var": 2,
25 | "no-shadow-restricted-names": 2,
26 | "no-undef-init": 2,
27 | "no-use-before-define": 2,
28 | "no-unused-vars": [2, {"args": "none"}],
29 | "no-undef": 2,
30 | "callback-return": [2, ["callback", "cb", "next"]],
31 | "global-require": 0,
32 | "no-console": 0,
33 | "generator-star-spacing": ["error", "after"],
34 | "require-yield": 0
35 | },
36 | "env": {
37 | "es6": true,
38 | "node": true,
39 | "browser": true
40 | },
41 | "globals": {
42 | "describe": true,
43 | "it": true,
44 | "before": true,
45 | "after": true
46 | },
47 | "parserOptions": {
48 | "ecmaVersion": 2018,
49 | "sourceType": "script",
50 | "ecmaFeatures": {
51 | "jsx": true
52 | }
53 | },
54 | "extends": "eslint:recommended"
55 | }
56 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 | schedule:
15 | - cron: '0 13 * * 2'
16 |
17 | jobs:
18 | analyze:
19 | name: Analyze
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | # Override automatic language detection by changing the below list
26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
27 | language: ['javascript']
28 | # Learn more...
29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v2
34 |
35 | # Initializes the CodeQL tools for scanning.
36 | - name: Initialize CodeQL
37 | uses: github/codeql-action/init@v1
38 | with:
39 | languages: ${{ matrix.language }}
40 | # If you wish to specify custom queries, you can do so here or in a config file.
41 | # By default, queries listed here will override any specified in a config file.
42 | # Prefix the list here with "+" to use these queries and those in the config file.
43 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
44 |
45 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
46 | # If this step fails, then you should remove it and run the build manually (see below)
47 | - name: Autobuild
48 | uses: github/codeql-action/autobuild@v1
49 |
50 | # ℹ️ Command-line programs to run using the OS shell.
51 | # 📚 https://git.io/JvXDl
52 |
53 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
54 | # and modify them (or add more) to build your code if your project
55 | # uses a compiled language
56 |
57 | #- run: |
58 | # make bootstrap
59 | # make release
60 |
61 | - name: Perform CodeQL Analysis
62 | uses: github/codeql-action/analyze@v1
63 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master, v1 ]
9 | pull_request:
10 | branches: [ master, v1 ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | node-version: [8.x, 10.x, 12.x, 14.x, 16.x, 18.x, 20.x, 22.x]
21 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Use Node.js ${{ matrix.node-version }}
26 | uses: actions/setup-node@v3
27 | with:
28 | node-version: ${{ matrix.node-version }}
29 | - run: npm install
30 | - run: npm run ci
31 |
32 | - name: Upload Coverage Report
33 | uses: codecov/codecov-action@v4
34 | with:
35 | token: ${{ secrets.CODECOV_TOKEN }} # required
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | coverage
3 | test/config.js
4 | .idea/
5 | demo.js
6 | .nyc_output/
7 | .DS_Store
8 | .env
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "12"
5 | - "10"
6 | - "8"
7 |
8 | branches:
9 | only:
10 | - master
11 |
12 | script:
13 | - npm run ci
14 | - test -z $ACCESS_KEY_ID -a -z $ACCESS_KEY_SECRET || npm run test-integration
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.8.0 (2024-12-09)
2 |
3 | Feature:
4 |
5 | - 凭证支持接入带getCredentials()接口的CredentialsProvider
6 |
7 | ## 1.0.2 (2017-01-16)
8 |
9 | Feature:
10 |
11 | - 返回出错的结果,挂载到返回error的data对象上,err.data = resData
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # @alicloud/pop-core
2 |
3 | The core SDK of POP API.
4 |
5 | [![NPM version][npm-image]][npm-url]
6 | [![build status][travis-image]][travis-url]
7 | [![codecov][cov-image]][cov-url]
8 |
9 | [npm-image]: https://img.shields.io/npm/v/@alicloud/pop-core.svg?style=flat-square
10 | [npm-url]: https://npmjs.org/package/@alicloud/pop-core
11 | [travis-image]: https://img.shields.io/travis/aliyun/openapi-core-nodejs-sdk/master.svg?style=flat-square
12 | [travis-url]: https://travis-ci.org/aliyun/openapi-core-nodejs-sdk
13 | [cov-image]: https://codecov.io/gh/aliyun/openapi-core-nodejs-sdk/branch/master/graph/badge.svg
14 | [cov-url]: https://codecov.io/gh/aliyun/openapi-core-nodejs-sdk
15 |
16 | The Alibaba Cloud V1.0 SDK will soon enter the Basic Security Maintenance phase and is no longer recommended for use. It is suggested to use the V2.0 SDK instead.
17 |
18 | ## Installation
19 |
20 | Install it and write into package.json dependences.
21 |
22 | ```sh
23 | $ npm install @alicloud/pop-core -S
24 | ```
25 |
26 | ## Prerequisite
27 |
28 | Node.js >= 8.x
29 |
30 | ### Notes
31 |
32 | You must know your `AK`(`accessKeyId/accessKeySecret`), and the cloud product's `endpoint` and `apiVersion`.
33 |
34 | For example, The ECS OpenAPI(https://help.aliyun.com/document_detail/25490.html), the API version is `2014-05-26`.
35 |
36 | And the endpoint list can be found at [here](https://help.aliyun.com/document_detail/25489.html), the center endpoint is ecs.aliyuncs.com. Add http protocol `http` or `https`, should be `http://ecs.aliyuncs.com/`.
37 |
38 | ## Important Updates
39 |
40 | - Starting from version 1.8.0, CredentialsProvider is supported, and the signature logic of internal functions is updated. Developers who change the internal logic for calling need to pay attention to these changes.
41 |
42 | ## Online Demo
43 |
44 | **[API Developer Portal](https://api.aliyun.com)** provides the ability to call the cloud product OpenAPI online, and dynamically generate SDK Example code and quick retrieval interface, which can significantly reduce the difficulty of using the cloud API. **It is highly recommended**.
45 |
46 |
47 |
48 |
49 |
50 |
51 | ## Usage
52 |
53 | The RPC style client:
54 |
55 | ```js
56 | var RPCClient = require('@alicloud/pop-core').RPCClient;
57 |
58 | var client = new RPCClient({
59 | accessKeyId: '',
60 | accessKeySecret: '',
61 | endpoint: '',
62 | apiVersion: ''
63 | });
64 |
65 | // => returns Promise
66 | client.request(action, params);
67 | // co/yield, async/await
68 |
69 | // options
70 | client.request(action, params, {
71 | timeout: 3000, // default 3000 ms
72 | formatAction: true, // default true, format the action to Action
73 | formatParams: true, // default true, format the parameter name to first letter upper case
74 | method: 'GET', // set the http method, default is GET
75 | headers: {}, // set the http request headers
76 | });
77 | ```
78 |
79 | The ROA style client:
80 |
81 | ```js
82 | var ROAClient = require('@alicloud/pop-core').ROAClient;
83 |
84 | var client = new ROAClient({
85 | accessKeyId: '',
86 | accessKeySecret: '',
87 | endpoint: '',
88 | apiVersion: ''
89 | });
90 |
91 | // => returns Promise
92 | // request(HTTPMethod, uriPath, queries, body, headers, options);
93 | // options => {timeout}
94 | client.request('GET', '/regions');
95 | // co/yield, async/await
96 | ```
97 |
98 | ### Custom opts
99 |
100 | We offer two ways to customize request opts.
101 |
102 | One way is passing opts through the Client constructor. You should treat opts passed through the constructor as default custom opts, because all requests will use this opts.
103 |
104 | ```js
105 | var client = new RPCClient({
106 | accessKeyId: '',
107 | accessKeySecret: '',
108 | endpoint: '',
109 | apiVersion: '',
110 | opts: {
111 | timeout: 3000
112 | }
113 | });
114 | ```
115 |
116 | Another way is passing opts through the function's parameter. You should use this way when you want to just pass opts in specific functions.
117 |
118 | ```js
119 | client.request(action, params, {
120 | timeout: 3000
121 | });
122 | ```
123 |
124 | When both ways are used, opts will be merged. But for the opt with the same key, the opts provided by the function parameter overrides the opts provided by the constructor.
125 |
126 | ### Http Proxy Support
127 |
128 | ```js
129 | var tunnel = require('tunnel-agent');
130 |
131 | var RPCClient = require('@alicloud/pop-core').RPCClient;
132 |
133 | var client = new RPCClient({
134 | accessKeyId: '',
135 | accessKeySecret: '',
136 | endpoint: '',
137 | apiVersion: ''
138 | });
139 |
140 |
141 | client.request(action, params, {
142 | agent: tunnel.httpOverHttp({
143 | proxy: {
144 | host: 'host',
145 | port: port
146 | }
147 | });
148 | });
149 | ```
150 |
151 | ## License
152 | The MIT License
153 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = require('./lib/rpc');
4 | module.exports.ROAClient = require('./lib/roa');
5 | module.exports.RPCClient = require('./lib/rpc');
6 |
--------------------------------------------------------------------------------
/lib/helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const os = require('os');
4 |
5 | const pkg = require('../package.json');
6 |
7 | exports.DEFAULT_UA = `AlibabaCloud (${os.platform()}; ${os.arch()}) ` +
8 | `Node.js/${process.version} Core/${pkg.version}`;
9 | exports.DEFAULT_CLIENT = `Node.js(${process.version}), ${pkg.name}: ${pkg.version}`;
10 |
--------------------------------------------------------------------------------
/lib/roa.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 | const url = require('url');
5 | const querystring = require('querystring');
6 |
7 | const kitx = require('kitx');
8 | const httpx = require('httpx');
9 | const xml2js = require('xml2js');
10 | const JSON = require('json-bigint');
11 | const debug = require('debug')('roa');
12 |
13 | const helper = require('./helper');
14 |
15 | function filter(value) {
16 | return value.replace(/[\t\n\r\f]/g, ' ');
17 | }
18 |
19 | function keyLowerify(headers) {
20 | const keys = Object.keys(headers);
21 | const newHeaders = {};
22 | for (let i = 0; i < keys.length; i++) {
23 | const key = keys[i];
24 | newHeaders[key.toLowerCase()] = headers[key];
25 | }
26 | return newHeaders;
27 | }
28 |
29 | function getCanonicalizedHeaders(headers) {
30 | const prefix = 'x-acs-';
31 | const keys = Object.keys(headers);
32 |
33 | const canonicalizedKeys = [];
34 | for (let i = 0; i < keys.length; i++) {
35 | const key = keys[i];
36 | if (key.startsWith(prefix)) {
37 | canonicalizedKeys.push(key);
38 | }
39 | }
40 |
41 | canonicalizedKeys.sort();
42 |
43 | var result = '';
44 | for (let i = 0; i < canonicalizedKeys.length; i++) {
45 | const key = canonicalizedKeys[i];
46 | result += `${key}:${filter(headers[key]).trim()}\n`;
47 | }
48 |
49 | return result;
50 | }
51 |
52 | function getCanonicalizedResource(uriPattern, query) {
53 | const keys = Object.keys(query).sort();
54 |
55 | if (keys.length === 0) {
56 | return uriPattern;
57 | }
58 |
59 | var result = [];
60 | for (var i = 0; i < keys.length; i++) {
61 | const key = keys[i];
62 | result.push(`${key}=${query[key]}`);
63 | }
64 |
65 | return `${uriPattern}?${result.join('&')}`;
66 | }
67 |
68 | function buildStringToSign(method, uriPattern, headers, query) {
69 | const accept = headers['accept'];
70 | const contentMD5 = headers['content-md5'] || '';
71 | const contentType = headers['content-type'] || '';
72 | const date = headers['date'] || '';
73 |
74 | const header = `${method}\n${accept}\n${contentMD5}\n${contentType}\n${date}\n`;
75 |
76 | const canonicalizedHeaders = getCanonicalizedHeaders(headers);
77 | const canonicalizedResource = getCanonicalizedResource(uriPattern, query);
78 |
79 | return `${header}${canonicalizedHeaders}${canonicalizedResource}`;
80 | }
81 |
82 | function parseXML(xml) {
83 | const parser = new xml2js.Parser({
84 | // explicitArray: false
85 | });
86 | return new Promise((resolve, reject) => {
87 | parser.parseString(xml, (err, result) => {
88 | if (err) {
89 | return reject(err);
90 | }
91 |
92 | resolve(result);
93 | });
94 | });
95 | }
96 |
97 | class ACSError extends Error {
98 | constructor(err) {
99 | const message = err.Message[0];
100 | const code = err.Code[0];
101 | const hostid = err.HostId[0];
102 | const requestid = err.RequestId[0];
103 | super(`${message} hostid: ${hostid}, requestid: ${requestid}`);
104 | this.code = code;
105 | }
106 | }
107 |
108 | class ROAClient {
109 | constructor(config) {
110 | assert(config, 'must pass "config"');
111 | assert(config.endpoint, 'must pass "config.endpoint"');
112 | if (!config.endpoint.startsWith('https://') &&
113 | !config.endpoint.startsWith('http://')) {
114 | throw new Error(`"config.endpoint" must starts with 'https://' or 'http://'.`);
115 | }
116 | assert(config.apiVersion, 'must pass "config.apiVersion"');
117 | if (config.credentialsProvider) {
118 | if (typeof config.credentialsProvider.getCredentials !== 'function') {
119 | throw new Error(`must pass "config.credentialsProvider" with function "getCredentials()"`);
120 | }
121 | this.credentialsProvider = config.credentialsProvider;
122 | } else {
123 | assert(config.accessKeyId, 'must pass "config.accessKeyId"');
124 | assert(config.accessKeySecret, 'must pass "config.accessKeySecret"');
125 | this.accessKeyId = config.accessKeyId;
126 | this.accessKeySecret = config.accessKeySecret;
127 | this.securityToken = config.securityToken;
128 | this.credentialsProvider = {
129 | getCredentials: async () => {
130 | return {
131 | accessKeyId: config.accessKeyId,
132 | accessKeySecret: config.accessKeySecret,
133 | securityToken: config.securityToken,
134 | };
135 | }
136 | };
137 | }
138 |
139 | this.endpoint = config.endpoint;
140 | this.apiVersion = config.apiVersion;
141 | this.host = url.parse(this.endpoint).hostname;
142 | this.opts = config.opts;
143 | var httpModule = this.endpoint.startsWith('https://') ? require('https') : require('http');
144 | this.keepAliveAgent = new httpModule.Agent({
145 | keepAlive: true,
146 | keepAliveMsecs: 3000
147 | });
148 | }
149 |
150 | buildHeaders() {
151 | const now = new Date();
152 | var defaultHeaders = {
153 | accept: 'application/json',
154 | date: now.toGMTString(),
155 | host: this.host,
156 | 'x-acs-signature-nonce': kitx.makeNonce(),
157 | 'x-acs-signature-method': 'HMAC-SHA1',
158 | 'x-acs-signature-version': '1.0',
159 | 'x-acs-version': this.apiVersion,
160 | 'user-agent': helper.DEFAULT_UA,
161 | 'x-sdk-client': helper.DEFAULT_CLIENT
162 | };
163 | if (this.securityToken) {
164 | defaultHeaders['x-acs-accesskey-id'] = this.accessKeyId;
165 | defaultHeaders['x-acs-security-token'] = this.securityToken;
166 | }
167 | return defaultHeaders;
168 | }
169 |
170 | signature(stringToSign) {
171 | const utf8Buff = Buffer.from(stringToSign, 'utf8');
172 |
173 | return kitx.sha1(utf8Buff, this.accessKeySecret, 'base64');
174 | }
175 |
176 | buildAuthorization(stringToSign) {
177 | return `acs ${this.accessKeyId}:${this.signature(stringToSign)}`;
178 | }
179 |
180 | async request(method, uriPattern, query = {}, body = '', headers = {}, opts = {}) {
181 | const credentials = await this.credentialsProvider.getCredentials();
182 |
183 | const now = new Date();
184 | var defaultHeaders = {
185 | accept: 'application/json',
186 | date: now.toGMTString(),
187 | host: this.host,
188 | 'x-acs-signature-nonce': kitx.makeNonce(),
189 | 'x-acs-version': this.apiVersion,
190 | 'user-agent': helper.DEFAULT_UA,
191 | 'x-sdk-client': helper.DEFAULT_CLIENT
192 | };
193 | if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
194 | defaultHeaders['x-acs-signature-method'] = 'HMAC-SHA1';
195 | defaultHeaders['x-acs-signature-version'] = '1.0';
196 | if (credentials.securityToken) {
197 | defaultHeaders['x-acs-accesskey-id'] = credentials.accessKeyId;
198 | defaultHeaders['x-acs-security-token'] = credentials.securityToken;
199 | }
200 | }
201 |
202 | var mixHeaders = Object.assign(defaultHeaders, keyLowerify(headers));
203 |
204 | var postBody = null;
205 |
206 | postBody = Buffer.from(body, 'utf8');
207 | mixHeaders['content-md5'] = kitx.md5(postBody, 'base64');
208 | mixHeaders['content-length'] = postBody.length;
209 |
210 | var url = `${this.endpoint}${uriPattern}`;
211 | if (Object.keys(query).length) {
212 | url += `?${querystring.stringify(query)}`;
213 | }
214 |
215 | if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
216 | const stringToSign = buildStringToSign(method, uriPattern, mixHeaders, query);
217 | debug('stringToSign: %s', stringToSign);
218 | const utf8Buff = Buffer.from(stringToSign, 'utf8');
219 | const signature = kitx.sha1(utf8Buff, credentials.accessKeySecret, 'base64');
220 | mixHeaders['authorization'] = `acs ${credentials.accessKeyId}:${signature}`;
221 | }
222 |
223 | const options = Object.assign({
224 | method,
225 | agent: this.keepAliveAgent,
226 | headers: mixHeaders,
227 | data: postBody
228 | }, this.opts, opts);
229 |
230 | const response = await httpx.request(url, options);
231 | const responseBody = await httpx.read(response, 'utf8');
232 |
233 | // Retrun raw body
234 | if (opts.rawBody) {
235 | return responseBody;
236 | }
237 |
238 | const contentType = response.headers['content-type'] || '';
239 | // JSON
240 | if (contentType.startsWith('application/json')) {
241 | const statusCode = response.statusCode;
242 | if (statusCode === 204) {
243 | return responseBody;
244 | }
245 |
246 | var result;
247 | try {
248 | result = JSON.parse(responseBody);
249 | } catch (err) {
250 | err.name = 'FormatError';
251 | err.message = 'parse response to json error';
252 | err.body = responseBody;
253 | throw err;
254 | }
255 |
256 | if (statusCode >= 400) {
257 | const errorMessage = result.Message || result.errorMsg || '';
258 | const errorCode = result.Code || result.errorCode || '';
259 | const requestId = result.RequestId || '';
260 | var err = new Error(`code: ${statusCode}, ${errorMessage}, requestid: ${requestId}`);
261 | err.name = `${errorCode}Error`;
262 | err.statusCode = statusCode;
263 | err.result = result;
264 | err.code = errorCode;
265 | throw err;
266 | }
267 |
268 | return result;
269 | }
270 |
271 | if (contentType.startsWith('text/xml')) {
272 | const result = await parseXML(responseBody);
273 | if (result.Error) {
274 | throw new ACSError(result.Error);
275 | }
276 |
277 | return result;
278 | }
279 |
280 | return responseBody;
281 | }
282 |
283 | put(path, query, body, headers, options) {
284 | return this.request('PUT', path, query, body, headers, options);
285 | }
286 |
287 | post(path, query, body, headers, options) {
288 | return this.request('POST', path, query, body, headers, options);
289 | }
290 |
291 | get(path, query, headers, options) {
292 | return this.request('GET', path, query, '', headers, options);
293 | }
294 |
295 | delete(path, query, headers, options) {
296 | return this.request('DELETE', path, query, '', headers, options);
297 | }
298 | }
299 |
300 | module.exports = ROAClient;
301 |
--------------------------------------------------------------------------------
/lib/rpc.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for [~THE LIBRARY NAME~] [~OPTIONAL VERSION NUMBER~]
2 | // Project: [~THE PROJECT NAME~]
3 | // Definitions by: [~YOUR NAME~] <[~A URL FOR YOU~]>
4 |
5 | /*~ This is the module template file for class modules.
6 | *~ You should rename it to index.d.ts and place it in a folder with the same name as the module.
7 | *~ For example, if you were writing a file for "super-greeter", this
8 | *~ file should be 'super-greeter/index.d.ts'
9 | */
10 |
11 | /*~ Note that ES6 modules cannot directly export class objects.
12 | *~ This file should be imported using the CommonJS-style:
13 | *~ import x = require('someLibrary');
14 | *~
15 | *~ Refer to the documentation to understand common
16 | *~ workarounds for this limitation of ES6 modules.
17 | */
18 |
19 |
20 | /*~ This declaration specifies that the class constructor function
21 | *~ is the exported object from the file
22 | */
23 | export = RPCClient;
24 |
25 | /*~ Write your module's methods and properties in this class */
26 | declare class RPCClient {
27 | constructor(config: RPCClient.Config);
28 |
29 | request(action: String, params: Object, options?: Object): Promise;
30 | }
31 |
32 | /*~ If you want to expose types from your module as well, you can
33 | *~ place them in this block.
34 | */
35 | declare namespace RPCClient {
36 | export interface Config {
37 | endpoint: string;
38 | apiVersion: string;
39 | accessKeyId: string;
40 | accessKeySecret: string;
41 | securityToken?: string;
42 | codes?: (string | number)[];
43 | opts?: object;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/lib/rpc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const assert = require('assert');
4 |
5 | const httpx = require('httpx');
6 | const kitx = require('kitx');
7 | const JSON = require('json-bigint');
8 |
9 | const helper = require('./helper');
10 |
11 | function firstLetterUpper(str) {
12 | return str.slice(0, 1).toUpperCase() + str.slice(1);
13 | }
14 |
15 | function formatParams(params) {
16 | var keys = Object.keys(params);
17 | var newParams = {};
18 | for (var i = 0; i < keys.length; i++) {
19 | var key = keys[i];
20 | newParams[firstLetterUpper(key)] = params[key];
21 | }
22 | return newParams;
23 | }
24 |
25 |
26 | function timestamp() {
27 | var date = new Date();
28 | var YYYY = date.getUTCFullYear();
29 | var MM = kitx.pad2(date.getUTCMonth() + 1);
30 | var DD = kitx.pad2(date.getUTCDate());
31 | var HH = kitx.pad2(date.getUTCHours());
32 | var mm = kitx.pad2(date.getUTCMinutes());
33 | var ss = kitx.pad2(date.getUTCSeconds());
34 | // 删除掉毫秒部分
35 | return `${YYYY}-${MM}-${DD}T${HH}:${mm}:${ss}Z`;
36 | }
37 |
38 | function encode(str) {
39 | var result = encodeURIComponent(str);
40 |
41 | return result.replace(/!/g, '%21')
42 | .replace(/'/g, '%27')
43 | .replace(/\(/g, '%28')
44 | .replace(/\)/g, '%29')
45 | .replace(/\*/g, '%2A');
46 | }
47 |
48 | function replaceRepeatList(target, key, repeat) {
49 | for (var i = 0; i < repeat.length; i++) {
50 | var item = repeat[i];
51 |
52 | if (Array.isArray(item)) {
53 | replaceRepeatList(target, `${key}.${i + 1}`, item);
54 | } else if (item && typeof item === 'object') {
55 | const keys = Object.keys(item);
56 | for (var j = 0; j < keys.length; j++) {
57 | if (Array.isArray(item[keys[j]])) {
58 | replaceRepeatList(target, `${key}.${i + 1}.${keys[j]}`, item[keys[j]]);
59 | } else {
60 | target[`${key}.${i + 1}.${keys[j]}`] = item[keys[j]];
61 | }
62 | }
63 | } else {
64 | target[`${key}.${i + 1}`] = item;
65 | }
66 | }
67 | }
68 |
69 | function flatParams(params) {
70 | var target = {};
71 | var keys = Object.keys(params);
72 | for (let i = 0; i < keys.length; i++) {
73 | var key = keys[i];
74 | var value = params[key];
75 | if (Array.isArray(value)) {
76 | replaceRepeatList(target, key, value);
77 | } else {
78 | target[key] = value;
79 | }
80 | }
81 | return target;
82 | }
83 |
84 | function normalize(params) {
85 | var list = [];
86 | var flated = flatParams(params);
87 | var keys = Object.keys(flated).sort();
88 | for (let i = 0; i < keys.length; i++) {
89 | var key = keys[i];
90 | var value = flated[key];
91 | list.push([encode(key), encode(value)]); //push []
92 | }
93 | return list;
94 | }
95 |
96 | function canonicalize(normalized) {
97 | var fields = [];
98 | for (var i = 0; i < normalized.length; i++) {
99 | var [key, value] = normalized[i];
100 | fields.push(key + '=' + value);
101 | }
102 | return fields.join('&');
103 | }
104 |
105 | class RPCClient {
106 | constructor(config, verbose) {
107 | assert(config, 'must pass "config"');
108 | assert(config.endpoint, 'must pass "config.endpoint"');
109 | if (!config.endpoint.startsWith('https://') &&
110 | !config.endpoint.startsWith('http://')) {
111 | throw new Error(`"config.endpoint" must starts with 'https://' or 'http://'.`);
112 | }
113 | assert(config.apiVersion, 'must pass "config.apiVersion"');
114 | if (config.credentialsProvider) {
115 | if (typeof config.credentialsProvider.getCredentials !== 'function') {
116 | throw new Error(`must pass "config.credentialsProvider" with function "getCredentials()"`);
117 | }
118 | this.credentialsProvider = config.credentialsProvider;
119 | } else {
120 | assert(config.accessKeyId, 'must pass "config.accessKeyId"');
121 | var accessKeySecret = config.secretAccessKey || config.accessKeySecret;
122 | assert(accessKeySecret, 'must pass "config.accessKeySecret"');
123 | this.accessKeyId = config.accessKeyId;
124 | this.accessKeySecret = accessKeySecret;
125 | this.securityToken = config.securityToken;
126 | this.credentialsProvider = {
127 | getCredentials: async () => {
128 | return {
129 | accessKeyId: config.accessKeyId,
130 | accessKeySecret: accessKeySecret,
131 | securityToken: config.securityToken,
132 | };
133 | }
134 | };
135 | }
136 |
137 |
138 | if (config.endpoint.endsWith('/')) {
139 | config.endpoint = config.endpoint.slice(0, -1);
140 | }
141 |
142 | this.endpoint = config.endpoint;
143 | this.apiVersion = config.apiVersion;
144 | this.verbose = verbose === true;
145 | // 非 codes 里的值,将抛出异常
146 | this.codes = new Set([200, '200', 'OK', 'Success', 'success']);
147 | if (config.codes) {
148 | // 合并 codes
149 | for (var elem of config.codes) {
150 | this.codes.add(elem);
151 | }
152 | }
153 |
154 | this.opts = config.opts || {};
155 |
156 | var httpModule = this.endpoint.startsWith('https://')
157 | ? require('https') : require('http');
158 | this.keepAliveAgent = new httpModule.Agent({
159 | keepAlive: true,
160 | keepAliveMsecs: 3000
161 | });
162 | }
163 |
164 | async request(action, params = {}, opts = {}) {
165 | const credentials = await this.credentialsProvider.getCredentials();
166 | // 1. compose params and opts
167 | opts = Object.assign({
168 | headers: {
169 | 'x-sdk-client': helper.DEFAULT_CLIENT,
170 | 'user-agent': helper.DEFAULT_UA,
171 | 'x-acs-action': action,
172 | 'x-acs-version': this.apiVersion
173 | }
174 | }, this.opts, opts);
175 |
176 | // format action until formatAction is false
177 | if (opts.formatAction !== false) {
178 | action = firstLetterUpper(action);
179 | }
180 |
181 | // format params until formatParams is false
182 | if (opts.formatParams !== false) {
183 | params = formatParams(params);
184 | }
185 | const defaultParams = {
186 | Format: 'JSON',
187 | Timestamp: timestamp(),
188 | Version: this.apiVersion,
189 | };
190 | if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
191 | defaultParams.SignatureMethod = 'HMAC-SHA1';
192 | defaultParams.SignatureVersion = '1.0';
193 | defaultParams.SignatureNonce = kitx.makeNonce();
194 | defaultParams.AccessKeyId = credentials.accessKeyId;
195 | if (credentials.securityToken) {
196 | defaultParams.SecurityToken = credentials.securityToken;
197 | }
198 | }
199 | params = Object.assign({ Action: action }, defaultParams, params);
200 |
201 | const method = (opts.method || 'GET').toUpperCase();
202 | const normalized = normalize(params);
203 | // 2. caculate signature
204 | if (credentials && credentials.accessKeyId && credentials.accessKeySecret) {
205 | const canonicalized = canonicalize(normalized);
206 | // 2.1 get string to sign
207 | const stringToSign = `${method}&${encode('/')}&${encode(canonicalized)}`;
208 | // 2.2 get signature
209 | const key = credentials.accessKeySecret + '&';
210 | const signature = kitx.sha1(stringToSign, key, 'base64');
211 | // add signature
212 | normalized.push(['Signature', encode(signature)]);
213 | }
214 |
215 | // 3. generate final url
216 | const url = opts.method === 'POST' ? `${this.endpoint}/` : `${this.endpoint}/?${canonicalize(normalized)}`;
217 | // 4. send request
218 | const entry = {
219 | url: url,
220 | request: null,
221 | response: null
222 | };
223 |
224 | if (opts && !opts.agent) {
225 | opts.agent = this.keepAliveAgent;
226 | }
227 |
228 | if (opts.method === 'POST') {
229 | opts.headers = opts.headers || {};
230 | opts.headers['content-type'] = 'application/x-www-form-urlencoded';
231 | opts.data = canonicalize(normalized);
232 | }
233 |
234 | const response = await httpx.request(url, opts);
235 | entry.request = {
236 | headers: response.req.getHeaders ? response.req.getHeaders() : response.req._headers
237 | };
238 | entry.response = {
239 | statusCode: response.statusCode,
240 | headers: response.headers
241 | };
242 |
243 | const buffer = await httpx.read(response);
244 | const json = JSON.parse(buffer);
245 | if (json.Code && !this.codes.has(json.Code)) {
246 | const err = new Error(`${json.Message}, URL: ${url}`);
247 | err.name = json.Code + 'Error';
248 | err.data = json;
249 | err.code = json.Code;
250 | err.url = url;
251 | err.entry = entry;
252 | throw err;
253 | }
254 |
255 | if (this.verbose) {
256 | return [json, entry];
257 | }
258 |
259 | return json;
260 | }
261 |
262 | _buildParams() {
263 | const defaultParams = {
264 | Format: 'JSON',
265 | SignatureMethod: 'HMAC-SHA1',
266 | SignatureNonce: kitx.makeNonce(),
267 | SignatureVersion: '1.0',
268 | Timestamp: timestamp(),
269 | AccessKeyId: this.accessKeyId,
270 | Version: this.apiVersion,
271 | };
272 | if (this.securityToken) {
273 | defaultParams.SecurityToken = this.securityToken;
274 | }
275 | return defaultParams;
276 | }
277 | }
278 |
279 | module.exports = RPCClient;
280 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alicloud/pop-core",
3 | "version": "1.8.0",
4 | "description": "AliCloud POP SDK core",
5 | "main": "index.js",
6 | "types": "lib/rpc.d.ts",
7 | "scripts": {
8 | "lint": "eslint --fix lib test",
9 | "test": "mocha -R spec test/*.test.js",
10 | "test-cov": "nyc -r=html -r=text -r=lcov mocha -t 3000 -R spec test/*.test.js",
11 | "test-integration": "mocha -R spec test/*.integration.js",
12 | "ci": "npm run lint && npm run test-cov"
13 | },
14 | "keywords": [
15 | "Aliyun",
16 | "AliCloud",
17 | "OpenAPI",
18 | "POP",
19 | "SDK",
20 | "Core"
21 | ],
22 | "author": "Jackson Tian",
23 | "license": "MIT",
24 | "dependencies": {
25 | "debug": "^3.1.0",
26 | "httpx": "^2.1.2",
27 | "json-bigint": "^1.0.0",
28 | "kitx": "^1.2.1",
29 | "xml2js": "^0.5.0"
30 | },
31 | "files": [
32 | "lib",
33 | "index.js"
34 | ],
35 | "devDependencies": {
36 | "eslint": "^6.6.0",
37 | "expect.js": "^0.3.1",
38 | "mocha": "^4",
39 | "muk": "^0.5.3",
40 | "nyc": "^12.0.2",
41 | "rewire": "^4.0.1"
42 | },
43 | "directories": {
44 | "test": "test"
45 | },
46 | "engines": {
47 | "node": ">=8.0.0"
48 | },
49 | "repository": {
50 | "type": "git",
51 | "url": "git+https://github.com/aliyun/openapi-core-nodejs-sdk.git"
52 | },
53 | "bugs": {
54 | "url": "https://github.com/aliyun/openapi-core-nodejs-sdk/issues"
55 | },
56 | "homepage": "https://github.com/aliyun/openapi-core-nodejs-sdk#readme"
57 | }
58 |
--------------------------------------------------------------------------------
/test/roa.integration.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('expect.js');
4 |
5 | const ROAClient = require('../lib/roa');
6 |
7 | describe('roa request', function () {
8 | var client = new ROAClient({
9 | accessKeyId: process.env.ACCESS_KEY_ID,
10 | accessKeySecret: process.env.ACCESS_KEY_SECRET,
11 | endpoint: 'http://ros.aliyuncs.com',
12 | apiVersion: '2015-09-01'
13 | });
14 |
15 | it('request', async function () {
16 | this.timeout(15000);
17 | var result = await client.request('GET', '/regions', {}, '', {}, {
18 | timeout: 15000
19 | });
20 | expect(result).to.have.property('Regions');
21 | });
22 |
23 | it('get should ok', async function () {
24 | this.timeout(10000);
25 | var result = await client.get('/regions', {}, {}, {
26 | timeout: 10000
27 | });
28 | expect(result).to.have.property('Regions');
29 | });
30 |
31 | it('get raw body should ok', async function () {
32 | this.timeout(10000);
33 | var opts = {
34 | rawBody: true,
35 | timeout: 10000
36 | };
37 | var result = await client.get('/regions', {}, {}, opts);
38 | expect(result).to.be.a('string');
39 | });
40 | });
41 |
42 | describe('nlp', function () {
43 | var client = new ROAClient({
44 | accessKeyId: process.env.ACCESS_KEY_ID,
45 | accessKeySecret: process.env.ACCESS_KEY_SECRET,
46 | endpoint: 'http://nlp.cn-shanghai.aliyuncs.com',
47 | apiVersion: '2018-04-08',
48 | });
49 |
50 | it('translate should ok', async function () {
51 | const params = {
52 | q: '你好',
53 | source: 'zh',
54 | target: 'en',
55 | format: 'text',
56 | };
57 |
58 | const res = await client.request(
59 | 'POST',
60 | '/nlp/api/translate/standard',
61 | {},
62 | JSON.stringify(params),
63 | { 'Content-Type': 'application/json' }
64 | );
65 |
66 | expect(res).to.be.ok();
67 | expect(res.data).to.be.ok();
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/test/roa.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('expect.js');
4 | const rewire = require('rewire');
5 | const httpx = require('httpx');
6 | const muk = require('muk');
7 |
8 | const ROAClient = require('../lib/roa');
9 |
10 | describe('roa core', function () {
11 | describe('ROAClient', function () {
12 | it('should pass into "config"', function () {
13 | expect(function () {
14 | new ROAClient();
15 | }).to.throwException(/must pass "config"/);
16 | });
17 |
18 | it('should pass into "config.endpoint"', function () {
19 | expect(function () {
20 | new ROAClient({});
21 | }).to.throwException(/must pass "config\.endpoint"/);
22 | });
23 |
24 | it('should pass into valid "config.endpoint"', function () {
25 | expect(function () {
26 | new ROAClient({
27 | endpoint: 'ecs.aliyuncs.com/'
28 | });
29 | }).to.throwException(/"config\.endpoint" must starts with 'https:\/\/' or 'http:\/\/'\./);
30 | });
31 |
32 | it('should pass into "config.apiVersion"', function () {
33 | expect(function () {
34 | new ROAClient({
35 | endpoint: 'http://ecs.aliyuncs.com/'
36 | });
37 | }).to.throwException(/must pass "config\.apiVersion"/);
38 | });
39 |
40 | it('should pass into "config.accessKeyId"', function () {
41 | expect(function () {
42 | new ROAClient({
43 | endpoint: 'http://ecs.aliyuncs.com/',
44 | apiVersion: '1.0'
45 | });
46 | }).to.throwException(/must pass "config\.accessKeyId"/);
47 | expect(function () {
48 | new ROAClient({
49 | endpoint: 'http://ecs.aliyuncs.com/',
50 | apiVersion: '1.0',
51 | credentialsProvider: null
52 | });
53 | }).to.throwException(/must pass "config\.accessKeyId"/);
54 | });
55 |
56 | it('should pass into "config.accessKeySecret"', function () {
57 | expect(function () {
58 | new ROAClient({
59 | endpoint: 'http://ecs.aliyuncs.com/',
60 | apiVersion: '1.0',
61 | accessKeyId: 'accessKeyId'
62 | });
63 | }).to.throwException(/must pass "config\.accessKeySecret"/);
64 | });
65 |
66 | it('should pass into "config.credentialsProvider" with getCredentials()', function () {
67 | expect(function () {
68 | new ROAClient({
69 | endpoint: 'http://ecs.aliyuncs.com/',
70 | apiVersion: '1.0',
71 | credentialsProvider: {
72 | accessKeyId: 'test',
73 | accessKeySecret: 'test',
74 | }
75 | });
76 | }).to.throwException(/must pass "config\.credentialsProvider" with function "getCredentials\(\)"/);
77 | });
78 |
79 | it('should ok with http protocol', function () {
80 | const client = new ROAClient({
81 | endpoint: 'http://ecs.aliyuncs.com/',
82 | apiVersion: '1.0',
83 | accessKeyId: 'accessKeyId',
84 | accessKeySecret: 'accessKeySecret',
85 | });
86 | expect(client.endpoint).to.be('http://ecs.aliyuncs.com/');
87 | expect(client.keepAliveAgent).to.be.ok();
88 | expect(client.keepAliveAgent).to.have.property('protocol', 'http:');
89 | });
90 |
91 | it('should ok with https protocol', function () {
92 | const client = new ROAClient({
93 | endpoint: 'https://ecs.aliyuncs.com/',
94 | apiVersion: '1.0',
95 | accessKeyId: 'accessKeyId',
96 | accessKeySecret: 'accessKeySecret',
97 | });
98 | expect(client.endpoint).to.be('https://ecs.aliyuncs.com/');
99 | expect(client.keepAliveAgent).to.be.ok();
100 | expect(client.keepAliveAgent).to.have.property('protocol', 'https:');
101 | });
102 | });
103 |
104 | it('buildHeaders should ok', function () {
105 | const client = new ROAClient({
106 | endpoint: 'https://ecs.aliyuncs.com/',
107 | apiVersion: '1.0',
108 | accessKeyId: 'accessKeyId',
109 | accessKeySecret: 'accessKeySecret',
110 | });
111 | const headers = client.buildHeaders();
112 | expect(headers).to.only.have.keys('accept', 'date', 'host',
113 | 'x-acs-signature-nonce', 'x-acs-signature-method',
114 | 'x-acs-signature-version', 'x-acs-version', 'x-sdk-client',
115 | 'user-agent');
116 | expect(headers).to.have.property('accept', 'application/json');
117 | expect(headers.date).to.match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/);
118 | expect(headers).to.have.property('host', 'ecs.aliyuncs.com');
119 | expect(headers['user-agent'].startsWith('AlibabaCloud')).to.be.ok();
120 | });
121 |
122 | it('buildHeaders should ok with securityToken', function () {
123 | const client = new ROAClient({
124 | endpoint: 'https://ecs.aliyuncs.com/',
125 | apiVersion: '1.0',
126 | accessKeyId: 'accessKeyId',
127 | accessKeySecret: 'accessKeySecret',
128 | securityToken: 'securityToken'
129 | });
130 | const headers = client.buildHeaders();
131 | expect(headers).to.only.have.keys('accept', 'date', 'host',
132 | 'x-acs-signature-nonce', 'x-acs-signature-method',
133 | 'x-acs-signature-version', 'x-acs-version', 'x-sdk-client',
134 | 'x-acs-accesskey-id', 'x-acs-security-token', 'user-agent');
135 | expect(headers).to.have.property('accept', 'application/json');
136 | expect(headers.date).to.match(/[A-Z][a-z]{2}, \d{2} [A-Z][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} GMT/);
137 | expect(headers).to.have.property('host', 'ecs.aliyuncs.com');
138 | expect(headers['user-agent'].startsWith('AlibabaCloud')).to.be.ok();
139 | });
140 |
141 | it('signature should ok', function () {
142 | const client = new ROAClient({
143 | endpoint: 'https://ecs.aliyuncs.com/',
144 | apiVersion: '1.0',
145 | accessKeyId: 'accessKeyId',
146 | accessKeySecret: 'accessKeySecret',
147 | securityToken: 'securityToken'
148 | });
149 | const sign = client.signature('stringtosign');
150 | expect(sign).to.be('9/j11e+L9jW8mC9eUA23Wn3c0fs=');
151 | });
152 |
153 | it('buildAuthorization should ok', function () {
154 | const client = new ROAClient({
155 | endpoint: 'https://ecs.aliyuncs.com/',
156 | apiVersion: '1.0',
157 | accessKeyId: 'accessKeyId',
158 | accessKeySecret: 'accessKeySecret',
159 | securityToken: 'securityToken'
160 | });
161 | const auth = client.buildAuthorization('stringtosign');
162 | expect(auth).to.be('acs accessKeyId:9/j11e+L9jW8mC9eUA23Wn3c0fs=');
163 | });
164 |
165 | function mock(response, body) {
166 | before(function () {
167 | muk(httpx, 'request', function (url, opts) {
168 | return Promise.resolve(response);
169 | });
170 |
171 | muk(httpx, 'read', function (response, encoding) {
172 | return Promise.resolve(body);
173 | });
174 | });
175 |
176 | after(function () {
177 | muk.restore();
178 | });
179 | }
180 |
181 | describe('request with raw body', function () {
182 | mock('', 'raw body');
183 |
184 | it('request with raw body should ok', async function () {
185 | const client = new ROAClient({
186 | endpoint: 'https://ecs.aliyuncs.com/',
187 | apiVersion: '1.0',
188 | accessKeyId: 'accessKeyId',
189 | accessKeySecret: 'accessKeySecret',
190 | securityToken: 'securityToken'
191 | });
192 | const result = await client.request('GET', '/', {}, '', {}, { rawBody: true });
193 | expect(result).to.be('raw body');
194 | });
195 |
196 | it('get with raw body should ok', async function () {
197 | const client = new ROAClient({
198 | endpoint: 'https://ecs.aliyuncs.com/',
199 | apiVersion: '1.0',
200 | accessKeyId: 'accessKeyId',
201 | accessKeySecret: 'accessKeySecret',
202 | securityToken: 'securityToken'
203 | });
204 | const result = await client.get('/', {}, {}, { rawBody: true });
205 | expect(result).to.be('raw body');
206 | });
207 |
208 | it('request with credentialsProvider should ok', async function () {
209 | const provider = {
210 | getCredentials: async () => {
211 | return {
212 | accessKeyId: 'accessKeyId',
213 | accessKeySecret: 'accessKeySecret',
214 | securityToken: 'securityToken',
215 | };
216 | }
217 | };
218 | const client = new ROAClient({
219 | endpoint: 'https://ecs.aliyuncs.com/',
220 | apiVersion: '1.0',
221 | credentialsProvider: provider
222 | });
223 | const result = await client.request('GET', '/', {}, '', {}, { rawBody: true });
224 | expect(result).to.be('raw body');
225 | });
226 | });
227 |
228 | describe('request with json response should ok', function () {
229 | mock({
230 | statusCode: 200,
231 | headers: {
232 | 'content-type': 'application/json'
233 | }
234 | }, JSON.stringify({ 'ok': true }));
235 |
236 | it('json response should ok', async function () {
237 | const client = new ROAClient({
238 | endpoint: 'https://ecs.aliyuncs.com/',
239 | apiVersion: '1.0',
240 | accessKeyId: 'accessKeyId',
241 | accessKeySecret: 'accessKeySecret',
242 | securityToken: 'securityToken'
243 | });
244 | const result = await client.request('GET', '/');
245 | expect(result).to.be.eql({
246 | ok: true
247 | });
248 | });
249 |
250 | it('json response with credentialsProvider should ok', async function () {
251 | const provider = {
252 | getCredentials: async () => {
253 | return {
254 | accessKeyId: 'accessKeyId',
255 | accessKeySecret: 'accessKeySecret',
256 | securityToken: 'securityToken',
257 | };
258 | }
259 | };
260 | const client = new ROAClient({
261 | endpoint: 'https://ecs.aliyuncs.com/',
262 | apiVersion: '1.0',
263 | credentialsProvider: provider
264 | });
265 | const result = await client.request('GET', '/');
266 | expect(result).to.be.eql({
267 | ok: true
268 | });
269 | });
270 | });
271 |
272 | describe('request(204) with json response should ok', function () {
273 | mock({
274 | statusCode: 204,
275 | headers: {
276 | 'content-type': 'application/json'
277 | }
278 | }, '');
279 |
280 | it('json response should ok', async function () {
281 | const client = new ROAClient({
282 | endpoint: 'https://ecs.aliyuncs.com/',
283 | apiVersion: '1.0',
284 | accessKeyId: 'accessKeyId',
285 | accessKeySecret: 'accessKeySecret',
286 | securityToken: 'securityToken'
287 | });
288 | const result = await client.request('GET', '/');
289 | expect(result).to.be('');
290 | });
291 |
292 | it('json response with credentialsProvider should ok', async function () {
293 | const provider = {
294 | getCredentials: async () => {
295 | return {
296 | accessKeyId: 'accessKeyId',
297 | accessKeySecret: 'accessKeySecret',
298 | securityToken: 'securityToken',
299 | };
300 | }
301 | };
302 | const client = new ROAClient({
303 | endpoint: 'https://ecs.aliyuncs.com/',
304 | apiVersion: '1.0',
305 | credentialsProvider: provider
306 | });
307 | const result = await client.request('GET', '/');
308 | expect(result).to.be('');
309 | });
310 | });
311 |
312 | describe('request(400) with json response should ok', function () {
313 | mock({
314 | statusCode: 400,
315 | headers: {
316 | 'content-type': 'application/json'
317 | }
318 | }, JSON.stringify({
319 | 'Message': 'error message',
320 | 'RequestId': 'requestid',
321 | 'Code': 'errorcode'
322 | }));
323 |
324 | it('json response should ok', async function () {
325 | const client = new ROAClient({
326 | endpoint: 'https://ecs.aliyuncs.com/',
327 | apiVersion: '1.0',
328 | accessKeyId: 'accessKeyId',
329 | accessKeySecret: 'accessKeySecret',
330 | securityToken: 'securityToken'
331 | });
332 | try {
333 | await client.request('GET', '/');
334 | } catch (ex) {
335 | expect(ex.message).to.be('code: 400, error message, requestid: requestid');
336 | expect(ex.name).to.be('errorcodeError');
337 | expect(ex.statusCode).to.be(400);
338 | expect(ex.code).to.be('errorcode');
339 | return;
340 | }
341 | // should never be executed
342 | expect(false).to.be.ok();
343 | });
344 |
345 | it('json response with credentialsProvider should ok', async function () {
346 | const provider = {
347 | getCredentials: async () => {
348 | return {
349 | accessKeyId: 'accessKeyId',
350 | accessKeySecret: 'accessKeySecret',
351 | securityToken: 'securityToken',
352 | };
353 | }
354 | };
355 | const client = new ROAClient({
356 | endpoint: 'https://ecs.aliyuncs.com/',
357 | apiVersion: '1.0',
358 | credentialsProvider: provider
359 | });
360 | try {
361 | await client.request('GET', '/');
362 | } catch (ex) {
363 | expect(ex.message).to.be('code: 400, error message, requestid: requestid');
364 | expect(ex.name).to.be('errorcodeError');
365 | expect(ex.statusCode).to.be(400);
366 | expect(ex.code).to.be('errorcode');
367 | return;
368 | }
369 | // should never be executed
370 | expect(false).to.be.ok();
371 | });
372 | });
373 |
374 | describe('request(400) with json response and errorMsg should ok', function () {
375 | mock({
376 | statusCode: 400,
377 | headers: {
378 | 'content-type': 'application/json'
379 | }
380 | }, JSON.stringify({
381 | 'errorMsg': 'RAM/STS verification error',
382 | 'errorCode': 10007
383 | }));
384 |
385 | it('json response should ok', async function () {
386 | const client = new ROAClient({
387 | endpoint: 'https://ecs.aliyuncs.com/',
388 | apiVersion: '1.0',
389 | accessKeyId: 'accessKeyId',
390 | accessKeySecret: 'accessKeySecret',
391 | securityToken: 'securityToken'
392 | });
393 | try {
394 | await client.request('GET', '/');
395 | } catch (ex) {
396 | expect(ex.message).to.be('code: 400, RAM/STS verification error, requestid: ');
397 | expect(ex.name).to.be('10007Error');
398 | expect(ex.statusCode).to.be(400);
399 | expect(ex.code).to.be(10007);
400 | return;
401 | }
402 | // should never be executed
403 | expect(false).to.be.ok();
404 | });
405 |
406 | it('json response with credentialsProvider should ok', async function () {
407 | const provider = {
408 | getCredentials: async () => {
409 | return {
410 | accessKeyId: 'accessKeyId',
411 | accessKeySecret: 'accessKeySecret',
412 | securityToken: 'securityToken',
413 | };
414 | }
415 | };
416 | const client = new ROAClient({
417 | endpoint: 'https://ecs.aliyuncs.com/',
418 | apiVersion: '1.0',
419 | credentialsProvider: provider
420 | });
421 | try {
422 | await client.request('GET', '/');
423 | } catch (ex) {
424 | expect(ex.message).to.be('code: 400, RAM/STS verification error, requestid: ');
425 | expect(ex.name).to.be('10007Error');
426 | expect(ex.statusCode).to.be(400);
427 | expect(ex.code).to.be(10007);
428 | return;
429 | }
430 | // should never be executed
431 | expect(false).to.be.ok();
432 | });
433 | });
434 |
435 | describe('request with unexpect json string response should ok', function () {
436 | mock({
437 | statusCode: 400,
438 | headers: {
439 | 'content-type': 'application/json'
440 | }
441 | }, '{foo:bar}');
442 |
443 | it('json response should ok', async function () {
444 | const client = new ROAClient({
445 | endpoint: 'https://ecs.aliyuncs.com/',
446 | apiVersion: '1.0',
447 | accessKeyId: 'accessKeyId',
448 | accessKeySecret: 'accessKeySecret',
449 | securityToken: 'securityToken'
450 | });
451 | try {
452 | await client.request('GET', '/');
453 | } catch (ex) {
454 | expect(ex.message).to.be('parse response to json error');
455 | expect(ex.name).to.be('FormatError');
456 | return;
457 | }
458 | // should never be executed
459 | expect(false).to.be.ok();
460 | });
461 |
462 | it('json response with credentialsProvider should ok', async function () {
463 | const provider = {
464 | getCredentials: async () => {
465 | return {
466 | accessKeyId: 'accessKeyId',
467 | accessKeySecret: 'accessKeySecret',
468 | securityToken: 'securityToken',
469 | };
470 | }
471 | };
472 | const client = new ROAClient({
473 | endpoint: 'https://ecs.aliyuncs.com/',
474 | apiVersion: '1.0',
475 | credentialsProvider: provider
476 | });
477 | try {
478 | await client.request('GET', '/');
479 | } catch (ex) {
480 | expect(ex.message).to.be('parse response to json error');
481 | expect(ex.name).to.be('FormatError');
482 | return;
483 | }
484 | // should never be executed
485 | expect(false).to.be.ok();
486 | });
487 | });
488 |
489 | describe('request with xml response should ok', function () {
490 | mock({
491 | statusCode: 200,
492 | headers: {
493 | 'content-type': 'text/xml'
494 | }
495 | }, `
496 | George
497 | John
498 | Reminder
499 | Don't forget the meeting!
500 | `);
501 |
502 | it('json response should ok', async function () {
503 | const client = new ROAClient({
504 | endpoint: 'https://ecs.aliyuncs.com/',
505 | apiVersion: '1.0',
506 | accessKeyId: 'accessKeyId',
507 | accessKeySecret: 'accessKeySecret',
508 | securityToken: 'securityToken'
509 | });
510 | const result = await client.request('GET', '/');
511 | expect(result).to.be.eql({
512 | 'note': {
513 | 'body': [
514 | "Don't forget the meeting!"
515 | ],
516 | 'from': [
517 | 'John'
518 | ],
519 | 'heading': [
520 | 'Reminder'
521 | ],
522 | 'to': [
523 | 'George'
524 | ]
525 | }
526 | });
527 | });
528 | });
529 |
530 | describe('request(400) with xml response should ok', function () {
531 | mock({
532 | statusCode: 400,
533 | headers: {
534 | 'content-type': 'text/xml'
535 | }
536 | }, `
537 | error message
538 | requestid
539 | hostid
540 | errorcode
541 | `);
542 |
543 | it('xml response should ok', async function () {
544 | const client = new ROAClient({
545 | endpoint: 'https://ecs.aliyuncs.com/',
546 | apiVersion: '1.0',
547 | accessKeyId: 'accessKeyId',
548 | accessKeySecret: 'accessKeySecret',
549 | securityToken: 'securityToken'
550 | });
551 | try {
552 | await client.request('GET', '/');
553 | } catch (ex) {
554 | console.log(ex.stack);
555 | expect(ex.message).to.be('error message hostid: hostid, requestid: requestid');
556 | return;
557 | }
558 | // should never be executed
559 | expect(false).to.be.ok();
560 | });
561 | });
562 |
563 | describe('request(200) with plain response should ok', function () {
564 | mock({
565 | statusCode: 200,
566 | headers: {}
567 | }, `plain text`);
568 |
569 | it('plain response should ok', async function () {
570 | const client = new ROAClient({
571 | endpoint: 'https://ecs.aliyuncs.com/',
572 | apiVersion: '1.0',
573 | accessKeyId: 'accessKeyId',
574 | accessKeySecret: 'accessKeySecret',
575 | securityToken: 'securityToken'
576 | });
577 | const result = await client.request('GET', '/');
578 | expect(result).to.be('plain text');
579 | });
580 | });
581 |
582 | describe('post should ok', function () {
583 | mock({
584 | statusCode: 200,
585 | headers: {
586 | 'content-type': 'application/json'
587 | }
588 | }, JSON.stringify({ 'ok': true }));
589 |
590 | it('should ok', async function () {
591 | const client = new ROAClient({
592 | endpoint: 'https://ecs.aliyuncs.com/',
593 | apiVersion: '1.0',
594 | accessKeyId: 'accessKeyId',
595 | accessKeySecret: 'accessKeySecret',
596 | securityToken: 'securityToken'
597 | });
598 | const result = await client.post('/', {}, 'text', {}, {});
599 | expect(result).to.be.eql({ 'ok': true });
600 | });
601 |
602 | it('should ok with credentialsProvider', async function () {
603 | const provider = {
604 | getCredentials: async () => {
605 | return {
606 | accessKeyId: 'accessKeyId',
607 | accessKeySecret: 'accessKeySecret',
608 | securityToken: 'securityToken',
609 | };
610 | }
611 | };
612 | const client = new ROAClient({
613 | endpoint: 'https://ecs.aliyuncs.com/',
614 | apiVersion: '1.0',
615 | credentialsProvider: provider
616 | });
617 | const result = await client.post('/', {}, 'text', {}, {});
618 | expect(result).to.be.eql({ 'ok': true });
619 | });
620 |
621 | it('should ok with query', async function () {
622 | const client = new ROAClient({
623 | endpoint: 'https://ecs.aliyuncs.com/',
624 | apiVersion: '1.0',
625 | accessKeyId: 'accessKeyId',
626 | accessKeySecret: 'accessKeySecret',
627 | securityToken: 'securityToken'
628 | });
629 | const result = await client.post('/', { 'k': 'v' }, 'text', {}, {});
630 | expect(result).to.be.eql({ 'ok': true });
631 | });
632 | });
633 |
634 | describe('put should ok', function () {
635 | mock({
636 | statusCode: 200,
637 | headers: {
638 | 'content-type': 'application/json'
639 | }
640 | }, JSON.stringify({ 'ok': true }));
641 |
642 | it('should ok', async function () {
643 | const client = new ROAClient({
644 | endpoint: 'https://ecs.aliyuncs.com/',
645 | apiVersion: '1.0',
646 | accessKeyId: 'accessKeyId',
647 | accessKeySecret: 'accessKeySecret',
648 | securityToken: 'securityToken'
649 | });
650 | const result = await client.put('/', {}, 'text', {}, {});
651 | expect(result).to.be.eql({ 'ok': true });
652 | });
653 |
654 | it('should ok with credentialsProvider', async function () {
655 | const provider = {
656 | getCredentials: async () => {
657 | return {
658 | accessKeyId: 'accessKeyId',
659 | accessKeySecret: 'accessKeySecret',
660 | securityToken: 'securityToken',
661 | };
662 | }
663 | };
664 | const client = new ROAClient({
665 | endpoint: 'https://ecs.aliyuncs.com/',
666 | apiVersion: '1.0',
667 | credentialsProvider: provider
668 | });
669 | const result = await client.put('/', {}, 'text', {}, {});
670 | expect(result).to.be.eql({ 'ok': true });
671 | });
672 | });
673 |
674 | describe('delete should ok', function () {
675 | mock({
676 | statusCode: 200,
677 | headers: {
678 | 'content-type': 'application/json'
679 | }
680 | }, JSON.stringify({ 'ok': true }));
681 |
682 | it('should ok', async function () {
683 | const client = new ROAClient({
684 | endpoint: 'https://ecs.aliyuncs.com/',
685 | apiVersion: '1.0',
686 | accessKeyId: 'accessKeyId',
687 | accessKeySecret: 'accessKeySecret',
688 | securityToken: 'securityToken'
689 | });
690 | const result = await client.delete('/', {}, {}, {});
691 | expect(result).to.be.eql({ 'ok': true });
692 | });
693 |
694 | it('should ok with credentialsProvider', async function () {
695 | const provider = {
696 | getCredentials: async () => {
697 | return {
698 | accessKeyId: 'accessKeyId',
699 | accessKeySecret: 'accessKeySecret',
700 | securityToken: 'securityToken',
701 | };
702 | }
703 | };
704 | const client = new ROAClient({
705 | endpoint: 'https://ecs.aliyuncs.com/',
706 | apiVersion: '1.0',
707 | credentialsProvider: provider
708 | });
709 | const result = await client.delete('/', {}, {}, {});
710 | expect(result).to.be.eql({ 'ok': true });
711 | });
712 | });
713 |
714 | describe('ROA private methods', function () {
715 | const roa = rewire('../lib/roa');
716 |
717 | it('filter should ok', function () {
718 | const filter = roa.__get__('filter');
719 | expect(filter('hello')).to.be('hello');
720 | expect(filter('he\t\tllo')).to.be('he llo');
721 | expect(filter('he\n\nllo')).to.be('he llo');
722 | expect(filter('he\r\rllo')).to.be('he llo');
723 | expect(filter('he\f\fllo')).to.be('he llo');
724 | });
725 |
726 | it('keyLowerify should ok', function () {
727 | const keyLowerify = roa.__get__('keyLowerify');
728 | expect(keyLowerify({})).to.be.eql({});
729 | expect(keyLowerify({ 'low': 'value' })).to.be.eql({ 'low': 'value' });
730 | expect(keyLowerify({ 'Low': 'value' })).to.be.eql({ 'low': 'value' });
731 | });
732 |
733 | it('parseXML should ok', async function () {
734 | const parseXML = roa.__get__('parseXML');
735 | try {
736 | await parseXML('<>');
737 | } catch (ex) {
738 | expect(ex).to.be.ok();
739 | expect(ex.message).to.be('Unencoded <\nLine: 0\nColumn: 2\nChar: >');
740 | return;
741 | }
742 | // never run
743 | expect(false).to.be.ok();
744 | });
745 |
746 | it('parseXML should ok', async function () {
747 | const parseXML = roa.__get__('parseXML');
748 | const result = await parseXML(`
749 | George
750 | John
751 | Reminder
752 | Don't forget the meeting!
753 | `);
754 | expect(result).to.be.eql({
755 | 'note': {
756 | 'body': [
757 | "Don't forget the meeting!"
758 | ],
759 | 'from': [
760 | 'John'
761 | ],
762 | 'heading': [
763 | 'Reminder'
764 | ],
765 | 'to': [
766 | 'George'
767 | ]
768 | }
769 | });
770 | });
771 |
772 | it('getCanonicalizedHeaders should ok', function () {
773 | const getCanonicalizedHeaders = roa.__get__('getCanonicalizedHeaders');
774 | expect(getCanonicalizedHeaders({})).to.be('');
775 | expect(getCanonicalizedHeaders({ key: 'value' })).to.be('');
776 | expect(getCanonicalizedHeaders({ 'x-acs-key': 'value' })).to.be('x-acs-key:value\n');
777 | });
778 |
779 | it('getCanonicalizedResource should ok', function () {
780 | const getCanonicalizedResource = roa.__get__('getCanonicalizedResource');
781 | expect(getCanonicalizedResource('/', {})).to.be('/');
782 | expect(getCanonicalizedResource('/', { key: 'value' })).to.be('/?key=value');
783 | const q = { key: 'value', 'key1': 'value2' };
784 | expect(getCanonicalizedResource('/', q)).to.be('/?key=value&key1=value2');
785 | });
786 |
787 | it('buildStringToSign should ok', function () {
788 | const buildStringToSign = roa.__get__('buildStringToSign');
789 | expect(buildStringToSign('GET', '/', { 'accept': 'application/json' }, {}))
790 | .to.be('GET\napplication/json\n\n\n\n/');
791 | const headers = {
792 | 'accept': 'application/json',
793 | 'content-md5': 'md5',
794 | 'content-type': 'application/json',
795 | 'date': 'date'
796 | };
797 | expect(buildStringToSign('GET', '/', headers, {}))
798 | .to.be('GET\napplication/json\nmd5\napplication/json\ndate\n/');
799 | });
800 | });
801 |
802 | describe('ROA private class', function () {
803 | const roa = rewire('../lib/roa');
804 |
805 | it('ACSError should ok', function () {
806 | const ACSError = roa.__get__('ACSError');
807 | const err = new ACSError({
808 | Message: ['error message'],
809 | Code: ['errorcode'],
810 | HostId: ['hostid'],
811 | RequestId: ['requestid']
812 | });
813 |
814 | expect(err.message).to.be('error message hostid: hostid, requestid: requestid');
815 | expect(err.code).to.be('errorcode');
816 | });
817 | });
818 | });
819 |
--------------------------------------------------------------------------------
/test/rpc.integration.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('expect.js');
4 |
5 | const RPCClient = require('../lib/rpc');
6 |
7 | describe('rpc request', function () {
8 | var client = new RPCClient({
9 | accessKeyId: process.env.ACCESS_KEY_ID,
10 | accessKeySecret: process.env.ACCESS_KEY_SECRET,
11 | endpoint: 'https://ecs.aliyuncs.com',
12 | apiVersion: '2014-05-26'
13 | });
14 |
15 | it('should ok', async function () {
16 | this.timeout(15000);
17 |
18 | var params = {
19 | key: ['1', '2', '3', '4', '5', '6', '7', '8', '9',
20 | '10', '11']
21 | };
22 |
23 | var requestOption = {
24 | method: 'POST',
25 | timeout: 15000
26 | };
27 |
28 | const result = await client.request('DescribeRegions', params, requestOption);
29 | expect(result).to.have.key('RequestId');
30 | expect(result).to.have.key('Regions');
31 | });
32 |
33 | it('should ok with repeat list less 10 item', async function () {
34 | this.timeout(15000);
35 |
36 | var params = {
37 | key: ['1', '2', '3', '4', '5', '6', '7', '8', '9']
38 | };
39 |
40 | var requestOption = {
41 | method: 'POST',
42 | timeout: 15000
43 | };
44 |
45 | const result = await client.request('DescribeRegions', params, requestOption);
46 | expect(result).to.have.key('RequestId');
47 | expect(result).to.have.key('Regions');
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/test/rpc.test.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const expect = require('expect.js');
4 | const rewire = require('rewire');
5 | const httpx = require('httpx');
6 | const muk = require('muk');
7 |
8 | const RPCClient = require('../lib/rpc');
9 |
10 | describe('rpc core', function () {
11 | describe('RPCClient', function () {
12 | it('should pass into "config"', function () {
13 | expect(function () {
14 | new RPCClient();
15 | }).to.throwException(/must pass "config"/);
16 | });
17 |
18 | it('should pass into "config.endpoint"', function () {
19 | expect(function () {
20 | new RPCClient({});
21 | }).to.throwException(/must pass "config\.endpoint"/);
22 | });
23 |
24 | it('should pass into valid "config.endpoint"', function () {
25 | expect(function () {
26 | new RPCClient({
27 | endpoint: 'ecs.aliyuncs.com/'
28 | });
29 | }).to.throwException(/"config\.endpoint" must starts with 'https:\/\/' or 'http:\/\/'\./);
30 | });
31 |
32 | it('should pass into "config.apiVersion"', function () {
33 | expect(function () {
34 | new RPCClient({
35 | endpoint: 'http://ecs.aliyuncs.com/'
36 | });
37 | }).to.throwException(/must pass "config\.apiVersion"/);
38 | });
39 |
40 | it('should pass into "config.accessKeyId"', function () {
41 | expect(function () {
42 | new RPCClient({
43 | endpoint: 'http://ecs.aliyuncs.com/',
44 | apiVersion: '1.0'
45 | });
46 | }).to.throwException(/must pass "config\.accessKeyId"/);
47 | expect(function () {
48 | new RPCClient({
49 | endpoint: 'http://ecs.aliyuncs.com/',
50 | apiVersion: '1.0',
51 | credentialsProvider: null
52 | });
53 | }).to.throwException(/must pass "config\.accessKeyId"/);
54 | });
55 |
56 | it('should pass into "config.accessKeySecret"', function () {
57 | expect(function () {
58 | new RPCClient({
59 | endpoint: 'http://ecs.aliyuncs.com/',
60 | apiVersion: '1.0',
61 | accessKeyId: 'accessKeyId'
62 | });
63 | }).to.throwException(/must pass "config\.accessKeySecret"/);
64 | });
65 |
66 | it('should pass into "config.credentialsProvider" with getCredentials()', function () {
67 | expect(function () {
68 | new RPCClient({
69 | endpoint: 'http://ecs.aliyuncs.com/',
70 | apiVersion: '1.0',
71 | credentialsProvider: {
72 | accessKeyId: 'test',
73 | accessKeySecret: 'test',
74 | }
75 | });
76 | }).to.throwException(/must pass "config\.credentialsProvider" with function "getCredentials\(\)"/);
77 | });
78 |
79 | it('should ok with http endpoint', function () {
80 | const client = new RPCClient({
81 | endpoint: 'http://ecs.aliyuncs.com',
82 | apiVersion: '1.0',
83 | accessKeyId: 'accessKeyId',
84 | accessKeySecret: 'accessKeySecret'
85 | });
86 | expect(client.endpoint).to.be('http://ecs.aliyuncs.com');
87 | expect(client.keepAliveAgent.protocol).to.be('http:');
88 | });
89 |
90 | it('should ok with https endpoint', function () {
91 | const client = new RPCClient({
92 | endpoint: 'https://ecs.aliyuncs.com/',
93 | apiVersion: '1.0',
94 | accessKeyId: 'accessKeyId',
95 | accessKeySecret: 'accessKeySecret'
96 | });
97 | expect(client.endpoint).to.be('https://ecs.aliyuncs.com');
98 | expect(client.keepAliveAgent.protocol).to.be('https:');
99 | });
100 |
101 | it('should ok with codes', function () {
102 | const client = new RPCClient({
103 | endpoint: 'https://ecs.aliyuncs.com/',
104 | apiVersion: '1.0',
105 | accessKeyId: 'accessKeyId',
106 | accessKeySecret: 'accessKeySecret',
107 | codes: ['True']
108 | });
109 | expect(client.codes.has('True')).to.be.ok();
110 | });
111 | });
112 |
113 | describe('_buildParams', function () {
114 | it('should ok', function () {
115 | const client = new RPCClient({
116 | endpoint: 'https://ecs.aliyuncs.com/',
117 | apiVersion: '1.0',
118 | accessKeyId: 'accessKeyId',
119 | accessKeySecret: 'accessKeySecret'
120 | });
121 | const defaults = client._buildParams();
122 | expect(defaults).to.only.have.keys('Format', 'SignatureMethod',
123 | 'SignatureNonce', 'SignatureVersion', 'Timestamp', 'AccessKeyId',
124 | 'Version');
125 | });
126 |
127 | it('should ok with securityToken', function () {
128 | const client = new RPCClient({
129 | endpoint: 'https://ecs.aliyuncs.com/',
130 | apiVersion: '1.0',
131 | accessKeyId: 'accessKeyId',
132 | accessKeySecret: 'accessKeySecret',
133 | securityToken: 'securityToken'
134 | });
135 | const defaults = client._buildParams();
136 | expect(defaults).to.only.have.keys('Format', 'SignatureMethod',
137 | 'SignatureNonce', 'SignatureVersion', 'Timestamp', 'AccessKeyId',
138 | 'Version', 'SecurityToken');
139 | });
140 | });
141 |
142 | function mock(response, body) {
143 | before(function () {
144 | muk(httpx, 'request', function (url, opts) {
145 | response.req.getHeaders = function () {
146 | return response.req._headers;
147 | };
148 | return Promise.resolve(response);
149 | });
150 |
151 | muk(httpx, 'read', function (response, encoding) {
152 | return Promise.resolve(body);
153 | });
154 | });
155 |
156 | after(function () {
157 | muk.restore();
158 | });
159 | }
160 |
161 | describe('request', function () {
162 | mock({
163 | req: {
164 | _headers: {}
165 | }
166 | }, '{}');
167 |
168 | it('get with raw body should ok', async function () {
169 | const client = new RPCClient({
170 | endpoint: 'https://ecs.aliyuncs.com/',
171 | apiVersion: '1.0',
172 | accessKeyId: 'accessKeyId',
173 | accessKeySecret: 'accessKeySecret',
174 | });
175 | const result = await client.request('action', {});
176 | expect(result).to.be.eql({});
177 | });
178 |
179 | it('get with credentialsProvider should ok', async function () {
180 | const provider = {
181 | getCredentials: async () => {
182 | return {
183 | accessKeyId: 'accessKeyId',
184 | accessKeySecret: 'accessKeySecret',
185 | };
186 | }
187 | };
188 | const client = new RPCClient({
189 | endpoint: 'https://ecs.aliyuncs.com/',
190 | apiVersion: '1.0',
191 | credentialsProvider: provider
192 | });
193 | const result = await client.request('action', {});
194 | expect(result).to.be.eql({});
195 | });
196 | });
197 |
198 | describe('request with post', function () {
199 | mock({
200 | req: {
201 | _headers: {}
202 | },
203 | statusCode: 200,
204 | headers: {}
205 | }, '{}');
206 |
207 | it('should ok', async function () {
208 | const client = new RPCClient({
209 | endpoint: 'https://ecs.aliyuncs.com/',
210 | apiVersion: '1.0',
211 | accessKeyId: 'accessKeyId',
212 | accessKeySecret: 'accessKeySecret',
213 | });
214 | const result = await client.request('action');
215 | expect(result).to.be.eql({});
216 | });
217 |
218 | it('should ok with credentialsProvider', async function () {
219 | const provider = {
220 | getCredentials: async () => {
221 | return {
222 | accessKeyId: 'accessKeyId',
223 | accessKeySecret: 'accessKeySecret',
224 | };
225 | }
226 | };
227 | const client = new RPCClient({
228 | endpoint: 'https://ecs.aliyuncs.com/',
229 | apiVersion: '1.0',
230 | credentialsProvider: provider
231 | });
232 | const result = await client.request('action');
233 | expect(result).to.be.eql({});
234 | });
235 |
236 | it('should ok with formatAction', async function () {
237 | const client = new RPCClient({
238 | endpoint: 'https://ecs.aliyuncs.com/',
239 | apiVersion: '1.0',
240 | accessKeyId: 'accessKeyId',
241 | accessKeySecret: 'accessKeySecret',
242 | });
243 | const result = await client.request('action', {}, {
244 | formatAction: false
245 | });
246 | expect(result).to.be.eql({});
247 | });
248 |
249 | it('should ok with formatParams', async function () {
250 | const client = new RPCClient({
251 | endpoint: 'https://ecs.aliyuncs.com/',
252 | apiVersion: '1.0',
253 | accessKeyId: 'accessKeyId',
254 | accessKeySecret: 'accessKeySecret',
255 | });
256 | const result = await client.request('action', {}, {
257 | formatParams: false
258 | });
259 | expect(result).to.be.eql({});
260 | });
261 |
262 | it('should ok with keepAlive', async function () {
263 | const client = new RPCClient({
264 | endpoint: 'https://ecs.aliyuncs.com/',
265 | apiVersion: '1.0',
266 | accessKeyId: 'accessKeyId',
267 | accessKeySecret: 'accessKeySecret',
268 | });
269 | const result = await client.request('action', {}, {
270 | agent: new require('https').Agent({
271 | keepAlive: true,
272 | keepAliveMsecs: 3000
273 | })
274 | });
275 | expect(result).to.be.eql({});
276 | });
277 |
278 | it('get with raw body should ok', async function () {
279 | const client = new RPCClient({
280 | endpoint: 'https://ecs.aliyuncs.com/',
281 | apiVersion: '1.0',
282 | accessKeyId: 'accessKeyId',
283 | accessKeySecret: 'accessKeySecret',
284 | });
285 | const result = await client.request('action', {}, {
286 | method: 'POST'
287 | });
288 | expect(result).to.be.eql({});
289 | });
290 |
291 | it('get with verbose should ok', async function () {
292 | const client = new RPCClient({
293 | endpoint: 'https://ecs.aliyuncs.com/',
294 | apiVersion: '1.0',
295 | accessKeyId: 'accessKeyId',
296 | accessKeySecret: 'accessKeySecret'
297 | }, true);
298 | const [json, entry] = await client.request('action', {}, {});
299 | expect(json).to.be.eql({});
300 | expect(entry.request).to.be.eql({
301 | headers: {}
302 | });
303 | expect(entry.response).to.be.eql({
304 | statusCode: 200,
305 | headers: {}
306 | });
307 | });
308 | });
309 |
310 | describe('request with error', function () {
311 | mock({
312 | req: {
313 | _headers: {}
314 | }
315 | }, JSON.stringify({
316 | Code: '400',
317 | Message: 'error message'
318 | }));
319 |
320 | it('request with 400 should ok', async function () {
321 | const client = new RPCClient({
322 | endpoint: 'https://ecs.aliyuncs.com/',
323 | apiVersion: '1.0',
324 | accessKeyId: 'accessKeyId',
325 | accessKeySecret: 'accessKeySecret',
326 | });
327 | try {
328 | await client.request('action', {});
329 | } catch (ex) {
330 | expect(ex.message.startsWith('error message, URL: ')).to.be.ok();
331 | return;
332 | }
333 | // should never be executed
334 | expect(false).to.be.ok();
335 | });
336 |
337 | it('request with 400 should ok', async function () {
338 | const provider = {
339 | getCredentials: async () => {
340 | return {
341 | accessKeyId: 'accessKeyId',
342 | accessKeySecret: 'accessKeySecret',
343 | };
344 | }
345 | };
346 | const client = new RPCClient({
347 | endpoint: 'https://ecs.aliyuncs.com/',
348 | apiVersion: '1.0',
349 | credentialsProvider: provider
350 | });
351 | try {
352 | await client.request('action', {});
353 | } catch (ex) {
354 | expect(ex.message.startsWith('error message, URL: ')).to.be.ok();
355 | return;
356 | }
357 | // should never be executed
358 | expect(false).to.be.ok();
359 | });
360 | });
361 |
362 | describe('RPC private methods', function () {
363 | const rpc = rewire('../lib/rpc');
364 |
365 | it('firstLetterUpper should ok', function () {
366 | const firstLetterUpper = rpc.__get__('firstLetterUpper');
367 | expect(firstLetterUpper('hello')).to.be('Hello');
368 | });
369 |
370 | it('formatParams should ok', function () {
371 | const formatParams = rpc.__get__('formatParams');
372 | expect(formatParams({ 'hello': 'world' })).to.be.eql({
373 | Hello: 'world'
374 | });
375 | });
376 |
377 | it('timestamp should ok', function () {
378 | const timestamp = rpc.__get__('timestamp');
379 | expect(timestamp()).to.be.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/);
380 | });
381 |
382 | it('encode should ok', function () {
383 | const encode = rpc.__get__('encode');
384 | expect(encode('str')).to.be('str');
385 | expect(encode('str\'str')).to.be('str%27str');
386 | expect(encode('str(str')).to.be('str%28str');
387 | expect(encode('str)str')).to.be('str%29str');
388 | expect(encode('str*str')).to.be('str%2Astr');
389 | });
390 |
391 | it('replaceRepeatList should ok', function () {
392 | const replaceRepeatList = rpc.__get__('replaceRepeatList');
393 | function helper(target, key, repeat) {
394 | replaceRepeatList(target, key, repeat);
395 | return target;
396 | }
397 | expect(helper({}, 'key', [])).to.be.eql({});
398 | expect(helper({}, 'key', ['value'])).to.be.eql({
399 | 'key.1': 'value'
400 | });
401 | expect(helper({}, 'key', [{
402 | Domain: '1.com'
403 | }])).to.be.eql({
404 | 'key.1.Domain': '1.com'
405 | });
406 | });
407 |
408 | it('flatParams should ok', function () {
409 | const flatParams = rpc.__get__('flatParams');
410 | expect(flatParams({})).to.be.eql({});
411 | expect(flatParams({ key: ['value'] })).to.be.eql({
412 | 'key.1': 'value'
413 | });
414 | expect(flatParams({
415 | 'key': 'value'
416 | })).to.be.eql({
417 | 'key': 'value'
418 | });
419 | expect(flatParams({
420 | key: [
421 | {
422 | Domain: '1.com'
423 | }
424 | ]
425 | })).to.be.eql({
426 | 'key.1.Domain': '1.com'
427 | });
428 | });
429 |
430 | it('normalize should ok', function () {
431 | const normalize = rpc.__get__('normalize');
432 | expect(normalize({})).to.be.eql([]);
433 | expect(normalize({ key: ['value'] })).to.be.eql([
434 | ['key.1', 'value']
435 | ]);
436 | expect(normalize({
437 | 'key': 'value'
438 | })).to.be.eql([
439 | ['key', 'value']
440 | ]);
441 | expect(normalize({
442 | key: [
443 | {
444 | Domain: '1.com'
445 | }
446 | ]
447 | })).to.be.eql([
448 | ['key.1.Domain', '1.com']
449 | ]);
450 | expect(normalize({
451 | 'a': 'value',
452 | 'c': 'value',
453 | 'b': 'value'
454 | })).to.be.eql([
455 | ['a', 'value'],
456 | ['b', 'value'],
457 | ['c', 'value']
458 | ]);
459 | });
460 |
461 | it('canonicalize should ok', function () {
462 | const canonicalize = rpc.__get__('canonicalize');
463 | expect(canonicalize([])).to.be('');
464 | expect(canonicalize([
465 | ['key.1', 'value']
466 | ])).to.be('key.1=value');
467 | expect(canonicalize([
468 | ['key', 'value']
469 | ])).to.be('key=value');
470 | expect(canonicalize([
471 | ['key.1.Domain', '1.com']
472 | ])).to.be('key.1.Domain=1.com');
473 | expect(canonicalize([
474 | ['a', 'value'],
475 | ['b', 'value'],
476 | ['c', 'value']
477 | ])).to.be('a=value&b=value&c=value');
478 | });
479 | });
480 | });
481 |
--------------------------------------------------------------------------------