apipost-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js ADDED
@@ -0,0 +1,214 @@
1
+ #!/usr/bin/env node
2
+ const { Command } = require('commander');
3
+ const pkginfo = require('pkginfo');
4
+ const { Collection, Runtime } = require('apipost-runtime');
5
+ const { isObject } = require('lodash');
6
+ const downloadTestReport = require('./template');
7
+ const nodeFetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
8
+ const os = require('os');
9
+ const dayjs = require('dayjs');
10
+ const APTools = require('apipost-inside-tools');
11
+ const _ = require('lodash');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const program = new Command();
15
+ const homedir = os.homedir();
16
+ const request = require('request');
17
+ let now = dayjs();
18
+ let formattedTime = now.format('YYYY-MM-DD HH:mm:ss');
19
+
20
+ program.version(pkginfo.version, '-v, --version', 'apipost-cli 当前版本')
21
+
22
+ let last_msg = "";
23
+
24
+ const cliOption = {
25
+ reporters: 'cli',
26
+ outDir: './apipost-reports',
27
+ outFile: '',
28
+ iterationData: '',
29
+ iterationCount: 1,
30
+ ignoreRedirects: 0,
31
+ maxRequestLoop: 5,
32
+ timeoutRequest: 0,
33
+ timeoutScript: 5000,
34
+ delayRequest: 0,
35
+ externalProgramPath: process.cwd(),
36
+ insecure: 1,
37
+ rejectUnauthorized: 1,
38
+ sslClientCertList: '', //客户端证书配置列表文件的路径。此选项优先于sslClientCert、sslClientKey和sslClientPassphrase。
39
+ sslClientCert: '', //"certificate": '', // 客户端证书地址
40
+ sslClientKey: '', // "key": '', //客户端证书私钥文件地址
41
+ sslClientPassphrase: '', // "passphrase": '' // 私钥密码
42
+ sslClientPfx: '', //"pfx": '', // pfx 证书地址
43
+ sslExtraCaCerts: '', //"certificateAuthority": '', // ca证书地址
44
+ webHook: ''
45
+ }
46
+
47
+ const emitRuntimeEvent = (msg, request) => {
48
+ try {
49
+ last_msg = msg; //只记录最后结果
50
+ if (isObject(msg)) {
51
+ // 完成
52
+ if (msg?.action === 'complate') {
53
+ // if (validator.isURL(options.webHook)) {}
54
+ downloadTestReport(msg?.test_report || {}, cliOption, request);
55
+ }
56
+ }
57
+ } catch (error) { }
58
+ }
59
+
60
+ const runTestEvents = async (data, options) => {
61
+ if (!isObject(data)) {
62
+ fs.appendFileSync(path.join(homedir, 'apipost-cli-error.log'), formattedTime + '\t链接地址数据不正确\n');
63
+ process.exit(1)
64
+ return;
65
+ }
66
+ _.merge(cliOption, options)
67
+
68
+ const newOptions = {
69
+ ...data,
70
+ option: {
71
+ ...data.option,
72
+ iterationCount: _.toNumber(cliOption.iterationCount),
73
+ sleep: _.toNumber(cliOption.delayRequest)
74
+ }
75
+ }
76
+
77
+ // 外部数据文件
78
+ try {
79
+ let interData = await APTools.str2testDataAsync(fs.readFileSync(String(cliOption.iterationData), "utf-8"));
80
+
81
+ if (_.isArray(interData)) {
82
+ _.set(newOptions, 'option.iterationData', interData)
83
+ }
84
+ } catch (e) {
85
+ fs.appendFileSync(path.join(homedir, 'apipost-cli-error.log'), `${formattedTime}\t数据文件 ${String(cliOption.iterationData)} 不存在\n`);
86
+ }
87
+
88
+ // 设置发送相关选项参数
89
+ _.chain(newOptions)
90
+ .set('option.requester.maxrequstloop', _.toNumber(cliOption.maxRequstLoop))
91
+ .set('option.requester.timeout', _.toNumber(cliOption.timeoutRequest))
92
+ .set('option.requester.timeoutScript', _.toNumber(cliOption.timeoutScript))
93
+ .set('option.requester.followRedirect', cliOption.ignoreRedirects === '1' ? 0 : 1)
94
+ .set('option.requester.strictSSL', _.toNumber(cliOption.insecure) ? 0 : 1)
95
+ .set('option.requester.externalPrograms', cliOption.externalProgramPath)
96
+ .tap(() => {
97
+ try {
98
+ const sslConfigPath = fs.readFileSync(cliOption.sslClientCertList, 'utf8');
99
+ if (_.isPlainObject(sslConfigPath)) {
100
+ _.merge(cliOption, JSON.parse(sslConfigPath));
101
+ }
102
+ } catch (err) { }
103
+ })
104
+ .set('option.requester.https', {
105
+ rejectUnauthorized: _.toNumber(cliOption.insecure) > 0 ? 1 : -1,
106
+ certificateAuthority: cliOption.sslExtraCaCerts,
107
+ certificate: cliOption.sslClientCert,
108
+ key: cliOption.sslClientKey,
109
+ pfx: cliOption.sslClientPfx,
110
+ passphrase: cliOption.sslClientPassphrase
111
+ })
112
+ .value();
113
+
114
+ const myCollection = new Collection(newOptions?.test_events, { iterationCount: newOptions?.option?.iterationCount, sleep: newOptions?.option.sleep });
115
+ const runTimeEvent = new Runtime(emitRuntimeEvent, false);
116
+ await runTimeEvent.run(myCollection.definition, newOptions?.option);
117
+
118
+ // 处理文件过大错误问题
119
+ let task_status = 0; //默认错的
120
+ if (!last_msg) {
121
+ // error
122
+ } else {
123
+ if (typeof last_msg == 'object') {
124
+ if (last_msg['test_report'] && last_msg['test_report']['event_status']) {
125
+ for (const event_key in last_msg['test_report']['event_status']) {
126
+ if (last_msg['test_report']['event_status'][event_key] == 'passed') {
127
+ task_status = 1;
128
+ } else {
129
+ task_status = 0;
130
+ break
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ if (task_status == 0) {
138
+ process.exit(1)
139
+ } else {
140
+ process.exit(0); //正常时候错误码为0
141
+ }
142
+ }
143
+
144
+ const parseCommandString = async (url, options) => {
145
+ _.merge(cliOption, _.mapKeys(options, (value, key) => _.camelCase(key)))
146
+
147
+ if (_.intersection(_.split(cliOption.reporters, ','), ['html', 'json'])) {
148
+ const urlRegex = /^(?:(?:https?|ftp):\/\/)?(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[\w-]+(?:\.[\w-]+)+)(?::\d+)?(?:\/[\w-]+)*(?:\?[\w-]+=[\w-]+(?:&[\w-]+=[\w-]+)*)?(?:#[\w-]+)?$/;
149
+ if (!urlRegex.test(url)) {
150
+ console.log(`请执行正确的url链接 [${url}]`);
151
+ fs.appendFileSync(path.join(homedir, 'apipost-cli-error.log'), `${formattedTime}\t请执行正确的url链接 [${url}]\n`);
152
+ return;
153
+ }
154
+ let response = await nodeFetch(url);
155
+ let runData = await response.json();
156
+
157
+ if (_.has(runData, 'code')) {
158
+ if (runData.code === 10000 && _.isObject(_.get(runData, 'data'))) {
159
+ runData = _.get(runData, 'data');
160
+ } else {
161
+ console.log(`执行失败, 原因 [${runData.msg}]`);
162
+ fs.appendFileSync(path.join(homedir, 'apipost-cli-error.log'), `${formattedTime}\t执行失败, 原因 [${runData.msg}]\n`);
163
+ return;
164
+ }
165
+ }
166
+
167
+ if (!_.isArray(_.get(runData, 'test_events')) || !_.isObject(_.get(runData, 'option'))) {
168
+ console.log(`执行失败, 原因 [Json 数据格式不匹配]`);
169
+ fs.appendFileSync(path.join(homedir, 'apipost-cli-error.log'), `${formattedTime}\t执行失败, 原因 [Json 数据格式不匹配]\n`);
170
+ return;
171
+ }
172
+
173
+ runTestEvents(runData, cliOption);
174
+ }
175
+ }
176
+
177
+ const bindEvent = (program) => {
178
+ program.command('run <url>')
179
+ .option('-r, --reporters <reporters>', `指定测试报告类型, 支持 cli,html,json `, `${cliOption.reporters}`)
180
+ .option('-n, --iteration-count <n>', `设置循环次数。默认值 ${cliOption.iterationCount}`)
181
+ .option('-d, --iteration-data <path>', `设置用例循环的 [公共] 测试数据路径 (JSON 或 CSV)。如设置将替换默认 [公共] 测试数据。`)
182
+ .option('--external-program-path <path>', `指定 [外部程序] 的所处文件路径,默认值为命令当前执行目录`)
183
+ .option('--out-dir <outDir>', `输出测试报告目录,默认为当前目录下的 ${cliOption.outDir}`)
184
+ .option('--out-file <outFile>', '输出测试报告文件名,不需要添加后缀,默认格式为 apipost-reports-当前 YYYY-MM-DD HH:mm:ss')
185
+ .option('--ignore-redirects <0/1>', `阻止 Apipost 自动重定向返回 3XX 状态码的请求。0 阻止, 1 不阻止`, `${cliOption.ignoreRedirects}`)
186
+ .option('--max-requst-loop <n>', `3XX重定向时的最大定向次数`, cliOption.maxRequestLoop)
187
+ .option('--timeout-request <n>', `指定接口请求超时时间`, cliOption.timeoutRequest)
188
+ .option('--timeout-script <n>', '指定脚本预执行/后执行接口运行超时时间', cliOption.timeoutScript)
189
+ // delay-request
190
+ .option('--delay-request <n>', `指定请求之间停顿间隔 (default: ${cliOption.delayRequest})`, cliOption.delayRequest)
191
+ .option('-k --insecure <n>', `关闭 SSL 校验 (1 关闭, 0 开启。default: ${cliOption.insecure})`, cliOption.insecure)
192
+ .option('--ssl-client-cert-list <path>', `客户端证书配置文件(JSON)的路径。此选项优先于sslClientCert、sslClientKey和sslClientPassphrase。`)
193
+ .option('--ssl-client-cert <path>', `指定客户端证书路径 (CRT file)`)
194
+ .option('--ssl-client-pfx <path>', `指定客户端证书路径 (PFX file)`)
195
+ .option('--ssl-client-key <path>', `指定客户端证书私钥路径 (KEY file) `)
196
+ .option('--ssl-client-passphrase <passphrase>', `指定客户端证书密码 (for protected key)`)
197
+ .option('--ssl-extra-ca-certs <path>', `指定额外受信任的 CA 证书 (PEM)`)
198
+ .option('--web-hook <url>', `Web-hook用于在任务完成后向指定URL发送数据 (POST) `)
199
+ .action((url, options) => {
200
+ parseCommandString(url, options)
201
+ });
202
+
203
+ program.on('-h --help', () => {
204
+ console.log('如何使用 Apipost');
205
+ });
206
+
207
+ program.parse();
208
+ }
209
+
210
+ const init = () => {
211
+ bindEvent(program);
212
+ }
213
+
214
+ init();