feat(接口定制): 基础保存功能完善
This commit is contained in:
parent
9bdb4f9625
commit
a887840214
|
@ -2,6 +2,7 @@ package io.metersphere.api.controller;
|
|||
|
||||
import com.github.pagehelper.Page;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.dto.delimit.ApiDelimitRequest;
|
||||
import io.metersphere.api.dto.delimit.ApiDelimitResult;
|
||||
import io.metersphere.api.dto.delimit.SaveApiDelimitRequest;
|
||||
|
@ -59,4 +60,18 @@ public class ApiDelimitController {
|
|||
return apiDelimitService.get(id);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/run/debug", consumes = {"multipart/form-data"})
|
||||
public String runDebug(@RequestPart("request") SaveApiDelimitRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
return apiDelimitService.run(request, file, bodyFiles);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/run", consumes = {"multipart/form-data"})
|
||||
public String run(@RequestPart("request") SaveApiDelimitRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||
return apiDelimitService.run(request, file, bodyFiles);
|
||||
}
|
||||
|
||||
@GetMapping("/report/get/{testId}/{test}")
|
||||
public APIReportResult getReport(@PathVariable String testId, @PathVariable String test) {
|
||||
return apiDelimitService.getResult(testId, test);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package io.metersphere.api.dto.delimit;
|
||||
|
||||
import io.metersphere.api.dto.scenario.request.Request;
|
||||
import io.metersphere.api.dto.delimit.response.Response;
|
||||
import io.metersphere.base.domain.Schedule;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
@ -13,6 +14,8 @@ public class SaveApiDelimitRequest {
|
|||
|
||||
private String id;
|
||||
|
||||
private String reportId;
|
||||
|
||||
private String projectId;
|
||||
|
||||
private String name;
|
||||
|
@ -31,6 +34,10 @@ public class SaveApiDelimitRequest {
|
|||
|
||||
private Request request;
|
||||
|
||||
private Response response;
|
||||
|
||||
private String environmentId;
|
||||
|
||||
private String userId;
|
||||
|
||||
private Schedule schedule;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package io.metersphere.api.dto.delimit.response;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import io.metersphere.api.dto.scenario.Body;
|
||||
import io.metersphere.api.dto.scenario.KeyValue;
|
||||
import io.metersphere.api.dto.scenario.request.RequestType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@JSONType(typeName = RequestType.HTTP)
|
||||
public class HttpResponse extends Response {
|
||||
// type 必须放最前面,以便能够转换正确的类
|
||||
private String type = RequestType.HTTP;
|
||||
@JSONField(ordinal = 1)
|
||||
private List<KeyValue> headers;
|
||||
@JSONField(ordinal = 2)
|
||||
private List<KeyValue> statusCode;
|
||||
@JSONField(ordinal = 3)
|
||||
private Body body;
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package io.metersphere.api.dto.delimit.response;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import com.alibaba.fastjson.annotation.JSONType;
|
||||
import com.fasterxml.jackson.annotation.JsonSubTypes;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import io.metersphere.api.dto.scenario.request.RequestType;
|
||||
import lombok.Data;
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
|
||||
@JsonSubTypes({
|
||||
@JsonSubTypes.Type(value = HttpResponse.class, name = RequestType.HTTP),
|
||||
})
|
||||
@JSONType(seeAlso = {HttpResponse.class}, typeKey = "type")
|
||||
@Data
|
||||
public abstract class Response {
|
||||
@JSONField(ordinal = 1)
|
||||
private String id;
|
||||
@JSONField(ordinal = 2)
|
||||
private String name;
|
||||
@JSONField(ordinal = 3)
|
||||
private Boolean enable;
|
||||
}
|
|
@ -9,5 +9,7 @@ public class Body {
|
|||
private String type;
|
||||
private String raw;
|
||||
private String format;
|
||||
private Object json;
|
||||
private String xml;
|
||||
private List<KeyValue> kvs;
|
||||
}
|
||||
|
|
|
@ -14,15 +14,18 @@ public class KeyValue {
|
|||
private String description;
|
||||
private String contentType;
|
||||
private boolean enable;
|
||||
private boolean required;
|
||||
|
||||
public KeyValue() {
|
||||
this.enable = true;
|
||||
this.required = true;
|
||||
}
|
||||
|
||||
public KeyValue(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.enable = true;
|
||||
this.required = true;
|
||||
}
|
||||
|
||||
public KeyValue(String name, String value, String description) {
|
||||
|
@ -30,5 +33,6 @@ public class KeyValue {
|
|||
this.value = value;
|
||||
this.enable = true;
|
||||
this.description = description;
|
||||
this.required = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.base.domain.ApiDelimitExecResult;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class ApiTestResult {
|
||||
|
||||
private String id;
|
||||
|
||||
private String name;
|
||||
|
||||
private long responseTime;
|
||||
|
||||
private int error = 0;
|
||||
|
||||
private int success = 0;
|
||||
|
||||
private int totalAssertions = 0;
|
||||
|
||||
private int passAssertions = 0;
|
||||
|
||||
private final List<RequestResult> requestResults = new ArrayList<>();
|
||||
|
||||
public void addRequestResult(RequestResult result) {
|
||||
requestResults.add(result);
|
||||
}
|
||||
|
||||
public ApiTestResult() {
|
||||
|
||||
}
|
||||
|
||||
public ApiTestResult(ApiDelimitExecResult result) {
|
||||
this.id = result.getId();
|
||||
this.responseTime = result.getEndTime();
|
||||
this.addRequestResult(JSON.parseObject(result.getContent(), RequestResult.class));
|
||||
}
|
||||
|
||||
public void addResponseTime(long time) {
|
||||
this.responseTime += time;
|
||||
}
|
||||
|
||||
public void addError(int count) {
|
||||
this.error += count;
|
||||
}
|
||||
|
||||
public void addSuccess() {
|
||||
this.success++;
|
||||
}
|
||||
public int getSuccess() {
|
||||
return this.success;
|
||||
}
|
||||
|
||||
public void addTotalAssertions(int count) {
|
||||
this.totalAssertions += count;
|
||||
}
|
||||
|
||||
public void addPassAssertions(int count) {
|
||||
this.passAssertions += count;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,165 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import io.metersphere.api.service.ApiDelimitExecResultService;
|
||||
import io.metersphere.api.service.ApiDelimitService;
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.utils.CommonBeanFactory;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.jmeter.assertions.AssertionResult;
|
||||
import org.apache.jmeter.samplers.SampleResult;
|
||||
import org.apache.jmeter.visualizers.backend.AbstractBackendListenerClient;
|
||||
import org.apache.jmeter.visualizers.backend.BackendListenerContext;
|
||||
import org.springframework.http.HttpMethod;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* JMeter BackendListener扩展, jmx脚本中使用
|
||||
*/
|
||||
public class DelimitBackendListenerClient extends AbstractBackendListenerClient implements Serializable {
|
||||
|
||||
public final static String TEST_ID = "ms.test.id";
|
||||
|
||||
private final List<SampleResult> queue = new ArrayList<>();
|
||||
|
||||
public String runMode = ApiRunMode.RUN.name();
|
||||
private ApiDelimitService apiDelimitService;
|
||||
private ApiDelimitExecResultService apiDelimitExecResultService;
|
||||
// 测试ID
|
||||
private String testId;
|
||||
// 运行结果报告ID
|
||||
private String reportId;
|
||||
|
||||
@Override
|
||||
public void setupTest(BackendListenerContext context) throws Exception {
|
||||
setParam(context);
|
||||
apiDelimitService = CommonBeanFactory.getBean(ApiDelimitService.class);
|
||||
if (apiDelimitService == null) {
|
||||
LogUtil.error("apiDelimitService is required");
|
||||
}
|
||||
apiDelimitExecResultService = CommonBeanFactory.getBean(ApiDelimitExecResultService.class);
|
||||
if (apiDelimitExecResultService == null) {
|
||||
LogUtil.error("apiDelimitExecResultService is required");
|
||||
}
|
||||
super.setupTest(context);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void handleSampleResults(List<SampleResult> sampleResults, BackendListenerContext context) {
|
||||
queue.addAll(sampleResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardownTest(BackendListenerContext context) throws Exception {
|
||||
ApiTestResult testResult = new ApiTestResult();
|
||||
testResult.setId(testId);
|
||||
queue.forEach(result -> {
|
||||
RequestResult reqResult = getRequestResult(result);
|
||||
testResult.addRequestResult(reqResult);
|
||||
testResult.addPassAssertions(reqResult.getPassAssertions());
|
||||
testResult.addTotalAssertions(reqResult.getTotalAssertions());
|
||||
});
|
||||
// 调试操作,不需要存储结果
|
||||
if (StringUtils.isBlank(reportId)) {
|
||||
apiDelimitService.addResult(testResult);
|
||||
} else {
|
||||
apiDelimitExecResultService.saveApiResult(testResult);
|
||||
}
|
||||
queue.clear();
|
||||
super.teardownTest(context);
|
||||
}
|
||||
|
||||
|
||||
private RequestResult getRequestResult(SampleResult result) {
|
||||
RequestResult reqResult = new RequestResult();
|
||||
reqResult.setName(result.getSampleLabel());
|
||||
reqResult.setUrl(result.getUrlAsString());
|
||||
reqResult.setMethod(getMethod(result));
|
||||
reqResult.setBody(result.getSamplerData());
|
||||
reqResult.setHeaders(result.getRequestHeaders());
|
||||
reqResult.setRequestSize(result.getSentBytes());
|
||||
reqResult.setStartTime(result.getStartTime());
|
||||
reqResult.setTotalAssertions(result.getAssertionResults().length);
|
||||
reqResult.setSuccess(result.isSuccessful());
|
||||
reqResult.setError(result.getErrorCount());
|
||||
for (SampleResult subResult : result.getSubResults()) {
|
||||
reqResult.getSubRequestResults().add(getRequestResult(subResult));
|
||||
}
|
||||
|
||||
ResponseResult responseResult = reqResult.getResponseResult();
|
||||
responseResult.setBody(result.getResponseDataAsString());
|
||||
responseResult.setHeaders(result.getResponseHeaders());
|
||||
responseResult.setLatency(result.getLatency());
|
||||
responseResult.setResponseCode(result.getResponseCode());
|
||||
responseResult.setResponseSize(result.getResponseData().length);
|
||||
responseResult.setResponseTime(result.getTime());
|
||||
responseResult.setResponseMessage(result.getResponseMessage());
|
||||
|
||||
if (JMeterVars.get(result.hashCode()) != null) {
|
||||
List<String> vars = new LinkedList<>();
|
||||
JMeterVars.get(result.hashCode()).entrySet().parallelStream().reduce(vars, (first, second) -> {
|
||||
first.add(second.getKey() + ":" + second.getValue());
|
||||
return first;
|
||||
}, (first, second) -> {
|
||||
if (first == second) {
|
||||
return first;
|
||||
}
|
||||
first.addAll(second);
|
||||
return first;
|
||||
});
|
||||
responseResult.setVars(StringUtils.join(vars, "\n"));
|
||||
JMeterVars.remove(result.hashCode());
|
||||
}
|
||||
for (AssertionResult assertionResult : result.getAssertionResults()) {
|
||||
ResponseAssertionResult responseAssertionResult = getResponseAssertionResult(assertionResult);
|
||||
if (responseAssertionResult.isPass()) {
|
||||
reqResult.addPassAssertions();
|
||||
}
|
||||
responseResult.getAssertions().add(responseAssertionResult);
|
||||
}
|
||||
return reqResult;
|
||||
}
|
||||
|
||||
private String getMethod(SampleResult result) {
|
||||
String body = result.getSamplerData();
|
||||
// Dubbo Protocol
|
||||
String start = "RPC Protocol: ";
|
||||
String end = "://";
|
||||
if (StringUtils.contains(body, start)) {
|
||||
return StringUtils.substringBetween(body, start, end).toUpperCase();
|
||||
} else {
|
||||
// Http Method
|
||||
String method = StringUtils.substringBefore(body, " ");
|
||||
for (HttpMethod value : HttpMethod.values()) {
|
||||
if (StringUtils.equals(method, value.name())) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return "Request";
|
||||
}
|
||||
}
|
||||
|
||||
private void setParam(BackendListenerContext context) {
|
||||
this.testId = context.getParameter(TEST_ID);
|
||||
this.runMode = context.getParameter("runMode");
|
||||
this.reportId = context.getParameter("reportId");
|
||||
if (StringUtils.isBlank(this.runMode)) {
|
||||
this.runMode = ApiRunMode.RUN.name();
|
||||
}
|
||||
}
|
||||
|
||||
private ResponseAssertionResult getResponseAssertionResult(AssertionResult assertionResult) {
|
||||
ResponseAssertionResult responseAssertionResult = new ResponseAssertionResult();
|
||||
responseAssertionResult.setName(assertionResult.getName());
|
||||
responseAssertionResult.setPass(!assertionResult.isFailure() && !assertionResult.isError());
|
||||
|
||||
if (!responseAssertionResult.isPass()) {
|
||||
responseAssertionResult.setMessage(assertionResult.getFailureMessage());
|
||||
}
|
||||
return responseAssertionResult;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
package io.metersphere.api.jmeter;
|
||||
|
||||
import io.metersphere.commons.constants.ApiRunMode;
|
||||
import io.metersphere.commons.exception.MSException;
|
||||
import io.metersphere.commons.utils.LogUtil;
|
||||
import io.metersphere.config.JmeterProperties;
|
||||
import io.metersphere.i18n.Translator;
|
||||
import org.apache.jmeter.config.Arguments;
|
||||
import org.apache.jmeter.save.SaveService;
|
||||
import org.apache.jmeter.util.JMeterUtils;
|
||||
import org.apache.jmeter.visualizers.backend.BackendListener;
|
||||
import org.apache.jorphan.collections.HashTree;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
@Service
|
||||
public class DelimitJMeterService {
|
||||
|
||||
@Resource
|
||||
private JmeterProperties jmeterProperties;
|
||||
|
||||
public void run(String testId, String reportId, InputStream is) {
|
||||
String home = getJmeterHome();
|
||||
String jmeterProperties = home + "/bin/jmeter.properties";
|
||||
JMeterUtils.loadJMeterProperties(jmeterProperties);
|
||||
JMeterUtils.setJMeterHome(home);
|
||||
JMeterUtils.setLocale(LocaleContextHolder.getLocale());
|
||||
|
||||
try {
|
||||
Object scriptWrapper = SaveService.loadElement(is);
|
||||
HashTree testPlan = getHashTree(scriptWrapper);
|
||||
JMeterVars.addJSR223PostProcessor(testPlan);
|
||||
addBackendListener(testId, reportId, testPlan);
|
||||
LocalRunner runner = new LocalRunner(testPlan);
|
||||
runner.run();
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), e);
|
||||
MSException.throwException(Translator.get("api_load_script_error"));
|
||||
}
|
||||
}
|
||||
|
||||
public String getJmeterHome() {
|
||||
String home = getClass().getResource("/").getPath() + "jmeter";
|
||||
try {
|
||||
File file = new File(home);
|
||||
if (file.exists()) {
|
||||
return home;
|
||||
} else {
|
||||
return jmeterProperties.getHome();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return jmeterProperties.getHome();
|
||||
}
|
||||
}
|
||||
|
||||
private HashTree getHashTree(Object scriptWrapper) throws Exception {
|
||||
Field field = scriptWrapper.getClass().getDeclaredField("testPlan");
|
||||
field.setAccessible(true);
|
||||
return (HashTree) field.get(scriptWrapper);
|
||||
}
|
||||
|
||||
private void addBackendListener(String testId, String debugReportId, HashTree testPlan) {
|
||||
BackendListener backendListener = new BackendListener();
|
||||
backendListener.setName(testId);
|
||||
Arguments arguments = new Arguments();
|
||||
arguments.addArgument(DelimitBackendListenerClient.TEST_ID, testId);
|
||||
|
||||
arguments.addArgument("runMode", ApiRunMode.DEBUG.name());
|
||||
arguments.addArgument("reportId", debugReportId);
|
||||
|
||||
backendListener.setArguments(arguments);
|
||||
backendListener.setClassname(DelimitBackendListenerClient.class.getCanonicalName());
|
||||
testPlan.add(testPlan.getArray()[0], backendListener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import io.metersphere.api.jmeter.ApiTestResult;
|
||||
import io.metersphere.base.domain.ApiDelimitExecResult;
|
||||
import io.metersphere.base.mapper.ApiDelimitExecResultMapper;
|
||||
import io.metersphere.commons.utils.SessionUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public class ApiDelimitExecResultService {
|
||||
@Resource
|
||||
private ApiDelimitExecResultMapper apiDelimitExecResultMapper;
|
||||
|
||||
|
||||
public void saveApiResult(ApiTestResult result) {
|
||||
result.getRequestResults().forEach(item -> {
|
||||
// 清理原始资源,每个执行 保留一条结果
|
||||
apiDelimitExecResultMapper.deleteByResourceId(item.getName());
|
||||
ApiDelimitExecResult saveResult = new ApiDelimitExecResult();
|
||||
saveResult.setId(UUID.randomUUID().toString());
|
||||
saveResult.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
|
||||
saveResult.setName(item.getUrl());
|
||||
saveResult.setResourceId(item.getName());
|
||||
saveResult.setContent(JSON.toJSONString(item));
|
||||
saveResult.setStartTime(item.getStartTime());
|
||||
saveResult.setEndTime(item.getResponseResult().getResponseTime());
|
||||
saveResult.setStatus(item.getResponseResult().getResponseCode().equals("200") ? "success" : "error");
|
||||
apiDelimitExecResultMapper.insert(saveResult);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,10 +1,15 @@
|
|||
package io.metersphere.api.service;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.metersphere.api.dto.APIReportResult;
|
||||
import io.metersphere.api.dto.delimit.ApiComputeResult;
|
||||
import io.metersphere.api.dto.delimit.ApiDelimitRequest;
|
||||
import io.metersphere.api.dto.delimit.ApiDelimitResult;
|
||||
import io.metersphere.api.dto.delimit.SaveApiDelimitRequest;
|
||||
import io.metersphere.api.jmeter.ApiTestResult;
|
||||
import io.metersphere.api.jmeter.DelimitJMeterService;
|
||||
import io.metersphere.api.parse.JmeterDocumentParser;
|
||||
import io.metersphere.base.domain.*;
|
||||
import io.metersphere.base.mapper.ApiDelimitExecResultMapper;
|
||||
import io.metersphere.base.mapper.ApiDelimitMapper;
|
||||
|
@ -23,6 +28,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import sun.security.util.Cache;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.*;
|
||||
|
@ -46,6 +52,9 @@ public class ApiDelimitService {
|
|||
private ApiTestCaseService apiTestCaseService;
|
||||
@Resource
|
||||
private ApiDelimitExecResultMapper apiDelimitExecResultMapper;
|
||||
@Resource
|
||||
private DelimitJMeterService jMeterService;
|
||||
private static Cache cache = Cache.newHardMemoryCache(0, 3600 * 24);
|
||||
|
||||
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||
|
||||
|
@ -79,7 +88,7 @@ public class ApiDelimitService {
|
|||
public void create(SaveApiDelimitRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
ApiDelimit test = createTest(request, file);
|
||||
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||
createBodyFiles(test.getId(), bodyUploadIds, bodyFiles);
|
||||
}
|
||||
|
||||
private ApiDelimit createTest(SaveApiDelimitRequest request, MultipartFile file) {
|
||||
|
@ -109,13 +118,13 @@ public class ApiDelimitService {
|
|||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
request.setBodyUploadIds(null);
|
||||
ApiDelimit test = updateTest(request);
|
||||
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||
createBodyFiles(test.getId(), bodyUploadIds, bodyFiles);
|
||||
saveFile(test.getId(), file);
|
||||
}
|
||||
|
||||
private void createBodyFiles(ApiDelimit test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
|
||||
private void createBodyFiles(String testId, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
|
||||
if (bodyUploadIds.size() > 0) {
|
||||
String dir = BODY_FILE_DIR + "/" + test.getId();
|
||||
String dir = BODY_FILE_DIR + "/" + testId;
|
||||
File testDir = new File(dir);
|
||||
if (!testDir.exists()) {
|
||||
testDir.mkdirs();
|
||||
|
@ -181,6 +190,8 @@ public class ApiDelimitService {
|
|||
test.setPath(request.getPath());
|
||||
test.setUrl(request.getUrl());
|
||||
test.setDescription(request.getDescription());
|
||||
test.setResponse(JSONObject.toJSONString(request.getResponse()));
|
||||
test.setEnvironmentId(request.getEnvironmentId());
|
||||
test.setUserId(request.getUserId());
|
||||
|
||||
apiDelimitMapper.updateByPrimaryKeySelective(test);
|
||||
|
@ -201,6 +212,8 @@ public class ApiDelimitService {
|
|||
test.setUpdateTime(System.currentTimeMillis());
|
||||
test.setStatus(APITestStatus.Underway.name());
|
||||
test.setModulePath(request.getModulePath());
|
||||
test.setResponse(JSONObject.toJSONString(request.getResponse()));
|
||||
test.setEnvironmentId(request.getEnvironmentId());
|
||||
if (request.getUserId() == null) {
|
||||
test.setUserId(Objects.requireNonNull(SessionUtils.getUser()).getId());
|
||||
} else {
|
||||
|
@ -212,18 +225,18 @@ public class ApiDelimitService {
|
|||
}
|
||||
|
||||
private void saveFile(String apiId, MultipartFile file) {
|
||||
final FileMetadata fileMetadata = fileService.saveFile(file);
|
||||
final FileMetadata metadata = fileService.saveFile(file);
|
||||
ApiTestFile apiTestFile = new ApiTestFile();
|
||||
apiTestFile.setTestId(apiId);
|
||||
apiTestFile.setFileId(fileMetadata.getId());
|
||||
apiTestFile.setFileId(metadata.getId());
|
||||
apiTestFileMapper.insert(apiTestFile);
|
||||
}
|
||||
|
||||
private void deleteFileByTestId(String apiId) {
|
||||
ApiTestFileExample ApiTestFileExample = new ApiTestFileExample();
|
||||
ApiTestFileExample.createCriteria().andTestIdEqualTo(apiId);
|
||||
final List<ApiTestFile> ApiTestFiles = apiTestFileMapper.selectByExample(ApiTestFileExample);
|
||||
apiTestFileMapper.deleteByExample(ApiTestFileExample);
|
||||
ApiTestFileExample apiTestFileExample = new ApiTestFileExample();
|
||||
apiTestFileExample.createCriteria().andTestIdEqualTo(apiId);
|
||||
final List<ApiTestFile> ApiTestFiles = apiTestFileMapper.selectByExample(apiTestFileExample);
|
||||
apiTestFileMapper.deleteByExample(apiTestFileExample);
|
||||
|
||||
if (!CollectionUtils.isEmpty(ApiTestFiles)) {
|
||||
final List<String> fileIds = ApiTestFiles.stream().map(ApiTestFile::getFileId).collect(Collectors.toList());
|
||||
|
@ -231,4 +244,61 @@ public class ApiDelimitService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试执行
|
||||
*
|
||||
* @param request
|
||||
* @param file
|
||||
* @param bodyFiles
|
||||
* @return
|
||||
*/
|
||||
public String run(SaveApiDelimitRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||
if (file == null) {
|
||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||
}
|
||||
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds());
|
||||
createBodyFiles(request.getId(), bodyUploadIds, bodyFiles);
|
||||
InputStream is = null;
|
||||
try {
|
||||
// 解析 xml 处理 mock 数据
|
||||
byte[] bytes = JmeterDocumentParser.parse(file.getBytes());
|
||||
is = new ByteArrayInputStream(bytes);
|
||||
} catch (IOException e) {
|
||||
LogUtil.error(e);
|
||||
}
|
||||
jMeterService.run(request.getId(), request.getReportId(), is);
|
||||
return request.getId();
|
||||
}
|
||||
|
||||
public void addResult(ApiTestResult res) {
|
||||
cache.put(res.getId(), res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取执行结果报告
|
||||
*
|
||||
* @param testId
|
||||
* @param test
|
||||
* @return
|
||||
*/
|
||||
public APIReportResult getResult(String testId, String test) {
|
||||
if (test.equals("debug")) {
|
||||
Object res = cache.get(testId);
|
||||
if (res != null) {
|
||||
cache.remove(testId);
|
||||
APIReportResult reportResult = new APIReportResult();
|
||||
reportResult.setContent(JSON.toJSONString(res));
|
||||
return reportResult;
|
||||
}
|
||||
} else {
|
||||
ApiDelimitExecResult data = apiDelimitExecResultMapper.selectByResourceId(testId);
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
APIReportResult reportResult = new APIReportResult();
|
||||
reportResult.setContent(data.getContent());
|
||||
return reportResult;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ public class ApiDelimit implements Serializable {
|
|||
|
||||
private String url;
|
||||
|
||||
private String description;
|
||||
private String environmentId;
|
||||
|
||||
private String status;
|
||||
|
||||
private String description;
|
||||
|
||||
private String userId;
|
||||
|
||||
private String moduleId;
|
||||
|
@ -32,5 +34,7 @@ public class ApiDelimit implements Serializable {
|
|||
|
||||
private String request;
|
||||
|
||||
private String response;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
|
@ -12,7 +12,7 @@ public class ApiDelimitExecResult implements Serializable {
|
|||
private String content;
|
||||
private String status;
|
||||
private String userId;
|
||||
private String startTime;
|
||||
private String endTime;
|
||||
private Long startTime;
|
||||
private Long endTime;
|
||||
|
||||
}
|
||||
|
|
|
@ -7,4 +7,9 @@ public interface ApiDelimitExecResultMapper {
|
|||
int deleteByResourceId(String id);
|
||||
|
||||
int insert(ApiDelimitExecResult record);
|
||||
|
||||
ApiDelimitExecResult selectByResourceId(String resourceId);
|
||||
|
||||
ApiDelimitExecResult selectByPrimaryKey(String id);
|
||||
|
||||
}
|
|
@ -10,7 +10,18 @@
|
|||
insert into api_delimit_exec_result
|
||||
(id, resource_id,name,content, status, user_id, start_time, end_time)
|
||||
values
|
||||
(#{id,jdbcType=VARCHAR}, #{resourceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}, #{status,jdbcType=VARCHAR},
|
||||
(#{id,jdbcType=VARCHAR}, #{resourceId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{content,jdbcType=LONGVARCHAR}, #{status,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR},
|
||||
#{startTime,jdbcType=BIGINT}, #{endTime,jdbcType=BIGINT})
|
||||
</insert>
|
||||
|
||||
<select id="selectByResourceId" parameterType="java.lang.String" resultType="io.metersphere.base.domain.ApiDelimitExecResult">
|
||||
select * from api_delimit_exec_result
|
||||
where resource_id = #{resourceId,jdbcType=VARCHAR}
|
||||
</select>
|
||||
|
||||
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultType="io.metersphere.base.domain.ApiDelimitExecResult">
|
||||
select * from api_delimit_exec_result
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</select>
|
||||
|
||||
</mapper>
|
|
@ -23,7 +23,6 @@ public interface ApiDelimitMapper {
|
|||
|
||||
int insert(ApiDelimit record);
|
||||
|
||||
int insertSelective(ApiDelimit record);
|
||||
|
||||
List<ApiDelimit> selectByExampleWithBLOBs(ApiDelimitExample example);
|
||||
|
||||
|
@ -31,11 +30,6 @@ public interface ApiDelimitMapper {
|
|||
|
||||
ApiDelimit selectByPrimaryKey(String id);
|
||||
|
||||
int updateByExampleSelective(@Param("record") ApiDelimit record, @Param("example") ApiDelimitExample example);
|
||||
|
||||
int updateByExampleWithBLOBs(@Param("record") ApiDelimit record, @Param("example") ApiDelimitExample example);
|
||||
|
||||
int updateByExample(@Param("record") ApiDelimit record, @Param("example") ApiDelimitExample example);
|
||||
|
||||
int updateByPrimaryKeySelective(ApiDelimit record);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
</resultMap>
|
||||
<resultMap extends="BaseResultMap" id="ResultMapWithBLOBs" type="io.metersphere.base.domain.ApiDelimit">
|
||||
<result column="request" jdbcType="LONGVARCHAR" property="request"/>
|
||||
<result column="response" jdbcType="LONGVARCHAR" property="response"/>
|
||||
</resultMap>
|
||||
<sql id="Example_Where_Clause">
|
||||
<where>
|
||||
|
@ -204,7 +205,7 @@
|
|||
<select id="list" resultType="io.metersphere.api.dto.delimit.ApiDelimitResult">
|
||||
select api_delimit.id, api_delimit.project_id,
|
||||
api_delimit.name,api_delimit.url,api_delimit.module_id,api_delimit.module_path,api_delimit.path,
|
||||
api_delimit.description,api_delimit.request,
|
||||
api_delimit.description,api_delimit.request,api_delimit.response,api_delimit.environment_id,
|
||||
api_delimit.status, api_delimit.user_id, api_delimit.create_time, api_delimit.update_time, project.name as
|
||||
project_name, user.name as user_name
|
||||
from api_delimit
|
||||
|
@ -280,6 +281,7 @@
|
|||
order by ${orderByClause}
|
||||
</if>
|
||||
</select>
|
||||
|
||||
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="ResultMapWithBLOBs">
|
||||
select
|
||||
<include refid="Base_Column_List"/>
|
||||
|
@ -292,103 +294,24 @@
|
|||
delete from api_delimit
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</delete>
|
||||
|
||||
<delete id="deleteByExample" parameterType="io.metersphere.base.domain.ApiDelimitExample">
|
||||
delete from api_delimit
|
||||
<if test="_parameter != null">
|
||||
<include refid="Example_Where_Clause"/>
|
||||
</if>
|
||||
</delete>
|
||||
|
||||
<insert id="insert" parameterType="io.metersphere.base.domain.ApiDelimit">
|
||||
insert into api_delimit (id, project_id, name, url,module_id,module_path,path,
|
||||
description, status, user_id,
|
||||
create_time, update_time, request
|
||||
)
|
||||
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{url,jdbcType=VARCHAR}, #{moduleId,jdbcType=VARCHAR},#{modulePath,jdbcType=VARCHAR},#{path,jdbcType=VARCHAR},
|
||||
#{description,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR},
|
||||
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{request,jdbcType=LONGVARCHAR}
|
||||
)
|
||||
</insert>
|
||||
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiDelimit">
|
||||
insert into api_delimit
|
||||
<trim prefix="(" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
id,
|
||||
</if>
|
||||
<if test="projectId != null">
|
||||
project_id,
|
||||
</if>
|
||||
<if test="name != null">
|
||||
name,
|
||||
</if>
|
||||
<if test="url != null">
|
||||
url,
|
||||
</if>
|
||||
<if test="path != null">
|
||||
path,
|
||||
</if>
|
||||
|
||||
<if test="moduleId != null">
|
||||
module_id,
|
||||
</if>
|
||||
<if test="modulePath != null">
|
||||
module_path,
|
||||
</if>
|
||||
<if test="description != null">
|
||||
description,
|
||||
</if>
|
||||
<if test="status != null">
|
||||
status,
|
||||
</if>
|
||||
<if test="userId != null">
|
||||
user_id,
|
||||
</if>
|
||||
<if test="createTime != null">
|
||||
create_time,
|
||||
</if>
|
||||
<if test="updateTime != null">
|
||||
update_time,
|
||||
</if>
|
||||
<if test="request != null">
|
||||
request,
|
||||
</if>
|
||||
</trim>
|
||||
<trim prefix="values (" suffix=")" suffixOverrides=",">
|
||||
<if test="id != null">
|
||||
#{id,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="projectId != null">
|
||||
#{projectId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="name != null">
|
||||
#{name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
|
||||
<if test="url != null">
|
||||
#{url,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="moduleId != null">
|
||||
#{moduleId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="description != null">
|
||||
#{description,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="status != null">
|
||||
#{status,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="userId != null">
|
||||
#{userId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="createTime != null">
|
||||
#{createTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="updateTime != null">
|
||||
#{updateTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="request != null">
|
||||
#{request,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
</trim>
|
||||
insert into api_delimit (id, project_id, name, url,module_id,module_path,path,
|
||||
description, status, user_id,create_time, update_time, request,response,environment_id )
|
||||
values
|
||||
(#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{url,jdbcType=VARCHAR}, #{moduleId,jdbcType=VARCHAR},#{modulePath,jdbcType=VARCHAR},#{path,jdbcType=VARCHAR},
|
||||
#{description,jdbcType=VARCHAR}, #{status,jdbcType=VARCHAR}, #{userId,jdbcType=VARCHAR},
|
||||
#{createTime,jdbcType=BIGINT}, #{updateTime,jdbcType=BIGINT}, #{request,jdbcType=LONGVARCHAR},#{response,jdbcType=LONGVARCHAR},#{environmentId,jdbcType=VARCHAR}
|
||||
)
|
||||
</insert>
|
||||
|
||||
<select id="countByExample" parameterType="io.metersphere.base.domain.ApiDelimitExample"
|
||||
resultType="java.lang.Long">
|
||||
select count(*) from api_delimit
|
||||
|
@ -396,89 +319,7 @@
|
|||
<include refid="Example_Where_Clause"/>
|
||||
</if>
|
||||
</select>
|
||||
<update id="updateByExampleSelective" parameterType="map">
|
||||
update api_delimit
|
||||
<set>
|
||||
<if test="record.id != null">
|
||||
id = #{record.id,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.projectId != null">
|
||||
project_id = #{record.projectId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.name != null">
|
||||
name = #{record.name,jdbcType=VARCHAR},
|
||||
</if>
|
||||
|
||||
<if test="record.moduleId != null">
|
||||
module_id = #{record.moduleId,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="record.modulePath != null">
|
||||
module_path = #{record.modulePath,jdbcType=BIGINT},
|
||||
</if>
|
||||
|
||||
<if test="record.url != null">
|
||||
url = #{record.url,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="record.path != null">
|
||||
path = #{record.path,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
|
||||
<if test="record.description != null">
|
||||
description = #{record.description,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.status != null">
|
||||
status = #{record.status,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.userId != null">
|
||||
user_id = #{record.userId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
<if test="record.createTime != null">
|
||||
create_time = #{record.createTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="record.updateTime != null">
|
||||
update_time = #{record.updateTime,jdbcType=BIGINT},
|
||||
</if>
|
||||
<if test="record.request != null">
|
||||
request = #{record.request,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause"/>
|
||||
</if>
|
||||
</update>
|
||||
<update id="updateByExampleWithBLOBs" parameterType="map">
|
||||
update api_delimit
|
||||
set id = #{record.id,jdbcType=VARCHAR},
|
||||
project_id = #{record.projectId,jdbcType=VARCHAR},
|
||||
name = #{record.name,jdbcType=VARCHAR},
|
||||
module_id = #{moduleId,jdbcType=VARCHAR},
|
||||
module_path = #{modulePath,jdbcType=VARCHAR},
|
||||
url = #{url,jdbcType=VARCHAR},
|
||||
path = #{path,jdbcType=VARCHAR},
|
||||
description = #{record.description,jdbcType=VARCHAR},
|
||||
status = #{record.status,jdbcType=VARCHAR},
|
||||
user_id = #{record.userId,jdbcType=VARCHAR},
|
||||
create_time = #{record.createTime,jdbcType=BIGINT},
|
||||
update_time = #{record.updateTime,jdbcType=BIGINT},
|
||||
request = #{record.request,jdbcType=LONGVARCHAR}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause"/>
|
||||
</if>
|
||||
</update>
|
||||
<update id="updateByExample" parameterType="map">
|
||||
update api_delimit
|
||||
set id = #{record.id,jdbcType=VARCHAR},
|
||||
project_id = #{record.projectId,jdbcType=VARCHAR},
|
||||
name = #{record.name,jdbcType=VARCHAR},
|
||||
description = #{record.description,jdbcType=VARCHAR},
|
||||
status = #{record.status,jdbcType=VARCHAR},
|
||||
user_id = #{record.userId,jdbcType=VARCHAR},
|
||||
create_time = #{record.createTime,jdbcType=BIGINT},
|
||||
update_time = #{record.updateTime,jdbcType=BIGINT}
|
||||
<if test="_parameter != null">
|
||||
<include refid="Update_By_Example_Where_Clause"/>
|
||||
</if>
|
||||
</update>
|
||||
<update id="updateByPrimaryKeySelective" parameterType="io.metersphere.base.domain.ApiDelimit">
|
||||
update api_delimit
|
||||
<set>
|
||||
|
@ -519,7 +360,12 @@
|
|||
<if test="request != null">
|
||||
request = #{request,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
|
||||
<if test="response != null">
|
||||
response = #{response,jdbcType=LONGVARCHAR},
|
||||
</if>
|
||||
<if test="environmentId != null">
|
||||
environment_id = #{environmentId,jdbcType=VARCHAR},
|
||||
</if>
|
||||
</set>
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
|
@ -536,7 +382,9 @@
|
|||
path = #{path,jdbcType=VARCHAR},
|
||||
create_time = #{createTime,jdbcType=BIGINT},
|
||||
update_time = #{updateTime,jdbcType=BIGINT},
|
||||
request = #{request,jdbcType=LONGVARCHAR}
|
||||
request = #{request,jdbcType=LONGVARCHAR},
|
||||
response = #{response,jdbcType=LONGVARCHAR},
|
||||
environment_id = #{environmentId,jdbcType=VARCHAR}
|
||||
where id = #{id,jdbcType=VARCHAR}
|
||||
</update>
|
||||
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.ApiDelimit">
|
||||
|
|
|
@ -17,27 +17,28 @@
|
|||
"@fortawesome/vue-fontawesome": "^0.1.9",
|
||||
"axios": "^0.19.0",
|
||||
"core-js": "^3.4.3",
|
||||
"diffable-html": "^4.0.0",
|
||||
"echarts": "^4.6.0",
|
||||
"el-table-infinite-scroll": "^1.0.10",
|
||||
"element-ui": "^2.13.0",
|
||||
"html2canvas": "^1.0.0-rc.7",
|
||||
"js-base64": "^3.4.4",
|
||||
"json-bigint": "^1.0.0",
|
||||
"jsoneditor": "^9.1.2",
|
||||
"jspdf": "^2.1.1",
|
||||
"md5": "^2.3.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"sha.js": "^2.4.11",
|
||||
"vue": "^2.6.10",
|
||||
"vue-calendar-heatmap": "^0.8.4",
|
||||
"vue-echarts": "^4.1.0",
|
||||
"vue-i18n": "^8.15.3",
|
||||
"vue-pdf": "^4.2.0",
|
||||
"vue-router": "^3.1.3",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuex": "^3.1.2",
|
||||
"vue-calendar-heatmap": "^0.8.4",
|
||||
"mockjs": "^1.1.0",
|
||||
"md5": "^2.3.0",
|
||||
"sha.js": "^2.4.11",
|
||||
"js-base64": "^3.4.4",
|
||||
"json-bigint": "^1.0.0",
|
||||
"html2canvas": "^1.0.0-rc.7",
|
||||
"jspdf": "^2.1.1",
|
||||
"yan-progress": "^1.0.3",
|
||||
"nprogress": "^0.2.0",
|
||||
"el-table-infinite-scroll": "^1.0.10",
|
||||
"vue-pdf": "^4.2.0",
|
||||
"diffable-html": "^4.0.0"
|
||||
"yan-progress": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^4.1.0",
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<!-- 测试-->
|
||||
<div v-else-if="item.type=== 'test'">
|
||||
<ms-run-test-http-page :api-data="runTestData" @saveAsApi="editApi"/>
|
||||
<ms-run-test-http-page :api-data="runTestData" @saveAsApi="editApi" :currentProject="currentProject"/>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
|
@ -182,7 +182,7 @@
|
|||
},
|
||||
saveApi(data) {
|
||||
this.setTabTitle(data);
|
||||
this.$refs.apiList[0].initTableData(data);
|
||||
this.$refs.apiList[0].initApiTable(data);
|
||||
},
|
||||
initTree(data) {
|
||||
this.moduleOptions = data;
|
||||
|
|
|
@ -62,7 +62,9 @@
|
|||
</el-col>
|
||||
<el-col :span="2">
|
||||
<div class="ms-api-header-select">
|
||||
<el-button size="small" @click="createCase">+{{$t('api_test.delimit.request.case')}}</el-button>
|
||||
<el-button size="small" style="background-color: #783887;color: white" @click="createCase">
|
||||
+{{$t('api_test.delimit.request.case')}}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="2">
|
||||
|
@ -78,7 +80,7 @@
|
|||
</el-header>
|
||||
|
||||
<!-- 用例部分 -->
|
||||
<el-main>
|
||||
<el-main v-loading="loading">
|
||||
<div v-for="(item,index) in apiCaseList" :key="index">
|
||||
<el-card style="margin-top: 10px" @click.native="selectTestCase(item,$event)">
|
||||
<el-row>
|
||||
|
@ -97,16 +99,21 @@
|
|||
<el-col :span="10">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': item.active}"
|
||||
@click="active(item)"/>
|
||||
<el-input v-if="item.type==='create'" size="small" v-model="item.name" :name="index"
|
||||
:key="index" class="ms-api-header-select" style="width: 180px"/>
|
||||
{{item.type!= 'create' ? item.name:''}}
|
||||
<el-input v-if="item.type==='create'" size="small" v-model="item.name" :name="index" :key="index"
|
||||
class="ms-api-header-select" style="width: 180px"
|
||||
@blur="saveTestCase(item)"
|
||||
/>
|
||||
<span v-else>
|
||||
{{item.type!= 'create' ? item.name:''}}
|
||||
<i class="el-icon-edit" style="cursor:pointer" @click="showInput(item)"/>
|
||||
</span>
|
||||
<div v-if="item.type!='create'" style="color: #999999;font-size: 12px">
|
||||
<span> {{item.createTime | timestampFormatDate }}</span> {{item.createUser}} 创建
|
||||
<span> {{item.updateTime | timestampFormatDate }}</span> {{item.updateUser}} 更新
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<ms-tip-button @click="runCase" :tip="$t('api_test.run')" icon="el-icon-video-play"
|
||||
<ms-tip-button @click="runCase(item)" :tip="$t('api_test.run')" icon="el-icon-video-play"
|
||||
style="background-color: #409EFF;color: white" size="mini" circle/>
|
||||
<ms-tip-button @click="copyCase(item)" :tip="$t('commons.copy')" icon="el-icon-document-copy"
|
||||
size="mini" circle/>
|
||||
|
@ -126,7 +133,15 @@
|
|||
<!-- 请求参数-->
|
||||
<el-collapse-transition>
|
||||
<div v-if="item.active">
|
||||
<p class="tip">{{$t('api_test.delimit.request.req_param')}} </p>
|
||||
|
||||
<ms-api-request-form :is-read-only="isReadOnly" :request="item.test.request"/>
|
||||
|
||||
<p class="tip">{{$t('api_test.delimit.request.assertions_rule')}} </p>
|
||||
|
||||
<ms-api-assertions :request="item.test.request" :is-read-only="isReadOnly"
|
||||
:assertions="item.test.request.assertions"/>
|
||||
|
||||
<!-- 保存操作 -->
|
||||
<el-button type="primary" size="small" style="margin: 20px; float: right" @click="saveTestCase(item)">
|
||||
{{$t('commons.save')}}
|
||||
|
@ -146,11 +161,12 @@
|
|||
import MsTag from "../../../common/components/MsTag";
|
||||
import MsTipButton from "../../../common/components/MsTipButton";
|
||||
import MsApiRequestForm from "./request/ApiRequestForm";
|
||||
import {Test, RequestFactory} from "../model/ScenarioModel";
|
||||
import {Test, RequestFactory} from "../model/ApiTestModel";
|
||||
import {downloadFile, getUUID} from "@/common/js/utils";
|
||||
import {parseEnvironment} from "../model/EnvironmentModel";
|
||||
import ApiEnvironmentConfig from "../../test/components/ApiEnvironmentConfig";
|
||||
import {PRIORITY} from "../model/JsonData";
|
||||
import MsApiAssertions from "./assertion/ApiAssertions";
|
||||
|
||||
export default {
|
||||
name: 'ApiCaseList',
|
||||
|
@ -158,18 +174,21 @@
|
|||
MsTag,
|
||||
MsTipButton,
|
||||
MsApiRequestForm,
|
||||
ApiEnvironmentConfig
|
||||
ApiEnvironmentConfig,
|
||||
MsApiAssertions
|
||||
},
|
||||
props: {
|
||||
api: {
|
||||
type: Object
|
||||
},
|
||||
loaded: Boolean,
|
||||
currentProject: {},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
grades: [],
|
||||
environments: [],
|
||||
environment: {},
|
||||
envValue: {},
|
||||
name: "",
|
||||
priorityValue: "",
|
||||
|
@ -177,6 +196,7 @@
|
|||
selectedEvent: Object,
|
||||
priority: PRIORITY,
|
||||
apiCaseList: [],
|
||||
loading: false,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -191,6 +211,7 @@
|
|||
},
|
||||
created() {
|
||||
this.getApiTest();
|
||||
this.getEnvironments();
|
||||
},
|
||||
methods: {
|
||||
getResult(data) {
|
||||
|
@ -202,11 +223,58 @@
|
|||
return '执行结果:未执行';
|
||||
}
|
||||
},
|
||||
showInput(row) {
|
||||
row.type = "create";
|
||||
row.active = true;
|
||||
this.active(row);
|
||||
},
|
||||
apiCaseClose() {
|
||||
this.apiCaseList = [];
|
||||
this.$emit('apiCaseClose');
|
||||
},
|
||||
runCase() {
|
||||
getReport(id) {
|
||||
let url = "/api/delimit/report/get/" + id + "/run";
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
this.loading = false;
|
||||
this.$success(this.$t('schedule.event_success'));
|
||||
this.$emit('refresh');
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
}
|
||||
});
|
||||
},
|
||||
runCase(row) {
|
||||
if (!this.environment) {
|
||||
this.$warning(this.$t('api_test.environment.select_environment'));
|
||||
return;
|
||||
}
|
||||
row.request = row.test.request;
|
||||
let url = "/api/delimit/run";
|
||||
let bodyFiles = this.getBodyUploadFiles(row);
|
||||
let env = this.environment.config.httpConfig.socket ? (this.environment.config.httpConfig.protocol + '://' + this.environment.config.httpConfig.socket) : '';
|
||||
if (env.endsWith("/")) {
|
||||
env = env.substr(0, env.length - 1);
|
||||
}
|
||||
let sendUrl = this.api.url;
|
||||
if (!sendUrl.startsWith("/")) {
|
||||
sendUrl = "/" + sendUrl;
|
||||
}
|
||||
row.test.request.url = env + sendUrl;
|
||||
row.test.request.path = this.api.path;
|
||||
row.test.request.name = row.id;
|
||||
row.test.request.connectTimeout = "6000";
|
||||
row.test.request.responseTimeout = "0";
|
||||
row.reportId = "run";
|
||||
this.loading = true;
|
||||
let jmx = row.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(url, file, bodyFiles, row, response => {
|
||||
this.getReport(row.id);
|
||||
}, erro => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
deleteCase(index, row) {
|
||||
this.$get('/api/testcase/delete/' + row.id, () => {
|
||||
|
@ -222,19 +290,24 @@
|
|||
active: false,
|
||||
test: data.test,
|
||||
};
|
||||
this.apiCaseList.push(obj);
|
||||
this.apiCaseList.unshift(obj);
|
||||
},
|
||||
createCase() {
|
||||
let test = new Test();
|
||||
createCase(row) {
|
||||
let obj = {
|
||||
id: this.apiCaseList.size + 1,
|
||||
name: '',
|
||||
priority: 'P0',
|
||||
type: 'create',
|
||||
active: false,
|
||||
test: test,
|
||||
};
|
||||
this.apiCaseList.push(obj);
|
||||
let request = {};
|
||||
if (row) {
|
||||
request = row.request;
|
||||
obj.apiDelimitId = row.apiDelimitId;
|
||||
} else {
|
||||
request: new RequestFactory(JSON.parse(this.api.request))
|
||||
}
|
||||
obj.test = new Test({request: request});
|
||||
this.apiCaseList.unshift(obj);
|
||||
},
|
||||
active(item) {
|
||||
item.active = !item.active;
|
||||
|
@ -289,7 +362,7 @@
|
|||
row.test.request.url = this.api.url;
|
||||
row.test.request.path = this.api.path;
|
||||
row.projectId = this.api.projectId;
|
||||
row.apiDelimitId = this.api.id;
|
||||
row.apiDelimitId = row.apiDelimitId || this.api.id;
|
||||
row.request = row.test.request;
|
||||
let jmx = row.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
|
@ -313,19 +386,17 @@
|
|||
let hasEnvironment = false;
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === this.api.environmentId) {
|
||||
this.api.environment = this.environments[i];
|
||||
this.environment = this.environments[i];
|
||||
hasEnvironment = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasEnvironment) {
|
||||
this.api.environmentId = '';
|
||||
this.api.environment = undefined;
|
||||
this.environment = undefined;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.api.environmentId = '';
|
||||
this.api.environment = undefined;
|
||||
this.environment = undefined;
|
||||
}
|
||||
},
|
||||
openEnvironmentConfig() {
|
||||
|
@ -338,7 +409,7 @@
|
|||
environmentChange(value) {
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === value) {
|
||||
this.api.environment = this.environments[i];
|
||||
this.environment = this.environments[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -347,7 +418,7 @@
|
|||
this.getEnvironments();
|
||||
},
|
||||
selectTestCase(item, $event) {
|
||||
if (item.type === "create") {
|
||||
if (item.type === "create" || !this.loaded) {
|
||||
return;
|
||||
}
|
||||
if ($event.currentTarget.className.indexOf('is-selected') > 0) {
|
||||
|
@ -413,6 +484,19 @@
|
|||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.tip {
|
||||
padding: 3px 5px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #783887;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.environment-button {
|
||||
margin-left: 20px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.is-selected {
|
||||
background: #EFF7FF;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<script>
|
||||
import MsAddCompleteHttpApi from "./complete/AddCompleteHttpApi";
|
||||
import {RequestFactory, Test} from "../model/ScenarioModel";
|
||||
import {RequestFactory, ResponseFactory, Test, Body} from "../model/ApiTestModel";
|
||||
import {getUUID} from "@/common/js/utils";
|
||||
|
||||
export default {
|
||||
|
@ -30,7 +30,13 @@
|
|||
},
|
||||
created() {
|
||||
this.test = new Test({
|
||||
request: this.currentApi.request != null ? new RequestFactory(JSON.parse(this.currentApi.request)) : null
|
||||
request: this.currentApi.request != null ? new RequestFactory(JSON.parse(this.currentApi.request)) : null,
|
||||
response: this.currentApi.response != null ? new ResponseFactory(JSON.parse(this.currentApi.response)) : {
|
||||
headers: [],
|
||||
body: new Body(),
|
||||
statusCode: [],
|
||||
type: "HTTP"
|
||||
}
|
||||
});
|
||||
if (this.currentApi != null && this.currentApi.id != null) {
|
||||
this.reqUrl = "/api/delimit/update";
|
||||
|
@ -41,27 +47,34 @@
|
|||
},
|
||||
methods: {
|
||||
runTest(data) {
|
||||
if (this.editApi(data) === true) {
|
||||
this.$emit('runTest', data);
|
||||
}
|
||||
},
|
||||
saveApi(data) {
|
||||
if (this.editApi(data) === true) {
|
||||
this.$emit('saveApi', data);
|
||||
}
|
||||
},
|
||||
editApi(data) {
|
||||
data.projectId = this.currentProject.id;
|
||||
data.request = data.test.request;
|
||||
let bodyFiles = this.getBodyUploadFiles(data);
|
||||
let jmx = data.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
|
||||
this.$fileUpload(this.reqUrl, file, bodyFiles, data, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.reqUrl = "/api/delimit/update";
|
||||
this.$emit('runTest', data);
|
||||
});
|
||||
},
|
||||
|
||||
saveApi(data) {
|
||||
data.projectId = this.currentProject.id;
|
||||
data.request = data.test.request;
|
||||
data.response = data.test.response;
|
||||
let bodyFiles = this.getBodyUploadFiles(data);
|
||||
let jmx = data.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(this.reqUrl, file, bodyFiles, data, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.reqUrl = "/api/delimit/update";
|
||||
return true;
|
||||
this.$emit('saveApi', data);
|
||||
});
|
||||
|
||||
},
|
||||
getBodyUploadFiles(data) {
|
||||
let bodyUploadFiles = [];
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</span>
|
||||
<div class="kv-row" v-for="(item, index) in items" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col class="kv-checkbox">
|
||||
<el-col class="kv-checkbox" v-if="isShowEnable">
|
||||
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
|
||||
:disabled="isReadOnly"/>
|
||||
</el-col>
|
||||
|
@ -34,7 +34,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue} from "../model/ScenarioModel";
|
||||
import {KeyValue} from "../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiKeyValue",
|
||||
|
@ -42,6 +42,10 @@
|
|||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
isShowEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
description: String,
|
||||
items: Array,
|
||||
isReadOnly: {
|
||||
|
@ -51,8 +55,7 @@
|
|||
suggestions: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
},
|
||||
computed: {
|
||||
keyText() {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
:label="$t('api_test.delimit.api_principal')"
|
||||
show-overflow-tooltip/>
|
||||
|
||||
<el-table-column width="200" :label="$t('api_test.delimit.api_last_time')" prop="updateTime">
|
||||
<el-table-column width="160" :label="$t('api_test.delimit.api_last_time')" prop="updateTime">
|
||||
<template v-slot:default="scope">
|
||||
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
|
||||
</template>
|
||||
|
@ -76,8 +76,7 @@
|
|||
show-overflow-tooltip/>
|
||||
|
||||
|
||||
<el-table-column
|
||||
:label="$t('commons.operating')" min-width="100">
|
||||
<el-table-column :label="$t('commons.operating')" min-width="130" align="center">
|
||||
<template v-slot:default="scope">
|
||||
<el-button type="text" @click="editApi(scope.row)">编辑</el-button>
|
||||
<el-button type="text" @click="handleTestCase(scope.row)">用例</el-button>
|
||||
|
@ -92,7 +91,7 @@
|
|||
</el-card>
|
||||
|
||||
<ms-bottom-container v-bind:enableAsideHidden="isHide">
|
||||
<ms-api-case-list @apiCaseClose="apiCaseClose" :api="selectApi" :current-project="currentProject"/>
|
||||
<ms-api-case-list @apiCaseClose="apiCaseClose" @refresh="initApiTable" :api="selectApi" :current-project="currentProject"/>
|
||||
</ms-bottom-container>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -7,16 +7,12 @@
|
|||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
|
||||
<el-col class="kv-checkbox">
|
||||
<input type="checkbox" v-if="!isDisable(index)" v-model="item.enable"
|
||||
:disabled="isReadOnly"/>
|
||||
</el-col>
|
||||
<el-col>
|
||||
|
||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
||||
@change="change" :placeholder="keyText" show-word-limit>
|
||||
<template v-slot:prepend>
|
||||
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type" @change="typeChange(item)">
|
||||
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type"
|
||||
@change="typeChange(item)">
|
||||
<el-option value="text"/>
|
||||
<el-option value="file"/>
|
||||
</el-select>
|
||||
|
@ -27,6 +23,13 @@
|
|||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col class="kv-select">
|
||||
<el-select v-model="item.required" size="small">
|
||||
<el-option v-for="req in requireds" :key="req.id" :label="req.name" :value="req.id"/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="item.type !== 'file'">
|
||||
<el-autocomplete
|
||||
:disabled="isReadOnly"
|
||||
|
@ -42,12 +45,22 @@
|
|||
</el-autocomplete>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<el-input v-model="item.description" size="small" maxlength="200"
|
||||
:placeholder="$t('commons.description')" show-word-limit>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="item.type === 'file'">
|
||||
<ms-api-body-file-upload :parameter="item"/>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="type === 'body'">
|
||||
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small" maxlength="100"
|
||||
<el-col v-if="type === 'body'" class="kv-select">
|
||||
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"
|
||||
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
|
||||
</el-input>
|
||||
</el-col>
|
||||
|
@ -56,6 +69,8 @@
|
|||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||
:disabled="isDisable(index) || isReadOnly"/>
|
||||
</el-col>
|
||||
|
||||
|
||||
</el-row>
|
||||
</div>
|
||||
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
|
||||
|
@ -65,10 +80,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue, Scenario} from "../model/ScenarioModel";
|
||||
import {KeyValue, Scenario} from "../model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
||||
import MsApiVariableAdvance from "./ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "./body/ApiBodyFileUpload";
|
||||
import {REQUIRED} from "../model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
|
@ -94,6 +110,7 @@
|
|||
data() {
|
||||
return {
|
||||
currentItem: null,
|
||||
requireds: REQUIRED
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -124,7 +141,12 @@
|
|||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue({type: 'text', enable: true, uuid: this.uuid(), contentType: 'text/plain'}));
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
|
@ -170,7 +192,13 @@
|
|||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
|
||||
this.parameters.push(new KeyValue( {type: 'text', enable: true, uuid: this.uuid(), contentType: 'text/plain'}));
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
required: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -189,6 +217,10 @@
|
|||
width: 60px;
|
||||
}
|
||||
|
||||
.kv-select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {calculate, Scenario} from "../model/ScenarioModel";
|
||||
import {calculate, Scenario} from "../model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<script>
|
||||
|
||||
import {Duration} from "../../model/ScenarioModel";
|
||||
import {Duration} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionDuration",
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {JSONPath} from "../../model/ScenarioModel";
|
||||
import {JSONPath} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionJsonPath",
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {ASSERTION_REGEX_SUBJECT, Regex} from "../../model/ScenarioModel";
|
||||
import {ASSERTION_REGEX_SUBJECT, Regex} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionRegex",
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {Regex, ASSERTION_REGEX_SUBJECT} from "../../model/ScenarioModel";
|
||||
import {Regex, ASSERTION_REGEX_SUBJECT} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAssertionText",
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
<div class="assertion-add">
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="4">
|
||||
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type" :placeholder="$t('api_test.request.assertions.select_type')"
|
||||
<el-select :disabled="isReadOnly" class="assertion-item" v-model="type"
|
||||
:placeholder="$t('api_test.request.assertions.select_type')"
|
||||
size="small">
|
||||
<el-option :label="$t('api_test.request.assertions.text')" :value="options.TEXT"/>
|
||||
<el-option :label="$t('api_test.request.assertions.regex')" :value="options.REGEX"/>
|
||||
|
@ -12,9 +13,12 @@
|
|||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="20">
|
||||
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT" :callback="after"/>
|
||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX" :callback="after"/>
|
||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath" v-if="type === options.JSON_PATH" :callback="after"/>
|
||||
<ms-api-assertion-text :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.TEXT"
|
||||
:callback="after"/>
|
||||
<ms-api-assertion-regex :is-read-only="isReadOnly" :list="assertions.regex" v-if="type === options.REGEX"
|
||||
:callback="after"/>
|
||||
<ms-api-assertion-json-path :is-read-only="isReadOnly" :list="assertions.jsonPath"
|
||||
v-if="type === options.JSON_PATH" :callback="after"/>
|
||||
<ms-api-assertion-duration :is-read-only="isReadOnly" v-model="time" :duration="assertions.duration"
|
||||
v-if="type === options.DURATION" :callback="after"/>
|
||||
<el-button v-if="!type" :disabled="true" type="primary" size="small">Add</el-button>
|
||||
|
@ -24,16 +28,17 @@
|
|||
|
||||
<div>
|
||||
<el-row :gutter="10" class="json-path-suggest-button">
|
||||
<el-button size="small" type="primary" @click="suggestJsonOpen">
|
||||
{{$t('api_test.request.assertions.json_path_suggest')}}
|
||||
</el-button>
|
||||
<el-button size="small" type="danger" @click="clearJson">
|
||||
{{$t('api_test.request.assertions.json_path_clear')}}
|
||||
</el-button>
|
||||
<el-link size="small" type="primary" @click="suggestJsonOpen">
|
||||
{{$t('api_test.request.assertions.json_path_suggest')}}
|
||||
</el-link>
|
||||
<el-link size="small" type="danger" @click="clearJson" style="margin-left: 20px">
|
||||
{{$t('api_test.request.assertions.json_path_clear')}}
|
||||
</el-link>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request" ref="jsonpathSuggestList"/>
|
||||
<ms-api-jsonpath-suggest-list @addJsonpathSuggest="addJsonpathSuggest" :request="request"
|
||||
ref="jsonpathSuggestList"/>
|
||||
|
||||
<ms-api-assertions-edit :is-read-only="isReadOnly" :assertions="assertions"/>
|
||||
</div>
|
||||
|
@ -43,7 +48,7 @@
|
|||
import MsApiAssertionText from "./ApiAssertionText";
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath} from "../../model/ScenarioModel";
|
||||
import {ASSERTION_TYPE, Assertions, HttpRequest, JSONPath} from "../../model/ApiTestModel";
|
||||
import MsApiAssertionsEdit from "./ApiAssertionsEdit";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
import MsApiJsonpathSuggestList from "./ApiJsonpathSuggestList";
|
||||
|
@ -54,7 +59,8 @@
|
|||
components: {
|
||||
MsApiJsonpathSuggestList,
|
||||
MsApiAssertionJsonPath,
|
||||
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText},
|
||||
MsApiAssertionsEdit, MsApiAssertionDuration, MsApiAssertionRegex, MsApiAssertionText
|
||||
},
|
||||
|
||||
props: {
|
||||
assertions: Assertions,
|
||||
|
@ -86,11 +92,11 @@
|
|||
},
|
||||
addJsonpathSuggest(jsonPathList) {
|
||||
jsonPathList.forEach(jsonPath => {
|
||||
let jsonItem = new JSONPath();
|
||||
jsonItem.expression = jsonPath.json_path;
|
||||
jsonItem.expect = jsonPath.json_value;
|
||||
jsonItem.setJSONPathDescription();
|
||||
this.assertions.jsonPath.push(jsonItem);
|
||||
let jsonItem = new JSONPath();
|
||||
jsonItem.expression = jsonPath.json_path;
|
||||
jsonItem.expect = jsonPath.json_value;
|
||||
jsonItem.setJSONPathDescription();
|
||||
this.assertions.jsonPath.push(jsonItem);
|
||||
});
|
||||
},
|
||||
clearJson() {
|
||||
|
@ -136,7 +142,10 @@
|
|||
}
|
||||
|
||||
.json-path-suggest-button {
|
||||
text-align: right;
|
||||
text-align: left;
|
||||
margin-left: 20px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<script>
|
||||
import MsApiAssertionRegex from "./ApiAssertionRegex";
|
||||
import MsApiAssertionDuration from "./ApiAssertionDuration";
|
||||
import {Assertions} from "../../model/ScenarioModel";
|
||||
import {Assertions} from "../../model/ApiTestModel";
|
||||
import MsApiAssertionJsonPath from "./ApiAssertionJsonPath";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
|
||||
<script>
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {HttpRequest} from "../../model/ScenarioModel";
|
||||
import {HttpRequest} from "../../model/ApiTestModel";
|
||||
export default {
|
||||
name: "MsApiJsonpathSuggestList",
|
||||
components: {MsDialogFooter},
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {HttpRequest} from "../../model/ScenarioModel";
|
||||
import {HttpRequest} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsApiAuthConfig",
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
<script>
|
||||
import MsDialogFooter from "../../../../common/components/MsDialogFooter";
|
||||
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
|
||||
import {Test} from "../../model/ScenarioModel"
|
||||
import {Test} from "../../model/ApiTestModel"
|
||||
import {REQ_METHOD} from "../../model/JsonData";
|
||||
import {getCurrentUser} from "../../../../../../common/js/utils";
|
||||
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
<template>
|
||||
<div>
|
||||
<span class="kv-description" v-if="description">
|
||||
{{ description }}
|
||||
</span>
|
||||
|
||||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col>
|
||||
<el-input v-model="item.description" size="small" maxlength="200"
|
||||
:placeholder="$t('commons.description')" show-word-limit>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<ms-api-body-file-upload :parameter="item"/>
|
||||
</el-col>
|
||||
|
||||
<el-col class="kv-delete">
|
||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||
:disabled="isDisable(index) || isReadOnly"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
|
||||
:parameters="parameters"
|
||||
:current-item="currentItem"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue, Scenario} from "../../model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
||||
import MsApiVariableAdvance from "../ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "../body/ApiBodyFileUpload";
|
||||
import {REQUIRED} from "../../model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
description: String,
|
||||
parameters: Array,
|
||||
rest: Array,
|
||||
environment: Object,
|
||||
scenario: Scenario,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
suggestions: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentItem: null,
|
||||
requireds: REQUIRED
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keyText() {
|
||||
return this.keyPlaceholder || this.$t("api_test.key");
|
||||
},
|
||||
valueText() {
|
||||
return this.valuePlaceholder || this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
// 移除整行输入控件及内容
|
||||
this.parameters.splice(index, 1);
|
||||
this.$emit('change', this.parameters);
|
||||
},
|
||||
change: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.parameters.forEach((item, index) => {
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.parameters.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.parameters.length - 1 === index;
|
||||
},
|
||||
querySearch(queryString, cb) {
|
||||
let suggestions = this.suggestions;
|
||||
let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions;
|
||||
cb(results);
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return (restaurant) => {
|
||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
||||
};
|
||||
},
|
||||
funcSearch(queryString, cb) {
|
||||
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
|
||||
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
|
||||
// 调用 callback 返回建议列表的数据
|
||||
cb(results);
|
||||
},
|
||||
funcFilter(queryString) {
|
||||
return (func) => {
|
||||
return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
|
||||
};
|
||||
},
|
||||
uuid: function () {
|
||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
||||
},
|
||||
advanced(item) {
|
||||
this.$refs.variableAdvance.open();
|
||||
this.currentItem = item;
|
||||
},
|
||||
typeChange(item) {
|
||||
if (item.type === 'file') {
|
||||
item.contentType = 'application/octet-stream';
|
||||
} else {
|
||||
item.contentType = 'text/plain';
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
required: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.kv-select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.advanced-item-value >>> .el-dialog__body {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.kv-type {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
color: #1E90FF;
|
||||
}
|
||||
</style>
|
|
@ -1,66 +1,87 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-radio-group v-model="body.type" size="mini">
|
||||
<el-radio-button :disabled="isReadOnly" :label="type.KV">
|
||||
<el-radio :disabled="isReadOnly" :label="type.KV">
|
||||
{{ $t('api_test.delimit.request.body_form_data') }}
|
||||
</el-radio-button>
|
||||
</el-radio>
|
||||
|
||||
<el-radio-button :disabled="isReadOnly" :label="type.RAW">
|
||||
{{ $t('api_test.delimit.request.body_raw') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button :disabled="isReadOnly" :label="type.WWW_FORM">
|
||||
<el-radio :disabled="isReadOnly" :label="type.WWW_FORM">
|
||||
{{ $t('api_test.delimit.request.body_x_www_from_urlencoded') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button :disabled="isReadOnly" :label="type.BINARY">
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.JSON">
|
||||
{{ $t('api_test.delimit.request.body_json') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.XML">
|
||||
{{ $t('api_test.delimit.request.body_xml') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.RAW">
|
||||
{{ $t('api_test.delimit.request.body_raw') }}
|
||||
</el-radio>
|
||||
|
||||
<el-radio :disabled="isReadOnly" :label="type.BINARY">
|
||||
{{ $t('api_test.delimit.request.body_binary') }}
|
||||
</el-radio-button>
|
||||
</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
<ms-dropdown :default-command="body.format" v-if="body.type == 'Raw'" :commands="modes" @command="modeChange"/>
|
||||
|
||||
<ms-api-variable :is-read-only="isReadOnly"
|
||||
:parameters="body.kvs"
|
||||
:environment="environment"
|
||||
:scenario="scenario"
|
||||
:extract="extract"
|
||||
type="body"
|
||||
:description="$t('api_test.request.parameters_desc')"
|
||||
v-if="body.isKV()"/>
|
||||
|
||||
<ms-api-variable :is-read-only="isReadOnly"
|
||||
:parameters="body.kvs"
|
||||
:environment="environment"
|
||||
:scenario="scenario"
|
||||
:extract="extract"
|
||||
type="body"
|
||||
:description="$t('api_test.request.parameters_desc')"
|
||||
v-if="body.type == 'WWW_Form'"/>
|
||||
<ms-api-from-url-variable :is-read-only="isReadOnly"
|
||||
:parameters="body.fromUrlencoded"
|
||||
:environment="environment"
|
||||
type="body"
|
||||
v-if="body.type == 'WWW_FORM'"/>
|
||||
|
||||
<div class="body-raw" v-if="body.type == 'JSON'">
|
||||
<ms-json-code-edit @json-change="jsonChange" @onError="jsonError" :value="body.json" ref="jsonCodeEdit"/>
|
||||
</div>
|
||||
|
||||
<div class="body-raw" v-if="body.type == 'XML'">
|
||||
<ms-code-edit :mode="body.format" :read-only="isReadOnly" :data.sync="body.xml" :modes="modes" ref="codeEdit"/>
|
||||
</div>
|
||||
|
||||
<ms-api-variable :is-read-only="isReadOnly"
|
||||
:parameters="body.kvs"
|
||||
:environment="environment"
|
||||
:scenario="scenario"
|
||||
:extract="extract"
|
||||
type="body"
|
||||
:description="$t('api_test.request.parameters_desc')"
|
||||
v-if="body.type == 'BINARY'"/>
|
||||
|
||||
<div class="body-raw" v-if="body.type == 'Raw'">
|
||||
<ms-code-edit :mode="body.format" :read-only="isReadOnly" :data.sync="body.raw" :modes="modes" ref="codeEdit"/>
|
||||
</div>
|
||||
|
||||
<ms-api-binary-variable :is-read-only="isReadOnly"
|
||||
:parameters="body.binary"
|
||||
:environment="environment"
|
||||
type="body"
|
||||
v-if="body.type == 'BINARY'"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../../model/ScenarioModel";
|
||||
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../../model/ApiTestModel";
|
||||
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||
import MsJsonCodeEdit from "../../../../common/components/MsJsonCodeEdit";
|
||||
|
||||
import MsDropdown from "../../../../common/components/MsDropdown";
|
||||
import MsApiVariable from "../ApiVariable";
|
||||
import MsApiBinaryVariable from "./ApiBinaryVariable";
|
||||
import MsApiFromUrlVariable from "./ApiFromUrlVariable";
|
||||
|
||||
export default {
|
||||
name: "MsApiBody",
|
||||
components: {MsApiVariable, MsDropdown, MsCodeEdit, MsApiKeyValue},
|
||||
components: {
|
||||
MsApiVariable,
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsApiKeyValue,
|
||||
MsApiBinaryVariable,
|
||||
MsApiFromUrlVariable,
|
||||
MsJsonCodeEdit
|
||||
},
|
||||
props: {
|
||||
body: Body,
|
||||
scenario: Scenario,
|
||||
|
@ -82,6 +103,12 @@
|
|||
methods: {
|
||||
modeChange(mode) {
|
||||
this.body.format = mode;
|
||||
},
|
||||
jsonChange(json) {
|
||||
this.body.json = json;
|
||||
},
|
||||
jsonError(e) {
|
||||
this.$error(e);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -108,7 +135,7 @@
|
|||
|
||||
.body-raw {
|
||||
padding: 15px 0;
|
||||
height: 300px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.el-dropdown {
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
<div class="upload-default">
|
||||
<i class="el-icon-plus"/>
|
||||
</div>
|
||||
<div class="upload-item" slot="file" slot-scope="{file}" >
|
||||
<div class="upload-item" slot="file" slot-scope="{file}">
|
||||
<span>{{file.file ? file.file.name : file.name}}</span>
|
||||
<span class="el-upload-list__item-actions">
|
||||
<!--<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleDownload(file)">-->
|
||||
<!--<i class="el-icon-download"/>-->
|
||||
<!--</span>-->
|
||||
<!--<i class="el-icon-download"/>-->
|
||||
<!--</span>-->
|
||||
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
|
||||
<i class="el-icon-delete"/>
|
||||
</span>
|
||||
|
@ -29,50 +29,50 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsApiBodyFileUpload",
|
||||
data() {
|
||||
return {
|
||||
disabled: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
parameter: Object,
|
||||
default() {
|
||||
return {}
|
||||
export default {
|
||||
name: "MsApiBodyFileUpload",
|
||||
data() {
|
||||
return {
|
||||
disabled: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
parameter: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRemove(file) {
|
||||
this.$refs.upload.handleRemove(file);
|
||||
for (let i = 0; i < this.parameter.files.length; i++) {
|
||||
let fileName = file.file ? file.file.name : file.name;
|
||||
let paramFileName = this.parameter.files[i].file ?
|
||||
this.parameter.files[i].file.name : this.parameter.files[i].name;
|
||||
if (fileName === paramFileName) {
|
||||
this.parameter.files.splice(i, 1);
|
||||
this.$refs.upload.handleRemove(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRemove(file) {
|
||||
this.$refs.upload.handleRemove(file);
|
||||
for (let i = 0; i < this.parameter.files.length; i++) {
|
||||
let fileName = file.file ? file.file.name : file.name;
|
||||
let paramFileName = this.parameter.files[i].file ?
|
||||
this.parameter.files[i].file.name : this.parameter.files[i].name;
|
||||
if (fileName === paramFileName) {
|
||||
this.parameter.files.splice(i, 1);
|
||||
this.$refs.upload.handleRemove(file);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
upload(file) {
|
||||
this.parameter.files.push(file);
|
||||
},
|
||||
uploadValidate(file) {
|
||||
if (file.size / 1024 / 1024 > 500) {
|
||||
this.$warning(this.$t('api_test.request.body_upload_limit_size'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
upload(file) {
|
||||
this.parameter.files.push(file);
|
||||
},
|
||||
created() {
|
||||
if (!this.parameter.files) {
|
||||
this.parameter.files = [];
|
||||
uploadValidate(file) {
|
||||
if (file.size / 1024 / 1024 > 500) {
|
||||
this.$warning(this.$t('api_test.request.body_upload_limit_size'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (!this.parameter.files) {
|
||||
this.parameter.files = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
@ -82,12 +82,12 @@
|
|||
}
|
||||
|
||||
.api-body-upload >>> .el-upload {
|
||||
height: 32px;
|
||||
height: 30px;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.upload-default {
|
||||
min-height: 32px;
|
||||
min-height: 30px;
|
||||
width: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
@ -97,7 +97,7 @@
|
|||
}
|
||||
|
||||
.api-body-upload >>> .el-upload-list__item {
|
||||
height: 32px;
|
||||
height: 30px;
|
||||
width: auto;
|
||||
padding: 6px;
|
||||
margin-bottom: 0px;
|
||||
|
@ -107,7 +107,7 @@
|
|||
}
|
||||
|
||||
.api-body-upload {
|
||||
min-height: 32px;
|
||||
min-height: 30px;
|
||||
border: 1px solid #EBEEF5;
|
||||
padding: 2px;
|
||||
border-radius: 4px;
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
<template>
|
||||
<div>
|
||||
<span class="kv-description" v-if="description">
|
||||
{{ description }}
|
||||
</span>
|
||||
|
||||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
|
||||
<el-col>
|
||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
||||
@change="change" :placeholder="keyText" show-word-limit>
|
||||
<template v-slot:prepend>
|
||||
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type"
|
||||
@change="typeChange(item)">
|
||||
<el-option value="text"/>
|
||||
<el-option value="file"/>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col class="kv-select">
|
||||
<el-select v-model="item.required" size="small">
|
||||
<el-option v-for="req in requireds" :key="req.id" :label="req.name" :value="req.id"/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="item.type !== 'file'">
|
||||
<el-autocomplete
|
||||
:disabled="isReadOnly"
|
||||
size="small"
|
||||
class="input-with-autocomplete"
|
||||
v-model="item.value"
|
||||
:fetch-suggestions="funcSearch"
|
||||
:placeholder="valueText"
|
||||
value-key="name"
|
||||
highlight-first-item
|
||||
@select="change">
|
||||
<i slot="suffix" class="el-input__icon el-icon-edit pointer" @click="advanced(item)"></i>
|
||||
</el-autocomplete>
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<el-input v-model="item.description" size="small" maxlength="200"
|
||||
:placeholder="$t('commons.description')" show-word-limit>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="item.type === 'file'">
|
||||
<ms-api-body-file-upload :parameter="item"/>
|
||||
</el-col>
|
||||
|
||||
<el-col v-if="type === 'body'" class="kv-select">
|
||||
<el-input :disabled="isReadOnly" v-model="item.contentType" size="small"
|
||||
@change="change" :placeholder="$t('api_test.request.content_type')" show-word-limit>
|
||||
</el-input>
|
||||
</el-col>
|
||||
|
||||
<el-col class="kv-delete">
|
||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||
:disabled="isDisable(index) || isReadOnly"/>
|
||||
</el-col>
|
||||
|
||||
|
||||
</el-row>
|
||||
</div>
|
||||
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
|
||||
:parameters="parameters"
|
||||
:current-item="currentItem"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue, Scenario} from "../../model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
||||
import MsApiVariableAdvance from "../ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "../body/ApiBodyFileUpload";
|
||||
import {REQUIRED} from "../../model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
description: String,
|
||||
parameters: Array,
|
||||
rest: Array,
|
||||
environment: Object,
|
||||
scenario: Scenario,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
suggestions: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentItem: null,
|
||||
requireds: REQUIRED
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keyText() {
|
||||
return this.keyPlaceholder || this.$t("api_test.key");
|
||||
},
|
||||
valueText() {
|
||||
return this.valuePlaceholder || this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
// 移除整行输入控件及内容
|
||||
this.parameters.splice(index, 1);
|
||||
this.$emit('change', this.parameters);
|
||||
},
|
||||
change: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.parameters.forEach((item, index) => {
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.parameters.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.parameters.length - 1 === index;
|
||||
},
|
||||
querySearch(queryString, cb) {
|
||||
let suggestions = this.suggestions;
|
||||
let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions;
|
||||
cb(results);
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return (restaurant) => {
|
||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
||||
};
|
||||
},
|
||||
funcSearch(queryString, cb) {
|
||||
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
|
||||
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
|
||||
// 调用 callback 返回建议列表的数据
|
||||
cb(results);
|
||||
},
|
||||
funcFilter(queryString) {
|
||||
return (func) => {
|
||||
return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
|
||||
};
|
||||
},
|
||||
uuid: function () {
|
||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
||||
},
|
||||
advanced(item) {
|
||||
this.$refs.variableAdvance.open();
|
||||
this.currentItem = item;
|
||||
},
|
||||
typeChange(item) {
|
||||
if (item.type === 'file') {
|
||||
item.contentType = 'application/octet-stream';
|
||||
} else {
|
||||
item.contentType = 'text/plain';
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
|
||||
this.parameters.push(new KeyValue({type: 'text', enable: true, required:true,uuid: this.uuid(), contentType: 'text/plain'}));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.kv-select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.advanced-item-value >>> .el-dialog__body {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.kv-type {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
color: #1E90FF;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,202 @@
|
|||
<template>
|
||||
<div>
|
||||
<span class="kv-description" v-if="description">
|
||||
{{ description }}
|
||||
</span>
|
||||
|
||||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||
<el-col>
|
||||
<el-input v-model="item.description" size="small" maxlength="200"
|
||||
:placeholder="$t('commons.description')" show-word-limit>
|
||||
</el-input>
|
||||
|
||||
<el-autocomplete :disabled="isReadOnly" v-if="suggestions" v-model="item.name" size="small"
|
||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||
|
||||
</el-col>
|
||||
|
||||
<el-col>
|
||||
<ms-api-body-file-upload :parameter="item"/>
|
||||
</el-col>
|
||||
|
||||
<el-col class="kv-delete">
|
||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||
:disabled="isDisable(index) || isReadOnly"/>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
<ms-api-variable-advance ref="variableAdvance" :environment="environment" :scenario="scenario"
|
||||
:parameters="parameters"
|
||||
:current-item="currentItem"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {KeyValue, Scenario} from "../../model/ApiTestModel";
|
||||
import {JMETER_FUNC, MOCKJS_FUNC} from "@/common/js/constants";
|
||||
import MsApiVariableAdvance from "../ApiVariableAdvance";
|
||||
import MsApiBodyFileUpload from "../body/ApiBodyFileUpload";
|
||||
import {REQUIRED} from "../../model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "MsApiVariable",
|
||||
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||
props: {
|
||||
keyPlaceholder: String,
|
||||
valuePlaceholder: String,
|
||||
description: String,
|
||||
parameters: Array,
|
||||
rest: Array,
|
||||
environment: Object,
|
||||
scenario: Scenario,
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
suggestions: Array
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentItem: null,
|
||||
requireds: REQUIRED
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
keyText() {
|
||||
return this.keyPlaceholder || this.$t("api_test.key");
|
||||
},
|
||||
valueText() {
|
||||
return this.valuePlaceholder || this.$t("api_test.value");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
remove: function (index) {
|
||||
// 移除整行输入控件及内容
|
||||
this.parameters.splice(index, 1);
|
||||
this.$emit('change', this.parameters);
|
||||
},
|
||||
change: function () {
|
||||
let isNeedCreate = true;
|
||||
let removeIndex = -1;
|
||||
this.parameters.forEach((item, index) => {
|
||||
if (!item.name && !item.value) {
|
||||
// 多余的空行
|
||||
if (index !== this.parameters.length - 1) {
|
||||
removeIndex = index;
|
||||
}
|
||||
// 没有空行,需要创建空行
|
||||
isNeedCreate = false;
|
||||
}
|
||||
});
|
||||
if (isNeedCreate) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
this.$emit('change', this.parameters);
|
||||
// TODO 检查key重复
|
||||
},
|
||||
isDisable: function (index) {
|
||||
return this.parameters.length - 1 === index;
|
||||
},
|
||||
querySearch(queryString, cb) {
|
||||
let suggestions = this.suggestions;
|
||||
let results = queryString ? suggestions.filter(this.createFilter(queryString)) : suggestions;
|
||||
cb(results);
|
||||
},
|
||||
createFilter(queryString) {
|
||||
return (restaurant) => {
|
||||
return (restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0);
|
||||
};
|
||||
},
|
||||
funcSearch(queryString, cb) {
|
||||
let funcs = MOCKJS_FUNC.concat(JMETER_FUNC);
|
||||
let results = queryString ? funcs.filter(this.funcFilter(queryString)) : funcs;
|
||||
// 调用 callback 返回建议列表的数据
|
||||
cb(results);
|
||||
},
|
||||
funcFilter(queryString) {
|
||||
return (func) => {
|
||||
return (func.name.toLowerCase().indexOf(queryString.toLowerCase()) > -1);
|
||||
};
|
||||
},
|
||||
uuid: function () {
|
||||
return (((1 + Math.random()) * 0x100000) | 0).toString(16).substring(1);
|
||||
},
|
||||
advanced(item) {
|
||||
this.$refs.variableAdvance.open();
|
||||
this.currentItem = item;
|
||||
},
|
||||
typeChange(item) {
|
||||
if (item.type === 'file') {
|
||||
item.contentType = 'application/octet-stream';
|
||||
} else {
|
||||
item.contentType = 'text/plain';
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.parameters.length === 0 || this.parameters[this.parameters.length - 1].name) {
|
||||
this.parameters.push(new KeyValue({
|
||||
type: 'text',
|
||||
enable: true,
|
||||
required: true,
|
||||
uuid: this.uuid(),
|
||||
contentType: 'text/plain'
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.kv-description {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.kv-row {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.kv-delete {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.kv-select {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.el-autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kv-checkbox {
|
||||
width: 20px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.advanced-item-value >>> .el-dialog__body {
|
||||
padding: 15px 25px;
|
||||
}
|
||||
|
||||
.el-row {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.kv-type {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
color: #1E90FF;
|
||||
}
|
||||
</style>
|
|
@ -9,8 +9,7 @@
|
|||
<el-button type="primary" size="small" @click="runTest">{{$t('commons.test')}}</el-button>
|
||||
</div>
|
||||
<br/>
|
||||
<div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div>
|
||||
<br/>
|
||||
<p class="tip">{{$t('test_track.plan_view.base_info')}} </p>
|
||||
<el-form-item :label="$t('commons.name')" prop="name">
|
||||
<el-input class="ms-http-input" size="small" v-model="httpForm.name"/>
|
||||
</el-form-item>
|
||||
|
@ -59,23 +58,21 @@
|
|||
:rows="2" size="small"/>
|
||||
</el-form-item>
|
||||
|
||||
|
||||
<div style="font-size: 16px;color: #333333;padding-top: 30px">{{$t('api_test.delimit.request.req_param')}}</div>
|
||||
<br/>
|
||||
<!-- HTTP 请求参数 -->
|
||||
<ms-api-request-form :request="test.request"/>
|
||||
|
||||
<p class="tip">{{$t('api_test.delimit.request.req_param')}} </p>
|
||||
<ms-api-request-form :request="test.request" :isShowEnable="isShowEnable"/>
|
||||
</el-form>
|
||||
<div style="font-size: 16px;color: #333333 ;padding-top: 30px">{{$t('api_test.delimit.request.res_param')}}</div>
|
||||
<br/>
|
||||
<ms-response-text :response="responseData"></ms-response-text>
|
||||
|
||||
<!-- 响应内容-->
|
||||
<p class="tip">{{$t('api_test.delimit.request.res_param')}} </p>
|
||||
<ms-response-text :response="test.response"></ms-response-text>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiRequestForm from "../request/ApiRequestForm";
|
||||
import MsResponseText from "../../../report/components/ResponseText";
|
||||
import MsResponseText from "../response/ResponseText";
|
||||
import {WORKSPACE_ID} from '../../../../../../common/js/constants';
|
||||
import {REQ_METHOD, API_STATUS} from "../../model/JsonData";
|
||||
|
||||
|
@ -95,8 +92,8 @@
|
|||
status: [{required: true, message: this.$t('commons.please_select'), trigger: 'change'}],
|
||||
},
|
||||
httpForm: {},
|
||||
isShowEnable: false,
|
||||
maintainerOptions: [],
|
||||
responseData: {},
|
||||
currentModule: {},
|
||||
reqOptions: REQ_METHOD,
|
||||
options: API_STATUS
|
||||
|
@ -168,6 +165,14 @@
|
|||
width: 500px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
padding: 3px 5px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #783887;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.ms-http-textarea {
|
||||
width: 500px;
|
||||
}
|
||||
|
|
|
@ -2,14 +2,13 @@
|
|||
|
||||
<div class="card-container">
|
||||
<el-card class="card-content">
|
||||
<el-form :model="httpForm" :rules="rules" ref="httpForm" :inline="true" label-position="right">
|
||||
<div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div>
|
||||
<br/>
|
||||
<el-form :model="debugForm" :rules="rules" ref="debugForm" :inline="true" label-position="right">
|
||||
<p class="tip">{{$t('test_track.plan_view.base_info')}} </p>
|
||||
|
||||
<el-form-item :label="$t('api_report.request')" prop="responsible">
|
||||
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="httpForm.url"
|
||||
<el-form-item :label="$t('api_report.request')" prop="url">
|
||||
<el-input :placeholder="$t('api_test.delimit.request.path_all_info')" v-model="debugForm.url"
|
||||
class="ms-http-input" size="small">
|
||||
<el-select v-model="httpForm.path" slot="prepend" style="width: 100px" size="small">
|
||||
<el-select v-model="debugForm.path" slot="prepend" style="width: 100px" size="small">
|
||||
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-input>
|
||||
|
@ -25,52 +24,122 @@
|
|||
</el-dropdown>
|
||||
</el-form-item>
|
||||
|
||||
<div style="font-size: 16px;color: #333333;padding-top: 30px">{{$t('api_test.delimit.request.req_param')}}</div>
|
||||
<br/>
|
||||
<p class="tip">{{$t('api_test.delimit.request.req_param')}} </p>
|
||||
<!-- HTTP 请求参数 -->
|
||||
<ms-api-request-form :request="test.request"/>
|
||||
|
||||
</el-form>
|
||||
<div style="font-size: 16px;color: #333333 ;padding-top: 30px">{{$t('api_test.delimit.request.res_param')}}</div>
|
||||
<br/>
|
||||
<ms-response-text :response="responseData"></ms-response-text>
|
||||
<!-- HTTP 请求返回数据 -->
|
||||
<p class="tip">{{$t('api_test.delimit.request.res_param')}} </p>
|
||||
<ms-request-result-tail v-loading="loading" :response="responseData" ref="debugResult"/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiRequestForm from "../request/ApiRequestForm";
|
||||
import {Test} from "../../model/ScenarioModel";
|
||||
import MsResponseText from "../../../report/components/ResponseText";
|
||||
import {Test} from "../../model/ApiTestModel";
|
||||
import MsResponseResult from "../response/ResponseResult";
|
||||
import MsRequestMetric from "../response/RequestMetric";
|
||||
import {getUUID, getCurrentUser} from "@/common/js/utils";
|
||||
import MsResponseText from "../response/ResponseText";
|
||||
|
||||
|
||||
import {REQ_METHOD} from "../../model/JsonData";
|
||||
import MsRequestResultTail from "../response/RequestResultTail";
|
||||
|
||||
export default {
|
||||
name: "ApiConfig",
|
||||
components: {MsResponseText, MsApiRequestForm},
|
||||
components: {MsRequestResultTail, MsResponseResult, MsApiRequestForm, MsRequestMetric, MsResponseText},
|
||||
data() {
|
||||
return {
|
||||
rules: {
|
||||
path: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
|
||||
url: [{required: true, message: this.$t('api_test.delimit.request.path_info'), trigger: 'blur'}],
|
||||
url: [{required: true, message: this.$t('api_test.delimit.request.path_all_info'), trigger: 'blur'}],
|
||||
},
|
||||
httpForm: {path: REQ_METHOD[0].id},
|
||||
debugForm: {path: REQ_METHOD[0].id},
|
||||
options: [],
|
||||
responseData: {},
|
||||
responseData: {type: 'HTTP', responseResult: {}, subRequestResults: []},
|
||||
loading: false,
|
||||
debugResultId: "",
|
||||
test: new Test(),
|
||||
reqOptions: REQ_METHOD,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugResultId() {
|
||||
this.getResult()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleCommand(e) {
|
||||
if (e === "save_as") {
|
||||
this.saveAs();
|
||||
} else {
|
||||
this.runDebug();
|
||||
}
|
||||
},
|
||||
getBodyUploadFiles(data) {
|
||||
let bodyUploadFiles = [];
|
||||
data.bodyUploadIds = [];
|
||||
let request = data.request;
|
||||
if (request.body) {
|
||||
request.body.kvs.forEach(param => {
|
||||
if (param.files) {
|
||||
param.files.forEach(item => {
|
||||
if (item.file) {
|
||||
let fileId = getUUID().substring(0, 8);
|
||||
item.name = item.file.name;
|
||||
item.id = fileId;
|
||||
data.bodyUploadIds.push(fileId);
|
||||
bodyUploadFiles.push(item.file);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return bodyUploadFiles;
|
||||
},
|
||||
runDebug() {
|
||||
this.$refs['debugForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
let url = "/api/delimit/run/debug";
|
||||
let bodyFiles = this.getBodyUploadFiles(this.test);
|
||||
this.test.request.url = this.debugForm.url;
|
||||
this.test.request.path = this.debugForm.path;
|
||||
this.test.id = getUUID().substring(0, 8);
|
||||
let jmx = this.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(url, file, bodyFiles, this.test, response => {
|
||||
this.debugResultId = response.data;
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
getResult() {
|
||||
if (this.debugResultId) {
|
||||
let url = "/api/delimit/report/get/" + this.debugResultId + "/" + "debug";
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
let testResult = JSON.parse(response.data.content);
|
||||
this.responseData = testResult.requestResults[0];
|
||||
this.loading = false;
|
||||
this.$refs.debugResult.reload();
|
||||
} else {
|
||||
setTimeout(this.getResult, 2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
saveAs() {
|
||||
this.$refs['httpForm'].validate((valid) => {
|
||||
this.$refs['debugForm'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.httpForm.request = JSON.stringify(this.test.request);
|
||||
this.$emit('saveAs', this.httpForm);
|
||||
this.debugForm.request = JSON.stringify(this.test.request);
|
||||
this.debugForm.userId = getCurrentUser().id;
|
||||
this.debugForm.status = "Underway";
|
||||
this.$emit('saveAs', this.debugForm);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
|
@ -86,4 +155,12 @@
|
|||
width: 500px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
padding: 3px 5px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #783887;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {EXTRACT_TYPE, Extract} from "../../model/ScenarioModel";
|
||||
import {EXTRACT_TYPE, Extract} from "../../model/ApiTestModel";
|
||||
import MsApiExtractEdit from "./ApiExtractEdit";
|
||||
import MsApiExtractCommon from "./ApiExtractCommon";
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {EXTRACT_TYPE, ExtractCommon} from "../../model/ScenarioModel";
|
||||
import {EXTRACT_TYPE, ExtractCommon} from "../../model/ApiTestModel";
|
||||
import MsApiVariableInput from "../ApiVariableInput";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import {Extract, EXTRACT_TYPE} from "../../model/ScenarioModel";
|
||||
import {Extract, EXTRACT_TYPE} from "../../model/ApiTestModel";
|
||||
import MsApiExtractCommon from "./ApiExtractCommon";
|
||||
|
||||
export default {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<el-tabs v-model="activeName">
|
||||
<!-- 请求头-->
|
||||
<el-tab-pane :label="$t('api_test.request.headers')" name="headers">
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="true" :suggestions="headerSuggestions"
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions"
|
||||
:items="request.headers"/>
|
||||
</el-tab-pane>
|
||||
|
||||
|
@ -12,13 +12,12 @@
|
|||
<ms-api-variable :is-read-only="isReadOnly"
|
||||
:parameters="request.parameters"
|
||||
:environment="request.environment"
|
||||
:extract="request.extract"
|
||||
:description="$t('api_test.request.parameters_desc')"/>
|
||||
:extract="request.extract"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<!--REST 参数-->
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.rest_param')" name="rest">
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="true" :suggestions="headerSuggestions"
|
||||
<ms-api-key-value :is-read-only="isReadOnly" :isShowEnable="isShowEnable" :suggestions="headerSuggestions"
|
||||
:items="request.rest"/>
|
||||
</el-tab-pane>
|
||||
|
||||
|
@ -49,7 +48,7 @@
|
|||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import MsApiBody from "../body/ApiBody";
|
||||
import MsApiAuthConfig from "../auth/ApiAuthConfig";
|
||||
import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel";
|
||||
import {HttpRequest, KeyValue, Scenario} from "../../model/ApiTestModel";
|
||||
import MsApiExtract from "../extract/ApiExtract";
|
||||
import ApiRequestMethodSelect from "../collapse/ApiRequestMethodSelect";
|
||||
import {REQUEST_HEADERS} from "@/common/js/constants";
|
||||
|
@ -66,6 +65,7 @@
|
|||
},
|
||||
props: {
|
||||
request: HttpRequest,
|
||||
isShowEnable:Boolean,
|
||||
jsonPathList: Array,
|
||||
scenario: Scenario,
|
||||
isReadOnly: {
|
||||
|
|
|
@ -1,42 +1,29 @@
|
|||
<template>
|
||||
<div class="request-form">
|
||||
<component @runDebug="runDebug" :is="component" :is-read-only="isReadOnly" :request="request" :scenario="scenario"/>
|
||||
<component :is="component" :is-read-only="isReadOnly" :request="request" :isShowEnable="isShowEnable"/>
|
||||
<el-divider v-if="isCompleted"></el-divider>
|
||||
<ms-request-result-tail v-loading="debugReportLoading" v-if="isCompleted"
|
||||
:request="request.debugRequestResult ? request.debugRequestResult : {responseResult: {}, subRequestResults: []}"
|
||||
:scenario-name="request.debugScenario ? request.debugScenario.name : ''"
|
||||
ref="msDebugResult"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {JSR223Processor, Request, RequestFactory, Scenario, Test} from "../../model/ScenarioModel";
|
||||
import {JSR223Processor, Request, RequestFactory} from "../../model/ApiTestModel";
|
||||
import MsApiHttpRequestForm from "./ApiHttpRequestForm";
|
||||
// import MsApiTcpRequestForm from "./ApiTcpRequestForm";
|
||||
// import MsApiDubboRequestForm from "./ApiDubboRequestForm";
|
||||
import MsScenarioResults from "../../../report/components/ScenarioResults";
|
||||
import MsRequestResultTail from "../../../report/components/RequestResultTail";
|
||||
// import MsApiSqlRequestForm from "./ApiSqlRequestForm";
|
||||
|
||||
export default {
|
||||
name: "MsApiRequestForm",
|
||||
components: {MsRequestResultTail, MsScenarioResults, MsApiHttpRequestForm},
|
||||
components: {MsApiHttpRequestForm},
|
||||
props: {
|
||||
scenario: Scenario,
|
||||
request: Request,
|
||||
isShowEnable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
isReadOnly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
debugReportId: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
reportId: "",
|
||||
content: {scenarios: []},
|
||||
debugReportLoading: false,
|
||||
showDebugReport: false,
|
||||
jsonPathList: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -61,11 +48,6 @@
|
|||
return !!this.request.debugReport;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
debugReportId() {
|
||||
this.getReport();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//兼容旧版本 beanshell
|
||||
if (!this.request.jsr223PreProcessor.script && this.request.beanShellPreProcessor) {
|
||||
|
@ -75,51 +57,6 @@
|
|||
this.request.jsr223PostProcessor = new JSR223Processor(this.request.beanShellPostProcessor);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getReport() {
|
||||
if (this.debugReportId) {
|
||||
this.debugReportLoading = true;
|
||||
this.showDebugReport = true;
|
||||
this.request.debugReport = {};
|
||||
let url = "/api/report/get/" + this.debugReportId;
|
||||
this.$get(url, response => {
|
||||
let report = response.data || {};
|
||||
let res = {};
|
||||
|
||||
if (response.data) {
|
||||
try {
|
||||
res = JSON.parse(report.content);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
if (res) {
|
||||
this.debugReportLoading = false;
|
||||
this.request.debugReport = res;
|
||||
if (res.scenarios && res.scenarios.length > 0) {
|
||||
this.request.debugScenario = res.scenarios[0];
|
||||
this.request.debugRequestResult = this.request.debugScenario.requestResults[0];
|
||||
this.deleteReport(this.debugReportId);
|
||||
} else {
|
||||
this.request.debugScenario = new Scenario();
|
||||
this.request.debugRequestResult = {responseResult: {}, subRequestResults: []};
|
||||
}
|
||||
this.$refs.msDebugResult.reload();
|
||||
} else {
|
||||
setTimeout(this.getReport, 2000)
|
||||
}
|
||||
} else {
|
||||
this.debugReportLoading = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
deleteReport(reportId) {
|
||||
this.$post('/api/report/delete', {id: reportId});
|
||||
},
|
||||
runDebug() {
|
||||
this.$emit('runDebug', this.request);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="metric-container">
|
||||
<el-row type="flex">
|
||||
<div class="metric">
|
||||
<div class="value">{{response.responseResult.responseTime}} ms</div>
|
||||
<div class="name">{{$t('api_report.response_time')}}</div>
|
||||
<br>
|
||||
<div class="value">{{response.responseResult.latency}} ms</div>
|
||||
<div class="name">{{$t('api_report.latency')}}</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="value">{{response.requestSize}} bytes</div>
|
||||
<div class="name">{{$t('api_report.request_size')}}</div>
|
||||
<br>
|
||||
<div class="value">{{response.responseResult.responseSize}} bytes</div>
|
||||
<div class="name">{{$t('api_report.response_size')}}</div>
|
||||
</div>
|
||||
|
||||
<div class="metric horizontal">
|
||||
<el-row type="flex">
|
||||
<div class="code">
|
||||
<div class="value" :class="{'error': error}">{{response.responseResult.responseCode}}</div>
|
||||
<div class="name">{{$t('api_report.response_code')}}</div>
|
||||
</div>
|
||||
<div class="split"></div>
|
||||
<div class="message">
|
||||
<div class="value">{{response.responseResult.responseMessage}}</div>
|
||||
<div class="name">{{$t('api_report.response_message')}}</div>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "MsRequestMetric",
|
||||
|
||||
props: {
|
||||
response: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
error() {
|
||||
return this.response.responseCode >= 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.metric-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.metric {
|
||||
padding: 20px;
|
||||
border: 1px solid #EBEEF5;
|
||||
min-width: 120px;
|
||||
height: 114px;
|
||||
}
|
||||
|
||||
.metric + .metric {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.metric .value {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.metric .name {
|
||||
color: #404040;
|
||||
opacity: 0.5;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.metric.horizontal {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.metric .code {
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.metric .code .value {
|
||||
color: #67C23A;
|
||||
}
|
||||
|
||||
.metric .code .value.error {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.metric .split {
|
||||
height: 114px;
|
||||
border-left: 1px solid #EBEEF5;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.metric .message {
|
||||
max-height: 114px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<div class="request-result">
|
||||
<ms-request-metric :response="response"/>
|
||||
<ms-response-result :response="response.responseResult"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsResponseResult from "../response/ResponseResult";
|
||||
import MsRequestMetric from "../response/RequestMetric";
|
||||
|
||||
export default {
|
||||
name: "MsRequestResultTail",
|
||||
components: {MsRequestMetric, MsResponseResult},
|
||||
props: {
|
||||
response: Object,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isCodeEditAlive: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
reload() {
|
||||
this.isCodeEditAlive = false;
|
||||
this.$nextTick(() => (this.isCodeEditAlive = true));
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.request-result {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.request-result .info {
|
||||
background-color: #F9F9F9;
|
||||
margin-left: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.request-result .method {
|
||||
color: #1E90FF;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.request-result .url {
|
||||
color: #7f7f7f;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-top: 4px;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.request-result .tab .el-tabs__header {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.request-result .text {
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sub-result .info {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
.sub-result .method {
|
||||
border-left: 5px solid #1E90FF;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.sub-result:last-child {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.request-result .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.response_header')" name="headers" class="pane">
|
||||
<pre>{{ response.headers }}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.response_body')" name="body" class="pane">
|
||||
<ms-code-edit :mode="mode" :read-only="true" :modes="modes" :data.sync="response.body" ref="codeEdit"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Cookie" name="cookie" class="pane cookie">
|
||||
<pre>{{response.cookies}}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.console')" name="console" class="pane">
|
||||
<pre>{{response.console}}</pre>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown :commands="modes" :default-command="mode" @command="modeChange"/>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../../common/components/MsDropdown";
|
||||
import {BODY_FORMAT} from "../../model/ApiTestModel";
|
||||
|
||||
export default {
|
||||
name: "MsResponseResult",
|
||||
|
||||
components: {
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
},
|
||||
|
||||
props: {
|
||||
response: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "headers",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
mode: BODY_FORMAT.TEXT
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
console.log(this.response.body);
|
||||
},
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
if (this.response.headers.indexOf("Content-Type: application/json") > 0) {
|
||||
this.mode = BODY_FORMAT.JSON;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.text-container .icon {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.text-container .collapse {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.text-container .collapse:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.text-container .icon.is-active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F5F5F5;
|
||||
padding: 0 10px;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.cookie {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,33 +1,33 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
<div @click="active" class="collapse">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{ $t('api_report.response') }}
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane label="Body" name="body" class="pane">
|
||||
<ms-code-edit :mode="mode" :read-only="true" :data="response.body" :modes="modes" ref="codeEdit"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="Headers" name="headers" class="pane">
|
||||
<pre>{{ response.headers }}</pre>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_report.assertions')" name="assertions" class="pane assertions">
|
||||
<ms-assertion-results :assertions="response.assertions"/>
|
||||
</el-tab-pane>
|
||||
<el-form :model="response" ref="response" label-width="100px">
|
||||
|
||||
<el-tab-pane :label="$t('api_test.request.extract.label')" name="label" class="pane">
|
||||
<pre>{{response.vars}}</pre>
|
||||
</el-tab-pane>
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.response_header')" name="headers" class="pane">
|
||||
<ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions"
|
||||
:items="response.headers"/>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.response_body')" name="body" class="pane">
|
||||
<ms-api-body
|
||||
:body="response.body"
|
||||
:extract="response.extract"/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane assertions">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown :commands="modes" :default-command="mode" @command="modeChange"/>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane :label="$t('api_test.delimit.request.status_code')" name="status_code" class="pane">
|
||||
<ms-api-key-value :isShowEnable="false" :suggestions="headerSuggestions"
|
||||
:items="response.statusCode"/>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
<el-tab-pane v-if="activeName == 'body'" :disabled="true" name="mode" class="pane cookie">
|
||||
<template v-slot:label>
|
||||
<ms-dropdown :commands="modes" :default-command="mode" @command="modeChange"/>
|
||||
</template>
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -35,7 +35,10 @@
|
|||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../../common/components/MsDropdown";
|
||||
import {BODY_FORMAT} from "../../model/ScenarioModel";
|
||||
import {BODY_FORMAT} from "../../model/ApiTestModel";
|
||||
import MsApiKeyValue from "../ApiKeyValue";
|
||||
import {REQUEST_HEADERS} from "@/common/js/constants";
|
||||
import MsApiBody from "../body/ApiBody";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
|
@ -44,6 +47,8 @@
|
|||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
MsApiKeyValue,
|
||||
MsApiBody,
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -53,9 +58,11 @@
|
|||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
activeName: "headers",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
mode: BODY_FORMAT.TEXT
|
||||
mode: BODY_FORMAT.TEXT,
|
||||
headerSuggestions: REQUEST_HEADERS
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -97,13 +104,13 @@
|
|||
}
|
||||
|
||||
.text-container .pane {
|
||||
background-color: #F5F5F5;
|
||||
background-color: white;
|
||||
padding: 0 10px;
|
||||
height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.text-container .pane.assertions {
|
||||
.text-container .pane.cookie {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,46 @@
|
|||
<template>
|
||||
|
||||
<div class="card-container">
|
||||
<el-card class="card-content">
|
||||
<el-card class="card-content" v-loading="loading">
|
||||
|
||||
<el-form :model="apiData" ref="apiData" :inline="true" label-position="right">
|
||||
<div style="font-size: 16px;color: #333333">{{$t('test_track.plan_view.base_info')}}</div>
|
||||
<br/>
|
||||
|
||||
<el-form-item :label="$t('api_report.request')" prop="responsible">
|
||||
|
||||
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="url"
|
||||
class="ms-http-input" size="small" :disabled="false">
|
||||
<el-select v-model="path" slot="prepend" style="width: 100px">
|
||||
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-input>
|
||||
<el-form :model="api" :rules="rules" ref="apiData" :inline="true" label-position="right">
|
||||
|
||||
<p class="tip">{{$t('test_track.plan_view.base_info')}} </p>
|
||||
<!-- 请求方法 -->
|
||||
<el-form-item :label="$t('api_report.request')" prop="path">
|
||||
<el-select v-model="api.path" style="width: 100px" size="small">
|
||||
<el-option v-for="item in reqOptions" :key="item.id" :label="item.label" :value="item.id"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 执行环境 -->
|
||||
<el-form-item prop="environmentId">
|
||||
<el-select v-model="api.environmentId" size="small" class="ms-htt-width"
|
||||
:placeholder="$t('api_test.delimit.request.run_env')"
|
||||
@change="environmentChange" clearable>
|
||||
<el-option v-for="(environment, index) in environments" :key="index"
|
||||
:label="environment.name + (environment.config.httpConfig.socket ? (': ' + environment.config.httpConfig.protocol + '://' + environment.config.httpConfig.socket) : '')"
|
||||
:value="environment.id"/>
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
|
||||
{{ $t('api_test.environment.environment_config') }}
|
||||
</el-button>
|
||||
<template v-slot:empty>
|
||||
<div class="empty-environment">
|
||||
<el-button class="environment-button" size="mini" type="primary" @click="openEnvironmentConfig">
|
||||
{{ $t('api_test.environment.environment_config') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 请求地址 -->
|
||||
<el-form-item prop="url">
|
||||
<el-input :placeholder="$t('api_test.delimit.request.path_info')" v-model="api.url" class="ms-htt-width"
|
||||
size="small" :disabled="false"/>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<el-form-item>
|
||||
<el-dropdown split-button type="primary" class="ms-api-buttion" @click="handleCommand('add')"
|
||||
@command="handleCommand" size="small">
|
||||
|
@ -33,50 +57,72 @@
|
|||
|
||||
</el-form-item>
|
||||
|
||||
<div style="font-size: 16px;color: #333333;padding-top: 30px">{{$t('api_test.delimit.request.req_param')}}</div>
|
||||
<br/>
|
||||
<p class="tip">{{$t('api_test.delimit.request.req_param')}} </p>
|
||||
<!-- HTTP 请求参数 -->
|
||||
<ms-api-request-form :request="apiData.request"/>
|
||||
<ms-api-request-form :request="api.request"/>
|
||||
|
||||
</el-form>
|
||||
<div style="font-size: 16px;color: #333333 ;padding-top: 30px">{{$t('api_test.delimit.request.res_param')}}</div>
|
||||
<br/>
|
||||
<ms-response-text :response="responseData"></ms-response-text>
|
||||
<!--返回结果-->
|
||||
<!-- HTTP 请求返回数据 -->
|
||||
<p class="tip">{{$t('api_test.delimit.request.res_param')}} </p>
|
||||
<ms-request-result-tail :response="responseData" ref="runResult"/>
|
||||
|
||||
</el-card>
|
||||
|
||||
<!-- 加载用例 -->
|
||||
<ms-bottom-container v-bind:enableAsideHidden="isHide">
|
||||
<ms-api-case-list @apiCaseClose="apiCaseClose" @selectTestCase="selectTestCase" :api="apiData" ref="caseList"/>
|
||||
<ms-api-case-list @apiCaseClose="apiCaseClose" @selectTestCase="selectTestCase" :api="api"
|
||||
:currentProject="currentProject" :loaded="loaded"
|
||||
ref="caseList"/>
|
||||
</ms-bottom-container>
|
||||
|
||||
<!-- 环境 -->
|
||||
<api-environment-config ref="environmentConfig" @close="environmentConfigClose"/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsApiRequestForm from "../request/ApiRequestForm";
|
||||
import {downloadFile, getUUID} from "@/common/js/utils";
|
||||
import MsResponseText from "../../../report/components/ResponseText";
|
||||
import MsApiCaseList from "../ApiCaseList";
|
||||
import MsContainer from "../../../../common/components/MsContainer";
|
||||
import MsBottomContainer from "../BottomContainer";
|
||||
import {RequestFactory, Test} from "../../model/ScenarioModel";
|
||||
import {RequestFactory, Test} from "../../model/ApiTestModel";
|
||||
import {parseEnvironment} from "../../model/EnvironmentModel";
|
||||
import ApiEnvironmentConfig from "../../../test/components/ApiEnvironmentConfig";
|
||||
import MsRequestResultTail from "../response/RequestResultTail";
|
||||
|
||||
import {REQ_METHOD} from "../../model/JsonData";
|
||||
|
||||
export default {
|
||||
name: "ApiConfig",
|
||||
components: {MsResponseText, MsApiRequestForm, MsApiCaseList, MsContainer, MsBottomContainer},
|
||||
components: {
|
||||
MsApiRequestForm,
|
||||
MsApiCaseList,
|
||||
MsContainer,
|
||||
MsBottomContainer,
|
||||
MsRequestResultTail,
|
||||
ApiEnvironmentConfig
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isHide: true,
|
||||
url: '',
|
||||
path: '',
|
||||
api: {},
|
||||
loaded: false,
|
||||
loading: false,
|
||||
currentRequest: {},
|
||||
responseData: {},
|
||||
responseData: {type: 'HTTP', responseResult: {}, subRequestResults: []},
|
||||
reqOptions: REQ_METHOD,
|
||||
environments: [],
|
||||
rules: {
|
||||
path: [{required: true, message: this.$t('test_track.case.input_maintainer'), trigger: 'change'}],
|
||||
url: [{required: true, message: this.$t('api_test.delimit.request.path_info'), trigger: 'blur'}],
|
||||
environmentId: [{required: true, message: this.$t('api_test.delimit.request.run_env'), trigger: 'change'}],
|
||||
}
|
||||
}
|
||||
},
|
||||
props: {apiData: {}},
|
||||
props: {apiData: {}, currentProject: {}},
|
||||
methods: {
|
||||
handleCommand(e) {
|
||||
switch (e) {
|
||||
|
@ -89,13 +135,59 @@
|
|||
case "save_as_api":
|
||||
return this.saveAsApi();
|
||||
default:
|
||||
return [];
|
||||
return this.runTest();
|
||||
}
|
||||
},
|
||||
runTest() {
|
||||
this.$refs['apiData'].validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
let url = "/api/delimit/run";
|
||||
let bodyFiles = this.getBodyUploadFiles();
|
||||
let env = this.api.environment.config.httpConfig.socket ? (this.api.environment.config.httpConfig.protocol + '://' + this.api.environment.config.httpConfig.socket) : '';
|
||||
if (env.endsWith("/")) {
|
||||
env = env.substr(0, env.length - 1);
|
||||
}
|
||||
let sendUrl = this.api.url;
|
||||
if (!sendUrl.startsWith("/")) {
|
||||
sendUrl = "/" + sendUrl;
|
||||
}
|
||||
this.api.test.request.url = env + sendUrl;
|
||||
this.api.test.request.path = this.api.path;
|
||||
this.api.test.request.name = this.api.id;
|
||||
this.api.reportId = "run";
|
||||
let jmx = this.api.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(url, file, bodyFiles, this.api, response => {
|
||||
this.getResult();
|
||||
}, erro => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getResult() {
|
||||
if (this.api.id) {
|
||||
let url = "/api/delimit/report/get/" + this.api.id + "/run";
|
||||
this.$get(url, response => {
|
||||
if (response.data) {
|
||||
let testResult = JSON.parse(response.data.content);
|
||||
this.responseData = testResult;
|
||||
this.loading = false;
|
||||
this.$refs.runResult.reload();
|
||||
} else {
|
||||
setTimeout(this.getResult, 2000)
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
saveAs() {
|
||||
this.$emit('saveAs', this.apiData);
|
||||
this.$emit('saveAs', this.api);
|
||||
},
|
||||
loadCase() {
|
||||
this.loaded = true;
|
||||
this.isHide = false;
|
||||
},
|
||||
apiCaseClose() {
|
||||
|
@ -103,8 +195,8 @@
|
|||
},
|
||||
getBodyUploadFiles() {
|
||||
let bodyUploadFiles = [];
|
||||
this.apiData.bodyUploadIds = [];
|
||||
let request = this.apiData.request;
|
||||
this.api.bodyUploadIds = [];
|
||||
let request = this.api.request;
|
||||
if (request.body) {
|
||||
request.body.kvs.forEach(param => {
|
||||
if (param.files) {
|
||||
|
@ -113,7 +205,7 @@
|
|||
let fileId = getUUID().substring(0, 8);
|
||||
item.name = item.file.name;
|
||||
item.id = fileId;
|
||||
this.apiData.bodyUploadIds.push(fileId);
|
||||
this.api.bodyUploadIds.push(fileId);
|
||||
bodyUploadFiles.push(item.file);
|
||||
}
|
||||
});
|
||||
|
@ -123,60 +215,109 @@
|
|||
return bodyUploadFiles;
|
||||
},
|
||||
saveAsCase() {
|
||||
this.isHide = false;
|
||||
this.loaded = false;
|
||||
let testCase = {};
|
||||
let test = new Test();
|
||||
test.request = this.apiData.request;
|
||||
testCase.test = test;
|
||||
testCase.request = this.apiData.request;
|
||||
testCase.name = this.apiData.name;
|
||||
testCase.request = this.api.request;
|
||||
testCase.apiDelimitId = this.api.id;
|
||||
testCase.priority = "P0";
|
||||
this.$refs.caseList.saveTestCase(testCase);
|
||||
this.$refs.caseList.createCase(testCase);
|
||||
},
|
||||
saveAsApi() {
|
||||
let data = {};
|
||||
data.request = JSON.stringify(this.apiData.request);
|
||||
data.path = this.apiData.path;
|
||||
data.url = this.apiData.url;
|
||||
data.status = this.apiData.status;
|
||||
data.userId = this.apiData.userId;
|
||||
data.description = this.apiData.description;
|
||||
data.request = JSON.stringify(this.api.request);
|
||||
data.path = this.api.path;
|
||||
data.url = this.api.url;
|
||||
data.status = this.api.status;
|
||||
data.userId = this.api.userId;
|
||||
data.description = this.api.description;
|
||||
this.$emit('saveAsApi', data);
|
||||
},
|
||||
editApi(url) {
|
||||
this.apiData.url = this.url;
|
||||
this.apiData.path = this.path;
|
||||
let bodyFiles = this.getBodyUploadFiles();
|
||||
let jmx = this.apiData.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(url, file, bodyFiles, this.apiData, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$emit('saveApi', this.apiData);
|
||||
});
|
||||
},
|
||||
updateApi() {
|
||||
let url = "/api/delimit/update";
|
||||
this.editApi(url);
|
||||
let bodyFiles = this.getBodyUploadFiles();
|
||||
let jmx = this.api.test.toJMX();
|
||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||
let file = new File([blob], jmx.name);
|
||||
this.$fileUpload(url, file, bodyFiles, this.api, () => {
|
||||
this.$success(this.$t('commons.save_success'));
|
||||
this.$emit('saveApi', this.api);
|
||||
});
|
||||
},
|
||||
selectTestCase(item) {
|
||||
if (item != null) {
|
||||
this.apiData.request = new RequestFactory(JSON.parse(item.request));
|
||||
this.api.request = new RequestFactory(JSON.parse(item.request));
|
||||
} else {
|
||||
this.apiData.request = this.currentRequest;
|
||||
this.api.request = this.currentRequest;
|
||||
}
|
||||
}
|
||||
},
|
||||
getEnvironments() {
|
||||
if (this.currentProject) {
|
||||
this.$get('/api/environment/list/' + this.currentProject.id, response => {
|
||||
this.environments = response.data;
|
||||
this.environments.forEach(environment => {
|
||||
parseEnvironment(environment);
|
||||
});
|
||||
let hasEnvironment = false;
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === this.api.environmentId) {
|
||||
this.api.environment = this.environments[i];
|
||||
hasEnvironment = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasEnvironment) {
|
||||
this.api.environmentId = '';
|
||||
this.api.environment = undefined;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.api.environmentId = '';
|
||||
this.api.environment = undefined;
|
||||
}
|
||||
},
|
||||
openEnvironmentConfig() {
|
||||
if (!this.currentProject) {
|
||||
this.$error(this.$t('api_test.select_project'));
|
||||
return;
|
||||
}
|
||||
this.$refs.environmentConfig.open(this.currentProject.id);
|
||||
},
|
||||
environmentChange(value) {
|
||||
for (let i in this.environments) {
|
||||
if (this.environments[i].id === value) {
|
||||
this.api.environment = this.environments[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
environmentConfigClose() {
|
||||
this.getEnvironments();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.currentRequest = this.apiData.request;
|
||||
this.url = this.apiData.url;
|
||||
this.path = this.apiData.path;
|
||||
this.api = this.apiData;
|
||||
this.getEnvironments();
|
||||
this.getResult();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ms-http-input {
|
||||
width: 500px;
|
||||
margin-top: 5px;
|
||||
.ms-htt-width {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.environment-button {
|
||||
margin-left: 20px;
|
||||
padding: 7px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
padding: 3px 5px;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #783887;
|
||||
margin: 20px 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -81,9 +81,10 @@ export const BODY_TYPE = {
|
|||
KV: "KeyValue",
|
||||
FORM_DATA: "Form Data",
|
||||
RAW: "Raw",
|
||||
WWW_FORM: "WWW_Form",
|
||||
WWW_FORM: "WWW_FORM",
|
||||
XML: "XML",
|
||||
BINARY: "BINARY"
|
||||
BINARY: "BINARY",
|
||||
JSON: "JSON"
|
||||
}
|
||||
|
||||
export const BODY_FORMAT = {
|
||||
|
@ -295,6 +296,29 @@ export class RequestFactory {
|
|||
}
|
||||
}
|
||||
|
||||
export class ResponseFactory {
|
||||
static TYPES = {
|
||||
HTTP: "HTTP",
|
||||
DUBBO: "DUBBO",
|
||||
SQL: "SQL",
|
||||
TCP: "TCP",
|
||||
}
|
||||
|
||||
constructor(options = {}) {
|
||||
options.type = options.type || ResponseFactory.TYPES.HTTP
|
||||
switch (options.type) {
|
||||
case RequestFactory.TYPES.DUBBO:
|
||||
return new DubboRequest(options);
|
||||
case RequestFactory.TYPES.SQL:
|
||||
return new SqlRequest(options);
|
||||
case RequestFactory.TYPES.TCP:
|
||||
return new TCPRequest(options);
|
||||
default:
|
||||
return new HttpResponse(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Request extends BaseConfig {
|
||||
constructor(type, options = {}) {
|
||||
super();
|
||||
|
@ -388,6 +412,32 @@ export class HttpRequest extends Request {
|
|||
|
||||
}
|
||||
|
||||
|
||||
export class Response extends BaseConfig {
|
||||
constructor(type, options = {}) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.id = options.id || uuid();
|
||||
this.name = options.name;
|
||||
this.enable = options.enable === undefined ? true : options.enable;
|
||||
this.assertions = new Assertions(options.assertions);
|
||||
this.extract = new Extract(options.extract);
|
||||
this.jsr223PreProcessor = new JSR223Processor(options.jsr223PreProcessor);
|
||||
this.jsr223PostProcessor = new JSR223Processor(options.jsr223PostProcessor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class HttpResponse extends Response {
|
||||
constructor(options) {
|
||||
super(ResponseFactory.TYPES.HTTP, options);
|
||||
this.headers = [];
|
||||
this.body = new Body(options.body);
|
||||
this.statusCode = [];
|
||||
this.sets({statusCode: KeyValue,headers: KeyValue}, options);
|
||||
}
|
||||
}
|
||||
|
||||
export class DubboRequest extends Request {
|
||||
static PROTOCOLS = {
|
||||
DUBBO: "dubbo://",
|
||||
|
@ -658,9 +708,12 @@ export class Body extends BaseConfig {
|
|||
this.type = undefined;
|
||||
this.raw = undefined;
|
||||
this.kvs = [];
|
||||
|
||||
this.fromUrlencoded = [];
|
||||
this.binary = [];
|
||||
this.xml = undefined;
|
||||
this.json = undefined;
|
||||
this.set(options);
|
||||
this.sets({kvs: KeyValue}, options);
|
||||
this.sets({kvs: KeyValue},{fromUrlencoded: KeyValue},{binary: KeyValue}, options);
|
||||
}
|
||||
|
||||
isValid() {
|
|
@ -1,4 +1,4 @@
|
|||
import {BaseConfig, DatabaseConfig, KeyValue} from "./ScenarioModel";
|
||||
import {BaseConfig, DatabaseConfig, KeyValue} from "./ApiTestModel";
|
||||
import {TCPConfig} from "@/business/components/api/test/model/ScenarioModel";
|
||||
|
||||
export class Environment extends BaseConfig {
|
||||
|
|
|
@ -37,3 +37,8 @@ export const API_METHOD_COLOUR = [
|
|||
['HEAD', '#8E58E7'], ['CONNECT', '#90AFAE'],
|
||||
['DUBBO', '#C36EEF'], ['SQL', '#0AEAD4'], ['TCP', '#0A52DF'],
|
||||
]
|
||||
|
||||
export const REQUIRED = [
|
||||
{name: '必填', id: true},
|
||||
{name: '非必填', id: false}
|
||||
]
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
<template>
|
||||
<div class="text-container">
|
||||
<div @click="active" class="collapse">
|
||||
<i class="icon el-icon-arrow-right" :class="{'is-active': isActive}"/>
|
||||
{{ $t('api_report.response') }}
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<el-tabs v-model="activeName" v-show="isActive">
|
||||
<el-tab-pane :class="'body-pane'" label="Body" name="body" class="pane">
|
||||
<ms-sql-result-table v-if="isSqlType" :body="response.body"/>
|
||||
|
@ -23,64 +28,65 @@
|
|||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../common/components/MsDropdown";
|
||||
import {BODY_FORMAT, RequestFactory, Request, SqlRequest} from "../../test/model/ScenarioModel";
|
||||
import MsSqlResultTable from "./SqlResultTable";
|
||||
import MsAssertionResults from "./AssertionResults";
|
||||
import MsCodeEdit from "../../../common/components/MsCodeEdit";
|
||||
import MsDropdown from "../../../common/components/MsDropdown";
|
||||
import {BODY_FORMAT, RequestFactory, Request, SqlRequest} from "../../test/model/ScenarioModel";
|
||||
import MsSqlResultTable from "./SqlResultTable";
|
||||
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
export default {
|
||||
name: "MsResponseText",
|
||||
|
||||
components: {
|
||||
MsSqlResultTable,
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
},
|
||||
|
||||
props: {
|
||||
requestType: String,
|
||||
response: Object
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
mode: BODY_FORMAT.TEXT
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
components: {
|
||||
MsSqlResultTable,
|
||||
MsDropdown,
|
||||
MsCodeEdit,
|
||||
MsAssertionResults,
|
||||
},
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
if (this.response.headers.indexOf("Content-Type: application/json") > 0) {
|
||||
this.mode = BODY_FORMAT.JSON;
|
||||
}
|
||||
},
|
||||
props: {
|
||||
requestType: String,
|
||||
response: Object
|
||||
},
|
||||
|
||||
computed: {
|
||||
isSqlType() {
|
||||
return (this.requestType === RequestFactory.TYPES.SQL && this.response.responseCode === '200');
|
||||
data() {
|
||||
return {
|
||||
isActive: true,
|
||||
activeName: "body",
|
||||
modes: ['text', 'json', 'xml', 'html'],
|
||||
mode: BODY_FORMAT.TEXT
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
active() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
modeChange(mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (!this.response.headers) {
|
||||
return;
|
||||
}
|
||||
if (this.response.headers.indexOf("Content-Type: application/json") > 0) {
|
||||
this.mode = BODY_FORMAT.JSON;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isSqlType() {
|
||||
return (this.requestType === RequestFactory.TYPES.SQL && this.response.responseCode === '200');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="jsoneditor-vue" style="height: 400px"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'jsoneditor/dist/jsoneditor.css';
|
||||
import JsonEditor from 'jsoneditor'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: [String, Number, Object, Array],
|
||||
expandedOnStart: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "tree"
|
||||
},
|
||||
modes: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return ["tree", "code"];
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
async handler(val) {
|
||||
if (!this.internalChange) {
|
||||
await this.setEditor(val);
|
||||
this.expandAll();
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editor: null,
|
||||
error: false,
|
||||
json: this.value,
|
||||
internalChange: false,
|
||||
expandedModes: ["tree", "view", "form"],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
let self = this;
|
||||
let options = {
|
||||
mode: this.mode,
|
||||
modes: this.modes, // allowed modes
|
||||
onChange() {
|
||||
try {
|
||||
let json = self.editor.get();
|
||||
self.json = json;
|
||||
self.$emit("json-change", json);
|
||||
self.internalChange = true;
|
||||
self.$emit("input", json);
|
||||
self.$nextTick(function () {
|
||||
self.internalChange = false;
|
||||
});
|
||||
} catch (e) {
|
||||
self.$emit("has-error", e);
|
||||
}
|
||||
},
|
||||
onModeChange() {
|
||||
self.expandAll();
|
||||
},
|
||||
onError(error) {
|
||||
self.$emit("onError", error);
|
||||
}
|
||||
};
|
||||
this.editor = new JsonEditor(
|
||||
this.$el.querySelector(".jsoneditor-vue"),
|
||||
options,
|
||||
this.json
|
||||
);
|
||||
},
|
||||
methods: {
|
||||
expandAll() {
|
||||
if (
|
||||
this.expandedOnStart &&
|
||||
this.expandedModes.includes(this.editor.getMode())
|
||||
) {
|
||||
this.editor.expandAll();
|
||||
}
|
||||
},
|
||||
async setEditor(value) {
|
||||
if (this.editor) this.editor.set(value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ace_line_group {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.json-editor-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.json-editor-container .tree-mode {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.json-editor-container .code-mode {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.jsoneditor-btns {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.jsoneditor-vue .jsoneditor-outer {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.jsoneditor-vue div.jsoneditor-tree {
|
||||
min-height: 350px;
|
||||
}
|
||||
|
||||
.json-save-btn {
|
||||
background-color: #20A0FF;
|
||||
border: none;
|
||||
color: #fff;
|
||||
padding: 5px 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.json-save-btn:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.json-save-btn[disabled] {
|
||||
background-color: #1D8CE0;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
/deep/ .jsoneditor-poweredBy {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/deep/ .jsoneditor-contextmenu .jsoneditor-menu li button.jsoneditor-selected {
|
||||
background-color: #1E9FFB;
|
||||
}
|
||||
|
||||
/deep/ jsoneditor-tree {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/deep/ .jsoneditor {
|
||||
border-color: #1E9FFB;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
|
@ -480,6 +480,7 @@ export default {
|
|||
case: "Case",
|
||||
title: "Create api",
|
||||
path_info: "Please enter the URL of the interface, such as /api/demo/#{id}, where id is the path parameter",
|
||||
path_all_info: "Please enter the complete test address",
|
||||
fast_debug: "Fast debug",
|
||||
close_all_label: "close all label",
|
||||
save_as: "Save as new interface",
|
||||
|
@ -499,9 +500,14 @@ export default {
|
|||
verified: "verified",
|
||||
encryption: "encryption",
|
||||
req_param: "Request parameter",
|
||||
res_param: "Response template",
|
||||
res_param: "Response content",
|
||||
batch_delete: "Batch deletion",
|
||||
delete_confirm: "confirm deletion",
|
||||
delete_confirm: "Confirm deletion",
|
||||
assertions_rule: "Assertion rule",
|
||||
response_header: "Response header",
|
||||
response_body: "Response body",
|
||||
console: "Console",
|
||||
status_code: "Status code",
|
||||
}
|
||||
},
|
||||
environment: {
|
||||
|
|
|
@ -481,6 +481,7 @@ export default {
|
|||
responsible: "责任人",
|
||||
title: "创建接口",
|
||||
path_info: "请输入接口的URL,如/api/demo/#{id},其中id为路径参数",
|
||||
path_all_info: "请输入完整测试地址",
|
||||
fast_debug: "快捷调试",
|
||||
close_all_label: "关闭所有标签",
|
||||
save_as: "另存为新接口",
|
||||
|
@ -500,9 +501,15 @@ export default {
|
|||
verified: "认证",
|
||||
encryption: "加密",
|
||||
req_param: "请求参数",
|
||||
res_param: "响应模版",
|
||||
res_param: "响应内容",
|
||||
batch_delete: "批量删除",
|
||||
delete_confirm: "确认删除接口",
|
||||
assertions_rule: "断言规则",
|
||||
response_header: "响应头",
|
||||
response_body: "响应体",
|
||||
console: "控制台",
|
||||
status_code: "状态码",
|
||||
|
||||
}
|
||||
},
|
||||
environment: {
|
||||
|
|
|
@ -481,6 +481,7 @@ export default {
|
|||
responsible: "责任人",
|
||||
title: "创建接口",
|
||||
path_info: "請輸入接口的URL,如/api/demo/#{id},其中id為路徑參數",
|
||||
path_all_info: "請輸入完整測試地址",
|
||||
fast_debug: "快捷調試",
|
||||
close_all_label: "關閉所有標簽",
|
||||
save_as: "另存為新接口",
|
||||
|
@ -500,9 +501,15 @@ export default {
|
|||
verified: "認證",
|
||||
encryption: "加密",
|
||||
req_param: "請求參數",
|
||||
res_param: "響應模版",
|
||||
res_param: "響應内容",
|
||||
batch_delete: "批量删除",
|
||||
delete_confirm: "確認刪除接口",
|
||||
assertions_rule: "斷言規則",
|
||||
response_header: "響應頭",
|
||||
response_body: "響應體",
|
||||
console: "控制臺",
|
||||
status_code: "狀態碼",
|
||||
|
||||
}
|
||||
},
|
||||
environment: {
|
||||
|
|
Loading…
Reference in New Issue