diff --git a/backend/pom.xml b/backend/pom.xml index ddb8681c7b..edd70316fa 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -422,6 +422,12 @@ ${jmeter.version} + + com.jayway.jsonpath + json-path + 2.5.0 + + diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/SaveApiPlanRequest.java b/backend/src/main/java/io/metersphere/api/dto/automation/SaveApiPlanRequest.java index 516efd56e8..2e91f5999e 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/SaveApiPlanRequest.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/SaveApiPlanRequest.java @@ -34,4 +34,14 @@ public class SaveApiPlanRequest { private String projectId; + /** + * 项目环境对应关系 + */ + private Map envMap; + + /** + * 用例的环境的对应关系 + */ + private Map> mapping; + } diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/HarScenarioParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/HarScenarioParser.java new file mode 100644 index 0000000000..92cc0b654c --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/HarScenarioParser.java @@ -0,0 +1,150 @@ +package io.metersphere.api.dto.automation.parse; + +import com.alibaba.fastjson.JSONArray; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.parse.har.HarUtils; +import io.metersphere.api.dto.definition.parse.har.model.*; +import io.metersphere.api.dto.definition.request.MsScenario; +import io.metersphere.api.dto.definition.request.MsTestElement; +import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.api.jmeter.RequestResult; +import io.metersphere.api.jmeter.ResponseResult; +import io.metersphere.api.parse.HarScenarioAbstractParser; +import io.metersphere.api.service.ApiScenarioModuleService; +import io.metersphere.base.domain.ApiScenarioModule; +import io.metersphere.base.domain.ApiScenarioWithBLOBs; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.LogUtil; +import org.apache.commons.lang3.ObjectUtils; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +public class HarScenarioParser extends HarScenarioAbstractParser { + + @Override + public ScenarioImport parse(InputStream source, ApiTestImportRequest request) { + Har har = null; + try { + String sourceStr = getApiTestStr(source); + har = HarUtils.read(sourceStr); + } catch (Exception e) { + MSException.throwException(e.getMessage()); + LogUtil.error(e.getMessage(), e); + } + + if (ObjectUtils.isEmpty(har)) { + MSException.throwException("解析失败,请确认选择的是 Har 格式!"); + } + + ScenarioImport scenarioImport = new ScenarioImport(); + + String harName = request.getFileName(); + // 场景步骤 + LinkedList apiScenarioWithBLOBs = new LinkedList<>(); +// ApiScenarioWithBLOBs scenario = new ApiScenarioWithBLOBs(); +// scenario.setName(harName); + + MsScenario msScenario = new MsScenario(); + msScenario.setName(harName); + this.projectId = request.getProjectId(); + if (!ObjectUtils.isEmpty(har.log)&&!ObjectUtils.isEmpty(har.log.entries)) { + parseItem(har.log.entries, msScenario, apiScenarioWithBLOBs); + } + + // 生成场景对象 + List scenarioWithBLOBs = new LinkedList<>(); + parseScenarioWithBLOBs(scenarioWithBLOBs, msScenario, request); + scenarioImport.setData(scenarioWithBLOBs); + return scenarioImport; + } + + private void parseScenarioWithBLOBs(List scenarioWithBLOBsList, MsScenario msScenario, ApiTestImportRequest request) { + ApiScenarioModule module = ApiScenarioImportUtil.getSelectModule(request.getModuleId()); + if (module == null) { + ApiScenarioModuleService apiModuleService = CommonBeanFactory.getBean(ApiScenarioModuleService.class); + module = apiModuleService.getNewModule(msScenario.getName(), projectId, 1); + } + + ApiScenarioWithBLOBs scenarioWithBLOBs = parseScenario(msScenario); + if (module != null) { + scenarioWithBLOBs.setApiScenarioModuleId(module.getId()); + scenarioWithBLOBs.setModulePath("/" + module.getName()); + } + scenarioWithBLOBsList.add(scenarioWithBLOBs); + } + + private void parseItem(List items, MsScenario scenario, LinkedList results) { + for (HarEntry item : items) { + MsHTTPSamplerProxy request = parseHar(item); + if (request != null) { + results.add(request); + } + request.setRequestResult(getRequestResult(request,item)); + } + scenario.setHashTree(results); + } + +// private List parseScenarioVariable(List postmanKeyValues) { +// if (postmanKeyValues == null) { +// return null; +// } +// List keyValues = new ArrayList<>(); +// postmanKeyValues.forEach(item -> keyValues.add(new ScenarioVariable(item.getKey(), item.getValue(), item.getDescription(), VariableTypeConstants.CONSTANT.name()))); +// return keyValues; +// } +private RequestResult getRequestResult(MsHTTPSamplerProxy samplerProxy,HarEntry harEntry) { + HarRequest request = harEntry.request; + HarResponse response = harEntry.response; + + RequestResult requestResult = new RequestResult(); + requestResult.setName("Response"); + requestResult.setUrl(request.url); + requestResult.setMethod(request.method); + if(samplerProxy.getBody()!= null){ + List keyValueList = new ArrayList<>(); + if(!ObjectUtils.isEmpty(request.queryString)){ + for (HarQueryParm model : request.queryString) { + KeyValue keyValue = new KeyValue(model.name,model.value); + keyValueList.add(keyValue); + } + } + if(!ObjectUtils.isEmpty(request.postData)&&!ObjectUtils.isEmpty(request.postData.params)){ + for (HarPostParam model : request.postData.params) { + KeyValue keyValue = new KeyValue(model.name,model.value); + keyValueList.add(keyValue); + } + } + + requestResult.setBody(JSONArray.toJSONString(keyValueList)); + } + + requestResult.setHeaders(JSONArray.toJSONString(request.headers)); + requestResult.setRequestSize(request.bodySize); +// requestResult.setStartTime(result.getStartTime()); +// requestResult.setEndTime(result.getEndTime()); +// requestResult.setTotalAssertions(result.getAssertionResults().length); +// requestResult.setSuccess(result.isSuccessful()); +// requestResult.setError(result.getErrorCount()); + if(!ObjectUtils.isEmpty(request.cookies)){ + requestResult.setCookies(JSONArray.toJSONString(request.cookies)); + } + + ResponseResult responseResult = requestResult.getResponseResult(); + responseResult.setHeaders(JSONArray.toJSONString(response.headers)); +// responseResult.setLatency(result.getLatency()); + responseResult.setResponseCode(String.valueOf(response.status)); + responseResult.setResponseSize(response.bodySize); +// responseResult.setResponseTime(result.getTime()); + if(response.content != null && response.content.text != null){ + responseResult.setBody(response.content.text); + responseResult.setResponseMessage(response.content.text); + } + + return requestResult; +} +} diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java index 905cfca561..971ea265f9 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/MsJmeterParser.java @@ -489,6 +489,7 @@ public class MsJmeterParser extends ApiImportAbstractParser { assertionJsonPath.setDescription(jsonPathAssertion.getName()); assertionJsonPath.setExpression(jsonPathAssertion.getJsonPath()); assertionJsonPath.setExpect(jsonPathAssertion.getExpectedValue()); + assertionJsonPath.setOption(jsonPathAssertion.getPropertyAsString("ASS_OPTION")); assertions.setName(jsonPathAssertion.getName()); assertions.getJsonPath().add(assertionJsonPath); } else if (key instanceof XPath2Assertion) { diff --git a/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java index b2509a9eed..ff47ce30bb 100644 --- a/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java +++ b/backend/src/main/java/io/metersphere/api/dto/automation/parse/ScenarioImportParserFactory.java @@ -12,6 +12,8 @@ public class ScenarioImportParserFactory { return new PostmanScenarioParser(); } else if (StringUtils.equals(ApiImportPlatform.Jmeter.name(), platform)) { return new MsJmeterParser(); + } else if (StringUtils.equals(ApiImportPlatform.Har.name(), platform)) { + return new HarScenarioParser(); } return null; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportParserFactory.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportParserFactory.java index 3b73d92f0c..f7db9334b0 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportParserFactory.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/ApiDefinitionImportParserFactory.java @@ -12,6 +12,8 @@ public class ApiDefinitionImportParserFactory { return new PostmanDefinitionParser(); } else if (StringUtils.equals(ApiImportPlatform.Swagger2.name(), platform)) { return new Swagger2Parser(); + }else if (StringUtils.equals(ApiImportPlatform.Har.name(), platform)) { + return new HarParser(); } return null; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/HarAbstractParser.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/HarAbstractParser.java new file mode 100644 index 0000000000..b437567bb0 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/HarAbstractParser.java @@ -0,0 +1,27 @@ +package io.metersphere.api.dto.definition.parse; + + +import io.metersphere.api.parse.ApiImportAbstractParser; +import io.metersphere.base.domain.ApiDefinitionWithBLOBs; +import io.metersphere.base.domain.ApiModule; + +import java.util.List; + +/** + * @author song.tianyang + * @Date 2021/3/10 11:15 上午 + * @Description + */ +public abstract class HarAbstractParser extends ApiImportAbstractParser { + + protected void buildModule(ApiModule parentModule, ApiDefinitionWithBLOBs apiDefinition, List tags) { + if (tags != null) { + tags.forEach(tag -> { + ApiModule module = ApiDefinitionImportUtil.buildModule(parentModule, tag, this.projectId); + apiDefinition.setModuleId(module.getId()); + }); + }else { + apiDefinition.setModuleId(parentModule.getId()); + } + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/HarParser.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/HarParser.java new file mode 100644 index 0000000000..ed54de8e5e --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/HarParser.java @@ -0,0 +1,218 @@ +package io.metersphere.api.dto.definition.parse; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.api.dto.ApiTestImportRequest; +import io.metersphere.api.dto.definition.parse.har.HarUtils; +import io.metersphere.api.dto.definition.parse.har.model.*; +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.base.domain.ApiDefinitionWithBLOBs; +import io.metersphere.base.domain.ApiModule; +import io.metersphere.commons.exception.MSException; +import io.metersphere.commons.utils.LogUtil; +import io.metersphere.commons.utils.XMLUtils; +import io.swagger.models.Model; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author song.tianyang + * @Date 2021/3/10 11:14 上午 + * @Description + */ +public class HarParser extends HarAbstractParser { + + private Map definitions = null; + + @Override + public ApiDefinitionImport parse(InputStream source, ApiTestImportRequest request) { + Har har = null; + try { + String sourceStr = getApiTestStr(source); + har = HarUtils.read(sourceStr); + } catch (Exception e) { + MSException.throwException(e.getMessage()); + LogUtil.error(e.getMessage(), e); + } + + if (ObjectUtils.isEmpty(har)) { + MSException.throwException("解析失败,请确认选择的是 Har 格式!"); + } + + ApiDefinitionImport definitionImport = new ApiDefinitionImport(); + this.projectId = request.getProjectId(); + definitionImport.setData(parseRequests(har, request)); + return definitionImport; + } + + + private List parseRequests(Har har, ApiTestImportRequest importRequest) { + List results = new ArrayList<>(); + + ApiModule parentNode = ApiDefinitionImportUtil.getSelectModule(importRequest.getModuleId()); + + List harEntryList = new ArrayList<>(); + if (har.log != null && har.log.entries != null) { + harEntryList = har.log.entries; + } + + for (HarEntry entry : harEntryList) { + HarRequest harRequest = entry.request; + + //默认取路径的最后一块 + String reqName = ""; + if (harRequest.url != null) { + String[] nameArr = harRequest.url.split("/"); + reqName = nameArr[nameArr.length - 1]; + } + + if (harRequest != null) { + MsHTTPSamplerProxy request = super.buildRequest(reqName, harRequest.url, harRequest.method); + ApiDefinitionWithBLOBs apiDefinition = super.buildApiDefinition(request.getId(), reqName, harRequest.url, harRequest.method, importRequest); + parseParameters(harRequest, request); + parseRequestBody(harRequest, request.getBody()); + addBodyHeader(request); + apiDefinition.setRequest(JSON.toJSONString(request)); + apiDefinition.setResponse(JSON.toJSONString(parseResponse(entry.response))); + buildModule(parentNode, apiDefinition, null); + results.add(apiDefinition); + } + } + + return results; + } + + private void parseParameters(HarRequest harRequest, MsHTTPSamplerProxy request) { + List queryStringList = harRequest.queryString; + queryStringList.forEach(harQueryParm -> { + parseQueryParameters(harQueryParm, request.getArguments()); + }); + List harHeaderList = harRequest.headers; + harHeaderList.forEach(harHeader -> { + parseHeaderParameters(harHeader, request.getHeaders()); + }); + List harCookieList = harRequest.cookies; + harCookieList.forEach(harCookie -> { + parseCookieParameters(harCookie, request.getHeaders()); + }); + } + + + private String getDefaultStringValue(String val) { + return StringUtils.isBlank(val) ? "" : val; + } + + private void parseCookieParameters(HarCookie harCookie, List headers) { + addCookie(headers, harCookie.name, harCookie.value, harCookie.comment, false); + } + + private void parseHeaderParameters(HarHeader harHeader, List headers) { + addHeader(headers, harHeader.name, harHeader.value,harHeader.comment, "", false); + } + + private HttpResponse parseResponse(HarResponse response) { + HttpResponse msResponse = new HttpResponse(); + msResponse.setBody(new Body()); + msResponse.setHeaders(new ArrayList<>()); + msResponse.setType(RequestType.HTTP); + // todo 状态码要调整? + msResponse.setStatusCode(new ArrayList<>()); + if (response != null) { + String responseCode = String.valueOf(response.status); + msResponse.getStatusCode().add(new KeyValue(responseCode, responseCode)); + parseResponseHeader(response, msResponse.getHeaders()); + parseResponseBody(response, msResponse.getBody()); + } + return msResponse; + } + + private void parseResponseHeader(HarResponse response, List msHeaders) { + List harHeaders = response.headers; + if (harHeaders != null) { + for (HarHeader header : harHeaders) { + msHeaders.add(new KeyValue(header.name, header.value, header.comment)); + } + } + } + + private void parseResponseBody(HarResponse response, Body body) { + parseResponseBody(response.content, body); + } + + private void parseRequestBody(HarRequest requestBody, Body body) { + if (requestBody == null) { + return; + } + HarPostData content = requestBody.postData; + if (!StringUtils.equalsIgnoreCase("GET", requestBody.method) || requestBody.postData == null) { + return; + } + String contentType = content.mimeType; + if (StringUtils.isEmpty(contentType)) { + body.setRaw(content.text); + } else { + Map infoMap = new HashMap(); + + if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; + List postParams = content.params; + for (HarPostParam postParam : postParams) { + KeyValue kv = new KeyValue(postParam.name,postParam.value); + body.getKvs().add(kv); + } + } else if (contentType.startsWith(org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)) { + contentType = org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + List postParams = content.params; + for (HarPostParam postParam : postParams) { + KeyValue kv = new KeyValue(postParam.name,postParam.value); + body.getKvs().add(kv); + } + } else if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + body.setRaw(content.text); + } else if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_XML_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_XML_VALUE; + body.setRaw(parseXmlBody(content.text)); + } else if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; + List postParams = content.params; + for (HarPostParam postParam : postParams) { + KeyValue kv = new KeyValue(postParam.name,postParam.value); + body.getKvs().add(kv); + } + } else { + body.setRaw(content.text); + } + } + body.setType(getBodyType(contentType)); + } + + private void parseResponseBody(HarContent content, Body body) { + if (content == null) { + return; + } + String contentType = content.mimeType; + body.setType(getBodyType(contentType)); + body.setRaw(content.text); + } + + private String parseXmlBody(String xmlString) { + JSONObject object = JSONObject.parseObject(getDefaultStringValue(xmlString)); + return XMLUtils.jsonToXmlStr(object); + } + + private void parseQueryParameters(HarQueryParm harQueryParm, List arguments) { + arguments.add(new KeyValue(harQueryParm.name, harQueryParm.value, harQueryParm.comment, false)); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/HarUtils.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/HarUtils.java new file mode 100644 index 0000000000..f6cd6b0f41 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/HarUtils.java @@ -0,0 +1,54 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har; + +import com.alibaba.fastjson.JSONObject; +import com.google.gson.JsonSyntaxException; +import io.metersphere.api.dto.definition.parse.har.model.Har; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.io.IOException; + +/** + * Utility class for working with HAR files. + * + * @author sangupta + * + */ +public class HarUtils { + + public static Har read(File file) throws JsonSyntaxException, IOException { + Har har = JSONObject.parseObject(FileUtils.readFileToString(file), Har.class); + return har; + } + + + public static Har read(String harJson) throws JsonSyntaxException, IOException { + if(StringUtils.isEmpty(harJson)) { + throw new IllegalArgumentException("HAR Json cannot be null/empty"); + } + Har har = JSONObject.parseObject(harJson, Har.class); + return har; + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/command/ViewHar.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/command/ViewHar.java new file mode 100644 index 0000000000..0051bbe0f7 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/command/ViewHar.java @@ -0,0 +1,74 @@ +///** +// * +// * har - HAR file reader, writer and viewer +// * Copyright (c) 2014, Sandeep Gupta +// * +// * http://sangupta.com/projects/har +// * +// * Licensed under the Apache License, Version 2.0 (the "License"); +// * you may not use this file except in compliance with the License. +// * You may obtain a copy of the License at +// * +// * http://www.apache.org/licenses/LICENSE-2.0 +// * +// * Unless required by applicable law or agreed to in writing, software +// * distributed under the License is distributed on an "AS IS" BASIS, +// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// * See the License for the specific language governing permissions and +// * limitations under the License. +// * +// */ +// +//package io.metersphere.api.dto.definition.parse.har.command; +// +//import java.io.File; +// +//import com.sangupta.har.HarUtils; +//import com.sangupta.har.model.Har; +//import com.sangupta.har.model.HarEntry; +//import com.sangupta.har.model.HarPage; +//import com.sangupta.jerry.util.AssertUtils; +// +//import io.airlift.command.Arguments; +//import io.airlift.command.Command; +// +//@Command(name = "view", description = "View HAR file") +//public class ViewHar implements Runnable { +// +// @Arguments +// private String file; +// +// @Override +// public void run() { +// Har har = null; +// +// try { +// har = HarUtils.read(new File(this.file)); +// } catch(Exception e) { +// System.out.println("Error reading HAR file: " + e.getMessage()); +// return; +// } +// +// if(har.log == null || AssertUtils.isEmpty(har.log.pages)) { +// System.out.println("HAR file has no pages!"); +// return; +// } +// +// // connect references +// HarUtils.connectReferences(har); +// +// // start displaying +// System.out.println("Number of pages viewed: " + har.log.pages.size()); +// System.out.println(); +// +// for(HarPage page : har.log.pages) { +// System.out.println(page); +// +// // output the calls for this page +// for(HarEntry entry : page.entries) { +// System.out.println("\t" + entry); +// } +// } +// } +// +//} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/Har.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/Har.java new file mode 100644 index 0000000000..7aab6c0b54 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/Har.java @@ -0,0 +1,32 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class Har { + + public HarLog log; + + @Override + public String toString() { + return "Har [log=" + log + "]"; + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCache.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCache.java new file mode 100644 index 0000000000..cb27a63554 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCache.java @@ -0,0 +1,32 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarCache { + + public HarCacheDetails beforeRequest; + + public HarCacheDetails afterRequest; + + public String comment; + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCacheDetails.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCacheDetails.java new file mode 100644 index 0000000000..ce782f13ad --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCacheDetails.java @@ -0,0 +1,36 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarCacheDetails { + + public String expires; + + public String lastAccess; + + public String etag; + + public String hitCount; + + public String comment; + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarContent.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarContent.java new file mode 100644 index 0000000000..b755e5f373 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarContent.java @@ -0,0 +1,38 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarContent { + + public long size; + + public String mimeType; + + public long compression; + + public String text; + + public String comment; + + public String encoding; + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCookie.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCookie.java new file mode 100644 index 0000000000..58b25254be --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCookie.java @@ -0,0 +1,67 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarCookie { + + public String name; + + public String value; + + public String path; + + public String expires; + + public boolean httpOnly; + + public boolean secure; + + public String comment; + + @Override + public String toString() { + return "[Cookie: " + this.name + "=" + this.value + "]"; + } + + @Override + public int hashCode() { + if(this.name == null) { + return -1; + } + + return this.name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof HarCookie)) { + return false; + } + + if(this.name == null) { + return false; + } + + HarCookie harCookie = (HarCookie) obj; + return this.name.equals(harCookie.name); + } +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCreator.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCreator.java new file mode 100644 index 0000000000..6daa8b391c --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarCreator.java @@ -0,0 +1,39 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarCreator { + + public String name; + + public String version; + + public String comment; + + @Override + public String toString() { + return "HarCreator [name=" + name + ", version=" + version + ", comment=" + comment + "]"; + } + + + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarEntry.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarEntry.java new file mode 100644 index 0000000000..235e3ae8b5 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarEntry.java @@ -0,0 +1,66 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarEntry implements Comparable { + + public String pageref; + + public String startedDateTime; + + public double time; + + public HarRequest request; + + public HarResponse response; + + public HarCache cache; + + public HarTiming timings; + + public String serverIPAddress; + + public String connection; + + public String comment; + + + + @Override + public String toString() { + return "HarEntry [pageref=" + pageref + ", startedDateTime=" + startedDateTime + ", time=" + time + ", request=" + + request + ", response=" + response + ", cache=" + cache + ", timings=" + timings + + ", serverIPAddress=" + serverIPAddress + ", connection=" + connection + ", comment=" + comment + "]"; + } + + + + @Override + public int compareTo(HarEntry o) { + if(o == null) { + return -1; + } + // parse the time and then return + return this.startedDateTime.compareTo(o.startedDateTime); + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarHeader.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarHeader.java new file mode 100644 index 0000000000..3fb506b984 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarHeader.java @@ -0,0 +1,60 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarHeader { + + public String name; + + public String value; + + public String comment; + + @Override + public String toString() { + return "[Header: " + this.name + "=" + this.value + "]"; + } + + @Override + public int hashCode() { + if(this.name == null) { + return -1; + } + + return this.name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof HarHeader)) { + return false; + } + + if(this.name == null) { + return false; + } + + HarHeader harHeader = (HarHeader) obj; + return this.name.equals(harHeader.name); + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarLog.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarLog.java new file mode 100644 index 0000000000..407ab69ef5 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarLog.java @@ -0,0 +1,50 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +import java.util.List; + +public class HarLog { + + public static final String DEFAULT_HAR_VERSION = "1.2"; + + public String version = DEFAULT_HAR_VERSION; + + public HarCreator creator; + + public HarCreator browser; + + public List pages; + + public List entries; + + public String comment; + + @Override + public String toString() { + return "HarLog [version=" + version + ", creator=" + creator + ", browser=" + browser + ", pages=" + pages + + ", entries=" + entries + ", comment=" + comment + "]"; + } + + + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPage.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPage.java new file mode 100644 index 0000000000..da589cc63b --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPage.java @@ -0,0 +1,71 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +import java.util.List; + +public class HarPage { + + public String startedDateTime; + + public String id; + + public String title; + + public HarPageTiming pageTimings; + + public String comment; + + public transient List entries; + + + + @Override + public String toString() { + return "HarPage [startedDateTime=" + startedDateTime + ", id=" + id + ", title=" + title + ", pageTimings=" + + pageTimings + ", comment=" + comment + "]"; + } + + @Override + public int hashCode() { + if(this.id == null) { + return -1; + } + + return this.id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof HarPage)) { + return false; + } + + if(this.id == null) { + return false; + } + + HarPage harPage = (HarPage) obj; + return this.id.equals(harPage.id); + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPageTiming.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPageTiming.java new file mode 100644 index 0000000000..4664e11688 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPageTiming.java @@ -0,0 +1,39 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarPageTiming { + + public double onContentLoad; + + public double onLoad; + + public String comment; + + @Override + public String toString() { + return "HarPageTiming [onContentLoad=" + onContentLoad + ", onLoad=" + onLoad + ", comment=" + comment + "]"; + } + + + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPostData.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPostData.java new file mode 100644 index 0000000000..caf871c355 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPostData.java @@ -0,0 +1,36 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +import java.util.List; + +public class HarPostData { + + public String mimeType; + + public List params; + + public String text; + + public String comment; + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPostParam.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPostParam.java new file mode 100644 index 0000000000..d6bb6723a4 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarPostParam.java @@ -0,0 +1,64 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarPostParam { + + public String name; + + public String value; + + public String fileName; + + public String contentType; + + public String comment; + + @Override + public String toString() { + return "[Post Param: " + this.name + "=" + this.value + "]"; + } + + @Override + public int hashCode() { + if(this.name == null) { + return -1; + } + + return this.name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof HarPostParam)) { + return false; + } + + if(this.name == null) { + return false; + } + + HarPostParam harPostParam = (HarPostParam) obj; + return this.name.equals(harPostParam.name); + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarQueryParm.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarQueryParm.java new file mode 100644 index 0000000000..37ed8cf749 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarQueryParm.java @@ -0,0 +1,60 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarQueryParm { + + public String name; + + public String value; + + public String comment; + + @Override + public String toString() { + return "[Query Param: " + this.name + "=" + this.value + "]"; + } + + @Override + public int hashCode() { + if(this.name == null) { + return -1; + } + + return this.name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof HarQueryParm)) { + return false; + } + + if(this.name == null) { + return false; + } + + HarQueryParm harQueryParm = (HarQueryParm) obj; + return this.name.equals(harQueryParm.name); + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarRequest.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarRequest.java new file mode 100644 index 0000000000..8b3da8bd90 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarRequest.java @@ -0,0 +1,53 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +import java.util.List; + +public class HarRequest { + + public String method; + + public String url; + + public String httpVersion; + + public List cookies; + + public List headers; + + public List queryString; + + public HarPostData postData; + + public long headersSize; + + public long bodySize; + + public String comment; + + @Override + public String toString() { + return this.method + " " + this.url + " " + this.httpVersion; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarResponse.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarResponse.java new file mode 100644 index 0000000000..dbef3a0b81 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarResponse.java @@ -0,0 +1,51 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +import java.util.List; + +public class HarResponse { + + public int status; + + public String statusText; + + public String httpVersion; + + public List headers; + + public List cookies; + + public HarContent content; + + public String redirectURL; + + public long headersSize; + + public long bodySize; + + @Override + public String toString() { + return "HTTP " + this.status + " (" + this.statusText + ")"; + } + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarTiming.java b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarTiming.java new file mode 100644 index 0000000000..81be64f5d3 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/dto/definition/parse/har/model/HarTiming.java @@ -0,0 +1,50 @@ +/** + * + * har - HAR file reader, writer and viewer + * Copyright (c) 2014, Sandeep Gupta + * + * http://sangupta.com/projects/har + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.metersphere.api.dto.definition.parse.har.model; + +public class HarTiming { + + public double blocked; + + public double dns; + + public double connect; + + public double send; + + public double wait; + + public double receive; + + public double ssl; + + public String comment; + + @Override + public String toString() { + return "HarTiming [blocked=" + blocked + ", dns=" + dns + ", connect=" + connect + ", send=" + send + ", wait=" + + wait + ", receive=" + receive + ", ssl=" + ssl + ", comment=" + comment + "]"; + } + + + +} diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertionJsonPath.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertionJsonPath.java index a86686a369..7bc4989498 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertionJsonPath.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertionJsonPath.java @@ -10,6 +10,7 @@ public class MsAssertionJsonPath extends MsAssertionType { private String expect; private String expression; private String description; + private String option = "REGEX"; public MsAssertionJsonPath() { setType(MsAssertionType.JSON_PATH); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java index 1dfbe65be0..1a4de305d3 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/assertions/MsAssertions.java @@ -96,7 +96,12 @@ public class MsAssertions extends MsTestElement { assertion.setJsonValidationBool(true); assertion.setExpectNull(false); assertion.setInvert(false); - assertion.setIsRegex(true); + assertion.setProperty("ASS_OPTION",assertionJsonPath.getOption()); + if (StringUtils.isEmpty(assertionJsonPath.getOption()) || "REGEX".equals(assertionJsonPath.getOption())) { + assertion.setIsRegex(true); + } else { + assertion.setIsRegex(false); + } return assertion; } diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/MsJSR223Processor.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/MsJSR223Processor.java index 718df9b625..ba03b6a7a0 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/MsJSR223Processor.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/processors/MsJSR223Processor.java @@ -40,6 +40,8 @@ public class MsJSR223Processor extends MsTestElement { if (StringUtils.isNotEmpty(name) && !config.isOperating()) { processor.setName(this.getName() + "<->" + name); } + processor.setProperty("MS-ID", this.getId()); + processor.setProperty(TestElement.TEST_CLASS, JSR223Sampler.class.getName()); processor.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI")); processor.setProperty("cacheKey", "true"); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java index 75800b50ad..43f0534cf5 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsDubboSampler.java @@ -82,6 +82,7 @@ public class MsDubboSampler extends MsTestElement { } sampler.setProperty(TestElement.TEST_CLASS, DubboSample.class.getName()); sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("DubboSampleGui")); + sampler.setProperty("MS-ID", this.getId()); sampler.addTestElement(configCenter(this.getConfigCenter())); sampler.addTestElement(registryCenter(this.getRegistryCenter())); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java index dfaa8841bf..73238aab23 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsHTTPSamplerProxy.java @@ -85,8 +85,8 @@ public class MsHTTPSamplerProxy extends MsTestElement { @JSONField(ordinal = 34) private List arguments; -// @JSONField(ordinal = 35) -// private Object requestResult; + @JSONField(ordinal = 35) + private Object requestResult; @JSONField(ordinal = 36) private MsAuthManager authManager; @@ -106,6 +106,7 @@ public class MsHTTPSamplerProxy extends MsTestElement { } sampler.setProperty(TestElement.TEST_CLASS, HTTPSamplerProxy.class.getName()); sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("HttpTestSampleGui")); + sampler.setProperty("MS-ID", this.getId()); sampler.setMethod(this.getMethod()); sampler.setContentEncoding("UTF-8"); sampler.setConnectTimeout(this.getConnectTimeout() == null ? "6000" : this.getConnectTimeout()); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java index baf4ea93a4..1b44c9141d 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsJDBCSampler.java @@ -119,6 +119,8 @@ public class MsJDBCSampler extends MsTestElement { } sampler.setProperty(TestElement.TEST_CLASS, JDBCSampler.class.getName()); sampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TestBeanGUI")); + sampler.setProperty("MS-ID", this.getId()); + // request.getDataSource() 是ID,需要转换为Name sampler.setProperty("dataSource", this.dataSource.getName()); sampler.setProperty("query", this.getQuery()); diff --git a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java index bc9d4be3bd..d50913b27d 100644 --- a/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java +++ b/backend/src/main/java/io/metersphere/api/dto/definition/request/sampler/MsTCPSampler.java @@ -117,6 +117,7 @@ public class MsTCPSampler extends MsTestElement { if (StringUtils.isNotEmpty(name) && !config.isOperating()) { tcpSampler.setName(this.getName() + "<->" + name); } + tcpSampler.setProperty("MS-ID", this.getId()); tcpSampler.setProperty(TestElement.TEST_CLASS, TCPSampler.class.getName()); tcpSampler.setProperty(TestElement.GUI_CLASS, SaveService.aliasToClass("TCPSamplerGui")); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java index 3923ea49e6..0a704845f9 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/APIBackendListenerClient.java @@ -239,7 +239,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl LogUtil.error(e.getMessage(), e); } } - sendTask(report, reportUrl, testResult); + sendTask(report, reportUrl, testResult); } private static void sendTask(ApiTestReport report, String reportUrl, TestResult testResult) { @@ -303,6 +303,7 @@ public class APIBackendListenerClient extends AbstractBackendListenerClient impl private RequestResult getRequestResult(SampleResult result) { RequestResult requestResult = new RequestResult(); + requestResult.setId(result.getSamplerId()); requestResult.setName(result.getSampleLabel()); requestResult.setUrl(result.getUrlAsString()); requestResult.setMethod(getMethod(result)); diff --git a/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java b/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java index b90c0c0db0..74ae9c12d4 100644 --- a/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java +++ b/backend/src/main/java/io/metersphere/api/jmeter/RequestResult.java @@ -7,6 +7,8 @@ import java.util.List; @Data public class RequestResult { + // 请求ID + private String id; private String name; diff --git a/backend/src/main/java/io/metersphere/api/parse/HarScenarioAbstractParser.java b/backend/src/main/java/io/metersphere/api/parse/HarScenarioAbstractParser.java new file mode 100644 index 0000000000..d438d60109 --- /dev/null +++ b/backend/src/main/java/io/metersphere/api/parse/HarScenarioAbstractParser.java @@ -0,0 +1,217 @@ +package io.metersphere.api.parse; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.metersphere.api.dto.definition.parse.har.model.*; +import io.metersphere.api.dto.definition.request.MsTestElement; +import io.metersphere.api.dto.definition.request.processors.pre.MsJSR223PreProcessor; +import io.metersphere.api.dto.definition.request.sampler.MsHTTPSamplerProxy; +import io.metersphere.api.dto.parse.postman.PostmanEvent; +import io.metersphere.api.dto.parse.postman.PostmanKeyValue; +import io.metersphere.api.dto.parse.postman.PostmanRequest; +import io.metersphere.api.dto.parse.postman.PostmanScript; +import io.metersphere.api.dto.scenario.Body; +import io.metersphere.api.dto.scenario.KeyValue; +import io.metersphere.commons.constants.MsRequestBodyType; +import io.metersphere.commons.constants.PostmanRequestBodyMode; +import io.metersphere.commons.utils.XMLUtils; +import io.swagger.v3.oas.models.media.Schema; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.stream.Collectors; + +public abstract class HarScenarioAbstractParser extends ApiImportAbstractParser { + + protected MsHTTPSamplerProxy parseHar(HarEntry harEntry) { + HarRequest harRequest = harEntry.request; + if (harRequest == null) { + return null; + } + MsHTTPSamplerProxy request = buildRequest(harRequest.url, harRequest.url,harRequest.method); + if (StringUtils.isNotBlank(request.getPath())) { + String path = request.getPath().split("\\?")[0]; + path = path.replace("{{", "${"); + path = path.replace("}}", "}"); + request.setPath(path); + } else { + request.setPath("/"); + } + parseParameters(harRequest, request); + parseRequestBody(harRequest, request.getBody()); + addBodyHeader(request); + return request; + } + + private void parseParameters(HarRequest harRequest, MsHTTPSamplerProxy request) { + List queryStringList = harRequest.queryString; + queryStringList.forEach(harQueryParm -> { + parseQueryParameters(harQueryParm, request.getArguments()); + }); + List harHeaderList = harRequest.headers; + harHeaderList.forEach(harHeader -> { + parseHeaderParameters(harHeader, request.getHeaders()); + }); + List harCookieList = harRequest.cookies; + harCookieList.forEach(harCookie -> { + parseCookieParameters(harCookie, request.getHeaders()); + }); + } + private void parseRequestBody(HarRequest requestBody, Body body) { + if (requestBody == null) { + return; + } + HarPostData content = requestBody.postData; + if (!StringUtils.equalsIgnoreCase("GET", requestBody.method) || requestBody.postData == null) { + return; + } + String contentType = content.mimeType; + if (StringUtils.isEmpty(contentType)) { + body.setRaw(content.text); + } else { + Map infoMap = new HashMap(); + + if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE; + List postParams = content.params; + for (HarPostParam postParam : postParams) { + KeyValue kv = new KeyValue(postParam.name,postParam.value); + body.getKvs().add(kv); + } + } else if (contentType.startsWith(org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE)) { + contentType = org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; + List postParams = content.params; + for (HarPostParam postParam : postParams) { + KeyValue kv = new KeyValue(postParam.name,postParam.value); + body.getKvs().add(kv); + } + } else if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_JSON_VALUE; + body.setRaw(content.text); + } else if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_XML_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_XML_VALUE; + body.setRaw(parseXmlBody(content.text)); + } else if (contentType.startsWith(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE)) { + contentType = org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; + List postParams = content.params; + for (HarPostParam postParam : postParams) { + KeyValue kv = new KeyValue(postParam.name,postParam.value); + body.getKvs().add(kv); + } + } else { + body.setRaw(content.text); + } + } + body.setType(getBodyType(contentType)); + } + + + private void parseQueryParameters(HarQueryParm harQueryParm, List arguments) { + arguments.add(new KeyValue(harQueryParm.name, harQueryParm.value, harQueryParm.comment, false)); + } + private void parseCookieParameters(HarCookie harCookie, List headers) { + addCookie(headers, harCookie.name, harCookie.value, harCookie.comment, false); + } + + private void parseHeaderParameters(HarHeader harHeader, List headers) { + addHeader(headers, harHeader.name, harHeader.value,harHeader.comment, "", false); + } + + private void addPreScript(MsHTTPSamplerProxy request, List event) { + if (request != null && CollectionUtils.isNotEmpty(event)) { + StringBuilder scriptStr = new StringBuilder(); + event = event.stream() + .filter(item -> item.getScript() != null) + .collect(Collectors.toList()); + event.forEach(item -> { + PostmanScript script = item.getScript(); + if (script != null && item.getListen().contains("prerequest")) { + List exec = script.getExec(); + if (CollectionUtils.isNotEmpty(exec)) { + exec.forEach(col -> { + if (StringUtils.isNotEmpty(col)) { + scriptStr.append(col + "\n"); + } + }); + } + } + }); + if (StringUtils.isNotBlank(scriptStr)) { + MsJSR223PreProcessor jsr223PreProcessor = new MsJSR223PreProcessor(); + jsr223PreProcessor.setName("JSR223PreProcessor"); + jsr223PreProcessor.setScriptLanguage("javascript"); + jsr223PreProcessor.setScript(scriptStr.toString()); + LinkedList hashTree = new LinkedList<>(); + hashTree.add(jsr223PreProcessor); + request.setHashTree(hashTree); + } + } + } + + private List parseKeyValue(List postmanKeyValues) { + if (postmanKeyValues == null) { + return null; + } + List keyValues = new ArrayList<>(); + postmanKeyValues.forEach(item -> keyValues.add(new KeyValue(item.getKey(), item.getValue(), item.getDescription(), item.getContentType()))); + return keyValues; + } + + private void parseBody(Body body, PostmanRequest requestDesc) { + JSONObject postmanBody = requestDesc.getBody(); + if (postmanBody == null) { + return; + } + String bodyMode = postmanBody.getString("mode"); + if (StringUtils.isBlank(bodyMode)) { + return; + } + if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.RAW.value())) { + parseRawBody(body, postmanBody, bodyMode); + } else if (StringUtils.equalsAny(bodyMode, PostmanRequestBodyMode.FORM_DATA.value(), PostmanRequestBodyMode.URLENCODED.value())) { + List postmanKeyValues = JSON.parseArray(postmanBody.getString(bodyMode), PostmanKeyValue.class); + body.setKvs(parseKeyValue(postmanKeyValues)); + if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FORM_DATA.value())) { + body.setType(Body.FORM_DATA); + } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.URLENCODED.value())) { + body.setType(Body.WWW_FROM); + } + } else if (StringUtils.equals(bodyMode, PostmanRequestBodyMode.FILE.value())) { + body.setType(Body.BINARY); + body.setKvs(new ArrayList<>()); + } + } + + private String parseXmlBody(String xmlString) { + JSONObject object = JSONObject.parseObject(getDefaultStringValue(xmlString)); + return XMLUtils.jsonToXmlStr(object); + } + + private void parseRawBody(Body body, JSONObject postmanBody, String bodyMode) { + body.setRaw(postmanBody.getString(bodyMode)); + body.setType(MsRequestBodyType.RAW.value()); + JSONObject options = postmanBody.getJSONObject("options"); + if (options != null) { + JSONObject raw = options.getJSONObject(PostmanRequestBodyMode.RAW.value()); + if (raw != null) { + String bodyType = ""; + switch (raw.getString("language")) { + case "json": + bodyType = Body.JSON; + break; + case "xml": + bodyType = Body.XML; + break; + default: + bodyType = Body.RAW; + } + body.setType(bodyType); + } + } + } + + private String getDefaultStringValue(String val) { + return StringUtils.isBlank(val) ? "" : val; + } +} diff --git a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java index d6a92e7d44..4846a2b422 100644 --- a/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java +++ b/backend/src/main/java/io/metersphere/api/service/ApiAutomationService.java @@ -611,27 +611,38 @@ public class ApiAutomationService { if (CollectionUtils.isEmpty(request.getPlanIds())) { MSException.throwException(Translator.get("plan id is null ")); } - List scenarioIds = request.getScenarioIds(); - if (request.isSelectAllDate()) { - scenarioIds = this.getAllScenarioIdsByFontedSelect( - request.getModuleIds(), request.getName(), request.getProjectId(), request.getFilters(), request.getUnSelectIds()); - } +// List scenarioIds = request.getScenarioIds(); +// if (request.isSelectAllDate()) { +// scenarioIds = this.getAllScenarioIdsByFontedSelect( +// request.getModuleIds(), request.getName(), request.getProjectId(), request.getFilters(), request.getUnSelectIds()); +// } + Map> mapping = request.getMapping(); + Map envMap = request.getEnvMap(); + Set set = mapping.keySet(); List list = extTestPlanMapper.selectByIds(request.getPlanIds()); SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH); ExtTestPlanScenarioCaseMapper scenarioBatchMapper = sqlSession.getMapper(ExtTestPlanScenarioCaseMapper.class); ExtTestPlanApiCaseMapper apiCaseBatchMapper = sqlSession.getMapper(ExtTestPlanApiCaseMapper.class); for (TestPlanDTO testPlan : list) { - if (scenarioIds != null) { - for (String scenarioId : scenarioIds) { + if (!set.isEmpty()) { + set.forEach(id -> { + Map newEnvMap = new HashMap<>(16); + if (envMap != null && !envMap.isEmpty()) { + List lt = mapping.get(id); + lt.forEach(l -> { + newEnvMap.put(l, envMap.get(l)); + }); + } TestPlanApiScenario testPlanApiScenario = new TestPlanApiScenario(); testPlanApiScenario.setId(UUID.randomUUID().toString()); - testPlanApiScenario.setApiScenarioId(scenarioId); + testPlanApiScenario.setApiScenarioId(id); testPlanApiScenario.setTestPlanId(testPlan.getId()); testPlanApiScenario.setCreateTime(System.currentTimeMillis()); testPlanApiScenario.setUpdateTime(System.currentTimeMillis()); + testPlanApiScenario.setEnvironment(JSON.toJSONString(newEnvMap)); scenarioBatchMapper.insertIfNotExists(testPlanApiScenario); - } + }); } if (request.getApiIds() != null) { for (String caseId : request.getApiIds()) { diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiDefinition.java b/backend/src/main/java/io/metersphere/base/domain/ApiDefinition.java index 44816f4f6d..4e6bbe85d8 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiDefinition.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiDefinition.java @@ -1,8 +1,7 @@ package io.metersphere.base.domain; -import lombok.Data; - import java.io.Serializable; +import lombok.Data; @Data public class ApiDefinition implements Serializable { @@ -38,5 +37,7 @@ public class ApiDefinition implements Serializable { private String tags; + private String originalState; + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiDefinitionExample.java b/backend/src/main/java/io/metersphere/base/domain/ApiDefinitionExample.java index cf1c6535ff..41b914ba8e 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiDefinitionExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiDefinitionExample.java @@ -1193,6 +1193,76 @@ public class ApiDefinitionExample { addCriterion("tags not between", value1, value2, "tags"); return (Criteria) this; } + + public Criteria andOriginalStateIsNull() { + addCriterion("original_state is null"); + return (Criteria) this; + } + + public Criteria andOriginalStateIsNotNull() { + addCriterion("original_state is not null"); + return (Criteria) this; + } + + public Criteria andOriginalStateEqualTo(String value) { + addCriterion("original_state =", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotEqualTo(String value) { + addCriterion("original_state <>", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateGreaterThan(String value) { + addCriterion("original_state >", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateGreaterThanOrEqualTo(String value) { + addCriterion("original_state >=", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateLessThan(String value) { + addCriterion("original_state <", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateLessThanOrEqualTo(String value) { + addCriterion("original_state <=", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateLike(String value) { + addCriterion("original_state like", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotLike(String value) { + addCriterion("original_state not like", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateIn(List values) { + addCriterion("original_state in", values, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotIn(List values) { + addCriterion("original_state not in", values, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateBetween(String value1, String value2) { + addCriterion("original_state between", value1, value2, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotBetween(String value1, String value2) { + addCriterion("original_state not between", value1, value2, "originalState"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiScenario.java b/backend/src/main/java/io/metersphere/base/domain/ApiScenario.java index 81c0a35a33..8268740cce 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiScenario.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiScenario.java @@ -43,5 +43,7 @@ public class ApiScenario implements Serializable { private Integer num; + private String originalState; + private static final long serialVersionUID = 1L; } \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/domain/ApiScenarioExample.java b/backend/src/main/java/io/metersphere/base/domain/ApiScenarioExample.java index c679af53c1..9aaff91c8d 100644 --- a/backend/src/main/java/io/metersphere/base/domain/ApiScenarioExample.java +++ b/backend/src/main/java/io/metersphere/base/domain/ApiScenarioExample.java @@ -1393,6 +1393,76 @@ public class ApiScenarioExample { addCriterion("num not between", value1, value2, "num"); return (Criteria) this; } + + public Criteria andOriginalStateIsNull() { + addCriterion("original_state is null"); + return (Criteria) this; + } + + public Criteria andOriginalStateIsNotNull() { + addCriterion("original_state is not null"); + return (Criteria) this; + } + + public Criteria andOriginalStateEqualTo(String value) { + addCriterion("original_state =", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotEqualTo(String value) { + addCriterion("original_state <>", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateGreaterThan(String value) { + addCriterion("original_state >", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateGreaterThanOrEqualTo(String value) { + addCriterion("original_state >=", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateLessThan(String value) { + addCriterion("original_state <", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateLessThanOrEqualTo(String value) { + addCriterion("original_state <=", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateLike(String value) { + addCriterion("original_state like", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotLike(String value) { + addCriterion("original_state not like", value, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateIn(List values) { + addCriterion("original_state in", values, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotIn(List values) { + addCriterion("original_state not in", values, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateBetween(String value1, String value2) { + addCriterion("original_state between", value1, value2, "originalState"); + return (Criteria) this; + } + + public Criteria andOriginalStateNotBetween(String value1, String value2) { + addCriterion("original_state not between", value1, value2, "originalState"); + return (Criteria) this; + } } public static class Criteria extends GeneratedCriteria { diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiDefinitionMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ApiDefinitionMapper.xml index d636c33f5c..04d4050045 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ApiDefinitionMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiDefinitionMapper.xml @@ -18,6 +18,7 @@ + @@ -84,7 +85,7 @@ id, project_id, `name`, `method`, module_path, environment_id, schedule, `status`, - module_id, user_id, create_time, update_time, protocol, `path`, num, tags + module_id, user_id, create_time, update_time, protocol, `path`, num, tags, original_state description, request, response @@ -143,15 +144,15 @@ schedule, `status`, module_id, user_id, create_time, update_time, protocol, `path`, num, - tags, description, request, - response) + tags, original_state, description, + request, response) values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{method,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR}, #{environmentId,jdbcType=VARCHAR}, #{schedule,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{moduleId,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{protocol,jdbcType=VARCHAR}, #{path,jdbcType=VARCHAR}, #{num,jdbcType=INTEGER}, - #{tags,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}, #{request,jdbcType=LONGVARCHAR}, - #{response,jdbcType=LONGVARCHAR}) + #{tags,jdbcType=VARCHAR}, #{originalState,jdbcType=VARCHAR}, #{description,jdbcType=LONGVARCHAR}, + #{request,jdbcType=LONGVARCHAR}, #{response,jdbcType=LONGVARCHAR}) insert into api_definition @@ -204,6 +205,9 @@ tags, + + original_state, + description, @@ -263,6 +267,9 @@ #{tags,jdbcType=VARCHAR}, + + #{originalState,jdbcType=VARCHAR}, + #{description,jdbcType=LONGVARCHAR}, @@ -331,6 +338,9 @@ tags = #{record.tags,jdbcType=VARCHAR}, + + original_state = #{record.originalState,jdbcType=VARCHAR}, + description = #{record.description,jdbcType=LONGVARCHAR}, @@ -363,6 +373,7 @@ `path` = #{record.path,jdbcType=VARCHAR}, num = #{record.num,jdbcType=INTEGER}, tags = #{record.tags,jdbcType=VARCHAR}, + original_state = #{record.originalState,jdbcType=VARCHAR}, description = #{record.description,jdbcType=LONGVARCHAR}, request = #{record.request,jdbcType=LONGVARCHAR}, response = #{record.response,jdbcType=LONGVARCHAR} @@ -387,7 +398,8 @@ protocol = #{record.protocol,jdbcType=VARCHAR}, `path` = #{record.path,jdbcType=VARCHAR}, num = #{record.num,jdbcType=INTEGER}, - tags = #{record.tags,jdbcType=VARCHAR} + tags = #{record.tags,jdbcType=VARCHAR}, + original_state = #{record.originalState,jdbcType=VARCHAR} @@ -440,6 +452,9 @@ tags = #{tags,jdbcType=VARCHAR}, + + original_state = #{originalState,jdbcType=VARCHAR}, + description = #{description,jdbcType=LONGVARCHAR}, @@ -469,6 +484,7 @@ `path` = #{path,jdbcType=VARCHAR}, num = #{num,jdbcType=INTEGER}, tags = #{tags,jdbcType=VARCHAR}, + original_state = #{originalState,jdbcType=VARCHAR}, description = #{description,jdbcType=LONGVARCHAR}, request = #{request,jdbcType=LONGVARCHAR}, response = #{response,jdbcType=LONGVARCHAR} @@ -490,7 +506,8 @@ protocol = #{protocol,jdbcType=VARCHAR}, `path` = #{path,jdbcType=VARCHAR}, num = #{num,jdbcType=INTEGER}, - tags = #{tags,jdbcType=VARCHAR} + tags = #{tags,jdbcType=VARCHAR}, + original_state = #{originalState,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ApiScenarioMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ApiScenarioMapper.xml index dba0bfbdb4..96c4137138 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ApiScenarioMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ApiScenarioMapper.xml @@ -21,6 +21,7 @@ + @@ -87,7 +88,7 @@ id, project_id, tags, user_id, api_scenario_module_id, module_path, `name`, `level`, `status`, principal, step_total, follow_people, schedule, create_time, update_time, - pass_rate, last_result, report_id, num + pass_rate, last_result, report_id, num, original_state scenario_definition, description @@ -147,16 +148,16 @@ principal, step_total, follow_people, schedule, create_time, update_time, pass_rate, last_result, report_id, - num, scenario_definition, description - ) + num, original_state, scenario_definition, + description) values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{tags,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR}, #{apiScenarioModuleId,jdbcType=VARCHAR}, #{modulePath,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{level,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{principal,jdbcType=VARCHAR}, #{stepTotal,jdbcType=INTEGER}, #{followPeople,jdbcType=VARCHAR}, #{schedule,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{passRate,jdbcType=VARCHAR}, #{lastResult,jdbcType=VARCHAR}, #{reportId,jdbcType=VARCHAR}, - #{num,jdbcType=INTEGER}, #{scenarioDefinition,jdbcType=LONGVARCHAR}, #{description,jdbcType=LONGVARCHAR} - ) + #{num,jdbcType=INTEGER}, #{originalState,jdbcType=VARCHAR}, #{scenarioDefinition,jdbcType=LONGVARCHAR}, + #{description,jdbcType=LONGVARCHAR}) insert into api_scenario @@ -218,6 +219,9 @@ num, + + original_state, + scenario_definition, @@ -283,6 +287,9 @@ #{num,jdbcType=INTEGER}, + + #{originalState,jdbcType=VARCHAR}, + #{scenarioDefinition,jdbcType=LONGVARCHAR}, @@ -357,6 +364,9 @@ num = #{record.num,jdbcType=INTEGER}, + + original_state = #{record.originalState,jdbcType=VARCHAR}, + scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR}, @@ -389,6 +399,7 @@ last_result = #{record.lastResult,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, num = #{record.num,jdbcType=INTEGER}, + original_state = #{record.originalState,jdbcType=VARCHAR}, scenario_definition = #{record.scenarioDefinition,jdbcType=LONGVARCHAR}, description = #{record.description,jdbcType=LONGVARCHAR} @@ -415,7 +426,8 @@ pass_rate = #{record.passRate,jdbcType=VARCHAR}, last_result = #{record.lastResult,jdbcType=VARCHAR}, report_id = #{record.reportId,jdbcType=VARCHAR}, - num = #{record.num,jdbcType=INTEGER} + num = #{record.num,jdbcType=INTEGER}, + original_state = #{record.originalState,jdbcType=VARCHAR} @@ -477,6 +489,9 @@ num = #{num,jdbcType=INTEGER}, + + original_state = #{originalState,jdbcType=VARCHAR}, + scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR}, @@ -506,6 +521,7 @@ last_result = #{lastResult,jdbcType=VARCHAR}, report_id = #{reportId,jdbcType=VARCHAR}, num = #{num,jdbcType=INTEGER}, + original_state = #{originalState,jdbcType=VARCHAR}, scenario_definition = #{scenarioDefinition,jdbcType=LONGVARCHAR}, description = #{description,jdbcType=LONGVARCHAR} where id = #{id,jdbcType=VARCHAR} @@ -529,7 +545,8 @@ pass_rate = #{passRate,jdbcType=VARCHAR}, last_result = #{lastResult,jdbcType=VARCHAR}, report_id = #{reportId,jdbcType=VARCHAR}, - num = #{num,jdbcType=INTEGER} + num = #{num,jdbcType=INTEGER}, + original_state = #{originalState,jdbcType=VARCHAR} where id = #{id,jdbcType=VARCHAR} \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml index 46f321bd7b..179c83c956 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiDefinitionMapper.xml @@ -235,7 +235,7 @@ update api_definition - set + set original_state=status, status = 'Trash' where id in @@ -245,7 +245,7 @@ update api_definition - set + set original_state=status, status = 'Trash', module_path = null, module_id = null @@ -255,7 +255,7 @@ update api_definition set - status = 'Underway' + status = original_state where id in #{v} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml index 139de4d8d5..d9eb034876 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtApiScenarioMapper.xml @@ -275,7 +275,7 @@ update api_scenario - set + set original_state=status, status = 'Trash' where id in @@ -285,7 +285,7 @@ update api_scenario - set + set original_state=status, status = 'Trash', module_path = null, api_scenario_module_id = null @@ -295,7 +295,7 @@ update api_scenario set - status = 'Underway' + status = original_state where id in #{v} diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanScenarioCaseMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanScenarioCaseMapper.xml index ac1de53c06..95c735f953 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanScenarioCaseMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtTestPlanScenarioCaseMapper.xml @@ -5,8 +5,8 @@ -- 查询没有数据再插入 - INSERT INTO test_plan_api_scenario(id, test_plan_id, api_scenario_id, create_time, update_time) - SELECT #{request.id}, #{request.testPlanId}, #{request.apiScenarioId}, #{request.createTime}, #{request.updateTime} + INSERT INTO test_plan_api_scenario(id, test_plan_id, api_scenario_id, create_time, update_time, environment) + SELECT #{request.id}, #{request.testPlanId}, #{request.apiScenarioId}, #{request.createTime}, #{request.updateTime}, #{request.environment} FROM DUAL WHERE NOT EXISTS( SELECT id FROM diff --git a/backend/src/main/java/io/metersphere/commons/constants/ApiImportPlatform.java b/backend/src/main/java/io/metersphere/commons/constants/ApiImportPlatform.java index 7470f26b48..95aed8d749 100644 --- a/backend/src/main/java/io/metersphere/commons/constants/ApiImportPlatform.java +++ b/backend/src/main/java/io/metersphere/commons/constants/ApiImportPlatform.java @@ -1,5 +1,5 @@ package io.metersphere.commons.constants; public enum ApiImportPlatform { - Metersphere, Postman, Swagger2, Plugin, Jmeter + Metersphere, Postman, Swagger2, Plugin, Jmeter, Har } diff --git a/backend/src/main/java/io/metersphere/commons/user/SessionUser.java b/backend/src/main/java/io/metersphere/commons/user/SessionUser.java index 7ffa9b70b1..25c4785ff3 100644 --- a/backend/src/main/java/io/metersphere/commons/user/SessionUser.java +++ b/backend/src/main/java/io/metersphere/commons/user/SessionUser.java @@ -1,18 +1,35 @@ package io.metersphere.commons.user; +import io.metersphere.commons.utils.CodingUtil; import io.metersphere.dto.UserDTO; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +@Setter +@Getter public class SessionUser extends UserDTO implements Serializable { + public static final String secret = "9a9rdqPlTqhpZzkq"; + public static final String iv = "1Av7hf9PgHusUHRm"; private static final long serialVersionUID = -7149638440406959033L; + private String csrfToken; + + private SessionUser() { + } public static SessionUser fromUser(UserDTO user) { SessionUser sessionUser = new SessionUser(); BeanUtils.copyProperties(user, sessionUser); + List infos = Arrays.asList(user.getId(), RandomStringUtils.random(6), "" + System.currentTimeMillis()); + sessionUser.csrfToken = CodingUtil.aesEncrypt(StringUtils.join(infos, "|"), secret, iv); return sessionUser; } diff --git a/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java b/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java index 2e5b1cd2e4..b7ba547f1c 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/SessionUtils.java @@ -62,14 +62,14 @@ public class SessionUtils { } public static String getCurrentWorkspaceId() { - return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastWorkspaceId(); + return getUser().getLastWorkspaceId(); } public static String getCurrentOrganizationId() { - return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastOrganizationId(); + return getUser().getLastOrganizationId(); } public static String getCurrentProjectId() { - return Optional.ofNullable(getUser()).orElse(new SessionUser()).getLastProjectId(); + return getUser().getLastProjectId(); } } diff --git a/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java b/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java index e48a56c994..18c222d1ba 100644 --- a/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java +++ b/backend/src/main/java/io/metersphere/commons/utils/ShiroUtils.java @@ -44,6 +44,10 @@ public class ShiroUtils { // filterChainDefinitionMap.put("/document/**", "anon"); } + public static void ignoreCsrfFilter(Map filterChainDefinitionMap) { + filterChainDefinitionMap.put("/", "apikey, authc"); // 跳转到 / 不用校验 csrf + } + public static Cookie getSessionIdCookie(){ SimpleCookie sessionIdCookie = new SimpleCookie(); sessionIdCookie.setPath("/"); diff --git a/backend/src/main/java/io/metersphere/config/ShiroConfig.java b/backend/src/main/java/io/metersphere/config/ShiroConfig.java index 81f8848c51..4755bbe8d4 100644 --- a/backend/src/main/java/io/metersphere/config/ShiroConfig.java +++ b/backend/src/main/java/io/metersphere/config/ShiroConfig.java @@ -2,6 +2,7 @@ package io.metersphere.config; import io.metersphere.commons.utils.ShiroUtils; import io.metersphere.security.ApiKeyFilter; +import io.metersphere.security.CsrfFilter; import io.metersphere.security.UserModularRealmAuthenticator; import io.metersphere.security.realm.LdapRealm; import io.metersphere.security.realm.ShiroDBRealm; @@ -44,10 +45,14 @@ public class ShiroConfig implements EnvironmentAware { shiroFilterFactoryBean.setSuccessUrl("/"); shiroFilterFactoryBean.getFilters().put("apikey", new ApiKeyFilter()); + shiroFilterFactoryBean.getFilters().put("csrf", new CsrfFilter()); Map filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap(); + ShiroUtils.loadBaseFilterChain(filterChainDefinitionMap); - filterChainDefinitionMap.put("/**", "apikey, authc"); + ShiroUtils.ignoreCsrfFilter(filterChainDefinitionMap); + + filterChainDefinitionMap.put("/**", "apikey, csrf, authc"); return shiroFilterFactoryBean; } diff --git a/backend/src/main/java/io/metersphere/controller/LoginController.java b/backend/src/main/java/io/metersphere/controller/LoginController.java index 2850f8ff60..75a2f8455d 100644 --- a/backend/src/main/java/io/metersphere/controller/LoginController.java +++ b/backend/src/main/java/io/metersphere/controller/LoginController.java @@ -1,6 +1,5 @@ package io.metersphere.controller; -import io.metersphere.commons.constants.SsoMode; import io.metersphere.commons.constants.UserSource; import io.metersphere.commons.user.SessionUser; import io.metersphere.commons.utils.SessionUtils; @@ -10,7 +9,6 @@ import io.metersphere.service.UserService; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.core.env.Environment; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -24,8 +22,6 @@ public class LoginController { @Resource private UserService userService; @Resource - private Environment env; - @Resource private BaseDisplayService baseDisplayService; @GetMapping(value = "/isLogin") @@ -37,10 +33,6 @@ public class LoginController { } return ResultHolder.success(user); } - String ssoMode = env.getProperty("sso.mode"); - if (ssoMode != null && StringUtils.equalsIgnoreCase(SsoMode.CAS.name(), ssoMode)) { - return ResultHolder.error("sso"); - } return ResultHolder.error(""); } diff --git a/backend/src/main/java/io/metersphere/controller/SystemParameterController.java b/backend/src/main/java/io/metersphere/controller/SystemParameterController.java index 8fd47d8dc0..bcc25df6f9 100644 --- a/backend/src/main/java/io/metersphere/controller/SystemParameterController.java +++ b/backend/src/main/java/io/metersphere/controller/SystemParameterController.java @@ -39,6 +39,11 @@ public class SystemParameterController { return SystemParameterService.getVersion(); } + @GetMapping("/theme") + public String getTheme() { + return SystemParameterService.getValue("ui.theme"); + } + @GetMapping("/mail/info") @RequiresRoles(value = {RoleConstants.ADMIN}) public MailInfo mailInfo() { diff --git a/backend/src/main/java/io/metersphere/controller/UserController.java b/backend/src/main/java/io/metersphere/controller/UserController.java index 51c43eb0ee..5b08f1688e 100644 --- a/backend/src/main/java/io/metersphere/controller/UserController.java +++ b/backend/src/main/java/io/metersphere/controller/UserController.java @@ -1,11 +1,8 @@ package io.metersphere.controller; -import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; -import io.metersphere.base.domain.Organization; import io.metersphere.base.domain.User; -import io.metersphere.base.domain.Workspace; import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; @@ -29,7 +26,6 @@ import io.metersphere.service.WorkspaceService; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; -import org.checkerframework.checker.units.qual.C; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; diff --git a/backend/src/main/java/io/metersphere/performance/engine/EngineContext.java b/backend/src/main/java/io/metersphere/performance/engine/EngineContext.java index ec643c499a..dce38bf85a 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/EngineContext.java +++ b/backend/src/main/java/io/metersphere/performance/engine/EngineContext.java @@ -14,8 +14,7 @@ public class EngineContext { private String reportId; private Integer resourceIndex; private Map properties = new HashMap<>(); - private Map testData = new HashMap<>(); - private Map testJars = new HashMap<>(); + private Map testResourceFiles = new HashMap<>(); public String getTestId() { return testId; @@ -69,14 +68,6 @@ public class EngineContext { this.fileType = fileType; } - public Map getTestData() { - return testData; - } - - public void setTestData(Map testData) { - this.testData = testData; - } - public String getResourcePoolId() { return resourcePoolId; } @@ -111,11 +102,11 @@ public class EngineContext { } - public Map getTestJars() { - return testJars; + public Map getTestResourceFiles() { + return testResourceFiles; } - public void setTestJars(Map testJars) { - this.testJars = testJars; + public void setTestResourceFiles(Map testResourceFiles) { + this.testResourceFiles = testResourceFiles; } } diff --git a/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java b/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java index 1dc094a0ca..bfc2d9461f 100644 --- a/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java +++ b/backend/src/main/java/io/metersphere/performance/engine/EngineFactory.java @@ -19,7 +19,8 @@ import io.metersphere.service.FileService; import io.metersphere.service.KubernetesTestEngine; import io.metersphere.service.TestResourcePoolService; import org.apache.commons.beanutils.ConstructorUtils; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.ListUtils; import org.apache.commons.lang3.StringUtils; import org.reflections8.Reflections; import org.springframework.stereotype.Service; @@ -92,8 +93,7 @@ public class EngineFactory { } List jmxFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JMX.name())).collect(Collectors.toList()); - List csvFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.CSV.name())).collect(Collectors.toList()); - List jarFiles = fileMetadataList.stream().filter(f -> StringUtils.equalsIgnoreCase(f.getType(), FileType.JAR.name())).collect(Collectors.toList()); + List resourceFiles = ListUtils.subtract(fileMetadataList, jmxFiles); // 合并上传的jmx byte[] jmxBytes = mergeJmx(jmxFiles); final EngineContext engineContext = new EngineContext(); @@ -156,22 +156,13 @@ public class EngineFactory { MSException.throwException(e); } - if (CollectionUtils.isNotEmpty(csvFiles)) { - Map data = new HashMap<>(); - csvFiles.forEach(cf -> { - FileContent csvContent = fileService.getFileContent(cf.getId()); - data.put(cf.getName(), new String(csvContent.getFile())); - }); - engineContext.setTestData(data); - } - - if (CollectionUtils.isNotEmpty(jarFiles)) { + if (CollectionUtils.isNotEmpty(resourceFiles)) { Map data = new HashMap<>(); - jarFiles.forEach(jf -> { - FileContent content = fileService.getFileContent(jf.getId()); - data.put(jf.getName(), content.getFile()); + resourceFiles.forEach(cf -> { + FileContent csvContent = fileService.getFileContent(cf.getId()); + data.put(cf.getName(), csvContent.getFile()); }); - engineContext.setTestJars(data); + engineContext.setTestResourceFiles(data); } return engineContext; diff --git a/backend/src/main/java/io/metersphere/performance/service/JmeterFileService.java b/backend/src/main/java/io/metersphere/performance/service/JmeterFileService.java index ad918d1740..e87f8b9b51 100644 --- a/backend/src/main/java/io/metersphere/performance/service/JmeterFileService.java +++ b/backend/src/main/java/io/metersphere/performance/service/JmeterFileService.java @@ -51,17 +51,9 @@ public class JmeterFileService { // 每个测试生成一个文件夹 files.put(fileName, context.getContent().getBytes(StandardCharsets.UTF_8)); - // 保存测试数据文件 - Map testData = context.getTestData(); - if (!CollectionUtils.isEmpty(testData)) { - for (String k : testData.keySet()) { - String v = testData.get(k); - files.put(k, v.getBytes(StandardCharsets.UTF_8)); - } - } - // 保存 byte[] jar - Map jarFiles = context.getTestJars(); + // 保存 byte[] + Map jarFiles = context.getTestResourceFiles(); if (!CollectionUtils.isEmpty(jarFiles)) { for (String k : jarFiles.keySet()) { byte[] v = jarFiles.get(k); diff --git a/backend/src/main/java/io/metersphere/security/CsrfFilter.java b/backend/src/main/java/io/metersphere/security/CsrfFilter.java new file mode 100644 index 0000000000..ea21966c4b --- /dev/null +++ b/backend/src/main/java/io/metersphere/security/CsrfFilter.java @@ -0,0 +1,93 @@ +package io.metersphere.security; + +import io.metersphere.commons.user.SessionUser; +import io.metersphere.commons.utils.CodingUtil; +import io.metersphere.commons.utils.CommonBeanFactory; +import io.metersphere.commons.utils.SessionUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.web.filter.authc.AnonymousFilter; +import org.apache.shiro.web.util.WebUtils; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; + +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class CsrfFilter extends AnonymousFilter { + private static final String TOKEN_NAME = "CSRF-TOKEN"; + + @Override + protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) { + HttpServletRequest httpServletRequest = WebUtils.toHttp(request); + + if (!SecurityUtils.getSubject().isAuthenticated()) { + ((HttpServletResponse) response).setHeader("Authentication-Status", "invalid"); + return true; + } + // api 过来的请求 + if (ApiKeyHandler.isApiKeyCall(WebUtils.toHttp(request))) { + return true; + } + // websocket 不需要csrf + String websocketKey = httpServletRequest.getHeader("Sec-WebSocket-Key"); + if (StringUtils.isNotBlank(websocketKey)) { + return true; + } + + // 请求头取出的token value + String csrfToken = httpServletRequest.getHeader(TOKEN_NAME); + // 校验 token + validateToken(csrfToken); + // 校验 referer + validateReferer(httpServletRequest); + return true; + } + + private void validateReferer(HttpServletRequest request) { + Environment env = CommonBeanFactory.getBean(Environment.class); + String domains = env.getProperty("referer.urls"); + if (StringUtils.isBlank(domains)) { + // 没有配置不校验 + return; + } + + String[] split = StringUtils.split(domains, ","); + String referer = request.getHeader(HttpHeaders.REFERER); + if (split != null) { + if (!ArrayUtils.contains(split, referer)) { + throw new RuntimeException("csrf error"); + } + } + } + + private void validateToken(String csrfToken) { + if (StringUtils.isBlank(csrfToken)) { + throw new RuntimeException("csrf token is empty"); + } + csrfToken = CodingUtil.aesDecrypt(csrfToken, SessionUser.secret, SessionUser.iv); + + String[] signatureArray = StringUtils.split(StringUtils.trimToNull(csrfToken), "|"); + if (signatureArray.length != 3) { + throw new RuntimeException("invalid token"); + } + + long signatureTime; + try { + signatureTime = Long.parseLong(signatureArray[2]); + } catch (Exception e) { + throw new RuntimeException(e); + } + Environment env = CommonBeanFactory.getBean(Environment.class); + long timeout = env.getProperty("session.timeout", Long.class, 43200L); + if (Math.abs(System.currentTimeMillis() - signatureTime) > timeout * 1000) { + throw new RuntimeException("expired token"); + } + if (!StringUtils.equals(SessionUtils.getUserId(), signatureArray[0])) { + throw new RuntimeException("Please check csrf token."); + } + } +} diff --git a/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java b/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java index 5889ee5d35..8a65db8a7b 100644 --- a/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java +++ b/backend/src/main/java/io/metersphere/xmind/XmindCaseParser.java @@ -136,8 +136,8 @@ public class XmindCaseParser { data.setNodePath(nodePath); - if (data.getName().length() > 50) { - process.add(Translator.get("test_case") + Translator.get("test_track.length_less_than") + "50", nodePath + data.getName()); + if (data.getName().length() > 200) { + process.add(Translator.get("test_case") + Translator.get("test_track.length_less_than") + "200", nodePath + data.getName()); } if (!StringUtils.isEmpty(nodePath)) { diff --git a/backend/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java b/backend/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java new file mode 100644 index 0000000000..44a93bb84c --- /dev/null +++ b/backend/src/main/java/org/apache/jmeter/assertions/JSONPathAssertion.java @@ -0,0 +1,252 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.assertions; + +import java.io.Serializable; +import java.text.DecimalFormat; +import java.util.Map; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.jayway.jsonpath.JsonPath; +import org.apache.commons.lang3.StringUtils; +import org.apache.jmeter.samplers.SampleResult; +import org.apache.jmeter.testelement.AbstractTestElement; +import org.apache.jmeter.testelement.ThreadListener; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.oro.text.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is main class for JSONPath Assertion which verifies assertion on + * previous sample result using JSON path expression + * + * @since 4.0 + */ +public class JSONPathAssertion extends AbstractTestElement implements Serializable, Assertion, ThreadListener { + private static final Logger log = LoggerFactory.getLogger(JSONPathAssertion.class); + private static final long serialVersionUID = 2L; + public static final String JSONPATH = "JSON_PATH"; + public static final String EXPECTEDVALUE = "EXPECTED_VALUE"; + public static final String JSONVALIDATION = "JSONVALIDATION"; + public static final String EXPECT_NULL = "EXPECT_NULL"; + public static final String INVERT = "INVERT"; + public static final String ISREGEX = "ISREGEX"; + + private static ThreadLocal decimalFormatter = + ThreadLocal.withInitial(JSONPathAssertion::createDecimalFormat); + + public String getOption() { + return getPropertyAsString("ASS_OPTION"); + } + + private static DecimalFormat createDecimalFormat() { + DecimalFormat decimalFormatter = new DecimalFormat("#.#"); + decimalFormatter.setMaximumFractionDigits(340); // java.text.DecimalFormat.DOUBLE_FRACTION_DIGITS == 340 + decimalFormatter.setMinimumFractionDigits(1); + return decimalFormatter; + } + + public String getJsonPath() { + return getPropertyAsString(JSONPATH); + } + + public void setJsonPath(String jsonPath) { + setProperty(JSONPATH, jsonPath); + } + + public String getExpectedValue() { + return getPropertyAsString(EXPECTEDVALUE); + } + + public void setExpectedValue(String expectedValue) { + setProperty(EXPECTEDVALUE, expectedValue); + } + + public void setJsonValidationBool(boolean jsonValidation) { + setProperty(JSONVALIDATION, jsonValidation); + } + + public void setExpectNull(boolean val) { + setProperty(EXPECT_NULL, val); + } + + public boolean isExpectNull() { + return getPropertyAsBoolean(EXPECT_NULL); + } + + public boolean isJsonValidationBool() { + return getPropertyAsBoolean(JSONVALIDATION); + } + + public void setInvert(boolean invert) { + setProperty(INVERT, invert); + } + + public boolean isInvert() { + return getPropertyAsBoolean(INVERT); + } + + public void setIsRegex(boolean flag) { + setProperty(ISREGEX, flag); + } + + public boolean isUseRegex() { + return getPropertyAsBoolean(ISREGEX, true); + } + + private void doAssert(String jsonString) { + Object value = JsonPath.read(jsonString, getJsonPath()); + + if (!isJsonValidationBool()) { + return; + } + + if (value instanceof JSONArray) { + if (arrayMatched((JSONArray) value)) { + return; + } + } else { + if ((isExpectNull() && value == null) + || isEquals(value)) { + return; + } + } + + if (isExpectNull()) { + throw new IllegalStateException(String.format("Value expected to be null, but found '%s'", value)); + } else { + String msg; + if (isUseRegex()) { + msg = "Value expected to match regexp '%s', but it did not match: '%s'"; + } else { + msg = "Value expected to be '%s', but found '%s'"; + } + throw new IllegalStateException(String.format(msg, getExpectedValue(), objectToString(value))); + } + } + + private boolean arrayMatched(JSONArray value) { + if (value.isEmpty() && "[]".equals(getExpectedValue())) { + return true; + } + + for (Object subj : value.toArray()) { + if ((subj == null && isExpectNull()) + || isEquals(subj)) { + return true; + } + } + + return isEquals(value); + } + + private boolean isEquals(Object subj) { + String str = objectToString(subj); + if (isUseRegex()) { + Pattern pattern = JMeterUtils.getPatternCache().getPattern(getExpectedValue()); + return JMeterUtils.getMatcher().matches(str, pattern); + } else { + if (StringUtils.isNotEmpty(getOption())) { + boolean refFlag = false; + switch (getOption()) { + case "CONTAINS": + refFlag = str.contains(getExpectedValue()); + break; + case "NOT_CONTAINS": + refFlag = !str.contains(getExpectedValue()); + break; + case "EQUALS": + refFlag = str.equals(getExpectedValue()); + break; + case "NOT_EQUALS": + refFlag = !str.contains(getExpectedValue()); + break; + } + return refFlag; + } + return str.equals(getExpectedValue()); + } + } + + @Override + public AssertionResult getResult(SampleResult samplerResult) { + AssertionResult result = new AssertionResult(getName()); + String responseData = samplerResult.getResponseDataAsString(); + if (responseData.isEmpty()) { + return result.setResultForNull(); + } + + result.setFailure(false); + result.setFailureMessage(""); + + if (!isInvert()) { + try { + doAssert(responseData); + } catch (Exception e) { + log.debug("Assertion failed", e); + result.setFailure(true); + result.setFailureMessage(e.getMessage()); + } + } else { + try { + doAssert(responseData); + result.setFailure(true); + if (isJsonValidationBool()) { + if (isExpectNull()) { + result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches null"); + } else { + result.setFailureMessage("Failed that JSONPath " + getJsonPath() + " not matches " + getExpectedValue()); + } + } else { + result.setFailureMessage("Failed that JSONPath not exists: " + getJsonPath()); + } + } catch (Exception e) { + log.debug("Assertion failed, as expected", e); + } + } + return result; + } + + public static String objectToString(Object subj) { + String str; + if (subj == null) { + str = "null"; + } else if (subj instanceof Map) { + //noinspection unchecked + str = new JSONObject((Map) subj).toJSONString(); + } else if (subj instanceof Double || subj instanceof Float) { + str = decimalFormatter.get().format(subj); + } else { + str = subj.toString(); + } + return str; + } + + @Override + public void threadStarted() { + // nothing to do on thread start + } + + @Override + public void threadFinished() { + decimalFormatter.remove(); + } +} diff --git a/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java b/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java new file mode 100644 index 0000000000..e76248499f --- /dev/null +++ b/backend/src/main/java/org/apache/jmeter/samplers/SampleResult.java @@ -0,0 +1,1624 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.jmeter.samplers; + +import org.apache.jmeter.assertions.AssertionResult; +import org.apache.jmeter.gui.Searchable; +import org.apache.jmeter.testelement.TestPlan; +import org.apache.jmeter.threads.JMeterContext.TestLogicalAction; +import org.apache.jmeter.threads.JMeterContextService; +import org.apache.jmeter.threads.JMeterVariables; +import org.apache.jmeter.util.JMeterUtils; +import org.apache.jorphan.util.JOrphanUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +// For unit tests, @see TestSampleResult + +/** + * This is a nice packaging for the various information returned from taking a + * sample of an entry. + */ +public class SampleResult implements Serializable, Cloneable, Searchable { + + private static final long serialVersionUID = 241L; + + // Needs to be accessible from Test code + static Logger log = LoggerFactory.getLogger(SampleResult.class); + + /** + * The default encoding to be used if not overridden. + * The value is ISO-8859-1. + */ + public static final String DEFAULT_HTTP_ENCODING = StandardCharsets.ISO_8859_1.name(); + + private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK); + private static final String OK_MSG = "OK"; // $NON-NLS-1$ + private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$ + + + // Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries + // However the suggested System.getProperty("file.encoding") is Cp1252 on + // Windows + // So use a new property with the original value as default + // needs to be accessible from test code + /** + * The default encoding to be used to decode the responseData byte array. + * The value is defined by the property "sampleresult.default.encoding" + * with a default of DEFAULT_HTTP_ENCODING if that is not defined. + */ + protected static final String DEFAULT_ENCODING + = JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$ + DEFAULT_HTTP_ENCODING); + + /** + * The default used by {@link #setResponseData(String, String)} + */ + private static final String DEFAULT_CHARSET = Charset.defaultCharset().name(); + + /** + * Data type value ({@value}) indicating that the response data is text. + * + * @see #getDataType + * @see #setDataType(String) + */ + public static final String TEXT = "text"; // $NON-NLS-1$ + + /** + * Data type value ({@value}) indicating that the response data is binary. + * + * @see #getDataType + * @see #setDataType(String) + */ + public static final String BINARY = "bin"; // $NON-NLS-1$ + + private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false); + + // List of types that are known to be binary + private static final String[] BINARY_TYPES = { + "image/", //$NON-NLS-1$ + "audio/", //$NON-NLS-1$ + "video/", //$NON-NLS-1$ + }; + + // List of types that are known to be ascii, although they may appear to be binary + private static final String[] NON_BINARY_TYPES = { + "audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "audio/mpegurl", //$NON-NLS-1$ (HLS Media Manifest) + "video/f4m", //$NON-NLS-1$ (Flash Media Manifest) + "image/svg+xml" //$NON-NLS-1$ (SVG is xml) + }; + + + /** + * empty array which can be returned instead of null + */ + private static final byte[] EMPTY_BA = new byte[0]; + + private static final SampleResult[] EMPTY_SR = new SampleResult[0]; + + private static final AssertionResult[] EMPTY_AR = new AssertionResult[0]; + + private static final boolean START_TIMESTAMP = + JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$ + + /** + * Allow read-only access from test code + */ + private static final boolean USE_NANO_TIME = + JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$ + + /** + * How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread + */ + private static final long NANOTHREAD_SLEEP = + JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$ + + private static final String NULL_FILENAME = "NULL"; + + static { + if (START_TIMESTAMP) { + log.info("Note: Sample TimeStamps are START times"); + } else { + log.info("Note: Sample TimeStamps are END times"); + } + log.info("sampleresult.default.encoding is set to {}", DEFAULT_ENCODING); + log.info("sampleresult.useNanoTime={}", USE_NANO_TIME); + log.info("sampleresult.nanoThreadSleep={}", NANOTHREAD_SLEEP); + + if (USE_NANO_TIME && NANOTHREAD_SLEEP > 0) { + // Make sure we start with a reasonable value + NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs(); + NanoOffset nanoOffset = new NanoOffset(); + nanoOffset.setDaemon(true); + nanoOffset.setName("NanoOffset"); + nanoOffset.start(); + } + } + + private String samplerId; + + public String getSamplerId() { + return this.samplerId; + } + + private SampleSaveConfiguration saveConfig; + + private SampleResult parent; + + private byte[] responseData = EMPTY_BA; + + private String responseCode = "";// Never return null + + private String label = "";// Never return null + + /** + * Filename used by ResultSaver + */ + private String resultFileName = ""; + + /** + * The data used by the sampler + */ + private String samplerData; + + private String threadName = ""; // Never return null + + private String responseMessage = ""; + + private String responseHeaders = ""; // Never return null + + private String requestHeaders = ""; + + /** + * timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file) + * the time stamp - can be start or end + */ + private long timeStamp = 0; + + private long startTime = 0; + + private long endTime = 0; + + private long idleTime = 0;// Allow for non-sample time + + /** + * Start of pause (if any) + */ + private long pauseTime = 0; + + private List assertionResults; + + private List subResults; + + /** + * The data type of the sample + * + * @see #getDataType() + * @see #setDataType(String) + * @see #TEXT + * @see #BINARY + */ + private String dataType = ""; // Don't return null if not set + + private boolean success; + + /** + * Files that this sample has been saved in. + * In Non GUI mode and when best config is used, size never exceeds 1, + * but as a compromise set it to 2 + */ + private final Set files = ConcurrentHashMap.newKeySet(2); + + // TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead? + private String dataEncoding;// (is this really the character set?) e.g. + // ISO-8895-1, UTF-8 + + private String contentType = ""; // e.g. text/html; charset=utf-8 + + /** + * elapsed time + */ + private long elapsedTime = 0; + + /** + * time to first response + */ + private long latency = 0; + + /** + * time to end connecting + */ + private long connectTime = 0; + + /** + * Way to signal what to do on Test + */ + private TestLogicalAction testLogicalAction = TestLogicalAction.CONTINUE; + + /** + * Should thread terminate? + */ + private boolean stopThread = false; + + /** + * Should test terminate? + */ + private boolean stopTest = false; + + /** + * Should test terminate abruptly? + */ + private boolean stopTestNow = false; + + private int sampleCount = 1; + + private long bytes = 0; // Allows override of sample size in case sampler does not want to store all the data + + private int headersSize = 0; + + private long bodySize = 0; + + /** + * Currently active threads in this thread group + */ + private volatile int groupThreads = 0; + + /** + * Currently active threads in all thread groups + */ + private volatile int allThreads = 0; + + private final long nanoTimeOffset; + + // Allow testcode access to the settings + final boolean useNanoTime; + + final long nanoThreadSleep; + + private long sentBytes; + + private URL location; + + private transient boolean ignore; + + private transient int subResultIndex; + + /** + * Cache for responseData as string to avoid multiple computations + */ + private transient volatile String responseDataAsString; + + public SampleResult() { + this(USE_NANO_TIME, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime setting + SampleResult(boolean nanoTime) { + this(nanoTime, NANOTHREAD_SLEEP); + } + + // Allow test code to change the default useNanoTime and nanoThreadSleep settings + SampleResult(boolean nanoTime, long nanoThreadSleep) { + this.elapsedTime = 0; + this.useNanoTime = nanoTime; + this.nanoThreadSleep = nanoThreadSleep; + this.nanoTimeOffset = initOffset(); + // 增加请求ID的获取 + Sampler sampler = JMeterContextService.getContext().getCurrentSampler(); + if (sampler != null) { + this.samplerId = sampler.getPropertyAsString("MS-ID"); + } + + } + + /** + * Copy constructor. + * + * @param res existing sample result + */ + public SampleResult(SampleResult res) { + this(); + allThreads = res.allThreads;//OK + assertionResults = res.assertionResults; + bytes = res.bytes; + headersSize = res.headersSize; + bodySize = res.bodySize; + contentType = res.contentType;//OK + dataEncoding = res.dataEncoding;//OK + dataType = res.dataType;//OK + endTime = res.endTime;//OK + // files is created automatically, and applies per instance + groupThreads = res.groupThreads;//OK + idleTime = res.idleTime; + label = res.label;//OK + latency = res.latency; + connectTime = res.connectTime; + location = res.location;//OK + parent = res.parent; + pauseTime = res.pauseTime; + requestHeaders = res.requestHeaders;//OK + responseCode = res.responseCode;//OK + responseData = res.responseData;//OK + responseDataAsString = null; + responseHeaders = res.responseHeaders;//OK + responseMessage = res.responseMessage;//OK + + // Don't copy this; it is per instance resultFileName = res.resultFileName; + + sampleCount = res.sampleCount; + samplerData = res.samplerData; + saveConfig = res.saveConfig; + sentBytes = res.sentBytes; + startTime = res.startTime;//OK + stopTest = res.stopTest; + stopTestNow = res.stopTestNow; + stopThread = res.stopThread; + testLogicalAction = res.testLogicalAction; + subResults = res.subResults; + success = res.success;//OK + threadName = res.threadName;//OK + elapsedTime = res.elapsedTime; + timeStamp = res.timeStamp; + } + + /** + * Create a sample with a specific elapsed time but don't allow the times to + * be changed later + *

+ * (only used by HTTPSampleResult) + * + * @param elapsed time + * @param atend create the sample finishing now, else starting now + */ + protected SampleResult(long elapsed, boolean atend) { + this(); + long now = currentTimeInMillis(); + if (atend) { + setTimes(now - elapsed, now); + } else { + setTimes(now, now + elapsed); + } + } + + /** + * Allow users to create a sample with specific timestamp and elapsed times + * for cloning purposes, but don't allow the times to be changed later + *

+ * Currently used by CSVSaveService and + * StatisticalSampleResult + * + * @param stamp this may be a start time or an end time (both in + * milliseconds) + * @param elapsed time in milliseconds + */ + public SampleResult(long stamp, long elapsed) { + this(); + stampAndTime(stamp, elapsed); + } + + private long initOffset() { + if (useNanoTime) { + return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs(); + } else { + return Long.MIN_VALUE; + } + } + + /** + * @param propertiesToSave The propertiesToSave to set. + */ + public void setSaveConfig(SampleSaveConfiguration propertiesToSave) { + this.saveConfig = propertiesToSave; + } + + public SampleSaveConfiguration getSaveConfig() { + return saveConfig; + } + + public boolean isStampedAtStart() { + return START_TIMESTAMP; + } + + /** + * Create a sample with specific start and end times for test purposes, but + * don't allow the times to be changed later + *

+ * (used by StatVisualizerModel.Test) + * + * @param start start time in milliseconds since unix epoch + * @param end end time in milliseconds since unix epoch + * @return sample with given start and end time + */ + public static SampleResult createTestSample(long start, long end) { + SampleResult res = new SampleResult(); + res.setStartTime(start); + res.setEndTime(end); + return res; + } + + /** + * Create a sample with a specific elapsed time for test purposes, but don't + * allow the times to be changed later + * + * @param elapsed - desired elapsed time in milliseconds + * @return sample that starts 'now' and ends elapsed milliseconds later + */ + public static SampleResult createTestSample(long elapsed) { + long now = System.currentTimeMillis(); + return createTestSample(now, now + elapsed); + } + + private static long sampleNsClockInMs() { + return System.nanoTime() / 1000000; + } + + /** + * Helper method to get 1 ms resolution timing. + * + * @return the current time in milliseconds + * @throws RuntimeException when useNanoTime is true but + * nanoTimeOffset is not set + */ + public long currentTimeInMillis() { + if (useNanoTime) { + if (nanoTimeOffset == Long.MIN_VALUE) { + throw new IllegalStateException("Invalid call; nanoTimeOffset has not been set"); + } + return sampleNsClockInMs() + nanoTimeOffset; + } + return System.currentTimeMillis(); + } + + // Helper method to maintain timestamp relationships + private void stampAndTime(long stamp, long elapsed) { + if (START_TIMESTAMP) { + startTime = stamp; + endTime = stamp + elapsed; + } else { + startTime = stamp - elapsed; + endTime = stamp; + } + timeStamp = stamp; + elapsedTime = elapsed; + } + + /** + * For use by SaveService only. + * + * @param stamp this may be a start time or an end time (both in milliseconds) + * @param elapsed time in milliseconds + * @throws RuntimeException when startTime or endTime has been + * set already + */ + public void setStampAndTime(long stamp, long elapsed) { + if (startTime != 0 || endTime != 0) { + throw new IllegalStateException("Calling setStampAndTime() after start/end times have been set"); + } + stampAndTime(stamp, elapsed); + } + + /** + * Set the "marked" flag to show that the result has been written to the file. + * + * @param filename the name of the file + * @return true if the result was previously marked + */ + public boolean markFile(String filename) { + return !files.add(filename != null ? filename : NULL_FILENAME); + } + + public String getResponseCode() { + return responseCode; + } + + /** + * Set response code to OK, i.e. "200" + */ + public void setResponseCodeOK() { + responseCode = OK_CODE; + } + + public void setResponseCode(String code) { + responseCode = code; + } + + public boolean isResponseCodeOK() { + return responseCode.equals(OK_CODE); + } + + public String getResponseMessage() { + return responseMessage; + } + + public void setResponseMessage(String msg) { + responseMessage = msg; + } + + public void setResponseMessageOK() { + responseMessage = OK_MSG; + } + + /** + * Set result statuses OK - shorthand method to set: + *

    + *
  • ResponseCode
  • + *
  • ResponseMessage
  • + *
  • Successful status
  • + *
+ */ + public void setResponseOK() { + setResponseCodeOK(); + setResponseMessageOK(); + setSuccessful(true); + } + + public String getThreadName() { + return threadName; + } + + public void setThreadName(String threadName) { + this.threadName = threadName; + } + + /** + * Get the sample timestamp, which may be either the start time or the end time. + * + * @return timeStamp in milliseconds + * @see #getStartTime() + * @see #getEndTime() + */ + public long getTimeStamp() { + return timeStamp; + } + + public String getSampleLabel() { + return label; + } + + /** + * Get the sample label for use in summary reports etc. + * + * @param includeGroup whether to include the thread group name + * @return the label + */ + public String getSampleLabel(boolean includeGroup) { + if (includeGroup) { + return threadName.substring(0, threadName.lastIndexOf(' ')) + ":" + label; + } + return label; + } + + public void setSampleLabel(String label) { + this.label = label; + } + + public void addAssertionResult(AssertionResult assertResult) { + if (assertionResults == null) { + assertionResults = new ArrayList<>(); + } + assertionResults.add(assertResult); + } + + /** + * Gets the assertion results associated with this sample. + * + * @return an array containing the assertion results for this sample. + * Returns empty array if there are no assertion results. + */ + public AssertionResult[] getAssertionResults() { + if (assertionResults == null) { + return EMPTY_AR; + } + return assertionResults.toArray(new AssertionResult[assertionResults.size()]); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult the {@link SampleResult} to be added + */ + public void addSubResult(SampleResult subResult) { + addSubResult(subResult, isRenameSampleLabel()); + } + + /** + * see https://bz.apache.org/bugzilla/show_bug.cgi?id=63055 + * + * @return true if TestPlan is in functional mode or property subresults.disable_renaming is true + */ + public static boolean isRenameSampleLabel() { + return !(TestPlan.getFunctionalMode() || DISABLE_SUBRESULTS_RENAMING); + } + + /** + * Add a subresult and adjust the parent byte count and end-time. + * + * @param subResult the {@link SampleResult} to be added + * @param renameSubResults boolean do we rename subResults based on position + */ + public void addSubResult(SampleResult subResult, boolean renameSubResults) { + if (subResult == null) { + // see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778 + return; + } + String tn = getThreadName(); + if (tn.length() == 0) { + tn = Thread.currentThread().getName(); + this.setThreadName(tn); + } + subResult.setThreadName(tn); + + // Extend the time to the end of the added sample + setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855 + // Include the byte count for the added sample + setBytes(getBytesAsLong() + subResult.getBytesAsLong()); + setSentBytes(getSentBytes() + subResult.getSentBytes()); + setHeadersSize(getHeadersSize() + subResult.getHeadersSize()); + setBodySize(getBodySizeAsLong() + subResult.getBodySizeAsLong()); + addRawSubResult(subResult, renameSubResults); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult the {@link SampleResult} to be added + */ + public void addRawSubResult(SampleResult subResult) { + storeSubResult(subResult, isRenameSampleLabel()); + } + + /** + * Add a subresult to the collection without updating any parent fields. + * + * @param subResult the {@link SampleResult} to be added + */ + private void addRawSubResult(SampleResult subResult, boolean renameSubResults) { + storeSubResult(subResult, renameSubResults); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult the {@link SampleResult} to be added + */ + public void storeSubResult(SampleResult subResult) { + storeSubResult(subResult, isRenameSampleLabel()); + } + + /** + * Add a subresult read from a results file. + *

+ * As for {@link SampleResult#addSubResult(SampleResult) + * addSubResult(SampleResult)}, except that the fields don't need to be + * accumulated + * + * @param subResult the {@link SampleResult} to be added + * @param renameSubResults boolean do we rename subResults based on position + */ + private void storeSubResult(SampleResult subResult, boolean renameSubResults) { + if (subResults == null) { + subResults = new ArrayList<>(); + } + if (renameSubResults) { + subResult.setSampleLabel(getSampleLabel() + "-" + subResultIndex++); + } + subResults.add(subResult); + subResult.setParent(this); + } + + /** + * Gets the subresults associated with this sample. + * + * @return an array containing the subresults for this sample. Returns an + * empty array if there are no subresults. + */ + public SampleResult[] getSubResults() { + if (subResults == null) { + return EMPTY_SR; + } + return subResults.toArray(new SampleResult[subResults.size()]); + } + + /** + * Sets the responseData attribute of the SampleResult object. + *

+ * If the parameter is null, then the responseData is set to an empty byte array. + * This ensures that getResponseData() can never be null. + * + * @param response the new responseData value + */ + public void setResponseData(byte[] response) { + responseDataAsString = null; + responseData = response == null ? EMPTY_BA : response; + } + + /** + * Sets the responseData attribute of the SampleResult object. + * Should only be called after setting the dataEncoding (if necessary) + * + * @param response the new responseData value (String) + * @deprecated - only intended for use from BeanShell code + */ + @Deprecated + public void setResponseData(String response) { + responseDataAsString = null; + try { + responseData = response.getBytes(getDataEncodingWithDefault()); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string, using default encoding. " + e.getLocalizedMessage()); + responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here + } + } + + /** + * Sets the encoding and responseData attributes of the SampleResult object. + * + * @param response the new responseData value (String) + * @param encoding the encoding to set and then use (if null, use platform default) + */ + public void setResponseData(final String response, final String encoding) { + responseDataAsString = null; + String encodeUsing = encoding != null ? encoding : DEFAULT_CHARSET; + try { + responseData = response.getBytes(encodeUsing); + setDataEncoding(encodeUsing); + } catch (UnsupportedEncodingException e) { + log.warn("Could not convert string using '" + encodeUsing + + "', using default encoding: " + DEFAULT_CHARSET, e); + responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here + setDataEncoding(DEFAULT_CHARSET); + } + } + + /** + * Gets the responseData attribute of the SampleResult object. + *

+ * Note that some samplers may not store all the data, in which case + * getResponseData().length will be incorrect. + *

+ * Instead, always use {@link #getBytes()} to obtain the sample result byte count. + *

+ * + * @return the responseData value (cannot be null) + */ + public byte[] getResponseData() { + return responseData; + } + + /** + * Gets the responseData of the SampleResult object as a String + * + * @return the responseData value as a String, converted according to the encoding + */ + public String getResponseDataAsString() { + try { + if (responseDataAsString == null) { + responseDataAsString = new String(responseData, getDataEncodingWithDefault()); + } + return responseDataAsString; + } catch (UnsupportedEncodingException e) { + log.warn("Using platform default as " + getDataEncodingWithDefault() + " caused " + e); + return new String(responseData, Charset.defaultCharset()); // N.B. default charset is used deliberately here + } + } + + public void setSamplerData(String s) { + samplerData = s; + } + + public String getSamplerData() { + return samplerData; + } + + /** + * Get the time it took this sample to occur. + * + * @return elapsed time in milliseconds + */ + public long getTime() { + return elapsedTime; + } + + public boolean isSuccessful() { + return success; + } + + /** + * Sets the data type of the sample. + * + * @param dataType String containing {@link #BINARY} or {@link #TEXT} + * @see #BINARY + * @see #TEXT + */ + public void setDataType(String dataType) { + this.dataType = dataType; + } + + /** + * Returns the data type of the sample. + * + * @return String containing {@link #BINARY} or {@link #TEXT} or the empty string + * @see #BINARY + * @see #TEXT + */ + public String getDataType() { + return dataType; + } + + /** + * Extract and save the DataEncoding and DataType from the parameter provided. + * Does not save the full content Type. + * + * @param ct - content type (may be null) + * @see #setContentType(String) which should be used to save the full content-type string + */ + public void setEncodingAndType(String ct) { + if (ct != null) { + // Extract charset and store as DataEncoding + // N.B. The meta tag: + // + // is now processed by HTTPSampleResult#getDataEncodingWithDefault + final String charsetPrefix = "charset="; // $NON-NLS-1$ + int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(charsetPrefix); + if (cset >= 0) { + String charSet = ct.substring(cset + charsetPrefix.length()); + // handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed + int semiColon = charSet.indexOf(';'); + if (semiColon >= 0) { + charSet = charSet.substring(0, semiColon); + } + // Check for quoted string + if (charSet.startsWith("\"") || charSet.startsWith("\'")) { // $NON-NLS-1$ + setDataEncoding(charSet.substring(1, charSet.length() - 1)); // remove quotes + } else { + setDataEncoding(charSet); + } + } + if (isBinaryType(ct)) { + setDataType(BINARY); + } else { + setDataType(TEXT); + } + } + } + + /* + * Determine if content-type is known to be binary, i.e. not displayable as text. + * + * @param ct content type + * @return true if content-type is of type binary. + */ + public static boolean isBinaryType(String ct) { + for (String entry : NON_BINARY_TYPES) { + if (ct.startsWith(entry)) { + return false; + } + } + for (String binaryType : BINARY_TYPES) { + if (ct.startsWith(binaryType)) { + return true; + } + } + return false; + } + + /** + * Sets the successful attribute of the SampleResult object. + * + * @param success the new successful value + */ + public void setSuccessful(boolean success) { + this.success = success; + } + + /** + * Returns the display name. + * + * @return display name of this sample result + */ + @Override + public String toString() { + return getSampleLabel(); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @return the value of the dataEncoding or DEFAULT_ENCODING + */ + public String getDataEncodingWithDefault() { + return getDataEncodingWithDefault(DEFAULT_ENCODING); + } + + /** + * Returns the dataEncoding or the default if no dataEncoding was provided. + * + * @param defaultEncoding the default to be applied + * @return the value of the dataEncoding or the provided default + */ + protected String getDataEncodingWithDefault(String defaultEncoding) { + if (dataEncoding != null && dataEncoding.length() > 0) { + return dataEncoding; + } + return defaultEncoding; + } + + /** + * Returns the dataEncoding. May be null or the empty String. + * + * @return the value of the dataEncoding + */ + public String getDataEncodingNoDefault() { + return dataEncoding; + } + + /** + * Sets the dataEncoding. + * + * @param dataEncoding the dataEncoding to set, e.g. ISO-8895-1, UTF-8 + */ + public void setDataEncoding(String dataEncoding) { + this.dataEncoding = dataEncoding; + } + + /** + * @return whether to stop the test waiting for current running Sampler to end + */ + public boolean isStopTest() { + return stopTest; + } + + /** + * @return whether to stop the test now interrupting current running samplers + */ + public boolean isStopTestNow() { + return stopTestNow; + } + + /** + * @return whether to stop this thread + */ + public boolean isStopThread() { + return stopThread; + } + + public void setStopTest(boolean b) { + stopTest = b; + } + + public void setStopTestNow(boolean b) { + stopTestNow = b; + } + + public void setStopThread(boolean b) { + stopThread = b; + } + + /** + * @return the request headers + */ + public String getRequestHeaders() { + return requestHeaders; + } + + /** + * @return the response headers + */ + public String getResponseHeaders() { + return responseHeaders; + } + + /** + * @param string - + * request headers + */ + public void setRequestHeaders(String string) { + requestHeaders = string; + } + + /** + * @param string - + * response headers + */ + public void setResponseHeaders(String string) { + responseHeaders = string; + } + + /** + * @return the full content type - e.g. text/html [;charset=utf-8 ] + */ + public String getContentType() { + return contentType; + } + + /** + * Get the media type from the Content Type + * + * @return the media type - e.g. text/html (without charset, if any) + */ + public String getMediaType() { + return JOrphanUtils.trim(contentType, " ;").toLowerCase(java.util.Locale.ENGLISH); + } + + /** + * Stores the content-type string, e.g. text/xml; charset=utf-8 + * + * @param string the content-type to be set + * @see #setEncodingAndType(String) which can be used to extract the charset. + */ + public void setContentType(String string) { + contentType = string; + } + + /** + * @return idleTime + */ + public long getIdleTime() { + return idleTime; + } + + /** + * @return the end time + */ + public long getEndTime() { + return endTime; + } + + /** + * @return the start time + */ + public long getStartTime() { + return startTime; + } + + /* + * Helper methods N.B. setStartTime must be called before setEndTime + * + * setStartTime is used by HTTPSampleResult to clone the parent sampler and + * allow the original start time to be kept + */ + protected final void setStartTime(long start) { + startTime = start; + if (START_TIMESTAMP) { + timeStamp = startTime; + } + } + + public void setEndTime(long end) { + endTime = end; + if (!START_TIMESTAMP) { + timeStamp = endTime; + } + if (startTime == 0) { + log.error("setEndTime must be called after setStartTime", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } else { + elapsedTime = endTime - startTime - idleTime; + } + } + + /** + * Set idle time pause. + * For use by SampleResultConverter/CSVSaveService. + * + * @param idle long + */ + public void setIdleTime(long idle) { + idleTime = idle; + } + + private void setTimes(long start, long end) { + setStartTime(start); + setEndTime(end); + } + + /** + * Record the start time of a sample + */ + public void sampleStart() { + if (startTime == 0) { + setStartTime(currentTimeInMillis()); + } else { + log.error("sampleStart called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + } + + /** + * Record the end time of a sample and calculate the elapsed time + */ + public void sampleEnd() { + if (endTime == 0) { + setEndTime(currentTimeInMillis()); + } else { + log.error("sampleEnd called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + } + + /** + * Pause a sample + */ + public void samplePause() { + if (pauseTime != 0) { + log.error("samplePause called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + pauseTime = currentTimeInMillis(); + } + + /** + * Resume a sample + */ + public void sampleResume() { + if (pauseTime == 0) { + log.error("sampleResume without samplePause", new Throwable(INVALID_CALL_SEQUENCE_MSG)); + } + idleTime += currentTimeInMillis() - pauseTime; + pauseTime = 0; + } + + /** + * When a Sampler is working as a monitor + * + * @param monitor flag whether this sampler is working as a monitor + * @deprecated since 3.2 NOOP + */ + @Deprecated + public void setMonitor(boolean monitor) { + // NOOP + } + + /** + * If the sampler is a monitor, method will return true. + * + * @return true if the sampler is a monitor + * @deprecated since 3.2 always return false + */ + @Deprecated + public boolean isMonitor() { + return false; + } + + /** + * The statistical sample sender aggregates several samples to save on + * transmission costs. + * + * @param count number of samples represented by this instance + */ + public void setSampleCount(int count) { + sampleCount = count; + } + + /** + * return the sample count. by default, the value is 1. + * + * @return the sample count + */ + public int getSampleCount() { + return sampleCount; + } + + /** + * Returns the count of errors. + * + * @return 0 - or 1 if the sample failed + *

+ * TODO do we need allow for nested samples? + */ + public int getErrorCount() { + return success ? 0 : 1; + } + + public void setErrorCount(int i) {// for reading from CSV files + // ignored currently + } + + /* + * TODO: error counting needs to be sorted out. + * + * At present the Statistical Sampler tracks errors separately + * It would make sense to move the error count here, but this would + * mean lots of changes. + * It's also tricky maintaining the count - it can't just be incremented/decremented + * when the success flag is set as this may be done multiple times. + * The work-round for now is to do the work in the StatisticalSampleResult, + * which overrides this method. + * Note that some JMS samplers also create samples with > 1 sample count + * Also the Transaction Controller probably needs to be changed to do + * proper sample and error accounting. + * The purpose of this work-round is to allow at least minimal support for + * errors in remote statistical batch mode. + * + */ + + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length the number of bytes of the response data for this sample + */ + public void setBytes(long length) { + bytes = length; + } + + /** + * In the event the sampler does want to pass back the actual contents, we + * still want to calculate the throughput. The bytes are the bytes of the + * response data. + * + * @param length the number of bytes of the response data for this sample + * @deprecated use setBytes(long) + */ + @Deprecated + public void setBytes(int length) { + setBytes((long) length); + } + + /** + * @param sentBytesCount long sent bytes + */ + public void setSentBytes(long sentBytesCount) { + sentBytes = sentBytesCount; + } + + /** + * @return the sentBytes + */ + public long getSentBytes() { + return sentBytes; + } + + /** + * return the bytes returned by the response. + * + * @return byte count + * @deprecated use getBytesAsLong + */ + @Deprecated + public int getBytes() { + return (int) getBytesAsLong(); + } + + /** + * return the bytes returned by the response. + * + * @return byte count + */ + public long getBytesAsLong() { + long tmpSum = this.getHeadersSize() + this.getBodySizeAsLong(); + return tmpSum == 0 ? bytes : tmpSum; + } + + /** + * @return Returns the latency. + */ + public long getLatency() { + return latency; + } + + /** + * Set the time to the first response + */ + public void latencyEnd() { + latency = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param latency The latency to set. + */ + public void setLatency(long latency) { + this.latency = latency; + } + + /** + * @return Returns the connect time. + */ + public long getConnectTime() { + return connectTime; + } + + /** + * Set the time to the end of connecting + */ + public void connectEnd() { + connectTime = currentTimeInMillis() - startTime - idleTime; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param time The connect time to set. + */ + public void setConnectTime(long time) { + this.connectTime = time; + } + + /** + * This is only intended for use by SampleResultConverter! + * + * @param timeStamp The timeStamp to set. + */ + public void setTimeStamp(long timeStamp) { + this.timeStamp = timeStamp; + } + + + public void setURL(URL location) { + this.location = location; + } + + public URL getURL() { + return location; + } + + /** + * Get a String representation of the URL (if defined). + * + * @return ExternalForm of URL, or empty string if url is null + */ + public String getUrlAsString() { + return location == null ? "" : location.toExternalForm(); + } + + /** + * @return Returns the parent. + */ + public SampleResult getParent() { + return parent; + } + + /** + * @param parent The parent to set. + */ + public void setParent(SampleResult parent) { + this.parent = parent; + } + + public String getResultFileName() { + return resultFileName; + } + + public void setResultFileName(String resultFileName) { + this.resultFileName = resultFileName; + } + + public int getGroupThreads() { + return groupThreads; + } + + public void setGroupThreads(int n) { + this.groupThreads = n; + } + + public int getAllThreads() { + return allThreads; + } + + public void setAllThreads(int n) { + this.allThreads = n; + } + + // Bug 47394 + + /** + * Allow custom SampleSenders to drop unwanted assertionResults + */ + public void removeAssertionResults() { + this.assertionResults = null; + } + + /** + * Allow custom SampleSenders to drop unwanted subResults + */ + public void removeSubResults() { + this.subResults = null; + } + + /** + * Set the headers size in bytes + * + * @param size the number of bytes of the header + */ + public void setHeadersSize(int size) { + this.headersSize = size; + } + + /** + * Get the headers size in bytes + * + * @return the headers size + */ + public int getHeadersSize() { + return headersSize; + } + + /** + * @return the body size in bytes + * @deprecated replaced by getBodySizeAsLong() + */ + @Deprecated + public int getBodySize() { + return (int) getBodySizeAsLong(); + } + + /** + * @return the body size in bytes + */ + public long getBodySizeAsLong() { + return bodySize == 0 ? responseData.length : bodySize; + } + + /** + * @param bodySize the body size to set + */ + public void setBodySize(long bodySize) { + this.bodySize = bodySize; + } + + /** + * @param bodySize the body size to set + * @deprecated use setBodySize(long) + */ + @Deprecated + public void setBodySize(int bodySize) { + this.bodySize = bodySize; + } + + private static class NanoOffset extends Thread { + + private static volatile long nanoOffset; + + static long getNanoOffset() { + return nanoOffset; + } + + @Override + public void run() { + // Wait longer than a clock pulse (generally 10-15ms) + getOffset(30L); // Catch an early clock pulse to reduce slop. + while (true) { + getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks + } + } + + private static void getOffset(long wait) { + try { + TimeUnit.MILLISECONDS.sleep(wait); + long clock = System.currentTimeMillis(); + long nano = SampleResult.sampleNsClockInMs(); + nanoOffset = clock - nano; + } catch (InterruptedException ignore) { + // ignored + Thread.currentThread().interrupt(); + } + } + } + + /** + * @return the startNextThreadLoop + * @deprecated use {@link SampleResult#getTestLogicalAction()} + */ + @Deprecated + public boolean isStartNextThreadLoop() { + return testLogicalAction == TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; + } + + /** + * @param startNextThreadLoop the startNextLoop to set + * @deprecated use SampleResult#setTestLogicalAction(TestLogicalAction) + */ + @Deprecated + public void setStartNextThreadLoop(boolean startNextThreadLoop) { + if (startNextThreadLoop) { + testLogicalAction = TestLogicalAction.START_NEXT_ITERATION_OF_THREAD; + } else { + testLogicalAction = TestLogicalAction.CONTINUE; + } + } + + /** + * Clean up cached data + */ + public void cleanAfterSample() { + this.responseDataAsString = null; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new IllegalStateException("This should not happen"); + } + } + + @Override + public List getSearchableTokens() throws Exception { + List datasToSearch = new ArrayList<>(4); + datasToSearch.add(getSampleLabel()); + datasToSearch.add(getResponseDataAsString()); + datasToSearch.add(getRequestHeaders()); + datasToSearch.add(getResponseHeaders()); + return datasToSearch; + } + + /** + * @return boolean true if this SampleResult should not be sent to Listeners + */ + public boolean isIgnore() { + return ignore; + } + + /** + * Call this method to tell JMeter to ignore this SampleResult by Listeners + */ + public void setIgnore() { + this.ignore = true; + } + + /** + * @return String first non null assertion failure message if assertionResults is not null, null otherwise + */ + public String getFirstAssertionFailureMessage() { + String message = null; + AssertionResult[] results = getAssertionResults(); + + if (results != null) { + // Find the first non-null message + for (AssertionResult result : results) { + message = result.getFailureMessage(); + if (message != null) { + break; + } + } + } + return message; + } + + /** + * @return the testLogicalAction + */ + public TestLogicalAction getTestLogicalAction() { + return testLogicalAction; + } + + /** + * @param testLogicalAction the testLogicalAction to set + */ + public void setTestLogicalAction(TestLogicalAction testLogicalAction) { + this.testLogicalAction = testLogicalAction; + } +} diff --git a/backend/src/main/resources/db/migration/V78__v1.8_release.sql b/backend/src/main/resources/db/migration/V78__v1.8_release.sql index c75d13da28..48db340305 100644 --- a/backend/src/main/resources/db/migration/V78__v1.8_release.sql +++ b/backend/src/main/resources/db/migration/V78__v1.8_release.sql @@ -44,4 +44,10 @@ ALTER TABLE test_case_review ADD tags VARCHAR(2000) NULL; alter table test_plan_api_scenario change environment_id environment longtext null comment 'Relevance environment'; -- file add sort column -alter table file_metadata add sort int default 0; \ No newline at end of file +alter table file_metadata add sort int default 0; + +-- add Original state +alter table api_definition add original_state varchar(64); +alter table api_scenario add original_state varchar(64); +update api_definition set original_state='Underway'; +update api_scenario set original_state='Underway'; \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index f4051a6348..eecd89e847 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,7 +17,6 @@ "@fortawesome/vue-fontawesome": "^0.1.9", "axios": "^0.21.1", "core-js": "^3.4.3", - "default-passive-events": "^2.0.0", "diffable-html": "^4.0.0", "echarts": "^4.6.0", "el-table-infinite-scroll": "^1.0.10", diff --git a/frontend/src/business/App.vue b/frontend/src/business/App.vue index 6362aa95bd..76368547c8 100644 --- a/frontend/src/business/App.vue +++ b/frontend/src/business/App.vue @@ -8,18 +8,20 @@ - + - - + + + + @@ -29,11 +31,14 @@ import MsView from "./components/common/router/View"; import MsUser from "./components/common/head/HeaderUser"; import MsHeaderOrgWs from "./components/common/head/HeaderOrgWs"; import MsLanguageSwitch from "./components/common/head/LanguageSwitch"; -import {saveLocalStorage} from "@/common/js/utils"; +import {hasLicense, saveLocalStorage, setColor, setOriginColor} from "@/common/js/utils"; +import {registerRequestHeaders} from "@/common/js/ajax"; +import {ORIGIN_COLOR} from "@/common/js/constants"; const requireComponent = require.context('@/business/components/xpack/', true, /\.vue$/); const header = requireComponent.keys().length > 0 ? requireComponent("./license/LicenseMessage.vue") : {}; const display = requireComponent.keys().length > 0 ? requireComponent("./display/Display.vue") : {}; +const theme = requireComponent.keys().length > 0 ? requireComponent("./display/Theme.vue") : {}; export default { name: 'app', @@ -44,9 +49,21 @@ export default { auth: false, header: {}, logoId: '_blank', + color: '' } }, created() { + registerRequestHeaders(); + if (!hasLicense()) { + setOriginColor() + this.color = ORIGIN_COLOR; + } else { + // + this.$get('/system/theme', res => { + this.color = res.data ? res.data : ORIGIN_COLOR; + setColor(this.color, this.color, this.color, this.color); + }) + } if (localStorage.getItem("store")) { this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(localStorage.getItem("store")))) } @@ -81,7 +98,8 @@ export default { MsView, MsTopMenus, MsHeaderOrgWs, - "LicenseMessage": header.default + "LicenseMessage": header.default, + "Theme": theme.default } } @@ -91,7 +109,7 @@ export default { #header-top { width: 100%; padding: 0 10px; - background-color: rgb(44, 42, 72); + background-color: var(--color); color: rgb(245, 245, 245); font-size: 14px; height: 40px; diff --git a/frontend/src/business/components/api/automation/report/ApiReportDetail.vue b/frontend/src/business/components/api/automation/report/ApiReportDetail.vue index 10071b53b4..c0624d9ffd 100644 --- a/frontend/src/business/components/api/automation/report/ApiReportDetail.vue +++ b/frontend/src/business/components/api/automation/report/ApiReportDetail.vue @@ -100,6 +100,19 @@ active() { this.isActive = !this.isActive; }, + formatResult(res) { + let resMap = new Map; + if (res && res.scenarios) { + res.scenarios.forEach(item => { + if (item && item.requestResults) { + item.requestResults.forEach(req => { + resMap.set(req.id, req); + }) + } + }) + } + this.$emit('refresh', resMap); + }, getReport() { this.init(); if (this.reportId) { @@ -113,7 +126,7 @@ if (!this.content) { this.content = {scenarios: []}; } - this.$emit('refresh'); + this.formatResult(this.content); } catch (e) { throw e; } diff --git a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue index 6bb9514153..629d56bafa 100644 --- a/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue +++ b/frontend/src/business/components/api/automation/scenario/ApiScenarioList.vue @@ -145,7 +145,7 @@ - + @@ -276,7 +276,9 @@ }, { name: this.$t('test_track.case.batch_move_case'), handleClick: this.handleBatchMove - } + }, + {name: this.$t('api_test.definition.request.batch_delete'), handleClick: this.handleDeleteBatch}, + ], isSelectAllDate: false, selectRows: new Set(), @@ -318,7 +320,7 @@ ], principal: [], environmentId: [], - projectEnv: [] + projectEnv: [], }, } }, @@ -496,17 +498,31 @@ cancel() { this.planVisible = false; }, - addTestPlan(plans) { - let obj = {planIds: plans, scenarioIds: this.selection}; + addTestPlan(params) { + let obj = {planIds: params[0], scenarioIds: this.selection}; - obj.projectId = getCurrentProjectID(); - obj.selectAllDate = this.isSelectAllDate; - obj.unSelectIds = this.unSelection; - obj = Object.assign(obj, this.condition); + // obj.projectId = getCurrentProjectID(); + // obj.selectAllDate = this.isSelectAllDate; + // obj.unSelectIds = this.unSelection; + // obj = Object.assign(obj, this.condition); + + // todo 选取全部数据 + if (this.isSelectAllDate) { + this.$warning("暂不支持批量添加所有场景到测试计划!"); + } this.planVisible = false; + + let map = new Map(); + this.selectRows.forEach(row => { + map.set(row.id, row.projectIds); + }) + obj.mapping = strMapToObj(map); + obj.envMap = strMapToObj(params[1]); + this.$post("/api/automation/scenario/plan", obj, response => { this.$success(this.$t("commons.save_success")); + this.search(); }); }, getReport() { @@ -582,6 +598,29 @@ this.search(); }) }, + handleDeleteBatch(row) { + if (this.trashEnable) { + let ids = Array.from(this.selectRows).map(row => row.id); + this.$post('/api/automation/deleteBatch/', ids, () => { + this.$success(this.$t('commons.delete_success')); + this.search(); + }); + return; + } + this.$alert(this.$t('api_test.definition.request.delete_confirm') + " ?", '', { + confirmButtonText: this.$t('commons.confirm'), + callback: (action) => { + if (action === 'confirm') { + let ids = Array.from(this.selectRows).map(row => row.id); + this.$post('/api/automation/removeToGc/', ids, () => { + this.$success(this.$t('commons.delete_success')); + this.search(); + }); + } + } + }); + }, + execute(row) { this.infoDb = false; let url = "/api/automation/run"; diff --git a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue index af479b9619..d06a8b0e95 100644 --- a/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue +++ b/frontend/src/business/components/api/automation/scenario/EditApiScenario.vue @@ -184,7 +184,7 @@ - + @@ -224,6 +224,7 @@ import MsComponentConfig from "./component/ComponentConfig"; import {handleCtrlSEvent} from "../../../../../common/js/utils"; import EnvPopover from "@/business/components/api/automation/scenario/EnvPopover"; + let jsonPath = require('jsonpath'); export default { name: "EditApiScenario", @@ -290,7 +291,8 @@ response: {}, projectIds: new Set, projectEnvMap: new Map, - projectList: [] + projectList: [], + debugResult: new Map, } }, created() { @@ -400,7 +402,7 @@ }, { title: this.$t('api_test.automation.scenario_import'), - show:this.showButton("scenario"), + show: this.showButton("scenario"), titleColor: "#606266", titleBgColor: "#F4F4F5", icon: "movie", @@ -547,21 +549,32 @@ if (arr[i].hashTree != undefined && arr[i].hashTree.length > 0) { this.recursiveSorting(arr[i].hashTree); } + // 添加debug结果 + if (this.debugResult && this.debugResult.get(arr[i].id)) { + arr[i].requestResult = this.debugResult.get(arr[i].id); + } } }, sort() { for (let i in this.scenarioDefinition) { + // 排序 this.scenarioDefinition[i].index = Number(i) + 1; + // 设置循环控制 if (this.scenarioDefinition[i].type === ELEMENT_TYPE.LoopController && this.scenarioDefinition[i].hashTree && this.scenarioDefinition[i].hashTree.length > 1) { this.scenarioDefinition[i].countController.proceed = true; } + // 设置项目ID if (!this.scenarioDefinition[i].projectId) { - this.scenarioDefinition.projectId = getCurrentProjectID(); + this.scenarioDefinition[i].projectId = getCurrentProjectID(); } if (this.scenarioDefinition[i].hashTree != undefined && this.scenarioDefinition[i].hashTree.length > 0) { this.recursiveSorting(this.scenarioDefinition[i].hashTree); } + // 添加debug结果 + if (this.debugResult && this.debugResult.get(this.scenarioDefinition[i].id)) { + this.scenarioDefinition[i].requestResult = this.debugResult.get(this.scenarioDefinition[i].id); + } } }, addCustomizeApi(request) { @@ -575,6 +588,7 @@ this.customizeRequest = {}; this.sort(); this.reload(); + this.initProjectIds(); }, addScenario(arr) { if (arr && arr.length > 0) { @@ -677,7 +691,7 @@ } const index = hashTree.findIndex(d => d.resourceId === row.resourceId); if (index != -1) { - hashTree.splice(index+1, 0, obj); + hashTree.splice(index + 1, 0, obj); } else { hashTree.push(obj); } @@ -1019,11 +1033,16 @@ // 加载环境配置 this.$nextTick(() => { this.projectIds.clear(); - this.scenarioDefinition.forEach(data=>{ + this.scenarioDefinition.forEach(data => { let arr = jsonPath.query(data, "$..projectId"); arr.forEach(a => this.projectIds.add(a)); }) }) + }, + detailRefresh(result) { + // 把执行结果分发给各个请求 + this.debugResult = result; + this.sort() } } } diff --git a/frontend/src/business/components/api/automation/scenario/EnvSelect.vue b/frontend/src/business/components/api/automation/scenario/EnvSelect.vue index 281c466aea..c9eda69790 100644 --- a/frontend/src/business/components/api/automation/scenario/EnvSelect.vue +++ b/frontend/src/business/components/api/automation/scenario/EnvSelect.vue @@ -112,7 +112,17 @@ export default { } }) } else { - sign = false; + // 如果有环境,检查环境 + if (this.envMap && this.envMap.size > 0) { + this.projectIds.forEach(id => { + if (!this.envMap.get(id)) { + sign = false; + return false; + } + }) + } else { + sign = false; + } } if (!sign) { diff --git a/frontend/src/business/components/api/automation/scenario/Setting.js b/frontend/src/business/components/api/automation/scenario/Setting.js index 9636cf8e03..9ddffe168a 100644 --- a/frontend/src/business/components/api/automation/scenario/Setting.js +++ b/frontend/src/business/components/api/automation/scenario/Setting.js @@ -6,8 +6,8 @@ export const ELEMENTS = new Map([ ['JDBCSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['TCPSampler', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['OT_IMPORT', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], - ['IfController', ["IfController","scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], - ['LoopController', ["IfController", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], + ['IfController', ["IfController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], + ['LoopController', ["IfController", "scenario", "HTTPSamplerProxy", "DubboSampler", "JDBCSampler", "TCPSampler", "OT_IMPORT", "ConstantTimer", "JSR223Processor", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract", "CustomizeReq"]], ['ConstantTimer', []], ['JSR223Processor', ["ConstantTimer", "JSR223PreProcessor", "JSR223PostProcessor", "Assertions", "Extract"]], ['JSR223PreProcessor', []], diff --git a/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue b/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue index e099d29d16..54aa5e4516 100644 --- a/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue +++ b/frontend/src/business/components/api/automation/scenario/common/ScenarioImport.vue @@ -26,7 +26,7 @@ - + @@ -118,6 +118,13 @@ tip: this.$t('api_test.api_import.jmeter_tip'), exportTip: this.$t('api_test.api_import.jmeter_export_tip'), suffixes: new Set(['jmx']) + }, + { + name: 'Har', + value: 'Har', + tip: this.$t('api_test.api_import.har_tip'), + exportTip: this.$t('api_test.api_import.har_export_tip'), + suffixes: new Set(['har']) } ], selectedPlatform: {}, @@ -150,6 +157,11 @@ } }, }, + computed: { + isHar() { + return this.selectedPlatformValue === 'Har'; + }, + }, methods: { scheduleEdit() { if (!this.formData.swaggerUrl) { diff --git a/frontend/src/business/components/api/automation/scenario/testplan/TestPlanList.vue b/frontend/src/business/components/api/automation/scenario/testplan/TestPlanList.vue index 7e631ce3a3..bcbe85bf8b 100644 --- a/frontend/src/business/components/api/automation/scenario/testplan/TestPlanList.vue +++ b/frontend/src/business/components/api/automation/scenario/testplan/TestPlanList.vue @@ -3,9 +3,11 @@ - + s.id); this.$emit('selection', selection); }, + setScenarioSelectRows(rows) { + this.projectIds.clear(); + rows.forEach(row => { + row.projectIds.forEach(id => this.projectIds.add(id)); + }) + }, initTableData() { if (this.planId) { this.condition.planId = this.planId; @@ -284,6 +308,14 @@ export default { this.$refs.testCaseReportView.open(planId, reportId); } }, + checkEnv() { + return this.$refs.envPopover.checkEnv(); + }, + getWsProjects() { + this.$get("/project/listAll", res => { + this.projectList = res.data; + }) + }, } } diff --git a/frontend/src/business/components/api/definition/components/assertion/ApiAssertionJsonPath.vue b/frontend/src/business/components/api/definition/components/assertion/ApiAssertionJsonPath.vue index 62a37b75cf..8a02363413 100644 --- a/frontend/src/business/components/api/definition/components/assertion/ApiAssertionJsonPath.vue +++ b/frontend/src/business/components/api/definition/components/assertion/ApiAssertionJsonPath.vue @@ -1,13 +1,24 @@ + + diff --git a/frontend/src/business/components/api/definition/components/response/ResponseResult.vue b/frontend/src/business/components/api/definition/components/response/ResponseResult.vue index 2682f9dfef..61825a2c97 100644 --- a/frontend/src/business/components/api/definition/components/response/ResponseResult.vue +++ b/frontend/src/business/components/api/definition/components/response/ResponseResult.vue @@ -151,7 +151,7 @@ } this.reqMessages = this.$t('api_test.request.address') + ":\n" + this.response.url + "\n" + this.$t('api_test.scenario.headers') + ":\n" + this.response.headers + "\n" + "Cookies :\n" + - this.response.cookies + "\n" + "Bpdy:" + "\n" + this.response.body; + this.response.cookies + "\n" + "Body:" + "\n" + this.response.body; } }, }, diff --git a/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue b/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue index 5785c09907..a1988d2a19 100644 --- a/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/ApiInfoCard.vue @@ -147,7 +147,7 @@ export default { .count-number{ font-family:'ArialMT', 'Arial', sans-serif; font-size:33px; - color: #6C317C; + color: var(--count_number); } .main-number-show { @@ -155,7 +155,7 @@ export default { height: 100px; border-style: solid; border-width: 7px; - border-color: #CDB9D2; + border-color: var(--count_number_shallow); border-radius:50%; } diff --git a/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue b/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue index 0f5a5db41f..e048e5c050 100644 --- a/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/SceneInfoCard.vue @@ -137,7 +137,7 @@ export default { .count-number{ font-family:'ArialMT', 'Arial', sans-serif; font-size:33px; - color: #6C317C; + color: var(--count_number); margin:20px auto; } @@ -146,7 +146,7 @@ export default { height: 100px; border-style: solid; border-width: 7px; - border-color: #CDB9D2; + border-color: var(--count_number_shallow); border-radius:50%; } diff --git a/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue b/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue index 6f9d952ab6..158d819c6b 100644 --- a/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/ScheduleTaskInfoCard.vue @@ -124,7 +124,7 @@ export default { .count-number{ font-family:'ArialMT', 'Arial', sans-serif; font-size:33px; - color: #6C317C; + color: var(--count_number); } .main-number-show { @@ -132,7 +132,7 @@ export default { height: 100px; border-style: solid; border-width: 7px; - border-color: #CDB9D2; + border-color: var(--count_number_shallow); border-radius:50%; } diff --git a/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue b/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue index 51c80c0f69..7e61bfbde7 100644 --- a/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue +++ b/frontend/src/business/components/api/homepage/components/TestCaseInfoCard.vue @@ -173,7 +173,7 @@ export default { .count-number{ font-family:'ArialMT', 'Arial', sans-serif; font-size:33px; - color: #6C317C; + color: var(--count_number); } .main-number-show { @@ -181,7 +181,7 @@ export default { height: 100px; border-style: solid; border-width: 7px; - border-color: #CDB9D2; + border-color: var(--count_number_shallow); border-radius:50%; } diff --git a/frontend/src/business/components/common/components/MsTabButton.vue b/frontend/src/business/components/common/components/MsTabButton.vue index 9616de1780..45431edd4f 100644 --- a/frontend/src/business/components/common/components/MsTabButton.vue +++ b/frontend/src/business/components/common/components/MsTabButton.vue @@ -103,16 +103,16 @@ export default { .active { border: solid 1px #6d317c; - background-color: #7C3985; + background-color: var(--color); color: #FFFFFF; } .case-button { - border-left: solid 1px #6d317c; + border-left: solid 1px var(--color); } .item{ - border: solid 1px #6d317c; + border: solid 1px var(--color); } diff --git a/frontend/src/business/components/common/head/HeaderOrgWs.vue b/frontend/src/business/components/common/head/HeaderOrgWs.vue index 7a88e5947c..afb0f29954 100644 --- a/frontend/src/business/components/common/head/HeaderOrgWs.vue +++ b/frontend/src/business/components/common/head/HeaderOrgWs.vue @@ -1,7 +1,7 @@