Merge remote-tracking branch 'origin/master'

This commit is contained in:
wenyann 2020-07-16 11:20:04 +08:00
commit 4a22ef3224
36 changed files with 568 additions and 1385 deletions

View File

@ -167,6 +167,11 @@ curl -sSL https://github.com/metersphere/metersphere/releases/latest/download/qu
- 基础设施: [Docker](https://www.docker.com/), [Kubernetes](https://kubernetes.io/)
- 测试引擎: [JMeter](https://jmeter.apache.org/)
## 加入 MeterSphere 团队
我们正在招聘 MeterSphere 技术布道师,一起打造开源明星项目,请发简历到 metersphere@fit2cloud.com
点击查看 [岗位详情](https://www.zhipin.com/job_detail/b151c4b3d594688733Ny3dy1GFI~.html)
## 微信群
![wechat-group](https://metersphere.io/images/contact/wechat-group.png)

View File

@ -190,6 +190,13 @@
<artifactId>spring-boot-starter-data-ldap</artifactId>
</dependency>
<!-- swagger 解析 -->
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-parser</artifactId>
<version>1.0.51</version>
</dependency>
</dependencies>
<build>

View File

@ -14,4 +14,5 @@ public class SwaggerApi {
private List<String> schemes;
private List<SwaggerTag> tags;
private JSONObject paths;
private JSONObject definitions;
}

View File

@ -12,5 +12,5 @@ public class SwaggerRequest {
private String operationId;
private List<String> consumes;
private List<String> produces;
private SwaggerParameter parameters;
private List<SwaggerParameter> parameters;
}

View File

@ -6,6 +6,7 @@ import lombok.Data;
public class KeyValue {
private String name;
private String value;
private String description;
public KeyValue() {
}
@ -14,4 +15,10 @@ public class KeyValue {
this.name = name;
this.value = value;
}
public KeyValue(String name, String value, String description) {
this.name = name;
this.value = value;
this.description = description;
}
}

View File

@ -1,22 +1,30 @@
package io.metersphere.api.parse;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.Request;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jetty.http.HttpHeader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public abstract class ApiImportAbstractParser implements ApiImportParser {
protected String getApiTestStr(InputStream source) {
BufferedReader bufferedReader = null;
BufferedReader bufferedReader;
StringBuilder testStr = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(source, "UTF-8"));
bufferedReader = new BufferedReader(new InputStreamReader(source, StandardCharsets.UTF_8));
testStr = new StringBuilder();
String inputStr = null;
String inputStr;
while ((inputStr = bufferedReader.readLine()) != null) {
testStr.append(inputStr);
}
@ -34,4 +42,36 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
return testStr.toString();
}
protected void addContentType(Request request, String contentType) {
addHeader(request, HttpHeader.CONTENT_TYPE.toString(), contentType);
}
protected void addCookie(Request request, String key, String value) {
List<KeyValue> headers = Optional.ofNullable(request.getHeaders()).orElse(new ArrayList<>());
boolean hasCookie = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase(HttpHeader.COOKIE.name(), header.getName())) {
hasCookie = true;
String cookies = Optional.ofNullable(header.getValue()).orElse("");
header.setValue(cookies + key + "=" + value + ";");
}
}
if (!hasCookie) {
addHeader(request, HttpHeader.COOKIE.name(), key + "=" + value + ";");
}
}
protected void addHeader(Request request, String key, String value) {
List<KeyValue> headers = Optional.ofNullable(request.getHeaders()).orElse(new ArrayList<>());
boolean hasContentType = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase(header.getName(), key)) {
hasContentType = true;
}
}
if (!hasContentType) {
headers.add(new KeyValue(key, value));
}
request.setHeaders(headers);
}
}

View File

@ -12,6 +12,8 @@ public class ApiImportParserFactory {
return new MsParser();
} else if (StringUtils.equals(ApiImportPlatform.Postman.name(), platform)) {
return new PostmanParser();
} else if (StringUtils.equals(ApiImportPlatform.Swagger2.name(), platform)) {
return new Swagger2Parser();
}
return null;
}

View File

@ -2,17 +2,15 @@ package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import java.io.*;
import java.io.InputStream;
public class MsParser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
return JSON.parseObject(testStr.toString(), ApiImport.class);
return JSON.parseObject(testStr, ApiImport.class);
}
}

View File

@ -1,7 +1,6 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.parse.postman.*;
@ -11,14 +10,9 @@ import io.metersphere.api.dto.scenario.Request;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.commons.constants.MsRequestBodyType;
import io.metersphere.commons.constants.PostmanRequestBodyMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil;
import org.apache.commons.lang3.StringUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
@ -27,7 +21,7 @@ public class PostmanParser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
PostmanCollection postmanCollection = JSON.parseObject(testStr.toString(), PostmanCollection.class);
PostmanCollection postmanCollection = JSON.parseObject(testStr, PostmanCollection.class);
PostmanCollectionInfo info = postmanCollection.getInfo();
List<Request> requests = parseRequests(postmanCollection);
ApiImport apiImport = new ApiImport();
@ -45,9 +39,7 @@ public class PostmanParser extends ApiImportAbstractParser {
return null;
}
List<KeyValue> keyValues = new ArrayList<>();
postmanKeyValues.forEach(item -> {
keyValues.add(new KeyValue(item.getKey(), item.getValue()));
});
postmanKeyValues.forEach(item -> keyValues.add(new KeyValue(item.getKey(), item.getValue())));
return keyValues;
}
@ -64,32 +56,27 @@ public class PostmanParser extends ApiImportAbstractParser {
request.setMethod(requestDesc.getMethod());
request.setHeaders(parseKeyValue(requestDesc.getHeader()));
request.setParameters(parseKeyValue(url.getQuery()));
Body body = new Body();
JSONObject postmanBody = requestDesc.getBody();
String bodyMode = postmanBody.getString("mode");
if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) {
body.setRaw(postmanBody.getString(bodyMode));
body.setType(MsRequestBodyType.RAW.value());
String contentType = postmanBody.getJSONObject("options").getJSONObject("raw").getString("language");
List<KeyValue> headers = request.getHeaders();
boolean hasContentType = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase(header.getName(), "Content-Type")) {
hasContentType = true;
}
}
if (!hasContentType) {
headers.add(new KeyValue("Content-Type", contentType));
}
} else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value()) || StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) {
List<PostmanKeyValue> postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class);
body.setType(MsRequestBodyType.KV.value());
body.setKvs(parseKeyValue(postmanKeyValues));
}
request.setBody(body);
request.setBody(parseBody(requestDesc, request));
requests.add(request);
}
return requests;
}
private Body parseBody(PostmanRequest requestDesc, Request request) {
Body body = new Body();
JSONObject postmanBody = requestDesc.getBody();
String bodyMode = postmanBody.getString("mode");
if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) {
body.setRaw(postmanBody.getString(bodyMode));
body.setType(MsRequestBodyType.RAW.value());
String contentType = postmanBody.getJSONObject("options").getJSONObject("raw").getString("language");
addContentType(request, contentType);
} else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value()) || StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) {
List<PostmanKeyValue> postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class);
body.setType(MsRequestBodyType.KV.value());
body.setKvs(parseKeyValue(postmanKeyValues));
}
return body;
}
}

View File

@ -0,0 +1,185 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.Request;
import io.metersphere.api.dto.scenario.Scenario;
import io.metersphere.commons.constants.MsRequestBodyType;
import io.metersphere.commons.constants.SwaggerParameterType;
import io.swagger.models.*;
import io.swagger.models.parameters.*;
import io.swagger.models.properties.ArrayProperty;
import io.swagger.models.properties.ObjectProperty;
import io.swagger.models.properties.Property;
import io.swagger.models.properties.RefProperty;
import io.swagger.parser.SwaggerParser;
import java.io.InputStream;
import java.util.*;
public class Swagger2Parser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
// Swagger swagger = new SwaggerParser().read("http://petstore.swagger.io/v2/swagger.json");
Swagger swagger = new SwaggerParser().readWithInfo(testStr).getSwagger();
ApiImport apiImport = new ApiImport();
apiImport.setScenarios(parseRequests(swagger));
return apiImport;
}
private List<Scenario> parseRequests(Swagger swagger) {
Map<String, Path> paths = swagger.getPaths();
Set<String> pathNames = paths.keySet();
Map<String, Scenario> scenarioMap = new HashMap<>();
List<Scenario> scenarios = new ArrayList<>();
for (String pathName : pathNames) {
Path path = paths.get(pathName);
Map<HttpMethod, Operation> operationMap = path.getOperationMap();
Set<HttpMethod> httpMethods = operationMap.keySet();
for (HttpMethod method : httpMethods) {
Operation operation = operationMap.get(method);
Request request = new Request();
request.setName(operation.getOperationId());
request.setPath(pathName);
request.setUseEnvironment(true);
request.setMethod(method.name());
parseParameters(operation, swagger.getDefinitions(), request);
List<String> tags = operation.getTags();
if (tags != null) {
tags.forEach(tag -> {
Scenario scenario = Optional.ofNullable(scenarioMap.get(tag)).orElse(new Scenario());
List<Request> requests = Optional.ofNullable(scenario.getRequests()).orElse(new ArrayList<>());
requests.add(request);
scenario.setRequests(requests);scenario.setName(tag);
scenarioMap.put(tag, scenario);
});
} else {
Scenario scenario = Optional.ofNullable(scenarioMap.get("default")).orElse(new Scenario());
List<Request> requests = Optional.ofNullable(scenario.getRequests()).orElse(new ArrayList<>());
requests.add(request);
scenario.setRequests(requests);
scenarioMap.put("default", scenario);
}
}
}
scenarioMap.values().forEach(scenario -> {
scenarios.add(scenario);
});
return scenarios;
}
private void parseParameters(Operation operation, Map<String, Model> definitions, Request request) {
List<Parameter> parameters = operation.getParameters();
for (Parameter parameter : parameters) {
switch (parameter.getIn()){
// case SwaggerParameterType.PATH:
// parsePathParameters(parameter, request);
// break;
case SwaggerParameterType.QUERY:
parseQueryParameters(parameter, request);
break;
case SwaggerParameterType.FORM_DATA:
parseFormDataParameters(parameter, request);
break;
case SwaggerParameterType.BODY:
parseBodyParameters(parameter, request, definitions);
break;
case SwaggerParameterType.HEADER:
parseHeaderParameters(parameter, request);
break;
case SwaggerParameterType.COOKIE:
parseCookieParameters(parameter, request);
break;
// case SwaggerParameterType.FILE:
// parsePathParameters(parameter, request);
// break;
}
}
}
private void parseCookieParameters(Parameter parameter, Request request) {
CookieParameter cookieParameter = (CookieParameter) parameter;
addCookie(request, cookieParameter.getName(), cookieParameter.getDescription());
}
private void parseHeaderParameters(Parameter parameter, Request request) {
HeaderParameter headerParameter = (HeaderParameter) parameter;
addHeader(request, headerParameter.getName(), headerParameter.getDescription());
}
private void parseBodyParameters(Parameter parameter, Request request, Map<String, Model> definitions) {
BodyParameter bodyParameter = (BodyParameter) parameter;
Body body = Optional.ofNullable(request.getBody()).orElse(new Body());
body.setType(MsRequestBodyType.RAW.value());
Model schema = bodyParameter.getSchema();
if (schema instanceof RefModel) {
RefModel refModel = (RefModel) bodyParameter.getSchema();
Model model = definitions.get(refModel.getSimpleRef());
JSONObject bodyParameters = getBodyJSONObjectParameters(model.getProperties(), definitions);
body.setRaw(bodyParameters.toJSONString());
} else if (schema instanceof ArrayModel) {
ArrayModel arrayModel = (ArrayModel) bodyParameter.getSchema();
Property items = arrayModel.getItems();
if (items instanceof RefProperty) {
RefProperty refProperty = (RefProperty) items;
Model model = definitions.get(refProperty.getSimpleRef());
JSONArray propertyList = new JSONArray();
propertyList.add(getBodyJSONObjectParameters(model.getProperties(), definitions));
body.setRaw(propertyList.toString());
}
}
request.setBody(body);
addContentType(request, "application/json");
}
private JSONObject getBodyJSONObjectParameters(Map<String, Property> properties, Map<String, Model> definitions) {
JSONObject jsonObject = new JSONObject();
properties.forEach((key, value) -> {
if (value instanceof ObjectProperty) {
ObjectProperty objectProperty = (ObjectProperty) value;
jsonObject.put(key, getBodyJSONObjectParameters(objectProperty.getProperties(), definitions));
} else if (value instanceof ArrayProperty) {
ArrayProperty arrayProperty = (ArrayProperty) value;
Property items = arrayProperty.getItems();
if (items instanceof RefProperty) {
RefProperty refProperty = (RefProperty) items;
Model model = definitions.get(refProperty.getSimpleRef());
JSONArray propertyList = new JSONArray();
propertyList.add(getBodyJSONObjectParameters(model.getProperties(), definitions));
jsonObject.put(key, propertyList);
} else {
jsonObject.put(key, new ArrayList<>());
}
} else {
jsonObject.put(key, Optional.ofNullable(value.getDescription()).orElse(""));
}
});
return jsonObject;
}
private void parseFormDataParameters(Parameter parameter, Request request) {
Body body = Optional.ofNullable(request.getBody()).orElse(new Body());
body.setType(MsRequestBodyType.FORM_DATA.value());
List<KeyValue> keyValues = Optional.ofNullable(body.getKvs()).orElse(new ArrayList<>());
keyValues.add(new KeyValue(parameter.getName(), "", parameter.getDescription()));
body.setKvs(keyValues);
request.setBody(body);
}
private void parseQueryParameters(Parameter parameter, Request request) {
QueryParameter queryParameter = (QueryParameter) parameter;
List<KeyValue> parameters = Optional.ofNullable(request.getParameters()).orElse(new ArrayList<>());
parameters.add(new KeyValue(queryParameter.getName(), "", queryParameter.getDescription()));
request.setParameters(parameters);
}
}

View File

@ -1,106 +0,0 @@
package io.metersphere.api.parse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.parse.ApiImport;
import io.metersphere.api.dto.parse.postman.PostmanItem;
import io.metersphere.api.dto.parse.postman.PostmanRequest;
import io.metersphere.api.dto.parse.postman.PostmanUrl;
import io.metersphere.api.dto.parse.swagger.SwaggerApi;
import io.metersphere.api.dto.parse.swagger.SwaggerInfo;
import io.metersphere.api.dto.parse.swagger.SwaggerRequest;
import io.metersphere.api.dto.scenario.Request;
import io.metersphere.api.dto.scenario.Scenario;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class SwaggerParser extends ApiImportAbstractParser {
@Override
public ApiImport parse(InputStream source) {
String testStr = getApiTestStr(source);
SwaggerApi swaggerApi = JSON.parseObject(testStr.toString(), SwaggerApi.class);
SwaggerInfo info = swaggerApi.getInfo();
String title = info.getTitle();
// List<Request> requests = parseRequests(swaggerApi);
// ApiImport apiImport = new ApiImport();
// List<Scenario> scenarios = new ArrayList<>();
// Scenario scenario = new Scenario();
// scenario.setRequests(requests);
// scenario.setName(info.getName());
// scenarios.add(scenario);
// apiImport.setScenarios(scenarios);
// return apiImport;
return null;
}
// private List<Request> parseRequests(SwaggerApi swaggerApi) {
// JSONObject paths = swaggerApi.getPaths();
//
// Set<String> pathNames = paths.keySet();
//
// for (String path : pathNames) {
// JSONObject pathObject = paths.getJSONObject(path);
// Set<String> methods = pathObject.keySet();
// for (String method : methods) {
// SwaggerRequest swaggerRequest = JSON.parseObject(pathObject.getJSONObject(method).toJSONString(), SwaggerRequest.class);
// Request request = new Request();
// request.setName(swaggerRequest.getOperationId());
// request.setUrl(url.getRaw());
// request.setPath(.getRaw());
// request.setUseEnvironment(false);
// request.setMethod(requestDesc.getMethod());
// request.setHeaders(parseKeyValue(requestDesc.getHeader()));
// request.setParameters(parseKeyValue(url.getQuery()));
//
// }
// }
//
// List<PostmanItem> item = postmanCollection.getItem();
// List<Request> requests = new ArrayList<>();
// for (PostmanItem requestItem : item) {
// Request request = new Request();
// PostmanRequest requestDesc = requestItem.getRequest();
// PostmanUrl url = requestDesc.getUrl();
// request.setName(requestItem.getName());
// request.setUrl(url.getRaw());
// request.setUseEnvironment(false);
// request.setMethod(requestDesc.getMethod());
// request.setHeaders(parseKeyValue(requestDesc.getHeader()));
// request.setParameters(parseKeyValue(url.getQuery()));
// Body body = new Body();
// JSONObject postmanBody = requestDesc.getBody();
// String bodyMode = postmanBody.getString("mode");
// if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) {
// body.setRaw(postmanBody.getString(bodyMode));
// body.setType(MsRequestBodyType.RAW.value());
// String contentType = postmanBody.getJSONObject("options").getJSONObject("raw").getString("language");
// List<KeyValue> headers = request.getHeaders();
// boolean hasContentType = false;
// for (KeyValue header : headers) {
// if (StringUtils.equalsIgnoreCase(header.getName(), "Content-Type")) {
// hasContentType = true;
// }
// }
// if (!hasContentType) {
// headers.add(new KeyValue("Content-Type", contentType));
// }
// } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value()) || StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) {
// List<PostmanKeyValue> postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class);
// body.setType(MsRequestBodyType.KV.value());
// body.setKvs(parseKeyValue(postmanKeyValues));
// }
// request.setBody(body);
// requests.add(request);
// }
// return requests;
// }
}

View File

@ -33,5 +33,7 @@ public class TestCase implements Serializable {
private Integer sort;
private Integer num;
private static final long serialVersionUID = 1L;
}

View File

@ -1053,6 +1053,66 @@ public class TestCaseExample {
addCriterion("sort not between", value1, value2, "sort");
return (Criteria) this;
}
public Criteria andNumIsNull() {
addCriterion("num is null");
return (Criteria) this;
}
public Criteria andNumIsNotNull() {
addCriterion("num is not null");
return (Criteria) this;
}
public Criteria andNumEqualTo(Integer value) {
addCriterion("num =", value, "num");
return (Criteria) this;
}
public Criteria andNumNotEqualTo(Integer value) {
addCriterion("num <>", value, "num");
return (Criteria) this;
}
public Criteria andNumGreaterThan(Integer value) {
addCriterion("num >", value, "num");
return (Criteria) this;
}
public Criteria andNumGreaterThanOrEqualTo(Integer value) {
addCriterion("num >=", value, "num");
return (Criteria) this;
}
public Criteria andNumLessThan(Integer value) {
addCriterion("num <", value, "num");
return (Criteria) this;
}
public Criteria andNumLessThanOrEqualTo(Integer value) {
addCriterion("num <=", value, "num");
return (Criteria) this;
}
public Criteria andNumIn(List<Integer> values) {
addCriterion("num in", values, "num");
return (Criteria) this;
}
public Criteria andNumNotIn(List<Integer> values) {
addCriterion("num not in", values, "num");
return (Criteria) this;
}
public Criteria andNumBetween(Integer value1, Integer value2) {
addCriterion("num between", value1, value2, "num");
return (Criteria) this;
}
public Criteria andNumNotBetween(Integer value1, Integer value2) {
addCriterion("num not between", value1, value2, "num");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -16,6 +16,7 @@
<result column="update_time" jdbcType="BIGINT" property="updateTime" />
<result column="test_id" jdbcType="VARCHAR" property="testId" />
<result column="sort" jdbcType="INTEGER" property="sort" />
<result column="num" jdbcType="INTEGER" property="num" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestCaseWithBLOBs">
<result column="remark" jdbcType="LONGVARCHAR" property="remark" />
@ -81,7 +82,7 @@
</sql>
<sql id="Base_Column_List">
id, node_id, node_path, project_id, `name`, `type`, maintainer, priority, `method`,
prerequisite, create_time, update_time, test_id, sort
prerequisite, create_time, update_time, test_id, sort, num
</sql>
<sql id="Blob_Column_List">
remark, steps
@ -139,14 +140,14 @@
project_id, `name`, `type`,
maintainer, priority, `method`,
prerequisite, create_time, update_time,
test_id, sort, remark,
steps)
test_id, sort, num,
remark, steps)
values (#{id,jdbcType=VARCHAR}, #{nodeId,jdbcType=VARCHAR}, #{nodePath,jdbcType=VARCHAR},
#{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{type,jdbcType=VARCHAR},
#{maintainer,jdbcType=VARCHAR}, #{priority,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR},
#{prerequisite,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{testId,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{remark,jdbcType=LONGVARCHAR},
#{steps,jdbcType=LONGVARCHAR})
#{testId,jdbcType=VARCHAR}, #{sort,jdbcType=INTEGER}, #{num,jdbcType=INTEGER},
#{remark,jdbcType=LONGVARCHAR}, #{steps,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestCaseWithBLOBs">
insert into test_case
@ -193,6 +194,9 @@
<if test="sort != null">
sort,
</if>
<if test="num != null">
num,
</if>
<if test="remark != null">
remark,
</if>
@ -243,6 +247,9 @@
<if test="sort != null">
#{sort,jdbcType=INTEGER},
</if>
<if test="num != null">
#{num,jdbcType=INTEGER},
</if>
<if test="remark != null">
#{remark,jdbcType=LONGVARCHAR},
</if>
@ -302,6 +309,9 @@
<if test="record.sort != null">
sort = #{record.sort,jdbcType=INTEGER},
</if>
<if test="record.num != null">
num = #{record.num,jdbcType=INTEGER},
</if>
<if test="record.remark != null">
remark = #{record.remark,jdbcType=LONGVARCHAR},
</if>
@ -329,6 +339,7 @@
update_time = #{record.updateTime,jdbcType=BIGINT},
test_id = #{record.testId,jdbcType=VARCHAR},
sort = #{record.sort,jdbcType=INTEGER},
num = #{record.num,jdbcType=INTEGER},
remark = #{record.remark,jdbcType=LONGVARCHAR},
steps = #{record.steps,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
@ -350,7 +361,8 @@
create_time = #{record.createTime,jdbcType=BIGINT},
update_time = #{record.updateTime,jdbcType=BIGINT},
test_id = #{record.testId,jdbcType=VARCHAR},
sort = #{record.sort,jdbcType=INTEGER}
sort = #{record.sort,jdbcType=INTEGER},
num = #{record.num,jdbcType=INTEGER}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -397,6 +409,9 @@
<if test="sort != null">
sort = #{sort,jdbcType=INTEGER},
</if>
<if test="num != null">
num = #{num,jdbcType=INTEGER},
</if>
<if test="remark != null">
remark = #{remark,jdbcType=LONGVARCHAR},
</if>
@ -421,6 +436,7 @@
update_time = #{updateTime,jdbcType=BIGINT},
test_id = #{testId,jdbcType=VARCHAR},
sort = #{sort,jdbcType=INTEGER},
num = #{num,jdbcType=INTEGER},
remark = #{remark,jdbcType=LONGVARCHAR},
steps = #{steps,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
@ -439,7 +455,8 @@
create_time = #{createTime,jdbcType=BIGINT},
update_time = #{updateTime,jdbcType=BIGINT},
test_id = #{testId,jdbcType=VARCHAR},
sort = #{sort,jdbcType=INTEGER}
sort = #{sort,jdbcType=INTEGER},
num = #{num,jdbcType=INTEGER}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -17,4 +17,6 @@ public interface ExtTestCaseMapper {
List<TestCaseDTO> listBytestCaseIds(@Param("request") QueryTestCaseRequest request);
TestCase getMaxNumByProjectId(@Param("projectId") String projectId);
}

View File

@ -188,7 +188,8 @@
</where>
</select>
<select id="listBytestCaseIds" resultType="io.metersphere.track.dto.TestCaseDTO">
select test_case.*,api_test.name as apiName,load_test.name AS performName from test_case left join api_test on test_case.test_id=api_test.id left join load_test on test_case.test_id=load_test.id
select test_case.*,api_test.name as apiName,load_test.name AS performName from test_case left join api_test on
test_case.test_id=api_test.id left join load_test on test_case.test_id=load_test.id
<where>
<if test="request.testCaseIds!=null and request.testCaseIds.size() > 0">
and test_case.id in
@ -198,4 +199,8 @@
</if>
</where>
</select>
<select id="getMaxNumByProjectId" resultType="io.metersphere.base.domain.TestCase">
select * from test_case where test_case.project_id = #{projectId} order by num desc limit 1;
</select>
</mapper>

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum ApiImportPlatform {
Metersphere, Postman
Metersphere, Postman, Swagger2
}

View File

@ -0,0 +1,12 @@
package io.metersphere.commons.constants;
public class SwaggerParameterType {
public static final String PATH = "path";
public static final String FORM_DATA = "formData";
public static final String FILE = "file";
public static final String HEADER = "header";
public static final String BODY = "body";
public static final String COOKIE = "cookie";
public static final String QUERY = "query";
}

View File

@ -2,11 +2,13 @@ package io.metersphere.track.request.testcase;
import io.metersphere.base.domain.TestCaseNode;
import io.metersphere.track.dto.TestCaseNodeDTO;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Data
@Getter
@Setter
public class DragNodeRequest extends TestCaseNode {
List<String> nodeIds;

View File

@ -84,6 +84,7 @@ public class TestCaseService {
testCase.setId(UUID.randomUUID().toString());
testCase.setCreateTime(System.currentTimeMillis());
testCase.setUpdateTime(System.currentTimeMillis());
testCase.setNum(getNextNum(testCase.getProjectId()));
testCaseMapper.insert(testCase);
}
@ -253,9 +254,12 @@ public class TestCaseService {
TestCaseMapper mapper = sqlSession.getMapper(TestCaseMapper.class);
if (!testCases.isEmpty()) {
AtomicInteger sort = new AtomicInteger();
AtomicInteger num = new AtomicInteger();
num.set(getNextNum(projectId)+testCases.size());
testCases.forEach(testcase -> {
testcase.setNodeId(nodePathMap.get(testcase.getNodePath()));
testcase.setSort(sort.getAndIncrement());
testcase.setNum(num.decrementAndGet());
mapper.insert(testcase);
});
}
@ -429,4 +433,17 @@ public class TestCaseService {
MSException.throwException(Translator.get("related_case_del_fail_prefix") + " " + str + " " + Translator.get("related_case_del_fail_suffix"));
}
}
/**
* 获取项目下一个num (页面展示的ID)
* @return
*/
private int getNextNum(String projectId) {
TestCase testCase = extTestCaseMapper.getMaxNumByProjectId(projectId);
if (testCase == null) {
return 100001;
} else {
return Optional.ofNullable(testCase.getNum()+1).orElse(100001);
}
}
}

View File

@ -0,0 +1,50 @@
alter table test_case add num int null comment 'Manually controlled growth identifier';
DROP PROCEDURE IF EXISTS test_cursor;
DELIMITER //
CREATE PROCEDURE test_cursor()
BEGIN
DECLARE projectId VARCHAR(64);
DECLARE caseId VARCHAR(64);
DECLARE num INT;
DECLARE done INT DEFAULT 0;
DECLARE cursor1 CURSOR FOR (SELECT DISTINCT project_id
FROM test_case
WHERE num IS NULL);
DECLARE cursor2 CURSOR FOR (SELECT id
FROM test_case
WHERE project_id = projectId
ORDER BY create_time);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor1;
outer_loop:
LOOP
FETCH cursor1 INTO projectId;
IF done
THEN
LEAVE outer_loop;
END IF;
SET num = 100001;
OPEN cursor2;
inner_loop:
LOOP
FETCH cursor2 INTO caseId;
IF done
THEN
LEAVE inner_loop;
END IF;
UPDATE test_case
SET num = num
WHERE id = caseId;
SET num = num + 1;
END LOOP;
SET done = 0;
CLOSE cursor2;
END LOOP;
CLOSE cursor1;
END //
DELIMITER ;
CALL test_cursor();
DROP PROCEDURE IF EXISTS test_cursor;

View File

@ -39,8 +39,8 @@
<!-- jdbc连接信息 --> <!-- EduLoanManage EduTestDataBase -->
<!--<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://192.168.20.180:3306/fit2cloud"-->
<!--userId="root" password="Fit2cloud2015!" />-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="${spring.datasource.url}"
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="${spring.datasource.url}&amp;nullCatalogMeansCurrent=true"
userId="${spring.datasource.username}" password="${spring.datasource.password}"/>
<!-- javaTypeResolver式类型转换的信息 -->
@ -64,7 +64,7 @@
<!--要生成的数据库表 -->
<table tableName="load_test"/>
<table tableName="test_case"/>
</context>

File diff suppressed because one or more lines are too long

View File

@ -255,7 +255,7 @@
return this.test.isValid() && !this.change;
},
isDisabled() {
return !(this.test.isValid() && this.change)
return !(this.test.isValid())
}
},

View File

@ -90,14 +90,14 @@
activeName: "parameters",
rules: {
name: [
{max: 100, message: this.$t('commons.input_limit', [0, 100]), trigger: 'blur'}
{max: 100, message: this.$t('commons.input_limit', [1, 100]), trigger: 'blur'}
],
url: [
{max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
{validator: validateURL, trigger: 'blur'}
],
path: [
{max: 500, required: true, message: this.$t('commons.input_limit', [0, 500]), trigger: 'blur'},
{max: 500, required: true, message: this.$t('commons.input_limit', [1, 500]), trigger: 'blur'},
]
}
}
@ -143,6 +143,7 @@
this.$error(this.$t('api_test.request.please_add_environment_to_scenario'), 2000);
this.request.useEnvironment = false;
}
this.$refs["request"].clearValidate();
},
addProtocol(url) {
if (url) {

View File

@ -8,6 +8,11 @@
<el-select :disabled="isReadOnly" v-model="scenario.environmentId" class="environment-select" @change="environmentChange" clearable>
<el-option v-for="(environment, index) in environments" :key="index" :label="environment.name + ': ' + environment.protocol + '://' + environment.socket" :value="environment.id"/>
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
<template v-slot:empty>
<div class="empty-environment">
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">{{$t('api_test.environment.environment_config')}}</el-button>
</div>
</template>
</el-select>
</el-form-item>
@ -65,18 +70,32 @@
}
}
},
watch: {
projectId() {
this.getEnvironments();
}
},
methods: {
getEnvironments() {
if (this.projectId) {
this.result = this.$get('/api/environment/list/' + this.projectId, response => {
this.environments = response.data;
let hasEnvironment = false;
for (let i in this.environments) {
if (this.environments[i].id === this.scenario.environmentId) {
this.scenario.environment = this.environments[i];
hasEnvironment = true;
break;
}
}
if (!hasEnvironment) {
this.scenario.environmentId = '';
this.scenario.environment = undefined;
}
});
} else {
this.scenario.environmentId = '';
this.scenario.environment = undefined;
}
},
environmentChange(value) {
@ -97,6 +116,10 @@
}
},
openEnvironmentConfig() {
if (!this.projectId) {
this.$error(this.$t('api_test.select_project'));
return;
}
this.$refs.environmentConfig.open(this.projectId);
},
environmentConfigClose() {
@ -117,4 +140,8 @@
padding: 7px;
}
.empty-environment {
padding: 10px 0px;
}
</style>

View File

@ -61,7 +61,10 @@
return {
result: {},
rules: {
name :[{required: true, message: this.$t('commons.input_name'), trigger: 'blur'}],
name :[
{required: true, message: this.$t('commons.input_name'), trigger: 'blur'},
{max: 64, message: this.$t('commons.input_limit', [1, 64]), trigger: 'blur'}
],
socket :[{required: true, validator: socketValidator, trigger: 'blur'}],
},
}
@ -83,7 +86,9 @@
url = '/api/environment/update';
}
this.result = this.$post(url, param, response => {
this.environment.id = response.data;
if (!param.id) {
this.environment.id = response.data;
}
this.$success(this.$t('commons.save_success'));
});
},

View File

@ -56,7 +56,14 @@
name: 'Postman',
value: 'Postman',
tip: this.$t('api_test.api_import.postman_tip'),
exportTip: this.$t('api_test.api_import.post_man_export_tip'),
exportTip: this.$t('api_test.api_import.post_export_tip'),
suffixes: new Set(['json'])
},
{
name: 'Swagger',
value: 'Swagger2',
tip: this.$t('api_test.api_import.swagger_tip'),
exportTip: this.$t('api_test.api_import.swagger_export_tip'),
suffixes: new Set(['json'])
}
],

View File

@ -169,9 +169,6 @@ export class Scenario extends BaseConfig {
return false;
}
}
if (!this.name) {
return false;
}
return true;
}
}
@ -394,27 +391,37 @@ const JMX_ASSERTION_CONDITION = {
class JMXRequest {
constructor(request) {
if (request && request instanceof Request && request.url) {
let url = new URL(request.url);
this.method = request.method;
this.hostname = decodeURIComponent(url.hostname);
this.pathname = decodeURIComponent(url.pathname);
this.path = decodeURIComponent(request.path);
if (request && request instanceof Request && (request.url || request.path)) {
this.useEnvironment = request.useEnvironment;
this.environment = request.environment;
this.port = url.port;
this.protocol = url.protocol.split(":")[0];
if (this.method.toUpperCase() !== "GET") {
// this.pathname += url.search.replace('&', '&amp;');
this.pathname += '?';
request.parameters.forEach(parameter => {
if (parameter.name) {
this.pathname += (parameter.name + '=' + parameter.value + '&');
}
});
this.method = request.method;
if (!request.useEnvironment) {
let url = new URL(request.url);
this.hostname = decodeURIComponent(url.hostname);
this.pathname = decodeURIComponent(url.pathname);
this.port = url.port;
this.protocol = url.protocol.split(":")[0];
this.pathname = this.getPostQueryParameters(request, this.pathname);
} else {
this.environment = request.environment;
this.port = request.environment.port;
this.path = decodeURIComponent(request.path);
this.path = this.getPostQueryParameters(request, this.path);
}
}
}
getPostQueryParameters(request, path) {
if (this.method.toUpperCase() !== "GET") {
path += '?';
request.parameters.forEach(parameter => {
if (parameter.name) {
path += (parameter.name + '=' + parameter.value + '&');
}
});
}
return path;
}
}
class JMeterTestPlan extends Element {

View File

@ -4,11 +4,6 @@
<el-dialog :title="$t('commons.adv_search.combine')" :visible.sync="visible" custom-class="adv-dialog"
:append-to-body="true">
<div>
<!-- 如果有需求再加上-->
<!-- <div class="search-label">{{$t('commons.adv_search.combine')}}: </div>-->
<!-- <el-select v-model="logic" :placeholder="$t('commons.please_select')" size="small" class="search-combine">-->
<!-- <el-option v-for="o in options" :key="o.value" :label="o.label" :value="o.value"/>-->
<!-- </el-select>-->
<div class="search-items">
<component class="search-item" v-for="(component, index) in config.components" :key="index"
:is="component.name" :component="component"/>
@ -26,7 +21,7 @@
<script>
import components from "./search-components";
import _ from "lodash";
import {cloneDeep} from "lodash";
export default {
components: {...components},
@ -37,20 +32,12 @@
data() {
return {
visible: false,
config: this.init(),
options: [{
label: this.$t("commons.adv_search.and"),
value: "and"
}, {
label: this.$t("commons.adv_search.or"),
value: "or"
}],
logic: this.condition.logic || "and"
config: this.init()
}
},
methods: {
init() { //
let config = _.cloneDeep(this.condition);
init() {
let config = cloneDeep(this.condition);
config.components.forEach(component => {
let operator = component.operator.value;
component.operator.value = operator === undefined ? component.operator.options[0].value : operator;
@ -58,21 +45,19 @@
return config;
},
search() {
let condition = {
// logic: this.logic //
}
let condition = {}
this.config.components.forEach(component => {
let operator = component.operator.value;
let value = component.value;
if (Array.isArray(component.value)) {
if (component.value.length > 0) {
if (Array.isArray(value)) {
if (value.length > 0) {
condition[component.key] = {
operator: operator,
value: value
}
}
} else {
if (component.value !== undefined && component.value !== null && component.value !== "") {
if (value !== undefined && value !== null && value !== "") {
condition[component.key] = {
operator: operator,
value: value
@ -135,17 +120,6 @@
text-align: center;
}
.search-label {
display: inline-block;
width: 80px;
box-sizing: border-box;
padding-left: 5px;
}
.search-combine {
width: 160px;
}
.search-items {
width: 100%;
}

View File

@ -38,6 +38,12 @@
class="test-content">
<el-table-column
type="selection"/>
<el-table-column
prop="num"
sortable="custom"
:label="$t('commons.id')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="name"
:label="$t('commons.name')"
@ -310,6 +316,10 @@
this.initTableData();
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTableData();
}

View File

@ -33,7 +33,11 @@
<el-table-column
type="selection"></el-table-column>
<el-table-column
prop="num"
:label="$t('commons.id')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="name"
:label="$t('commons.name')"

View File

@ -5,6 +5,11 @@
<el-table
row-key="id"
:data="failureTestCases">
<el-table-column
prop="num"
:label="$t('commons.id')"
show-overflow-tooltip>
</el-table-column>
<el-table-column
prop="name"
:label="$t('commons.name')"

View File

@ -103,6 +103,7 @@ export default {
input_login_username: 'Please input the user ID or email',
input_name: 'Please enter name',
formatErr: 'Format Error',
id: 'ID',
date: {
select_date: 'Select date',
start_date: 'Start date',
@ -411,8 +412,10 @@ export default {
export_tip: "Export Tip",
ms_tip: "Support for Metersphere JSON format",
ms_export_tip: "Export jSON-formatted files via Metersphere website or browser plug-ins",
swagger_tip: "Only Swagger2.x json files are supported",
postman_tip: "Only Postman Collection V2.1 json files are supported",
post_man_export_tip: "Export the test collection by Postman",
postman_export_tip: "Export the test collection by Postman",
swagger_export_tip: "Export jSON-formatted files via Swagger website",
suffixFormatErr: "The file format does not meet the requirements",
}
},

View File

@ -103,6 +103,7 @@ export default {
input_login_username: '请输入用户 ID 或 邮箱',
input_name: '请输入名称',
formatErr: '格式错误',
id: 'ID',
date: {
select_date: '选择日期',
start_date: '开始日期',
@ -411,7 +412,9 @@ export default {
ms_tip: "支持 Metersphere json 格式",
ms_export_tip: "通过 Metersphere Api 测试页面或者浏览器插件导出 json 格式文件",
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
post_man_export_tip: "通过 Postman 导出测试集合",
swagger_tip: "只支持 Swagger2.x 版本的 json 文件",
post_export_tip: "通过 Postman 导出测试集合",
swagger_export_tip: "通过 Swagger 页面导出",
suffixFormatErr: "文件格式不符合要求",
}
},

View File

@ -101,6 +101,7 @@ export default {
delete_confirm: '請輸入以下內容,確認刪除:',
input_name: '請輸入名稱',
formatErr: '格式錯誤',
id: 'ID',
date: {
select_date: '選擇日期',
start_date: '開始日期',
@ -411,7 +412,9 @@ export default {
ms_tip: "支持 Metersphere json 格式",
ms_export_tip: "通過 Metersphere Api 測試頁面或者瀏覽器插件導出 json 格式文件",
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
post_man_export_tip: "通過 Postman 導出測試集合",
swagger_tip: "只支持 Swagger2.x 版本的 json 文件",
post_export_tip: "通過 Postman 導出測試集合",
swagger_export_tip: "通過 Swagger 頁面導出",
suffixFormatErr: "文件格式不符合要求",
}
},