This commit is contained in:
chenjianxing 2020-06-15 19:00:42 +08:00
commit 1e99e83ea4
24 changed files with 226 additions and 36 deletions

View File

@ -1,8 +1,11 @@
package io.metersphere.api.dto; package io.metersphere.api.dto;
import io.metersphere.api.dto.scenario.Scenario;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import java.util.List;
@Setter @Setter
@Getter @Getter
public class SaveAPITestRequest { public class SaveAPITestRequest {
@ -13,5 +16,5 @@ public class SaveAPITestRequest {
private String name; private String name;
private String scenarioDefinition; private List<Scenario> scenarioDefinition;
} }

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
import java.util.List;
@Data
public class Body {
private String type;
private String raw;
private List<KeyValue> kvs;
}

View File

@ -0,0 +1,9 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
@Data
public class KeyValue {
private String name;
private String value;
}

View File

@ -0,0 +1,19 @@
package io.metersphere.api.dto.scenario;
import io.metersphere.api.dto.scenario.assertions.Assertions;
import io.metersphere.api.dto.scenario.extract.Extract;
import lombok.Data;
import java.util.List;
@Data
public class Request {
private String name;
private String url;
private String method;
private List<KeyValue> parameters;
private List<KeyValue> headers;
private Body body;
private Assertions assertions;
private Extract extract;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.api.dto.scenario;
import lombok.Data;
import java.util.List;
@Data
public class Scenario {
private String name;
private String url;
private List<KeyValue> variables;
private List<KeyValue> headers;
private List<Request> requests;
}

View File

@ -0,0 +1,14 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class AssertionDuration extends AssertionType {
private long value;
public AssertionDuration() {
setType(AssertionType.DURATION);
}
}

View File

@ -0,0 +1,16 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class AssertionRegex extends AssertionType {
private String subject;
private String expression;
private String description;
public AssertionRegex() {
setType(AssertionType.REGEX);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
@Data
public class AssertionType {
public final static String REGEX = "Regex";
public final static String DURATION = "Duration";
public final static String TEXT = "Text";
private String type;
}

View File

@ -0,0 +1,11 @@
package io.metersphere.api.dto.scenario.assertions;
import lombok.Data;
import java.util.List;
@Data
public class Assertions {
private List<AssertionRegex> regex;
private AssertionDuration duration;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import java.util.List;
@Data
public class Extract {
private List<ExtractRegex> regex;
private List<ExtractJSONPath> json;
private List<ExtractXPath> xpath;
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractCommon extends ExtractType {
private String variable;
private String value; // value: ${variable}
private String expression;
private String description;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractJSONPath extends ExtractCommon {
public ExtractJSONPath() {
setType(ExtractType.JSON_PATH);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractRegex extends ExtractCommon {
public ExtractRegex() {
setType(ExtractType.REGEX);
}
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
@Data
public class ExtractType {
public final static String REGEX = "Regex";
public final static String JSON_PATH = "JSONPath";
public final static String XPATH = "XPath";
private String type;
}

View File

@ -0,0 +1,12 @@
package io.metersphere.api.dto.scenario.extract;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class ExtractXPath extends ExtractCommon {
public ExtractXPath() {
setType(ExtractType.XPATH);
}
}

View File

@ -1,5 +1,6 @@
package io.metersphere.api.service; package io.metersphere.api.service;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.APITestResult; import io.metersphere.api.dto.APITestResult;
import io.metersphere.api.dto.QueryAPITestRequest; import io.metersphere.api.dto.QueryAPITestRequest;
import io.metersphere.api.dto.SaveAPITestRequest; import io.metersphere.api.dto.SaveAPITestRequest;
@ -25,6 +26,7 @@ import java.io.ByteArrayInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -73,10 +75,17 @@ public class APITestService {
} }
public void copy(SaveAPITestRequest request) { public void copy(SaveAPITestRequest request) {
request.setName(request.getName() + " Copy");
try {
checkNameExist(request);
} catch (Exception e) {
request.setName(request.getName() + " " + new Random().nextInt(1000));
}
// copy test // copy test
ApiTestWithBLOBs copy = get(request.getId()); ApiTestWithBLOBs copy = get(request.getId());
copy.setId(UUID.randomUUID().toString()); copy.setId(UUID.randomUUID().toString());
copy.setName(copy.getName() + " Copy"); copy.setName(request.getName());
copy.setCreateTime(System.currentTimeMillis()); copy.setCreateTime(System.currentTimeMillis());
copy.setUpdateTime(System.currentTimeMillis()); copy.setUpdateTime(System.currentTimeMillis());
copy.setStatus(APITestStatus.Saved.name()); copy.setStatus(APITestStatus.Saved.name());
@ -96,6 +105,10 @@ public class APITestService {
return apiTestMapper.selectByPrimaryKey(id); return apiTestMapper.selectByPrimaryKey(id);
} }
public List<ApiTest> getApiTestByProjectId(String projectId) {
return extApiTestMapper.getApiTestByProjectId(projectId);
}
public void delete(String testId) { public void delete(String testId) {
deleteFileByTestId(testId); deleteFileByTestId(testId);
apiReportService.deleteByTestId(testId); apiReportService.deleteByTestId(testId);
@ -138,7 +151,7 @@ public class APITestService {
test.setId(request.getId()); test.setId(request.getId());
test.setName(request.getName()); test.setName(request.getName());
test.setProjectId(request.getProjectId()); test.setProjectId(request.getProjectId());
test.setScenarioDefinition(request.getScenarioDefinition()); test.setScenarioDefinition(JSONObject.toJSONString(request.getScenarioDefinition()));
test.setUpdateTime(System.currentTimeMillis()); test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Saved.name()); test.setStatus(APITestStatus.Saved.name());
apiTestMapper.updateByPrimaryKeySelective(test); apiTestMapper.updateByPrimaryKeySelective(test);
@ -151,7 +164,7 @@ public class APITestService {
test.setId(request.getId()); test.setId(request.getId());
test.setName(request.getName()); test.setName(request.getName());
test.setProjectId(request.getProjectId()); test.setProjectId(request.getProjectId());
test.setScenarioDefinition(request.getScenarioDefinition()); test.setScenarioDefinition(JSONObject.toJSONString(request.getScenarioDefinition()));
test.setCreateTime(System.currentTimeMillis()); test.setCreateTime(System.currentTimeMillis());
test.setUpdateTime(System.currentTimeMillis()); test.setUpdateTime(System.currentTimeMillis());
test.setStatus(APITestStatus.Saved.name()); test.setStatus(APITestStatus.Saved.name());
@ -194,7 +207,4 @@ public class APITestService {
} }
} }
public List<ApiTest> getApiTestByProjectId(String projectId) {
return extApiTestMapper.getApiTestByProjectId(projectId);
}
} }

View File

@ -5,7 +5,8 @@
<el-container class="test-container" v-loading="result.loading"> <el-container class="test-container" v-loading="result.loading">
<el-header> <el-header>
<el-row type="flex" align="middle"> <el-row type="flex" align="middle">
<el-input :disabled="isReadOnly" class="test-name" v-model="test.name" maxlength="60" :placeholder="$t('api_test.input_name')" <el-input :disabled="isReadOnly" class="test-name" v-model="test.name" maxlength="60"
:placeholder="$t('api_test.input_name')"
show-word-limit> show-word-limit>
<el-select :disabled="isReadOnly" class="test-project" v-model="test.projectId" slot="prepend" <el-select :disabled="isReadOnly" class="test-project" v-model="test.projectId" slot="prepend"
:placeholder="$t('api_test.select_project')"> :placeholder="$t('api_test.select_project')">
@ -17,7 +18,8 @@
{{$t('commons.save')}} {{$t('commons.save')}}
</el-button> </el-button>
<el-button type="primary" plain v-if="!isShowRun" :disabled="isDisabled || isReadOnly" @click="saveRunTest"> <el-button type="primary" plain v-if="!isShowRun" :disabled="isDisabled || isReadOnly"
@click="saveRunTest">
{{$t('load_test.save_and_run')}} {{$t('load_test.save_and_run')}}
</el-button> </el-button>
@ -25,7 +27,8 @@
{{$t('api_test.run')}} {{$t('api_test.run')}}
</el-button> </el-button>
<el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}</el-button> <el-button :disabled="isReadOnly" type="warning" plain @click="cancel">{{$t('commons.cancel')}}
</el-button>
<el-dropdown trigger="click" @command="handleCommand"> <el-dropdown trigger="click" @command="handleCommand">
<el-button class="el-dropdown-link more" icon="el-icon-more" plain/> <el-button class="el-dropdown-link more" icon="el-icon-more" plain/>
@ -165,13 +168,7 @@
}, },
getOptions(url) { getOptions(url) {
let formData = new FormData(); let formData = new FormData();
let request = { let requestJson = JSON.stringify(this.test);
id: this.test.id,
projectId: this.test.projectId,
name: this.test.name,
scenarioDefinition: JSON.stringify(this.test.scenarioDefinition)
}
let requestJson = JSON.stringify(request);
formData.append('request', new Blob([requestJson], { formData.append('request', new Blob([requestJson], {
type: "application/json" type: "application/json"

View File

@ -143,7 +143,7 @@
}); });
}, },
handleCopy(test) { handleCopy(test) {
this.result = this.$post("/api/copy", {id: test.id}, () => { this.result = this.$post("/api/copy", {projectId: test.projectId, id: test.id, name: test.name}, () => {
this.$success(this.$t('commons.copy_success')); this.$success(this.$t('commons.copy_success'));
this.search(); this.search();
}); });

View File

@ -15,13 +15,13 @@
<script> <script>
import {ResponseTime} from "../../model/ScenarioModel"; import {Duration} from "../../model/ScenarioModel";
export default { export default {
name: "MsApiAssertionResponseTime", name: "MsApiAssertionDuration",
props: { props: {
duration: ResponseTime, duration: Duration,
value: [Number, String], value: [Number, String],
edit: Boolean, edit: Boolean,
callback: Function, callback: Function,

View File

@ -6,14 +6,14 @@
size="small"> size="small">
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/> <el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/> <el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
<el-option :label="$t('api_test.request.assertions.response_time')" :value="options.RESPONSE_TIME"/> <el-option :label="$t('api_test.request.assertions.response_time')" :value="options.DURATION"/>
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="20"> <el-col :span="20">
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/> <ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/> <ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
<ms-api-assertion-response-time :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration" <ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
v-if="type === options.RESPONSE_TIME" :callback="after"/> v-if="type === options.DURATION" :callback="after"/>
</el-col> </el-col>
</el-row> </el-row>
@ -24,14 +24,14 @@
<script> <script>
import MsApiAssertionText from "./ApiAssertionText"; import MsApiAssertionText from "./ApiAssertionText";
import MsApiAssertionRegex from "./ApiAssertionRegex"; import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionResponseTime from "./ApiAssertionResponseTime"; import MsApiAssertionDuration from "./ApiAssertionDuration";
import {ASSERTION_TYPE, Assertions, ResponseTime} from "../../model/ScenarioModel"; import {ASSERTION_TYPE, Assertions} from "../../model/ScenarioModel";
import MsApiAssertionsEdit from "./ApiAssertionsEdit"; import MsApiAssertionsEdit from "./ApiAssertionsEdit";
export default { export default {
name: "MsApiAssertions", name: "MsApiAssertions",
components: {MsApiAssertionsEdit, MsApiAssertionResponseTime, MsApiAssertionRegex, MsApiAssertionText}, components: {MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
props: { props: {
assertions: Assertions, assertions: Assertions,

View File

@ -13,7 +13,7 @@
<div> <div>
{{$t("api_test.request.assertions.response_time")}} {{$t("api_test.request.assertions.response_time")}}
</div> </div>
<ms-api-assertion-response-time :is-read-only="isReadOnly" v-model="assertions.duration.value" :duration="assertions.duration" :edit="true"/> <ms-api-assertion-duration :is-read-only="isReadOnly" v-model="assertions.duration.value" :duration="assertions.duration" :edit="true"/>
</div> </div>
</div> </div>
@ -21,13 +21,13 @@
<script> <script>
import MsApiAssertionRegex from "./ApiAssertionRegex"; import MsApiAssertionRegex from "./ApiAssertionRegex";
import MsApiAssertionResponseTime from "./ApiAssertionResponseTime"; import MsApiAssertionDuration from "./ApiAssertionDuration";
import {Assertions} from "../../model/ScenarioModel"; import {Assertions} from "../../model/ScenarioModel";
export default { export default {
name: "MsApiAssertionsEdit", name: "MsApiAssertionsEdit",
components: {MsApiAssertionResponseTime, MsApiAssertionRegex}, components: {MsApiAssertionDuration, MsApiAssertionRegex},
props: { props: {
assertions: Assertions, assertions: Assertions,

View File

@ -40,7 +40,7 @@ export const BODY_TYPE = {
export const ASSERTION_TYPE = { export const ASSERTION_TYPE = {
TEXT: "Text", TEXT: "Text",
REGEX: "Regex", REGEX: "Regex",
RESPONSE_TIME: "Response Time" DURATION: "Duration"
} }
export const ASSERTION_REGEX_SUBJECT = { export const ASSERTION_REGEX_SUBJECT = {
@ -253,7 +253,7 @@ export class Assertions extends BaseConfig {
initOptions(options) { initOptions(options) {
options = options || {}; options = options || {};
options.duration = new ResponseTime(options.duration); options.duration = new Duration(options.duration);
return options; return options;
} }
} }
@ -291,9 +291,9 @@ export class Regex extends AssertionType {
} }
} }
export class ResponseTime extends AssertionType { export class Duration extends AssertionType {
constructor(options) { constructor(options) {
super(ASSERTION_TYPE.RESPONSE_TIME); super(ASSERTION_TYPE.DURATION);
this.value = undefined; this.value = undefined;
this.set(options); this.set(options);

View File

@ -61,7 +61,7 @@
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('commons.description')" prop="description"> <el-form-item :label="$t('commons.description')" prop="description">
<el-input v-model="form.description" autocomplete="off"/> <el-input v-model="form.description" autocomplete="off" type="textarea"/>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template v-slot:footer> <template v-slot:footer>

View File

@ -61,7 +61,7 @@
<el-form-item :label="$t('commons.name')" prop="name"> <el-form-item :label="$t('commons.name')" prop="name">
<el-input v-model="form.name" autocomplete="off"/> <el-input v-model="form.name" autocomplete="off"/>
</el-form-item> </el-form-item>
<el-form-item :label="$t('commons.description')"> <el-form-item :label="$t('commons.description')" prop="description">
<el-input type="textarea" v-model="form.description"></el-input> <el-input type="textarea" v-model="form.description"></el-input>
</el-form-item> </el-form-item>
<el-form-item :label="$t('workspace.organization_name')" prop="organizationId"> <el-form-item :label="$t('workspace.organization_name')" prop="organizationId">