runner-runtime 1.0.8

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.
@@ -0,0 +1,272 @@
1
+ const atomicSleep = require("atomic-sleep"),
2
+ jsonpath = require("jsonpath"),
3
+ JSON5 = require('json5'),
4
+ _ = require('lodash'),
5
+ { returnBoolean, variableReplace } = require('../libs/utils');
6
+ const executeApi = require('./api'),
7
+ executeWait = require('./wait');
8
+
9
+ const events = {
10
+ api: executeApi,
11
+ sample: executeApi,
12
+ sse: executeApi,
13
+ request: executeApi, // 特殊一些。1、相当于复制;2、无目录 todo....
14
+ script: executeApi,
15
+ assert: executeApi,
16
+ assert_visual: executeApi,
17
+ sql: executeApi,
18
+ wait: executeWait, // ok
19
+ if: async (event, option, callback, eventRuntimeData, eventResultList) => { // ok
20
+ try {
21
+ const { data, children } = event;
22
+ const exp = variableReplace(data?.var, eventRuntimeData?.variables, option),
23
+ compare = data?.compare, value = variableReplace(data?.value, eventRuntimeData?.variables, option);
24
+
25
+ if (returnBoolean(exp, compare, value)) {
26
+ if (_.isArray(children) && !_.isEmpty(children)) {
27
+ const { iterationData } = option;
28
+ await iterationEvent(children, _.assign(_.cloneDeep(option), { iterationData: [iterationData] }), callback, eventRuntimeData, eventResultList)
29
+ }
30
+
31
+ return ({
32
+ action: 'ifConditionSuccess',
33
+ condition: { var: exp, compare, value },
34
+ current_event_id: event?.event_id
35
+ });
36
+ } else {
37
+ return ({
38
+ action: 'ifConditionFailed',
39
+ condition: { var: exp, compare, value },
40
+ current_event_id: event?.event_id
41
+ })
42
+ }
43
+ } catch (e) {
44
+ return ({
45
+ action: 'stystemError',
46
+ message: String(e),
47
+ current_event_id: event?.event_id
48
+ })
49
+ }
50
+ },
51
+ begin: async (event, option, callback, eventRuntimeData, eventResultList) => { // ok
52
+ try {
53
+ const { data, children } = event;
54
+
55
+ if (_.isArray(children) && !_.isEmpty(children)) {
56
+ const { iterationData, enable_data } = data;
57
+
58
+ if (_.toInteger(enable_data) > 0) {
59
+ await iterationEvent(children, _.assign(_.cloneDeep(option), { iterationData }), callback, eventRuntimeData, eventResultList)
60
+ } else {
61
+ await iterationEvent(children, option, callback, eventRuntimeData, eventResultList)
62
+ }
63
+ }
64
+
65
+ return ({
66
+ action: 'beginComplete',
67
+ current_event_id: event?.event_id
68
+ });
69
+ } catch (e) {
70
+ return ({
71
+ action: 'stystemError',
72
+ message: String(e),
73
+ current_event_id: event?.event_id
74
+ })
75
+ }
76
+ },
77
+ loop: async (event, option, callback, eventRuntimeData, eventResultList) => { // ok
78
+ try {
79
+ const { data, children } = event;
80
+ const sleep = _.toInteger(variableReplace(_.get(data, 'sleep', 0), eventRuntimeData?.variables, option));
81
+ const startTime = _.toInteger(_.get(option, 'startTime', Date.now()));
82
+ const timeout = _.toInteger(_.get(option, 'timeout', _.toInteger(variableReplace(_.get(data, 'loop_timeout', 0), eventRuntimeData?.variables, option))));
83
+
84
+ if (_.isArray(children) && !_.isEmpty(children)) {
85
+ const iterationData = [];
86
+ const enableData = _.toInteger(data?.enable_data) || 1;
87
+
88
+ if (enableData > 0) {
89
+ const loopDataType = _.toInteger(data?.loop_data_type) || 1;
90
+ switch (loopDataType) {
91
+ case 1:
92
+ _.assign(iterationData, _.values(_.get(data, 'loop_traverse_data.iterationData', [])))
93
+ break;
94
+ case 2:
95
+ const previousEventId = _.trim(_.get(data, 'loop_iteration_data'));
96
+
97
+ if (_.isString(previousEventId) && previousEventId != '') {
98
+ const dataTypeMap = {
99
+ 1: `response.body`,
100
+ 2: `response.headers`,
101
+ 3: `response.cookies`,
102
+ 4: `response.code`,
103
+ 5: `request.url`,
104
+ 6: `request.method`,
105
+ 7: `request.querys`,
106
+ 8: `request.body`,
107
+ 9: `request.headers`,
108
+ 10: `request.cookies`,
109
+ };
110
+ const dataSourceType = _.toInteger(_.get(data, 'loop_extract.var'));
111
+ const responseData = _.get(eventRuntimeData, `${previousEventId}.${dataTypeMap[dataSourceType] || "response.body"}`);
112
+ const dataJsonPath = _.trim(String(_.get(data, 'loop_extract.value', '$')));
113
+
114
+ switch (dataSourceType) {
115
+ case 1:
116
+ case 8:
117
+ try {
118
+ _.assign(iterationData, _.values(jsonpath.value(responseData, dataJsonPath)));
119
+ } catch (e) { }
120
+ break;
121
+ case 2:
122
+ case 3:
123
+ case 7:
124
+ case 9:
125
+ case 10:
126
+ _.assign(iterationData, _.values(_.get(responseData, dataJsonPath)));
127
+ break;
128
+ case 4:
129
+ case 5:
130
+ case 6:
131
+ _.assign(iterationData, _.values(responseData));
132
+ break;
133
+ }
134
+ }
135
+
136
+ break;
137
+ case 3:
138
+ let loopVariable = variableReplace(_.get(data, 'loop_variable', ''), eventRuntimeData?.variables, option);
139
+
140
+ if (!_.isObject(loopVariable)) {
141
+ loopVariable = _.values(loopVariable);
142
+ } else {
143
+ try {
144
+ loopVariable = JSON5.parse(loopVariable);
145
+ } catch (e) {
146
+ loopVariable = [];
147
+ }
148
+ }
149
+ _.assign(iterationData, loopVariable);
150
+ break;
151
+ case 4:
152
+ _.assign(iterationData, _.split(_.get(data, 'loop_fixed_value', ''), ','))
153
+ break;
154
+ }
155
+ }
156
+
157
+ // 获取循环次数
158
+ const loopType = _.toInteger(_.get(data, 'loop_type', 1));
159
+
160
+ if (_.includes([1, 2], loopType)) {
161
+ const limit = loopType === 1 ? _.size(iterationData) : _.toInteger(_.get(data, 'limit', 1));
162
+
163
+ if (limit > 0) {
164
+ const events = _.flatten(_.times(limit, () => _.cloneDeep(children)));
165
+ await iterationEvent(events, _.assign(_.cloneDeep(option), { iterationData, sleep, startTime, timeout }), callback, eventRuntimeData, eventResultList)
166
+ }
167
+ } else if (_.includes([3], loopType)) {
168
+ const condition = _.get(option, 'condition', _.get(data, 'loop_condition', {}));
169
+ const exp = variableReplace(condition?.var, eventRuntimeData?.variables, option), compare = condition?.compare, value = variableReplace(condition?.value, eventRuntimeData?.variables, option);
170
+
171
+ while (true) {
172
+ if (startTime + timeout > Date.now() && !returnBoolean(exp, compare, value)) {
173
+ await iterationEvent(_.cloneDeep(children), _.assign(_.cloneDeep(option), { iterationData, sleep, condition, startTime, timeout }), callback, eventRuntimeData, eventResultList);
174
+ } else {
175
+ break;
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ return ({
182
+ action: 'loopComplete',
183
+ current_event_id: event?.event_id
184
+ });
185
+ } catch (e) {
186
+ return ({
187
+ action: 'stystemError',
188
+ message: String(e),
189
+ current_event_id: event?.event_id
190
+ })
191
+ }
192
+ }
193
+ };
194
+
195
+ // 调度器核心函数,用于递归执行具体场景步骤
196
+ const executeEvent = (event, option, callback, eventRuntimeData, eventResultList) => {
197
+ return new Promise((resolve, reject) => {
198
+ if (_.isUndefined(_.get(eventRuntimeData, event.event_id))) {
199
+ _.set(eventRuntimeData, event.event_id, {});
200
+ }
201
+ const eventCall = _.get(events, `${event?.type}`);
202
+
203
+ if (_.isFunction(eventCall)) {
204
+ try {
205
+ eventCall(event, option, callback, eventRuntimeData, eventResultList).then((data) => {
206
+ resolve(data);
207
+
208
+ const { total, scene } = option;
209
+ if (scene == 'auto_test' && _.isInteger(event?.progress)) {
210
+ callback({
211
+ action: 'inProcess',
212
+ progress: event?.progress + 1,
213
+ total,
214
+ current_event_id: event?.event_id
215
+ })
216
+ }
217
+ })
218
+ } catch (e) { // 中止跳出执行的系统错误
219
+ if (_.toInteger(option?.ignore_error) < 1) {
220
+ reject({
221
+ action: 'stystemError',
222
+ message: String(e),
223
+ current_event_id: event?.event_id
224
+ })
225
+ }
226
+ }
227
+ } else {
228
+ reject({
229
+ action: 'warningError',
230
+ message: `This function \`${event?.type}\` is not currently supported.`,
231
+ current_event_id: event?.event_id
232
+ })
233
+ }
234
+ })
235
+ }
236
+
237
+ const iterationEvent = async (events, option, callback, eventRuntimeData, eventResultList) => {
238
+ const event = events.shift();
239
+
240
+ if (_.isObject(event) && _.has(event, 'type') && _.toInteger(event?.enabled) > 0) {
241
+ let { iterationDataArr } = option;
242
+
243
+ if (!_.isArray(iterationDataArr)) {
244
+ iterationDataArr = _.values(iterationDataArr);
245
+ }
246
+
247
+ const iterationData = iterationDataArr.shift() || {};
248
+ try {
249
+ const { sleep } = option;
250
+ const data = await executeEvent(event, { ...option, iterationData }, callback, eventRuntimeData, eventResultList);
251
+ callback(data);
252
+
253
+ if (_.toInteger(sleep) > 0) {
254
+ atomicSleep(sleep);
255
+ }
256
+ } catch (e) {
257
+ callback(e);
258
+
259
+ if (_.includes(ABORT_RECURSION_ERROR, e?.action)) {
260
+ throw new Error(String(e?.message));
261
+ }
262
+ }
263
+
264
+ if (_.isArray(events) && !_.isEmpty(events)) {
265
+ await iterationEvent(events, { ...option, iterationDataArr }, callback, eventRuntimeData, eventResultList);
266
+ }
267
+ }
268
+ }
269
+
270
+ module.exports = {
271
+ executeEvent, iterationEvent
272
+ };
package/events/wait.js ADDED
@@ -0,0 +1,27 @@
1
+ const atomicSleep = require("atomic-sleep"),
2
+ _ = require('lodash');
3
+ const { variableReplace } = require("../libs/utils");
4
+
5
+ module.exports = (event, option, callback, eventRuntimeData,eventResultList) => {
6
+ return new Promise((resolve) => {
7
+ try {
8
+ const sleep = _.toInteger(variableReplace(event?.data?.sleep, eventRuntimeData?.variables, option)) || 0;
9
+
10
+ if (sleep > 0) {
11
+ atomicSleep(sleep)
12
+ }
13
+
14
+ resolve({
15
+ action: 'sleep',
16
+ sleep,
17
+ current_event_id: event?.event_id
18
+ })
19
+ } catch (e) {
20
+ resolve({
21
+ action: 'stystemError',
22
+ message: String(e),
23
+ current_event_id: event?.event_id
24
+ })
25
+ }
26
+ });
27
+ }
package/index.js ADDED
@@ -0,0 +1,75 @@
1
+ const { repeatArrayToLength, formatAutotestReport } = require('./libs/utils');
2
+ const _ = require('lodash');
3
+ const { executeEvent, iterationEvent } = require('./events');
4
+ const ABORT_RECURSION_ERROR = ['stystemError']; //需要跳出递归的错误
5
+
6
+ // 执行场景
7
+ const run = async (events, option, callback) => {
8
+ const { iterationCount, iterationData, scene } = option;
9
+ const tempEvents = _.cloneDeep(_.flatten(new Array(scene == 'auto_test' ? _.max([_.toInteger(iterationCount), 1]) : 1).fill(events))) || [];
10
+ const iterationDataArr = repeatArrayToLength(iterationData, _.size(tempEvents));
11
+ const eventOptions = _.pick(option, ["scene", "lang", "project", "env", "globals", "cookies", "system_configs", "custom_functions", "collection", "database_configs", "enable_sandbox", "sleep", "name", "testing_id"]);
12
+ const eventRuntimeData = {
13
+ variables: {}
14
+ };
15
+ const eventResultList = [];
16
+ const startTimeAt = Date.now();
17
+
18
+ // 单接口测试
19
+ if (scene == 'http_request') {
20
+ const event = _.first(tempEvents);
21
+
22
+ if (_.isObject(event) && _.has(event, 'type')) {
23
+ try {
24
+ const data = await executeEvent(event, _.assign(eventOptions, { iterationData: {} }), callback, eventRuntimeData, eventResultList);
25
+ callback(data)
26
+ } catch (e) {
27
+ callback(e);
28
+
29
+ if (_.includes(ABORT_RECURSION_ERROR, e?.action)) {
30
+ throw new Error(String(e?.message));
31
+ }
32
+ }
33
+ }
34
+ } else if (scene == 'get_parsed_request') {
35
+ const event = _.first(tempEvents);
36
+
37
+ if (_.isObject(event) && _.has(event, 'type')) {
38
+ try {
39
+ const data = await executeEvent(event, _.assign(eventOptions, { iterationData: {} }), callback, eventRuntimeData, eventResultList);
40
+ callback(data)
41
+ } catch (e) {
42
+ callback(e);
43
+
44
+ if (_.includes(ABORT_RECURSION_ERROR, e?.action)) {
45
+ throw new Error(String(e?.message));
46
+ }
47
+ }
48
+ }
49
+ } else {
50
+ const finalEvents = [];
51
+ _.forEach(tempEvents, (event, progress) => {
52
+ if (event?.enabled > 0) {
53
+ finalEvents.push(_.assign(_.cloneDeep(event), { progress }))
54
+ }
55
+ })
56
+
57
+ // 自动化测试
58
+ if (!_.isEmpty(finalEvents)) {
59
+ const total = _.size(finalEvents);
60
+ await iterationEvent(finalEvents, _.assign(eventOptions, { iterationDataArr, total }), callback, eventRuntimeData, eventResultList);
61
+ } else {
62
+ callback({
63
+ action: 'emptyEvents'
64
+ })
65
+ }
66
+
67
+ callback({
68
+ action: 'complete',
69
+ data: _.assign(formatAutotestReport(startTimeAt, eventResultList), {
70
+ list: eventResultList
71
+ })
72
+ })
73
+ }
74
+ }
75
+ module.exports = { run }
Binary file