ScenarioModel.js 9.9 KB
Newer Older
Q
q4speed 已提交
1 2 3 4 5 6 7
import {
  Element,
  HTTPSamplerProxy,
  HashTree,
  TestElement,
  TestPlan,
  ThreadGroup,
Q
q4speed 已提交
8 9
  PostThreadGroup,
  DebugSampler,
Q
q4speed 已提交
10 11 12 13
  HeaderManager,
  HTTPSamplerArguments,
  ResponseCodeAssertion,
  ResponseDataAssertion,
Q
NaN  
q4speed 已提交
14 15
  ResponseHeadersAssertion,
  BackendListener
Q
q4speed 已提交
16 17
} from "./JMX";

Q
q4speed 已提交
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
export const uuid = function () {
  let d = new Date().getTime()
  let d2 = (performance && performance.now && (performance.now() * 1000)) || 0;
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    let r = Math.random() * 16;
    if (d > 0) {
      r = (d + r) % 16 | 0;
      d = Math.floor(d / 16);
    } else {
      r = (d2 + r) % 16 | 0;
      d2 = Math.floor(d2 / 16);
    }
    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
  });
}
Q
q4speed 已提交
33 34

export const BODY_TYPE = {
Q
q4speed 已提交
35 36 37
  KV: "KeyValue",
  FORM_DATA: "Form Data",
  RAW: "Raw"
Q
q4speed 已提交
38 39 40
}

export const ASSERTION_TYPE = {
Q
q4speed 已提交
41 42 43
  TEXT: "Text",
  REGEX: "Regex",
  RESPONSE_TIME: "Response Time"
Q
q4speed 已提交
44 45
}

Q
q4speed 已提交
46 47 48 49 50 51 52
export const ASSERTION_REGEX_SUBJECT = {
  RESPONSE_CODE: "Response Code",
  RESPONSE_HEADERS: "Response Headers",
  RESPONSE_DATA: "Response Data"
}

export class BaseConfig {
Q
q4speed 已提交
53 54 55 56 57 58 59 60 61 62 63

  set(options) {
    options = this.initOptions(options)

    for (let name in options) {
      if (options.hasOwnProperty(name)) {
        if (!(this[name] instanceof Array)) {
          this[name] = options[name];
        }
      }
    }
Q
q4speed 已提交
64 65
  }

Q
q4speed 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
  sets(types, options) {
    options = this.initOptions(options)
    if (types) {
      for (let name in types) {
        if (types.hasOwnProperty(name) && options.hasOwnProperty(name)) {
          options[name].forEach((o) => {
            this[name].push(new types[name](o));
          })
        }
      }
    }
  }

  initOptions(options) {
    return options || {};
  }
Q
q4speed 已提交
82 83 84 85

  isValid() {
    return true;
  }
Q
q4speed 已提交
86 87 88 89 90
}

export class Test extends BaseConfig {
  constructor(options) {
    super();
Q
q4speed 已提交
91
    this.version = '1.0.0';
Q
q4speed 已提交
92
    this.id = uuid();
Q
q4speed 已提交
93 94 95 96
    this.name = null;
    this.projectId = null;
    this.scenarioDefinition = [];

Q
q4speed 已提交
97 98
    this.set(options);
    this.sets({scenarioDefinition: Scenario}, options);
Q
q4speed 已提交
99 100
  }

Q
q4speed 已提交
101
  initOptions(options) {
Q
q4speed 已提交
102 103 104 105
    options = options || {};
    options.scenarioDefinition = options.scenarioDefinition || [new Scenario()];
    return options;
  }
Q
q4speed 已提交
106 107 108 109 110 111 112

  toJMX() {
    return {
      name: this.name + '.jmx',
      xml: new JMXGenerator(this).toXML()
    };
  }
Q
q4speed 已提交
113 114
}

Q
q4speed 已提交
115
export class Scenario extends BaseConfig {
Q
q4speed 已提交
116
  constructor(options) {
Q
q4speed 已提交
117
    super();
Q
q4speed 已提交
118
    this.id = uuid();
Q
q4speed 已提交
119 120
    this.name = null;
    this.url = null;
Q
q4speed 已提交
121
    this.parameters = [];
Q
q4speed 已提交
122 123 124
    this.headers = [];
    this.requests = [];

Q
q4speed 已提交
125
    this.set(options);
Q
q4speed 已提交
126
    this.sets({parameters: KeyValue, headers: KeyValue, requests: Request}, options);
Q
q4speed 已提交
127 128
  }

Q
q4speed 已提交
129
  initOptions(options) {
Q
q4speed 已提交
130 131 132
    options = options || {};
    options.requests = options.requests || [new Request()];
    return options;
Q
q4speed 已提交
133 134 135
  }
}

Q
q4speed 已提交
136
export class Request extends BaseConfig {
Q
q4speed 已提交
137
  constructor(options) {
Q
q4speed 已提交
138
    super();
Q
q4speed 已提交
139
    this.id = uuid();
Q
q4speed 已提交
140 141 142 143 144
    this.name = null;
    this.url = null;
    this.method = null;
    this.parameters = [];
    this.headers = [];
Q
q4speed 已提交
145 146
    this.body = null;
    this.assertions = null;
Q
q4speed 已提交
147 148
    this.extract = [];

Q
q4speed 已提交
149 150
    this.set(options);
    this.sets({parameters: KeyValue, headers: KeyValue}, options);
Q
q4speed 已提交
151 152 153
    // TODO assigns extract
  }

Q
q4speed 已提交
154
  initOptions(options) {
Q
q4speed 已提交
155 156 157 158 159
    options = options || {};
    options.method = "GET";
    options.body = new Body(options.body);
    options.assertions = new Assertions(options.assertions);
    return options;
Q
q4speed 已提交
160
  }
Q
q4speed 已提交
161 162 163 164

  isValid() {
    return !!this.url && !!this.method
  }
Q
q4speed 已提交
165 166
}

Q
q4speed 已提交
167
export class Body extends BaseConfig {
Q
q4speed 已提交
168
  constructor(options) {
Q
q4speed 已提交
169
    super();
Q
q4speed 已提交
170
    this.type = null;
Q
q4speed 已提交
171
    this.raw = null;
Q
q4speed 已提交
172 173
    this.kvs = [];

Q
q4speed 已提交
174 175
    this.set(options);
    this.sets({kvs: KeyValue}, options);
Q
q4speed 已提交
176 177
  }

Q
q4speed 已提交
178 179 180 181 182 183 184 185 186 187
  isValid() {
    if (this.isKV()) {
      return this.kvs.some(kv => {
        return kv.isValid();
      })
    } else {
      return !!this.raw;
    }
  }

Q
q4speed 已提交
188 189 190 191 192
  isKV() {
    return this.type === BODY_TYPE.KV;
  }
}

Q
q4speed 已提交
193
export class KeyValue extends BaseConfig {
Q
q4speed 已提交
194 195 196 197 198 199 200 201 202 203 204
  constructor() {
    let options, key, value;
    if (arguments.length === 1) {
      options = arguments[0];
    }

    if (arguments.length === 2) {
      key = arguments[0];
      value = arguments[1];
    }

Q
q4speed 已提交
205
    super();
Q
q4speed 已提交
206 207
    this.name = key;
    this.value = value;
Q
q4speed 已提交
208

Q
q4speed 已提交
209
    this.set(options);
Q
q4speed 已提交
210
  }
Q
q4speed 已提交
211 212 213 214

  isValid() {
    return !!this.name || !!this.value;
  }
Q
q4speed 已提交
215 216
}

Q
q4speed 已提交
217
export class Assertions extends BaseConfig {
Q
q4speed 已提交
218
  constructor(options) {
Q
q4speed 已提交
219
    super();
Q
q4speed 已提交
220 221
    this.text = [];
    this.regex = [];
Q
q4speed 已提交
222
    this.duration = null;
Q
q4speed 已提交
223

Q
q4speed 已提交
224
    this.set(options);
Q
q4speed 已提交
225
    this.sets({text: Text, regex: Regex}, options);
Q
q4speed 已提交
226 227
  }

Q
q4speed 已提交
228
  initOptions(options) {
Q
q4speed 已提交
229
    options = options || {};
Q
q4speed 已提交
230
    options.duration = new ResponseTime(options.duration);
Q
q4speed 已提交
231
    return options;
Q
q4speed 已提交
232 233 234
  }
}

Q
q4speed 已提交
235
export class AssertionType extends BaseConfig {
Q
q4speed 已提交
236
  constructor(type) {
Q
q4speed 已提交
237
    super();
Q
q4speed 已提交
238 239 240 241 242 243 244 245 246 247 248
    this.type = type;
  }
}

export class Text extends AssertionType {
  constructor(options) {
    super(ASSERTION_TYPE.TEXT);
    this.subject = null;
    this.condition = null;
    this.value = null;

Q
q4speed 已提交
249
    this.set(options);
Q
q4speed 已提交
250 251 252 253 254 255 256 257 258 259
  }
}

export class Regex extends AssertionType {
  constructor(options) {
    super(ASSERTION_TYPE.REGEX);
    this.subject = null;
    this.expression = null;
    this.description = null;

Q
q4speed 已提交
260
    this.set(options);
Q
q4speed 已提交
261
  }
Q
q4speed 已提交
262 263 264 265

  isValid() {
    return !!this.subject && !!this.expression;
  }
Q
q4speed 已提交
266 267 268 269 270
}

export class ResponseTime extends AssertionType {
  constructor(options) {
    super(ASSERTION_TYPE.RESPONSE_TIME);
Q
q4speed 已提交
271
    this.value = null;
Q
q4speed 已提交
272

Q
q4speed 已提交
273
    this.set(options);
Q
q4speed 已提交
274
  }
Q
q4speed 已提交
275 276 277 278

  isValid() {
    return !!this.value;
  }
Q
q4speed 已提交
279 280
}

Q
NaN  
q4speed 已提交
281
/** ------------------------------------------------------------------------ **/
Q
q4speed 已提交
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
const JMX_ASSERTION_CONDITION = {
  MATCH: 1,
  CONTAINS: 1 << 1,
  NOT: 1 << 2,
  EQUALS: 1 << 3,
  SUBSTRING: 1 << 4,
  OR: 1 << 5
}

class JMXRequest {
  constructor(request) {
    if (request && request instanceof Request && request.url) {
      let url = new URL(request.url);
      this.method = request.method;
      this.hostname = url.hostname;
      this.pathname = url.pathname;
      this.port = url.port;
      this.protocol = url.protocol.split(":")[0];
      if (this.method.toUpperCase() !== "GET") {
        this.pathname += url.search.replace('&', '&amp;');
      }
    }
  }
}

class JMeterTestPlan extends Element {
  constructor() {
    super('jmeterTestPlan', {
      version: "1.2", properties: "5.0", jmeter: "5.2.1"
    });

    this.add(new HashTree());
  }

  put(te) {
    if (te instanceof TestElement) {
      this.elements[0].add(te);
    }
  }
}

class JMXGenerator {
  constructor(test) {
Q
q4speed 已提交
325 326 327 328 329 330
    if (!test || !(test instanceof Test)) return null;

    if (!test.id) {
      test.id = "#NULL_TEST_ID#";
    }
    const SPLIT = "@@:";
Q
q4speed 已提交
331 332 333

    let testPlan = new TestPlan(test.name);
    test.scenarioDefinition.forEach(scenario => {
Q
q4speed 已提交
334
      let threadGroup = new ThreadGroup(scenario.name + SPLIT + scenario.id);
Q
q4speed 已提交
335

Q
q4speed 已提交
336
      scenario.requests.forEach(request => {
Q
q4speed 已提交
337 338 339 340 341
        if (!request.isValid()) return;

        // test.id用于处理结果时区分属于哪个测试
        let name = request.name + SPLIT + test.id;
        let httpSamplerProxy = new HTTPSamplerProxy(name, new JMXRequest(request));
Q
q4speed 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354 355

        this.addRequestHeader(httpSamplerProxy, request);

        if (request.method.toUpperCase() === 'GET') {
          this.addRequestArguments(httpSamplerProxy, request);
        } else {
          this.addRequestBody(httpSamplerProxy, request);
        }

        this.addRequestAssertion(httpSamplerProxy, request);

        threadGroup.put(httpSamplerProxy);
      })

Q
q4speed 已提交
356
      this.addBackendListener(threadGroup);
Q
q4speed 已提交
357
      testPlan.put(threadGroup);
Q
q4speed 已提交
358 359 360 361 362 363 364

      // 暂时不加
      // let tearDownThreadGroup = new PostThreadGroup();
      // tearDownThreadGroup.put(new DebugSampler(test.id));
      // this.addBackendListener(tearDownThreadGroup);
      //
      // testPlan.put(tearDownThreadGroup);
Q
q4speed 已提交
365 366 367 368 369 370 371 372 373
    })

    this.jmeterTestPlan = new JMeterTestPlan();
    this.jmeterTestPlan.put(testPlan);
  }

  addRequestHeader(httpSamplerProxy, request) {
    let name = request.name + " Headers";
    let headers = request.headers.filter(this.filter);
Q
q4speed 已提交
374
    if (headers.length > 0) {
Q
q4speed 已提交
375
      httpSamplerProxy.put(new HeaderManager(name, headers));
Q
q4speed 已提交
376
    }
Q
q4speed 已提交
377 378 379 380
  }

  addRequestArguments(httpSamplerProxy, request) {
    let args = request.parameters.filter(this.filter)
Q
q4speed 已提交
381
    if (args.length > 0) {
Q
q4speed 已提交
382
      httpSamplerProxy.add(new HTTPSamplerArguments(args));
Q
q4speed 已提交
383
    }
Q
q4speed 已提交
384 385 386 387 388 389 390 391 392 393
  }

  addRequestBody(httpSamplerProxy, request) {
    let body = [];
    if (request.body.isKV()) {
      body = request.body.kvs.filter(this.filter);
    } else {
      body.push({name: '', value: request.body.raw});
    }

Q
q4speed 已提交
394 395
    httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
    httpSamplerProxy.add(new HTTPSamplerArguments(body));
Q
q4speed 已提交
396 397 398 399 400 401
  }

  addRequestAssertion(httpSamplerProxy, request) {
    let assertions = request.assertions;
    if (assertions.regex.length > 0) {
      assertions.regex.filter(this.filter).forEach(regex => {
Q
q4speed 已提交
402
        httpSamplerProxy.put(this.getAssertion(regex));
Q
q4speed 已提交
403 404 405 406
      })
    }

    if (assertions.duration.isValid()) {
Q
q4speed 已提交
407
      httpSamplerProxy.put(assertions.duration.type, assertions.duration.value);
Q
q4speed 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
    }
  }

  getAssertion(regex) {
    let name = regex.description;
    let type = JMX_ASSERTION_CONDITION.MATCH; // 固定用Match,自己写正则
    let value = regex.expression;
    switch (regex.subject) {
      case ASSERTION_REGEX_SUBJECT.RESPONSE_CODE:
        return new ResponseCodeAssertion(name, type, value);
      case ASSERTION_REGEX_SUBJECT.RESPONSE_DATA:
        return new ResponseDataAssertion(name, type, value);
      case ASSERTION_REGEX_SUBJECT.RESPONSE_HEADERS:
        return new ResponseHeadersAssertion(name, type, value);
    }
  }

Q
q4speed 已提交
425
  addBackendListener(threadGroup) {
Q
NaN  
q4speed 已提交
426 427
    let testName = 'API Backend Listener';
    let className = 'io.metersphere.api.jmeter.APIBackendListenerClient';
Q
q4speed 已提交
428
    threadGroup.put(new BackendListener(testName, className));
Q
NaN  
q4speed 已提交
429 430
  }

Q
q4speed 已提交
431 432 433 434 435 436 437 438 439 440 441 442
  filter(config) {
    return config.isValid();
  }

  toXML() {
    let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
    xml += this.jmeterTestPlan.toXML();
    return xml;
  }
}