Merge remote-tracking branch 'origin/v1.6' into v1.6

This commit is contained in:
song.tianyang 2020-12-31 15:31:58 +08:00
commit 0548e2361b
46 changed files with 887 additions and 180 deletions

View File

@ -1,9 +1,9 @@
---
name: Bug 提交
name: BUG 提交
about: 提交产品缺陷帮助我们更好的改进
title: "[BUG]"
labels: bug
assignees: ''
assignees: AgAngle
---

View File

@ -11,4 +11,4 @@ assignees: ''
**请描述你建议的实现方案**
**请描述你建议的实现方案**

View File

@ -3,9 +3,8 @@ name: 问题咨询
about: 提出针对本项目安装部署、使用及其他方面的相关问题
title: "[QUESTION]"
labels: question
assignees: 'wangzhen-fit2cloud'
assignees: wangzhen-fit2cloud
---
**请描述您的问题.**

14
.github/ISSUE_TEMPLATE/v1.6.1-issue.md vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: v1.6反馈
about: 提交关于 v1.6的缺陷与建议赢取Lv同款鼠标垫马克杯文化衫
title: "[v1.6]"
labels: v1.6
assignees: luty2018
---
**问题/需求描述**
简要描述您碰到的问题或您面临的需求
**截图**
如果有截图,请附上截图.

View File

@ -4,12 +4,7 @@ on:
push:
branches:
- master
- v1*
pull_request:
branches:
- master
- v1*
- v1*
workflow_dispatch:
jobs:

View File

@ -270,12 +270,12 @@
<version>1.0.51</version>
</dependency>
<!-- swagger3 解析 -->
<!--<dependency>-->
<!--<groupId>io.swagger.parser.v3</groupId>-->
<!--<artifactId>swagger-parser</artifactId>-->
<!--<version>2.0.24</version>-->
<!--</dependency>-->
<!-- swagger3 解析 最新版本会有swagger-core版本冲突 -->
<dependency>
<groupId>io.swagger.parser.v3</groupId>
<artifactId>swagger-parser</artifactId>
<version>2.0.18</version>
</dependency>
<!-- 执行 js 代码依赖 -->
<dependency>

View File

@ -85,15 +85,15 @@ public class ApiAutomationController {
}
@PostMapping(value = "/run")
public void run(@RequestBody RunScenarioRequest request) {
public String run(@RequestBody RunScenarioRequest request) {
request.setExecuteType(ExecuteType.Completed.name());
apiAutomationService.run(request);
return apiAutomationService.run(request);
}
@PostMapping(value = "/run/batch")
public void runBatch(@RequestBody RunScenarioRequest request) {
public String runBatch(@RequestBody RunScenarioRequest request) {
request.setExecuteType(ExecuteType.Saved.name());
apiAutomationService.run(request);
return apiAutomationService.run(request);
}

View File

@ -98,4 +98,9 @@ public class ApiTestCaseController {
public void testPlanRelevance(@RequestBody ApiCaseRelevanceRequest request) {
apiTestCaseService.relevanceByCase(request);
}
@PostMapping(value = "/jenkins/run")
public String jenkinsRun(@RequestPart("request") RunCaseRequest request) {
return apiTestCaseService.run(request);
}
}

View File

@ -0,0 +1,13 @@
package io.metersphere.api.dto.definition;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class RunCaseRequest {
private String caseId;
private String reportId;
}

View File

@ -109,12 +109,6 @@ public class MsScenario extends MsTestElement {
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
}
if (config != null && config.getConfig() != null && config.getConfig().getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().getHttpConfig().getHeaders())) {
config.getConfig().getHttpConfig().getHeaders().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
}
return arguments;
}
}

View File

@ -149,16 +149,6 @@ public abstract class MsTestElement {
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
}
if (config != null && config.getConfig() != null && config.getConfig().getCommonConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().getHttpConfig().getHeaders())) {
arguments.setEnabled(true);
arguments.setName(name + "Variables");
arguments.setProperty(TestElement.TEST_CLASS, Arguments.class.getName());
arguments.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("ArgumentsPanel"));
config.getConfig().getHttpConfig().getHeaders().stream().filter(KeyValue::isValid).filter(KeyValue::isEnable).forEach(keyValue ->
arguments.addArgument(keyValue.getName(), keyValue.getValue(), "=")
);
}
return arguments;
}
}

View File

@ -181,17 +181,25 @@ public class MsHTTPSamplerProxy extends MsTestElement {
}
// 请求体
if (!StringUtils.equals(this.getMethod(), "GET")) {
List<KeyValue> bodyParams = this.body.getBodyParams(sampler, this.getId());
if (this.body.getType().equals("Form Data")) {
sampler.setDoMultipart(true);
if (this.body != null) {
List<KeyValue> bodyParams = this.body.getBodyParams(sampler, this.getId());
if (StringUtils.isNotEmpty(this.body.getType()) && this.body.getType().equals("Form Data")) {
sampler.setDoMultipart(true);
}
sampler.setArguments(httpArguments(bodyParams));
}
sampler.setArguments(httpArguments(bodyParams));
}
final HashTree httpSamplerTree = tree.add(sampler);
if (CollectionUtils.isNotEmpty(this.headers)) {
setHeader(httpSamplerTree);
// 通用请求Headers
if (config != null && config.getConfig() != null && config.getConfig().getHttpConfig() != null
&& CollectionUtils.isNotEmpty(config.getConfig().getHttpConfig().getHeaders())) {
setHeader(httpSamplerTree, config.getConfig().getHttpConfig().getHeaders());
}
if (CollectionUtils.isNotEmpty(this.headers)) {
setHeader(httpSamplerTree, this.headers);
}
//判断是否要开启DNS
if (config != null && config.getConfig() != null && config.getConfig().getCommonConfig() != null
&& config.getConfig().getCommonConfig().isEnableHost()) {
@ -266,7 +274,7 @@ public class MsHTTPSamplerProxy extends MsTestElement {
return arguments;
}
public void setHeader(HashTree tree) {
public void setHeader(HashTree tree, List<KeyValue> headers) {
HeaderManager headerManager = new HeaderManager();
headerManager.setEnabled(true);
headerManager.setName(this.getName() + "Headers");

View File

@ -32,12 +32,20 @@ public class KeyValue {
}
public KeyValue(String name, String value, String description, String contentType) {
this(name, value, description, contentType, true);
}
public KeyValue(String name, String value, String description, String contentType, boolean required) {
this.name = name;
this.value = value;
this.description = description;
this.contentType = contentType;
this.enable = true;
this.required = true;
this.required = required;
}
public KeyValue(String name, String value, String description, boolean required) {
this(name, value, description, "", required);
}
public boolean isValid() {

View File

@ -70,6 +70,30 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
}
}
protected String getBodyType(String contentType) {
String bodyType = "";
switch (contentType) {
case "application/x-www-form-urlencoded":
bodyType = Body.WWW_FROM;
break;
case "multipart/form-data":
bodyType = Body.FORM_DATA;
break;
case "application/json":
bodyType = Body.JSON;
break;
case "application/xml":
bodyType = Body.XML;
break;
case "application/octet-stream":
bodyType = Body.BINARY;
break;
default:
bodyType = Body.RAW;
}
return bodyType;
}
protected ApiDefinitionResult buildApiDefinition(String id, String name, String path, String method) {
ApiDefinitionResult apiDefinition = new ApiDefinitionResult();
apiDefinition.setName(name);
@ -118,10 +142,10 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
}
protected void addCookie(List<KeyValue> headers, String key, String value) {
addCookie(headers, key, value, "");
addCookie(headers, key, value, "", true);
}
protected void addCookie(List<KeyValue> headers, String key, String value, String description) {
protected void addCookie(List<KeyValue> headers, String key, String value, String description, boolean required) {
boolean hasCookie = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase("Cookie", header.getName())) {
@ -131,15 +155,15 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
}
}
if (!hasCookie) {
addHeader(headers, "Cookie", key + "=" + value + ";", description);
addHeader(headers, "Cookie", key + "=" + value + ";", description, "", required);
}
}
protected void addHeader(List<KeyValue> headers, String key, String value) {
addHeader(headers, key, value, "");
addHeader(headers, key, value, "", "", true);
}
protected void addHeader(List<KeyValue> headers, String key, String value, String description) {
protected void addHeader(List<KeyValue> headers, String key, String value, String description, String contentType, boolean required) {
boolean hasContentType = false;
for (KeyValue header : headers) {
if (StringUtils.equalsIgnoreCase(header.getName(), key)) {
@ -147,20 +171,7 @@ public abstract class ApiImportAbstractParser implements ApiImportParser {
}
}
if (!hasContentType) {
headers.add(new KeyValue(key, value, description));
headers.add(new KeyValue(key, value, description, contentType, required));
}
}
// protected void addHeader(HttpRequest 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.save(new KeyValue(key, value));
// }
// request.setHeaders(headers);
// }
}

View File

@ -41,7 +41,9 @@ public class MsParser extends ApiImportAbstractParser {
List<ApiDefinitionResult> data = apiDefinitionImport.getData();
data.forEach(apiDefinition -> {
String id = UUID.randomUUID().toString();
// apiDefinition.setModuleId(null);
if (StringUtils.isBlank(apiDefinition.getModulePath())) {
apiDefinition.setModuleId(null);
}
parseModule(apiDefinition, importRequest.isSaved());
apiDefinition.setId(id);
apiDefinition.setProjectId(this.projectId);

View File

@ -32,11 +32,19 @@ public class Swagger2Parser extends ApiImportAbstractParser {
@Override
public ApiDefinitionImport parse(InputStream source, ApiTestImportRequest request) {
Swagger swagger;
String sourceStr = "";
if (StringUtils.isNotBlank(request.getSwaggerUrl())) {
swagger = new SwaggerParser().read(request.getSwaggerUrl());
} else {
swagger = new SwaggerParser().readWithInfo(getApiTestStr(source)).getSwagger();
sourceStr = getApiTestStr(source);
swagger = new SwaggerParser().readWithInfo(sourceStr).getSwagger();
}
if (swagger == null || swagger.getSwagger() == null) {
Swagger3Parser swagger3Parser = new Swagger3Parser();
return swagger3Parser.parse(sourceStr, request);
}
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
this.projectId = request.getProjectId();
definitionImport.setData(parseRequests(swagger, request.isSaved()));
@ -144,27 +152,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
return Body.RAW;
}
String contentType = operation.getConsumes().get(0);
String bodyType = "";
switch (contentType) {
case "application/x-www-form-urlencoded":
bodyType = Body.WWW_FROM;
break;
case "multipart/form-data":
bodyType = Body.FORM_DATA;
break;
case "application/json":
bodyType = Body.JSON;
break;
case "application/xml":
bodyType = Body.XML;
break;
case "":
bodyType = Body.BINARY;
break;
default:
bodyType = Body.RAW;
}
return bodyType;
return getBodyType(contentType);
}
private void parsePathParameters(Parameter parameter, List<KeyValue> rests) {
@ -178,12 +166,13 @@ public class Swagger2Parser extends ApiImportAbstractParser {
private void parseCookieParameters(Parameter parameter, List<KeyValue> headers) {
CookieParameter cookieParameter = (CookieParameter) parameter;
addCookie(headers, cookieParameter.getName(), "", getDefaultStringValue(cookieParameter.getDescription()));
addCookie(headers, cookieParameter.getName(), "", getDefaultStringValue(cookieParameter.getDescription()), parameter.getRequired());
}
private void parseHeaderParameters(Parameter parameter, List<KeyValue> headers) {
HeaderParameter headerParameter = (HeaderParameter) parameter;
addHeader(headers, headerParameter.getName(), "", getDefaultStringValue(headerParameter.getDescription()));
addHeader(headers, headerParameter.getName(), "", getDefaultStringValue(headerParameter.getDescription()),
"", parameter.getRequired());
}
private HttpResponse parseResponse(Map<String, Response> responses) {
@ -311,7 +300,7 @@ public class Swagger2Parser extends ApiImportAbstractParser {
private void parseFormDataParameters(FormParameter parameter, Body body) {
List<KeyValue> keyValues = Optional.ofNullable(body.getKvs()).orElse(new ArrayList<>());
KeyValue kv = new KeyValue(parameter.getName(), "", getDefaultStringValue(parameter.getDescription()));
KeyValue kv = new KeyValue(parameter.getName(), "", getDefaultStringValue(parameter.getDescription()), parameter.getRequired());
if (StringUtils.equals(parameter.getType(), "file")) {
kv.setType("file");
}
@ -321,6 +310,6 @@ public class Swagger2Parser extends ApiImportAbstractParser {
private void parseQueryParameters(Parameter parameter, List<KeyValue> arguments) {
QueryParameter queryParameter = (QueryParameter) parameter;
arguments.add(new KeyValue(queryParameter.getName(), "", getDefaultStringValue(queryParameter.getDescription())));
arguments.add(new KeyValue(queryParameter.getName(), "", getDefaultStringValue(queryParameter.getDescription()), queryParameter.getRequired()));
}
}

View File

@ -0,0 +1,374 @@
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.ApiTestImportRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.parse.ApiDefinitionImport;
import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy;
import io.metersphere.api.dto.definition.response.HttpResponse;
import io.metersphere.api.dto.scenario.Body;
import io.metersphere.api.dto.scenario.KeyValue;
import io.metersphere.api.dto.scenario.request.RequestType;
import io.metersphere.api.service.ApiModuleService;
import io.metersphere.base.domain.ApiModule;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.CommonBeanFactory;
import io.metersphere.commons.utils.LogUtil;
import io.metersphere.commons.utils.XMLUtils;
import io.swagger.parser.OpenAPIParser;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.*;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.parser.core.models.SwaggerParseResult;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpMethod;
import java.io.InputStream;
import java.util.*;
public class Swagger3Parser extends ApiImportAbstractParser {
private Components components;
@Override
public ApiDefinitionImport parse(InputStream source, ApiTestImportRequest request) {
String sourceStr = "";
if (StringUtils.isBlank(request.getSwaggerUrl())) {
sourceStr = getApiTestStr(source);
}
return parse(sourceStr, request);
}
public ApiDefinitionImport parse(String sourceStr, ApiTestImportRequest request) {
SwaggerParseResult result;
if (StringUtils.isNotBlank(request.getSwaggerUrl())) {
result = new OpenAPIParser().readLocation(request.getSwaggerUrl(), null, null);
} else {
result = new OpenAPIParser().readContents(sourceStr, null, null);
}
if (result == null) {
MSException.throwException("解析失败,请确认选择的是 swagger 格式!");
}
OpenAPI openAPI = result.getOpenAPI();
if (result.getMessages() != null) {
result.getMessages().forEach(msg -> LogUtil.error(msg)); // validation errors and warnings
}
ApiDefinitionImport definitionImport = new ApiDefinitionImport();
this.projectId = request.getProjectId();
definitionImport.setData(parseRequests(openAPI, request.isSaved()));
return definitionImport;
}
private List<ApiDefinitionResult> parseRequests(OpenAPI openAPI, boolean isSaved) {
Paths paths = openAPI.getPaths();
Set<String> pathNames = paths.keySet();
this.components = openAPI.getComponents();
List<ApiDefinitionResult> results = new ArrayList<>();
for (String pathName : pathNames) {
PathItem pathItem = paths.get(pathName);
Map<String, Operation> operationsMap = new HashMap<>();
operationsMap.put(HttpMethod.GET.name(), pathItem.getGet());
operationsMap.put(HttpMethod.POST.name(), pathItem.getPost());
operationsMap.put(HttpMethod.DELETE.name(), pathItem.getDelete());
operationsMap.put(HttpMethod.PUT.name(), pathItem.getPut());
operationsMap.put(HttpMethod.PATCH.name(), pathItem.getPatch());
operationsMap.put(HttpMethod.HEAD.name(), pathItem.getHead());
operationsMap.put(HttpMethod.OPTIONS.name(), pathItem.getOptions());
operationsMap.put(HttpMethod.TRACE.name(), pathItem.getTrace());
for (String method : operationsMap.keySet()) {
Operation operation = operationsMap.get(method);
if (operation != null) {
MsHTTPSamplerProxy request = buildRequest(operation, pathName, method);
ApiDefinitionResult apiDefinition = buildApiDefinition(request.getId(), operation, pathName, method);
parseParameters(operation, request);
parseRequestBody(operation.getRequestBody(), request.getBody());
apiDefinition.setRequest(JSON.toJSONString(request));
apiDefinition.setResponse(JSON.toJSONString(parseResponse(operation.getResponses())));
buildModule(apiDefinition, operation, isSaved);
results.add(apiDefinition);
}
}
}
return results;
}
private void buildModule(ApiDefinitionResult apiDefinition, Operation operation, boolean isSaved) {
List<String> tags = operation.getTags();
if (tags != null) {
tags.forEach(tag -> {
apiModuleService = CommonBeanFactory.getBean(ApiModuleService.class);
ApiModule module = apiModuleService.getNewModule(tag, this.projectId, 1);
if (isSaved) {
createModule(module);
}
apiDefinition.setModuleId(module.getId());
});
}
}
private ApiDefinitionResult buildApiDefinition(String id, Operation operation, String path, String method) {
String name = "";
if (StringUtils.isNotBlank(operation.getSummary())) {
name = operation.getSummary();
} else {
name = operation.getOperationId();
}
return buildApiDefinition(id, name, path, method);
}
private MsHTTPSamplerProxy buildRequest(Operation operation, String path, String method) {
String name = "";
if (StringUtils.isNotBlank(operation.getSummary())) {
name = operation.getSummary();
} else {
name = operation.getOperationId();
}
return buildRequest(name, path, method);
}
private void parseParameters(Operation operation, MsHTTPSamplerProxy request) {
List<Parameter> parameters = operation.getParameters();
if (CollectionUtils.isEmpty(parameters)) {
return;
}
// todo 路径变量 {xxx} 是否要转换
parameters.forEach(parameter -> {
if (parameter instanceof QueryParameter) {
parseQueryParameters(parameter, request.getArguments());
} else if (parameter instanceof PathParameter) {
parsePathParameters(parameter, request.getRest());
} else if (parameter instanceof HeaderParameter) {
parseHeaderParameters(parameter, request.getHeaders());
} else if (parameter instanceof CookieParameter) {
parseCookieParameters(parameter, request.getHeaders());
}
});
}
private void parsePathParameters(Parameter parameter, List<KeyValue> rests) {
PathParameter pathParameter = (PathParameter) parameter;
rests.add(new KeyValue(pathParameter.getName(), "", getDefaultStringValue(parameter.getDescription())));
}
private String getDefaultStringValue(String val) {
return StringUtils.isBlank(val) ? "" : val;
}
private void parseCookieParameters(Parameter parameter, List<KeyValue> headers) {
CookieParameter cookieParameter = (CookieParameter) parameter;
addCookie(headers, cookieParameter.getName(), "", getDefaultStringValue(cookieParameter.getDescription()), parameter.getRequired());
}
private void parseHeaderParameters(Parameter parameter, List<KeyValue> headers) {
HeaderParameter headerParameter = (HeaderParameter) parameter;
addHeader(headers, headerParameter.getName(), "", getDefaultStringValue(headerParameter.getDescription()), "", parameter.getRequired());
}
private HttpResponse parseResponse(ApiResponses responses) {
HttpResponse msResponse = new HttpResponse();
msResponse.setBody(new Body());
msResponse.setHeaders(new ArrayList<>());
msResponse.setType(RequestType.HTTP);
// todo 状态码要调整
msResponse.setStatusCode(new ArrayList<>());
if (responses != null) {
responses.forEach((responseCode, response) -> {
msResponse.getStatusCode().add(new KeyValue(responseCode, responseCode));
parseResponseHeader(response, msResponse.getHeaders());
parseResponseBody(response, msResponse.getBody());
});
}
return msResponse;
}
private void parseResponseHeader(ApiResponse response, List<KeyValue> msHeaders) {
Map<String, Header> headers = response.getHeaders();
if (headers != null) {
headers.forEach((k, v) -> {
msHeaders.add(new KeyValue(k, "", v.getDescription()));
});
}
}
private void parseResponseBody(ApiResponse response, Body body) {
body.setRaw(response.getDescription());
Content content = response.getContent();
if (content == null) {
body.setType(Body.RAW);
body.setRaw(response.getDescription());
} else {
parseBody(response.getContent(), body);
}
}
private void parseRequestBody(RequestBody requestBody, Body body) {
if (requestBody == null) {
return;
}
parseBody(requestBody.getContent(), body);
}
private void parseBody(Content content, Body body) {
if (content == null) {
return;
}
// 多个contentType 优先取json
String contentType = "";
MediaType mediaType = content.get(contentType);
if (mediaType == null) {
Set<String> contentTypes = content.keySet();
contentType = contentTypes.iterator().next();
if (StringUtils.isBlank(contentType)) {
return;
}
mediaType = content.get(contentType);
} else {
contentType = org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
}
Set<String> refSet = new HashSet<>();
Map<String, Schema> infoMap = new HashMap();
Schema schema = mediaType.getSchema();
Object bodyData = parseSchema(schema, refSet, infoMap);
if (bodyData == null) {
return;
}
body.setType(getBodyType(contentType));
if (StringUtils.equals(contentType, org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
parseKvBody(schema, body, bodyData, infoMap);
} else if (StringUtils.equals(contentType, org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)) {
body.setRaw(bodyData.toString());
} else if (StringUtils.equals(contentType, org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
body.setRaw(bodyData.toString());
} else if (StringUtils.equals(contentType, org.springframework.http.MediaType.APPLICATION_XML_VALUE)) {
body.setRaw(parseXmlBody(schema, bodyData));
} else if (StringUtils.equals(contentType, org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE)) {
parseKvBody(schema, body, bodyData, infoMap);
} else {
body.setRaw(bodyData.toString());
}
}
private void parseKvBody(Schema schema, Body body, Object data, Map<String, Schema> infoMap) {
if (data instanceof JSONObject) {
((JSONObject) data).forEach((k, v) -> {
KeyValue kv = new KeyValue(k, v.toString());
Schema schemaInfo = infoMap.get(k);
if (schemaInfo != null) {
kv.setDescription(schemaInfo.getDescription());
// kv.setRequired(schemaInfo.getRequired());
if (schemaInfo instanceof BinarySchema) {
kv.setType("file");
}
}
body.getKvs().add(kv);
});
} else {
KeyValue kv = new KeyValue(schema.getName(), data.toString(), schema.getDescription());
Schema schemaInfo = infoMap.get(schema.getName());
if (schemaInfo != null) {
kv.setDescription(schemaInfo.getDescription());
if (schemaInfo instanceof BinarySchema) {
kv.setType("file");
}
}
body.getKvs().add(kv);
}
}
private String parseXmlBody(Schema schema, Object data) {
if (data instanceof JSONObject) {
return XMLUtils.jsonToXmlStr((JSONObject) data);
} else {
JSONObject object = new JSONObject();
object.put(schema.getName(), getDefaultValueByPropertyType(schema));
return XMLUtils.jsonToXmlStr(object);
}
}
private Schema getModelByRef(String ref) {
if (StringUtils.isBlank(ref)) {
return null;
}
if (ref.split("/").length > 3) {
ref = ref.replace("#/components/schemas/", "");
}
return this.components.getSchemas().get(ref);
}
private Object parseSchema(Schema schema, Set<String> refSet, Map<String, Schema> infoMap) {
infoMap.put(schema.getName(), schema);
if (StringUtils.isNotBlank(schema.get$ref())) {
if (refSet.contains(schema.get$ref())) {
return new JSONObject();
}
refSet.add(schema.get$ref());
Object propertiesResult = parseSchemaProperties(getModelByRef(schema.get$ref()), refSet, infoMap);
return propertiesResult == null ? getDefaultValueByPropertyType(schema) : propertiesResult;
} else if (schema instanceof ArraySchema) {
JSONArray jsonArray = new JSONArray();
Schema items = ((ArraySchema) schema).getItems();
parseSchema(items, refSet, infoMap);
jsonArray.add(parseSchema(items, refSet, infoMap));
return jsonArray;
} else if (schema instanceof BinarySchema) {
return getDefaultValueByPropertyType(schema);
} else {
Object propertiesResult = parseSchemaProperties(schema, refSet, infoMap);
return propertiesResult == null ? getDefaultValueByPropertyType(schema) : propertiesResult;
}
}
private Object parseSchemaProperties(Schema schema, Set<String> refSet, Map<String, Schema> infoMap) {
Map<String, Schema> properties = schema.getProperties();
if (MapUtils.isEmpty(properties)) {
return null;
}
JSONObject jsonObject = new JSONObject();
properties.forEach((key, value) -> {
jsonObject.put(key, parseSchema(value, refSet, infoMap));
});
return jsonObject;
}
private Object getDefaultValueByPropertyType(Schema value) {
Object example = value.getExample();
if (value instanceof IntegerSchema) {
return example == null ? 0 : example;
} else if (value instanceof NumberSchema) {
return example == null ? 0.0 : example;
} else {// todo 其他类型?
return getDefaultStringValue(value.getDescription());
}
}
private void parseQueryParameters(Parameter parameter, List<KeyValue> arguments) {
QueryParameter queryParameter = (QueryParameter) parameter;
arguments.add(new KeyValue(queryParameter.getName(), "", getDefaultStringValue(queryParameter.getDescription()), parameter.getRequired()));
}
}

View File

@ -1,11 +1,14 @@
package io.metersphere.api.service;
import com.alibaba.fastjson.JSONObject;
import io.metersphere.api.dto.automation.ApiScenarioDTO;
import io.metersphere.api.dto.automation.ApiScenarioRequest;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.metersphere.api.dto.ApiCaseBatchRequest;
import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.ApiCaseBatchRequest;
import io.metersphere.api.dto.definition.request.MsTestElement;
import io.metersphere.api.jmeter.JMeterService;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ApiDefinitionMapper;
import io.metersphere.base.mapper.ApiTestCaseMapper;
@ -14,6 +17,7 @@ import io.metersphere.base.mapper.ext.ExtApiDefinitionExecResultMapper;
import io.metersphere.base.mapper.ext.ExtApiTestCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanApiCaseMapper;
import io.metersphere.base.mapper.ext.ExtTestPlanTestCaseMapper;
import io.metersphere.commons.constants.ApiRunMode;
import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.*;
import io.metersphere.i18n.Translator;
@ -21,11 +25,12 @@ import io.metersphere.service.FileService;
import io.metersphere.service.QuotaService;
import io.metersphere.service.UserService;
import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.jorphan.collections.HashTree;
import org.aspectj.util.FileUtil;
import org.python.antlr.ast.Str;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
@ -57,6 +62,8 @@ public class ApiTestCaseService {
private ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private JMeterService jMeterService;
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
@ -271,7 +278,7 @@ public class ApiTestCaseService {
}
public void deleteBatch(List<String> ids) {
for (String testId:ids) {
for (String testId : ids) {
extTestPlanTestCaseMapper.deleteByTestCaseID(testId);
}
ApiTestCaseExample example = new ApiTestCaseExample();
@ -331,10 +338,10 @@ public class ApiTestCaseService {
Date firstTime = startAndEndDateInWeek.get("firstTime");
Date lastTime = startAndEndDateInWeek.get("lastTime");
if(firstTime==null || lastTime == null){
return 0;
}else {
return extApiTestCaseMapper.countByProjectIDAndCreateInThisWeek(projectId,firstTime.getTime(),lastTime.getTime());
if (firstTime == null || lastTime == null) {
return 0;
} else {
return extApiTestCaseMapper.countByProjectIDAndCreateInThisWeek(projectId, firstTime.getTime(), lastTime.getTime());
}
}
@ -351,16 +358,16 @@ public class ApiTestCaseService {
public void deleteBatchByParam(ApiTestBatchRequest request) {
List<String> ids = request.getIds();
if(request.isSelectAllDate()){
ids = this.getAllApiCaseIdsByFontedSelect(request.getFilters(),request.getModuleIds(),request.getName(),request.getProjectId(),request.getProtocol(),request.getUnSelectIds(),request.getStatus());
if (request.isSelectAllDate()) {
ids = this.getAllApiCaseIdsByFontedSelect(request.getFilters(), request.getModuleIds(), request.getName(), request.getProjectId(), request.getProtocol(), request.getUnSelectIds(), request.getStatus());
}
this.deleteBatch(ids);
}
public void editApiBathByParam(ApiTestBatchRequest request) {
List<String> ids = request.getIds();
if(request.isSelectAllDate()){
ids = this.getAllApiCaseIdsByFontedSelect(request.getFilters(),request.getModuleIds(),request.getName(),request.getProjectId(),request.getProtocol(),request.getUnSelectIds(),request.getStatus());
if (request.isSelectAllDate()) {
ids = this.getAllApiCaseIdsByFontedSelect(request.getFilters(), request.getModuleIds(), request.getName(), request.getProjectId(), request.getProtocol(), request.getUnSelectIds(), request.getStatus());
}
request.cleanSelectParam();
ApiTestCaseExample apiDefinitionExample = new ApiTestCaseExample();
@ -371,7 +378,7 @@ public class ApiTestCaseService {
apiTestCaseMapper.updateByExampleSelective(apiDefinitionWithBLOBs, apiDefinitionExample);
}
private List<String> getAllApiCaseIdsByFontedSelect(Map<String, List<String>> filters,List<String>moduleIds, String name, String projectId, String protocol,List<String> unSelectIds,String status) {
private List<String> getAllApiCaseIdsByFontedSelect(Map<String, List<String>> filters, List<String> moduleIds, String name, String projectId, String protocol, List<String> unSelectIds, String status) {
ApiTestCaseRequest selectRequest = new ApiTestCaseRequest();
selectRequest.setFilters(filters);
selectRequest.setModuleIds(moduleIds);
@ -383,6 +390,27 @@ public class ApiTestCaseService {
List<ApiTestCaseResult> list = extApiTestCaseMapper.list(selectRequest);
List<String> allIds = list.stream().map(ApiTestCaseResult::getId).collect(Collectors.toList());
List<String> ids = allIds.stream().filter(id -> !unSelectIds.contains(id)).collect(Collectors.toList());
return ids;
return ids;
}
public String run(RunCaseRequest request) {
ApiTestCaseWithBLOBs testCaseWithBLOBs = apiTestCaseMapper.selectByPrimaryKey(request.getCaseId());
// 多态JSON普通转换会丢失内容需要通过 ObjectMapper 获取
if (testCaseWithBLOBs != null && StringUtils.isNotEmpty(testCaseWithBLOBs.getRequest())) {
try {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MsTestElement elements = mapper.readValue(testCaseWithBLOBs.getRequest(), new TypeReference<MsTestElement>() { });
HashTree hashTree = elements.generateHashTree();
String runMode = ApiRunMode.DELIMIT.name();
// 调用执行方法
jMeterService.runDefinition(request.getReportId(), hashTree, request.getReportId(), runMode);
} catch (Exception ex) {
LogUtil.error(ex.getMessage());
}
}
return request.getReportId();
}
}

View File

@ -1,8 +1,7 @@
package io.metersphere.base.domain;
import lombok.Data;
import java.io.Serializable;
import lombok.Data;
@Data
public class TestPlan implements Serializable {
@ -40,6 +39,8 @@ public class TestPlan implements Serializable {
private String creator;
private String projectId;
private String tags;
private static final long serialVersionUID = 1L;

View File

@ -1233,6 +1233,76 @@ public class TestPlanExample {
addCriterion("creator not between", value1, value2, "creator");
return (Criteria) this;
}
public Criteria andProjectIdIsNull() {
addCriterion("project_id is null");
return (Criteria) this;
}
public Criteria andProjectIdIsNotNull() {
addCriterion("project_id is not null");
return (Criteria) this;
}
public Criteria andProjectIdEqualTo(String value) {
addCriterion("project_id =", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotEqualTo(String value) {
addCriterion("project_id <>", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThan(String value) {
addCriterion("project_id >", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdGreaterThanOrEqualTo(String value) {
addCriterion("project_id >=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThan(String value) {
addCriterion("project_id <", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLessThanOrEqualTo(String value) {
addCriterion("project_id <=", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdLike(String value) {
addCriterion("project_id like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotLike(String value) {
addCriterion("project_id not like", value, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdIn(List<String> values) {
addCriterion("project_id in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotIn(List<String> values) {
addCriterion("project_id not in", values, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdBetween(String value1, String value2) {
addCriterion("project_id between", value1, value2, "projectId");
return (Criteria) this;
}
public Criteria andProjectIdNotBetween(String value1, String value2) {
addCriterion("project_id not between", value1, value2, "projectId");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -19,6 +19,7 @@
<result column="planned_end_time" jdbcType="BIGINT" property="plannedEndTime" />
<result column="actual_start_time" jdbcType="BIGINT" property="actualStartTime" />
<result column="creator" jdbcType="VARCHAR" property="creator" />
<result column="project_id" jdbcType="VARCHAR" property="projectId" />
</resultMap>
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.TestPlan">
<result column="tags" jdbcType="LONGVARCHAR" property="tags" />
@ -84,7 +85,7 @@
<sql id="Base_Column_List">
id, workspace_id, report_id, `name`, description, `status`, stage, principal, test_case_match_rule,
executor_match_rule, create_time, update_time, actual_end_time, planned_start_time,
planned_end_time, actual_start_time, creator
planned_end_time, actual_start_time, creator, project_id
</sql>
<sql id="Blob_Column_List">
tags
@ -143,15 +144,15 @@
stage, principal, test_case_match_rule,
executor_match_rule, create_time, update_time,
actual_end_time, planned_start_time, planned_end_time,
actual_start_time, creator, tags
)
actual_start_time, creator, project_id,
tags)
values (#{id,jdbcType=VARCHAR}, #{workspaceId,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR},
#{stage,jdbcType=VARCHAR}, #{principal,jdbcType=VARCHAR}, #{testCaseMatchRule,jdbcType=VARCHAR},
#{executorMatchRule,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT},
#{actualEndTime,jdbcType=BIGINT}, #{plannedStartTime,jdbcType=BIGINT}, #{plannedEndTime,jdbcType=BIGINT},
#{actualStartTime,jdbcType=BIGINT}, #{creator,jdbcType=VARCHAR}, #{tags,jdbcType=LONGVARCHAR}
)
#{actualStartTime,jdbcType=BIGINT}, #{creator,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR},
#{tags,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.TestPlan">
insert into test_plan
@ -207,6 +208,9 @@
<if test="creator != null">
creator,
</if>
<if test="projectId != null">
project_id,
</if>
<if test="tags != null">
tags,
</if>
@ -263,6 +267,9 @@
<if test="creator != null">
#{creator,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
#{projectId,jdbcType=VARCHAR},
</if>
<if test="tags != null">
#{tags,jdbcType=LONGVARCHAR},
</if>
@ -328,6 +335,9 @@
<if test="record.creator != null">
creator = #{record.creator,jdbcType=VARCHAR},
</if>
<if test="record.projectId != null">
project_id = #{record.projectId,jdbcType=VARCHAR},
</if>
<if test="record.tags != null">
tags = #{record.tags,jdbcType=LONGVARCHAR},
</if>
@ -355,6 +365,7 @@
planned_end_time = #{record.plannedEndTime,jdbcType=BIGINT},
actual_start_time = #{record.actualStartTime,jdbcType=BIGINT},
creator = #{record.creator,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR},
tags = #{record.tags,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -378,7 +389,8 @@
planned_start_time = #{record.plannedStartTime,jdbcType=BIGINT},
planned_end_time = #{record.plannedEndTime,jdbcType=BIGINT},
actual_start_time = #{record.actualStartTime,jdbcType=BIGINT},
creator = #{record.creator,jdbcType=VARCHAR}
creator = #{record.creator,jdbcType=VARCHAR},
project_id = #{record.projectId,jdbcType=VARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -434,6 +446,9 @@
<if test="creator != null">
creator = #{creator,jdbcType=VARCHAR},
</if>
<if test="projectId != null">
project_id = #{projectId,jdbcType=VARCHAR},
</if>
<if test="tags != null">
tags = #{tags,jdbcType=LONGVARCHAR},
</if>
@ -458,6 +473,7 @@
planned_end_time = #{plannedEndTime,jdbcType=BIGINT},
actual_start_time = #{actualStartTime,jdbcType=BIGINT},
creator = #{creator,jdbcType=VARCHAR},
project_id = #{projectId,jdbcType=VARCHAR},
tags = #{tags,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
@ -478,7 +494,8 @@
planned_start_time = #{plannedStartTime,jdbcType=BIGINT},
planned_end_time = #{plannedEndTime,jdbcType=BIGINT},
actual_start_time = #{actualStartTime,jdbcType=BIGINT},
creator = #{creator,jdbcType=VARCHAR}
creator = #{creator,jdbcType=VARCHAR},
project_id = #{projectId,jdbcType=VARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
</mapper>

View File

@ -333,6 +333,13 @@
and project_id= #{request.projectId}
</if>
</where>
UNION ALL
select id,name,status,project_id,"scenario" as type from api_scenario
<where>
<if test="request.projectId!=null">
and project_id= #{request.projectId}
</if>
</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

View File

@ -98,9 +98,9 @@
<select id="list" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric"
parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">
select DISTINCT test_plan.*, user.name as user_name from test_plan
select DISTINCT test_plan.*, user.name as user_name, project.name as projectName from test_plan
LEFT JOIN user ON user.id = test_plan.principal
JOIN test_plan_project on test_plan.id = test_plan_project.test_plan_id JOIN project on project.id = test_plan_project.project_id
JOIN project on project.id = test_plan.project_id
<where>
<if test="request.combine != null">
<include refid="combine">
@ -115,7 +115,7 @@
AND test_plan.workspace_id = #{request.workspaceId}
</if>
<if test="request.projectId != null">
AND test_plan_project.project_id = #{request.projectId}
AND test_plan.project_id = #{request.projectId}
</if>
<if test="request.id != null">
AND test_plan.id = #{request.id}
@ -164,11 +164,10 @@
<select id="listRelate" resultType="io.metersphere.track.dto.TestPlanDTOWithMetric">
select distinct test_plan.* from test_plan
inner join test_plan_project on test_plan.id = test_plan_project.test_plan_id
<where>
test_plan.workspace_id = #{request.workspaceId}
<if test="request.projectId != null">
and test_plan_project.project_id = #{request.projectId}
and test_plan.project_id = #{request.projectId}
</if>
and (test_plan.principal = #{request.principal}
<if test="request.planIds != null and request.planIds.size() > 0">
@ -190,7 +189,8 @@
</select>
<select id="checkIsHave" resultType="int">
SELECT COUNT(1)
select sum(c) from (
SELECT COUNT(1) as c
FROM test_plan_project, project
WHERE project_id = project.id AND test_plan_id = #{planId}
<if test="workspaceIds != null and workspaceIds.size() > 0">
@ -199,6 +199,15 @@
#{id}
</foreach>
</if>
union
select count(1) as c from test_plan, project
WHERE project_id = project.id AND test_plan.id = #{planId}
<if test="workspaceIds != null and workspaceIds.size() > 0">
AND project.workspace_id IN
<foreach collection="workspaceIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>) as temp
</select>
<select id="selectTestPlanByRelevancy" resultMap="BaseResultMap" parameterType="io.metersphere.track.request.testcase.QueryTestPlanRequest">

View File

@ -15,6 +15,8 @@ public interface ExtTestPlanTestCaseMapper {
List<TestPlanCaseDTO> list(@Param("request") QueryTestPlanCaseRequest request);
List<TestPlanCaseDTO> listByPlanId(@Param("request") QueryTestPlanCaseRequest request);
List<TestPlanCaseDTO> listByNode(@Param("request") QueryTestPlanCaseRequest request);
List<TestPlanCaseDTO> listByNodes(@Param("request") QueryTestPlanCaseRequest request);

View File

@ -272,13 +272,11 @@
select distinct plan_id from test_plan_test_case
inner join test_plan
on test_plan_test_case.plan_id = test_plan.id
inner join test_plan_project
on test_plan.id = test_plan_project.test_plan_id
<where>
test_plan_test_case.executor = #{userId}
and test_plan.workspace_id = #{workspaceId}
<if test="projectId != null">
and test_plan_project.project_id = #{projectId}
and test_plan.project_id = #{projectId}
</if>
</where>
</select>
@ -336,6 +334,34 @@
test_plan_test_case
where plan_id = #{planId}
</select>
<select id="listByPlanId" resultType="io.metersphere.track.dto.TestPlanCaseDTO">
SELECT test_plan_api_case.api_case_id as id,"definition" as type,api_test_case.name,test_plan_api_case.status
from test_plan_api_case left join api_test_case on test_plan_api_case.api_case_id=api_test_case.id
<where>
<if test="request.planId != null">
and test_plan_api_case.test_plan_id = #{request.planId}
</if>
</where>
UNION ALL
SELECT test_plan_api_scenario.api_scenario_id as id,"scenario" as type,api_scenario.name,test_plan_api_scenario.status
from test_plan_api_scenario left join api_scenario on test_plan_api_scenario.api_scenario_id=api_scenario.id
<where>
<if test="request.planId != null">
and test_plan_api_scenario.test_plan_id = #{request.planId}
</if>
</where>
UNION ALL
SELECT test_case.test_id as id,test_case.type as type,test_case.name,test_plan_test_case.status
from test_plan_test_case left join test_case on test_plan_test_case.case_id =test_case.id
<where>
<if test="request.planId != null">
and test_plan_test_case.plan_id = #{request.planId}
</if>
<if test="request.method != null">
and test_case.method = #{request.method}
</if>
</where>
</select>
<update id="updateTestCaseStates" parameterType="java.lang.String">
update test_plan_test_case

View File

@ -1,5 +1,5 @@
package io.metersphere.commons.constants;
public enum ApiRunMode {
RUN, DEBUG,DELIMIT,SCENARIO, API_PLAN, SCENARIO_PLAN
RUN, DEBUG,DELIMIT,SCENARIO, API_PLAN, SCENARIO_PLAN,API
}

View File

@ -0,0 +1,47 @@
package io.metersphere.commons.utils;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import java.util.Map;
import java.util.Set;
public class XMLUtils {
private static void jsonToXmlStr(JSONObject jObj, StringBuffer buffer) {
Set<Map.Entry<String, Object>> se = jObj.entrySet();
for (Map.Entry<String, Object> en : se) {
if ("com.alibaba.fastjson.JSONObject".equals(en.getValue().getClass().getName())) {
buffer.append("<").append(en.getKey()).append(">");
JSONObject jo = jObj.getJSONObject(en.getKey());
jsonToXmlStr(jo, buffer);
buffer.append("</").append(en.getKey()).append(">");
} else if ("com.alibaba.fastjson.JSONArray".equals(en.getValue().getClass().getName())) {
JSONArray jarray = jObj.getJSONArray(en.getKey());
for (int i = 0; i < jarray.size(); i++) {
buffer.append("<").append(en.getKey()).append(">");
if (StringUtils.isNotBlank(jarray.getString(i))) {
JSONObject jsonobject = jarray.getJSONObject(i);
jsonToXmlStr(jsonobject, buffer);
buffer.append("</").append(en.getKey()).append(">");
}
}
} else if ("java.lang.String".equals(en.getValue().getClass().getName())) {
buffer.append("<").append(en.getKey()).append(">").append(en.getValue());
buffer.append("</").append(en.getKey()).append(">");
}
}
}
public static String jsonToXmlStr(JSONObject jObj) {
StringBuffer buffer = new StringBuffer();
buffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
try {
jsonToXmlStr(jObj, buffer);
} catch (Exception e) {
LogUtil.error(e.getMessage(), e);
}
return buffer.toString();
}
}

View File

@ -5,6 +5,7 @@ import io.metersphere.base.domain.User;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.handler.annotation.NoResultHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@ -35,6 +36,15 @@ public class TestController {
return jsonObject;
}
@PostMapping(value = "/wwwform", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public Object testWwwForm(String id, User user, String name) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", id);
jsonObject.put("user", user.getName());
jsonObject.put("name", name);
return jsonObject;
}
@NoResultHolder
@GetMapping(value = "/xml")
public String getXmlString() {

View File

@ -57,7 +57,7 @@ public class TestCaseController {
return testCaseService.listTestCase(request);
}
/*jenkins项目下所有接口和性能测试用例*/
@GetMapping("/list/method/{projectId}")
public List<TestCaseDTO> listByMethod(@PathVariable String projectId) {
QueryTestCaseRequest request = new QueryTestCaseRequest();

View File

@ -30,13 +30,13 @@ public class TestPlanTestCaseController {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, testPlanTestCaseService.list(request));
}
/*jenkins测试计划下全部用例*/
@GetMapping("/list/{planId}")
public List<TestPlanCaseDTO> getTestPlanCaseByPlanId(@PathVariable String planId) {
QueryTestPlanCaseRequest request = new QueryTestPlanCaseRequest();
request.setPlanId(planId);
request.setMethod("auto");
return testPlanTestCaseService.list(request);
return testPlanTestCaseService.listByPlanId(request);
}
@GetMapping("/list/node/{planId}/{nodePaths}")

View File

@ -27,4 +27,6 @@ public class QueryTestPlanRequest extends TestPlan {
private Map<String, Object> combine;
private String projectId;
private String projectName;
}

View File

@ -1,10 +1,8 @@
package io.metersphere.track.service;
import io.metersphere.base.domain.Project;
import io.metersphere.base.domain.ProjectExample;
import io.metersphere.base.domain.TestPlanProject;
import io.metersphere.base.domain.TestPlanProjectExample;
import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.ProjectMapper;
import io.metersphere.base.mapper.TestPlanMapper;
import io.metersphere.base.mapper.TestPlanProjectMapper;
import io.metersphere.track.request.testplancase.TestCaseRelevanceRequest;
import org.apache.commons.lang3.StringUtils;
@ -24,15 +22,22 @@ public class TestPlanProjectService {
TestPlanProjectMapper testPlanProjectMapper;
@Resource
ProjectMapper projectMapper;
@Resource
private TestPlanMapper testPlanMapper;
public List<String> getProjectIdsByPlanId(String planId) {
TestPlan testPlan = testPlanMapper.selectByPrimaryKey(planId);
TestPlanProjectExample example = new TestPlanProjectExample();
example.createCriteria().andTestPlanIdEqualTo(planId);
List<String> projectIds = testPlanProjectMapper.selectByExample(example)
.stream()
.map(TestPlanProject::getProjectId)
.collect(Collectors.toList());
if (testPlan != null && StringUtils.isNotBlank(testPlan.getProjectId())) {
if (!projectIds.contains(testPlan.getProjectId())) {
projectIds.add(testPlan.getProjectId());
}
}
if (projectIds.isEmpty()) {
return null;
}

View File

@ -111,6 +111,7 @@ public class TestPlanService {
testPlan.setCreateTime(System.currentTimeMillis());
testPlan.setUpdateTime(System.currentTimeMillis());
testPlan.setCreator(SessionUtils.getUser().getId());
testPlan.setProjectId(SessionUtils.getCurrentProjectId());
testPlanMapper.insert(testPlan);
List<String> userIds = new ArrayList<>();
@ -177,8 +178,16 @@ public class TestPlanService {
}
private void editTestPlanProject(TestPlanDTO testPlan) {
// 将要进行关联的项目ID
List<String> projectIds = testPlan.getProjectIds();
// 如果将要关联的项目ID中包含测试计划所属ID则进行剔除
if (!CollectionUtils.isEmpty(projectIds)) {
if (projectIds.contains(testPlan.getProjectId())) {
projectIds.remove(testPlan.getProjectId());
}
}
// todo 优化 TestPlanList intoPlan 方法会触发此更新
if (StringUtils.isNotBlank(testPlan.getProjectId())) {
TestPlanProjectExample testPlanProjectExample1 = new TestPlanProjectExample();
testPlanProjectExample1.createCriteria().andTestPlanIdEqualTo(testPlan.getId());
List<TestPlanProject> testPlanProjects = testPlanProjectMapper.selectByExample(testPlanProjectExample1);
@ -195,16 +204,25 @@ public class TestPlanService {
});
TestPlanProjectExample testPlanProjectExample = new TestPlanProjectExample();
testPlanProjectExample.createCriteria().andTestPlanIdEqualTo(testPlan.getId()).andProjectIdNotIn(projectIds);
TestPlanProjectExample.Criteria criteria1 = testPlanProjectExample.createCriteria();
criteria1.andTestPlanIdEqualTo(testPlan.getId());
if (!CollectionUtils.isEmpty(projectIds)) {
criteria1.andProjectIdNotIn(projectIds);
}
testPlanProjectMapper.deleteByExample(testPlanProjectExample);
// 关联的项目下的用例idList
TestCaseExample example = new TestCaseExample();
example.createCriteria().andProjectIdIn(projectIds);
List<TestCase> caseList = testCaseMapper.selectByExample(example);
List<String> caseIds = caseList.stream().map(TestCase::getId).collect(Collectors.toList());
List<String> caseIds = null;
// 测试计划所属项目下的用例不解除关联
projectIds.add(testPlan.getProjectId());
if (!CollectionUtils.isEmpty(projectIds)) {
TestCaseExample example = new TestCaseExample();
example.createCriteria().andProjectIdIn(projectIds);
List<TestCase> caseList = testCaseMapper.selectByExample(example);
caseIds = caseList.stream().map(TestCase::getId).collect(Collectors.toList());
}
// 取消关联所属项目下的用例和计划的关系
// 取消关联项目下的用例和计划的关系
TestPlanTestCaseExample testPlanTestCaseExample = new TestPlanTestCaseExample();
TestPlanTestCaseExample.Criteria criteria = testPlanTestCaseExample.createCriteria().andPlanIdEqualTo(testPlan.getId());
if (!CollectionUtils.isEmpty(caseIds)) {
@ -415,13 +433,13 @@ public class TestPlanService {
if (StringUtils.isBlank(currentWorkspaceId)) {
return null;
}
TestPlanProjectExample testPlanProjectExample = new TestPlanProjectExample();
TestPlanProjectExample.Criteria criteria = testPlanProjectExample.createCriteria();
if (StringUtils.isNotBlank(SessionUtils.getCurrentProjectId())) {
TestPlanExample testPlanExample = new TestPlanExample();
TestPlanExample.Criteria criteria = testPlanExample.createCriteria();
criteria.andProjectIdEqualTo(SessionUtils.getCurrentProjectId());
List<TestPlanProject> testPlanProjects = testPlanProjectMapper.selectByExample(testPlanProjectExample);
if (!CollectionUtils.isEmpty(testPlanProjects)) {
List<String> testPlanIds = testPlanProjects.stream().map(TestPlanProject::getTestPlanId).collect(Collectors.toList());
List<TestPlan> testPlans = testPlanMapper.selectByExample(testPlanExample);
if (!CollectionUtils.isEmpty(testPlans)) {
List<String> testPlanIds = testPlans.stream().map(TestPlan::getId).collect(Collectors.toList());
TestPlanExample testPlanTestCaseExample = new TestPlanExample();
testPlanTestCaseExample.createCriteria().andWorkspaceIdEqualTo(currentWorkspaceId)
.andIdIn(testPlanIds)
@ -434,18 +452,18 @@ public class TestPlanService {
}
public List<TestPlan> listTestAllPlan(String currentWorkspaceId) {
TestPlanProjectExample testPlanProjectExample = new TestPlanProjectExample();
TestPlanProjectExample.Criteria criteria = testPlanProjectExample.createCriteria();
if (StringUtils.isNotBlank(SessionUtils.getCurrentProjectId())) {
TestPlanExample testPlanExample = new TestPlanExample();
TestPlanExample.Criteria criteria = testPlanExample.createCriteria();
criteria.andProjectIdEqualTo(SessionUtils.getCurrentProjectId());
List<TestPlanProject> testPlanProjects = testPlanProjectMapper.selectByExample(testPlanProjectExample);
if (!CollectionUtils.isEmpty(testPlanProjects)) {
List<String> testPlanIds = testPlanProjects.stream().map(TestPlanProject::getTestPlanId).collect(Collectors.toList());
TestPlanExample testPlanExample = new TestPlanExample();
TestPlanExample.Criteria testPlanCriteria = testPlanExample.createCriteria();
List<TestPlan> testPlans = testPlanMapper.selectByExample(testPlanExample);
if (!CollectionUtils.isEmpty(testPlans)) {
List<String> testPlanIds = testPlans.stream().map(TestPlan::getId).collect(Collectors.toList());
TestPlanExample testPlanExample1 = new TestPlanExample();
TestPlanExample.Criteria testPlanCriteria = testPlanExample1.createCriteria();
testPlanCriteria.andWorkspaceIdEqualTo(currentWorkspaceId);
testPlanCriteria.andIdIn(testPlanIds);
return testPlanMapper.selectByExample(testPlanExample);
return testPlanMapper.selectByExample(testPlanExample1);
}
}

View File

@ -55,6 +55,11 @@ public class TestPlanTestCaseService {
});
return list;
}
public List<TestPlanCaseDTO> listByPlanId(QueryTestPlanCaseRequest request) {
List<TestPlanCaseDTO> list = extTestPlanTestCaseMapper.listByPlanId(request);
return list;
}
public List<TestPlanCaseDTO> listByNode(QueryTestPlanCaseRequest request) {
List<TestPlanCaseDTO> list = extTestPlanTestCaseMapper.listByNode(request);

@ -1 +1 @@
Subproject commit 068127ce59ea8b016434ed52a9de4a7a4b13bdb4
Subproject commit f27d1609d77f7d6c988d37d709466e844d350e17

View File

@ -0,0 +1,56 @@
alter table test_plan add project_id varchar(50) null comment '测试计划所属项目';
ALTER TABLE api_test_case MODIFY COLUMN name varchar(255) NOT NULL COMMENT 'Test name';
DROP PROCEDURE IF EXISTS test_cursor;
DELIMITER //
CREATE PROCEDURE test_cursor()
BEGIN
DECLARE planId VARCHAR(64);
DECLARE done INT DEFAULT 0;
DECLARE cursor1 CURSOR FOR (SELECT id
FROM test_plan
WHERE project_id IS NULL);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor1;
outer_loop:
LOOP
FETCH cursor1 INTO planId;
IF done
THEN
LEAVE outer_loop;
END IF;
select count(1) as s, project_id
into @num, @projectId
from test_plan_test_case
join test_case on case_id = test_case.id
where plan_id = planId
group by project_id
order by s desc
limit 1;
IF @projectId = ''
THEN
select test_plan_project.project_id
into @projectId
from test_plan
join test_plan_project on test_plan.id = test_plan_project.test_plan_id
where test_plan.id = planId
limit 1;
END IF;
UPDATE test_plan SET test_plan.project_id = @projectId WHERE test_plan.id = planId;
DELETE FROM test_plan_project
WHERE test_plan_project.project_id = @projectId
AND test_plan_project.test_plan_id = planId;
SET @projectId = '';
SET done = 0;
END LOOP;
CLOSE cursor1;
END //
DELIMITER ;
CALL test_cursor();
DROP PROCEDURE IF EXISTS test_cursor;

View File

@ -230,9 +230,7 @@
let path = "/test/plan/project";
this.$post(path, {planId: this.tableData[i].id}, res => {
let arr = res.data;
let projectName = arr.map(data => data.name).join("、");
let projectIds = arr.map(data => data.id);
this.$set(this.tableData[i], "projectName", projectName);
this.$set(this.tableData[i], "projectIds", projectIds);
})
}

View File

@ -27,7 +27,7 @@
computed: {
error() {
return this.response.responseCode >= 400;
return this.response && this.response.responseCode && this.response.responseCode >= 400;
}
}
}

View File

@ -41,7 +41,7 @@
</div>
<div class="ms-div">
Body :
<pre>{{response.body}}</pre>
<pre>{{response.body ? response.body:""}}</pre>
</div>
</el-tab-pane>

View File

@ -307,7 +307,7 @@ export default {
rules: {
name: [
{required: true, message: this.$t('test_track.case.input_name'), trigger: 'blur'},
{max: 50, message: this.$t('test_track.length_less_than') + '50', trigger: 'blur'}
{max: 255, message: this.$t('test_track.length_less_than') + '255', trigger: 'blur'}
],
module: [{required: true, message: this.$t('test_track.case.input_module'), trigger: 'change'}],
maintainer: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],

View File

@ -58,11 +58,11 @@
<plan-stage-table-item :stage="scope.row.stage"/>
</template>
</el-table-column>
<el-table-column
prop="projectName"
:label="$t('test_track.plan.plan_project')"
show-overflow-tooltip>
</el-table-column>
<!-- <el-table-column-->
<!-- prop="projectName"-->
<!-- :label="$t('test_track.plan.plan_project')"-->
<!-- show-overflow-tooltip>-->
<!-- </el-table-column>-->
</el-table>
@ -94,12 +94,12 @@
}
this.result = this.$post('/test/plan/list/all/relate', {}, response => {
this.tableData = response.data;
for (let i = 0; i < this.tableData.length; i++) {
let path = "/test/plan/project/name/" + this.tableData[i].id;
this.$get(path, res => {
this.$set(this.tableData[i], "projectName", res.data);
})
}
// for (let i = 0; i < this.tableData.length; i++) {
// let path = "/test/plan/project/name/" + this.tableData[i].id;
// this.$get(path, res => {
// this.$set(this.tableData[i], "projectName", res.data);
// })
// }
});
},
intoPlan(row, event, column) {

View File

@ -21,10 +21,10 @@
</el-col>
<el-col :span="11" :offset="2">
<el-form-item :label="$t('test_track.plan.plan_project')" :label-width="formLabelWidth" prop="projectIds">
<el-form-item :label="$t('test_track.plan.related_project')" :label-width="formLabelWidth" prop="projectIds">
<el-select
v-model="form.projectIds"
:placeholder="$t('test_track.plan.input_plan_project')"
:placeholder="$t('test_track.plan.input_related_project')"
multiple
style="width: 100%"
filterable>
@ -134,7 +134,7 @@
import {WORKSPACE_ID} from '@/common/js/constants';
import TestPlanStatusButton from "../common/TestPlanStatusButton";
import {listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {getCurrentProjectID, listenGoBack, removeGoBackListener} from "@/common/js/utils";
import {LIST_CHANGE, TrackEvent} from "@/business/components/common/head/ListEvent";
export default {
@ -158,7 +158,7 @@ export default {
{required: true, message: this.$t('test_track.plan.input_plan_name'), trigger: 'blur'},
{max: 30, message: this.$t('test_track.length_less_than') + '30', trigger: 'blur'}
],
projectIds: [{required: true, message: this.$t('test_track.plan.input_plan_project'), trigger: 'change'}],
// projectIds: [{required: true, message: this.$t('test_track.plan.input_plan_project'), trigger: 'change'}],
principal: [{required: true, message: this.$t('test_track.plan.input_plan_principal'), trigger: 'change'}],
stage: [{required: true, message: this.$t('test_track.plan.input_plan_stage'), trigger: 'change'}],
description: [{max: 200, message: this.$t('test_track.length_less_than') + '200', trigger: 'blur'}]
@ -248,7 +248,7 @@ export default {
getProjects() {
this.$get("/project/listAll", (response) => {
if (response.success) {
this.projects = response.data;
this.projects = response.data.filter(da => da.id !== getCurrentProjectID());
} else {
this.$warning()(response.message);
}

View File

@ -228,9 +228,7 @@ export default {
let path = "/test/plan/project";
this.$post(path,{planId: this.tableData[i].id}, res => {
let arr = res.data;
let projectName = arr.map(data => data.name).join("、");
let projectIds = arr.map(data => data.id);
this.$set(this.tableData[i], "projectName", projectName);
let projectIds = arr.filter(d => d.id !== this.tableData[i].projectId).map(data => data.id);
this.$set(this.tableData[i], "projectIds", projectIds);
})
}

View File

@ -799,7 +799,7 @@ 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",
swagger_tip: "Swagger 2.0 and 3.0 json files are supported",
postman_tip: "Only Postman Collection V2.1 json files are supported",
postman_export_tip: "Export the test collection by Postman",
swagger_export_tip: "Export jSON-formatted files via Swagger website",
@ -1057,6 +1057,7 @@ export default {
edit_plan: "Edit test plan",
plan_name: "Test plan name",
plan_project: "Project",
related_project: "Related Project",
plan_stage: "Stage",
plan_status: "Status",
smoke_test: "Smoke test",
@ -1069,6 +1070,7 @@ export default {
input_plan_name: "Please input plan name",
input_plan_principal: "Please select principal",
input_plan_project: "Please select project",
input_related_project: "Please Related project",
input_plan_stage: "Please select stage",
plan_status_prepare: "Not started",
plan_status_running: "Starting",

View File

@ -801,7 +801,7 @@ export default {
ms_tip: "支持 Metersphere json 格式",
ms_export_tip: "通过 Metersphere 接口测试页面或者浏览器插件导出 json 格式文件",
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
swagger_tip: "只支持 Swagger 2.x 版本的 json 文件",
swagger_tip: "支持 Swagger 2.0 与 3.0 版本的 json 文件",
post_export_tip: "通过 Postman 导出测试集合",
swagger_export_tip: "通过 Swagger 页面导出",
suffixFormatErr: "文件格式不符合要求",
@ -1058,6 +1058,7 @@ export default {
edit_plan: "编辑测试计划",
plan_name: "计划名称",
plan_project: "所属项目",
related_project: "关联项目",
plan_stage: "测试阶段",
plan_status: "当前状态",
smoke_test: "冒烟测试",
@ -1070,6 +1071,7 @@ export default {
input_plan_name: "请输入测试计划名称",
input_plan_principal: "请选择负责人",
input_plan_project: "请选择所属项目",
input_related_project: "请选择关联项目",
input_plan_stage: "请选择测试阶段",
plan_status_prepare: "未开始",
plan_status_running: "进行中",

View File

@ -800,7 +800,7 @@ export default {
ms_tip: "支持 Metersphere json 格式",
ms_export_tip: "通過 Metersphere 接口測試頁面或者瀏覽器插件導出 json 格式文件",
postman_tip: "只支持 Postman Collection v2.1 格式的 json 文件",
swagger_tip: "只支持 Swagger 2.x 版本的 json 文件",
swagger_tip: "支持 Swagger 2.0 與 3.0版本的 json 文件",
post_export_tip: "通過 Postman 導出測試集合",
swagger_export_tip: "通過 Swagger 頁面導出",
suffixFormatErr: "文件格式不符合要求",
@ -1057,6 +1057,7 @@ export default {
edit_plan: "編輯測試計劃",
plan_name: "計劃名稱",
plan_project: "所屬項目",
related_project: "關聯項目",
plan_stage: "測試階段",
plan_status: "當前狀態",
smoke_test: "冒煙測試",
@ -1069,6 +1070,7 @@ export default {
input_plan_name: "請輸入測試計劃名稱",
input_plan_principal: "請選擇負責人",
input_plan_project: "請選擇所屬項目",
input_related_project: "請選擇關聯項目",
input_plan_stage: "請選擇測試階段",
plan_status_prepare: "未開始",
plan_status_running: "進行中",