feat(接口测试): 接口测试支持文件上传
This commit is contained in:
parent
9a9d702aca
commit
d54b32ced3
|
@ -60,13 +60,13 @@ public class APITestController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/create", consumes = {"multipart/form-data"})
|
@PostMapping(value = "/create", consumes = {"multipart/form-data"})
|
||||||
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file) {
|
public void create(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||||
apiTestService.create(request, file);
|
apiTestService.create(request, file, bodyFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
|
@PostMapping(value = "/update", consumes = {"multipart/form-data"})
|
||||||
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file) {
|
public void update(@RequestPart("request") SaveAPITestRequest request, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||||
apiTestService.update(request, file);
|
apiTestService.update(request, file, bodyFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/copy")
|
@PostMapping(value = "/copy")
|
||||||
|
|
|
@ -24,4 +24,6 @@ public class SaveAPITestRequest {
|
||||||
private Schedule schedule;
|
private Schedule schedule;
|
||||||
|
|
||||||
private String triggerMode;
|
private String triggerMode;
|
||||||
|
|
||||||
|
private List<String> bodyUploadIds;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
package io.metersphere.api.dto.scenario;
|
package io.metersphere.api.dto.scenario;
|
||||||
|
|
||||||
|
import io.metersphere.api.dto.scenario.request.BodyFile;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class KeyValue {
|
public class KeyValue {
|
||||||
private String name;
|
private String name;
|
||||||
private String value;
|
private String value;
|
||||||
|
private String type;
|
||||||
|
private List<BodyFile> files;
|
||||||
private String description;
|
private String description;
|
||||||
|
|
||||||
public KeyValue() {
|
public KeyValue() {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package io.metersphere.api.dto.scenario.request;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class BodyFile {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String name;
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import io.metersphere.service.ScheduleService;
|
||||||
import io.metersphere.track.service.TestCaseService;
|
import io.metersphere.track.service.TestCaseService;
|
||||||
import org.apache.dubbo.common.URL;
|
import org.apache.dubbo.common.URL;
|
||||||
import org.apache.dubbo.common.constants.CommonConstants;
|
import org.apache.dubbo.common.constants.CommonConstants;
|
||||||
|
import org.aspectj.util.FileUtil;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
@ -36,9 +37,7 @@ import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.*;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -63,6 +62,8 @@ public class APITestService {
|
||||||
@Resource
|
@Resource
|
||||||
private TestCaseService testCaseService;
|
private TestCaseService testCaseService;
|
||||||
|
|
||||||
|
private static final String BODY_FILE_DIR = "/opt/metersphere/data/body";
|
||||||
|
|
||||||
public List<APITestResult> list(QueryAPITestRequest request) {
|
public List<APITestResult> list(QueryAPITestRequest request) {
|
||||||
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
request.setOrders(ServiceUtils.getDefaultOrder(request.getOrders()));
|
||||||
return extApiTestMapper.list(request);
|
return extApiTestMapper.list(request);
|
||||||
|
@ -73,24 +74,63 @@ public class APITestService {
|
||||||
return extApiTestMapper.list(request);
|
return extApiTestMapper.list(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void create(SaveAPITestRequest request, MultipartFile file) {
|
public void create(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||||
}
|
}
|
||||||
checkQuota();
|
checkQuota();
|
||||||
|
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ;
|
||||||
|
request.setBodyUploadIds(null);
|
||||||
ApiTest test = createTest(request);
|
ApiTest test = createTest(request);
|
||||||
|
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||||
saveFile(test.getId(), file);
|
saveFile(test.getId(), file);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(SaveAPITestRequest request, MultipartFile file) {
|
public void update(SaveAPITestRequest request, MultipartFile file, List<MultipartFile> bodyFiles) {
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
throw new IllegalArgumentException(Translator.get("file_cannot_be_null"));
|
||||||
}
|
}
|
||||||
deleteFileByTestId(request.getId());
|
deleteFileByTestId(request.getId());
|
||||||
|
|
||||||
|
List<String> bodyUploadIds = new ArrayList<>(request.getBodyUploadIds()) ;
|
||||||
|
request.setBodyUploadIds(null);
|
||||||
ApiTest test = updateTest(request);
|
ApiTest test = updateTest(request);
|
||||||
|
createBodyFiles(test, bodyUploadIds, bodyFiles);
|
||||||
saveFile(test.getId(), file);
|
saveFile(test.getId(), file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createBodyFiles(ApiTest test, List<String> bodyUploadIds, List<MultipartFile> bodyFiles) {
|
||||||
|
if (bodyFiles == null || bodyFiles.isEmpty()) {
|
||||||
|
|
||||||
|
}
|
||||||
|
String dir = BODY_FILE_DIR + "/" + test.getId();
|
||||||
|
File testDir = new File(dir);
|
||||||
|
if (!testDir.exists()) {
|
||||||
|
testDir.mkdirs();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < bodyUploadIds.size(); i++) {
|
||||||
|
MultipartFile item = bodyFiles.get(i);
|
||||||
|
File file = new File(testDir + "/" + bodyUploadIds.get(i) + "_" + item.getOriginalFilename());
|
||||||
|
InputStream in = null;
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
file.createNewFile();
|
||||||
|
in = item.getInputStream();
|
||||||
|
out = new FileOutputStream(file);
|
||||||
|
FileUtil.copyStream(in, out);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
in.close();
|
||||||
|
out.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void copy(SaveAPITestRequest request) {
|
public void copy(SaveAPITestRequest request) {
|
||||||
checkQuota();
|
checkQuota();
|
||||||
request.setName(request.getName() + " Copy");
|
request.setName(request.getName() + " Copy");
|
||||||
|
@ -117,6 +157,20 @@ public class APITestService {
|
||||||
apiTestFile.setFileId(fileMetadata.getId());
|
apiTestFile.setFileId(fileMetadata.getId());
|
||||||
apiTestFileMapper.insert(apiTestFile);
|
apiTestFileMapper.insert(apiTestFile);
|
||||||
}
|
}
|
||||||
|
copyBodyFiles(copy.getId(), request.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyBodyFiles(String target, String source) {
|
||||||
|
String sourceDir = BODY_FILE_DIR + "/" + source;
|
||||||
|
String targetDir = BODY_FILE_DIR + "/" + target;
|
||||||
|
File sourceFile = new File(sourceDir);
|
||||||
|
if (sourceFile.exists()) {
|
||||||
|
try {
|
||||||
|
FileUtil.copyDir(sourceFile, new File(targetDir));
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public APITestResult get(String id) {
|
public APITestResult get(String id) {
|
||||||
|
@ -142,6 +196,15 @@ public class APITestService {
|
||||||
apiReportService.deleteByTestId(testId);
|
apiReportService.deleteByTestId(testId);
|
||||||
scheduleService.deleteByResourceId(testId);
|
scheduleService.deleteByResourceId(testId);
|
||||||
apiTestMapper.deleteByPrimaryKey(testId);
|
apiTestMapper.deleteByPrimaryKey(testId);
|
||||||
|
deleteBodyFiles(testId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteBodyFiles(String testId) {
|
||||||
|
File file = new File(BODY_FILE_DIR + "/" + testId);
|
||||||
|
FileUtil.deleteContents(file);
|
||||||
|
if (file.exists()) {
|
||||||
|
file.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String run(SaveAPITestRequest request) {
|
public String run(SaveAPITestRequest request) {
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
package io.metersphere.controller;
|
package io.metersphere.controller;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import io.metersphere.commons.utils.SessionUtils;
|
import io.metersphere.commons.utils.SessionUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(value = "/test")
|
@RequestMapping(value = "/test")
|
||||||
public class TestController {
|
public class TestController {
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping(value = "/upload", consumes = {"multipart/form-data"})
|
||||||
|
public Object testUpload(@RequestPart(value = "id") String id, @RequestPart(value = "file") MultipartFile file, @RequestPart(value = "files") List<MultipartFile> bodyFiles) {
|
||||||
|
JSONObject jsonObject = new JSONObject();
|
||||||
|
jsonObject.put("id", id);
|
||||||
|
jsonObject.put("file", file.getOriginalFilename());
|
||||||
|
jsonObject.put("files", bodyFiles.stream().map(MultipartFile::getOriginalFilename).collect(Collectors.toList()));
|
||||||
|
return jsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/{str}")
|
@GetMapping(value = "/{str}")
|
||||||
public Object getString(@PathVariable String str) throws InterruptedException {
|
public Object getString(@PathVariable String str) throws InterruptedException {
|
||||||
if (StringUtils.equals("error", str)) {
|
if (StringUtils.equals("error", str)) {
|
||||||
|
@ -28,4 +40,5 @@ public class TestController {
|
||||||
}
|
}
|
||||||
return ResultHolder.success(str);
|
return ResultHolder.success(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1 +1 @@
|
||||||
Subproject commit defbf12ffac99eef6a026b87491090f2263b769d
|
Subproject commit b86032cbbda9a9e6028308aa95a887cff2192f1c
|
|
@ -70,6 +70,7 @@
|
||||||
import {checkoutTestManagerOrTestUser, downloadFile} from "@/common/js/utils";
|
import {checkoutTestManagerOrTestUser, downloadFile} from "@/common/js/utils";
|
||||||
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
import MsScheduleConfig from "../../common/components/MsScheduleConfig";
|
||||||
import ApiImport from "./components/import/ApiImport";
|
import ApiImport from "./components/import/ApiImport";
|
||||||
|
import {getUUID} from "../../../../common/js/utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiTestConfig",
|
name: "MsApiTestConfig",
|
||||||
|
@ -149,13 +150,15 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.change = false;
|
this.change = false;
|
||||||
|
let bodyFiles = this.getBodyUploadFiles();
|
||||||
let url = this.create ? "/api/create" : "/api/update";
|
let url = this.create ? "/api/create" : "/api/update";
|
||||||
let jmx = this.test.toJMX();
|
let jmx = this.test.toJMX();
|
||||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||||
let file = new File([blob], jmx.name);
|
let file = new File([blob], jmx.name);
|
||||||
this.result = this.$fileUpload(url, file, this.test,response => {
|
this.result = this.$fileUpload(url, file, bodyFiles, this.test,response => {
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
this.create = false;
|
this.create = false;
|
||||||
|
this.resetBodyFile();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
saveTest() {
|
saveTest() {
|
||||||
|
@ -184,6 +187,49 @@
|
||||||
this.runTest();
|
this.runTest();
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getBodyUploadFiles() {
|
||||||
|
let bodyUploadFiles = [];
|
||||||
|
this.test.bodyUploadIds = [];
|
||||||
|
this.test.scenarioDefinition.forEach(scenario => {
|
||||||
|
scenario.requests.forEach(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;
|
||||||
|
this.test.bodyUploadIds.push(fileId);
|
||||||
|
bodyUploadFiles.push(item.file);
|
||||||
|
// item.file = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return bodyUploadFiles;
|
||||||
|
},
|
||||||
|
resetBodyFile() {
|
||||||
|
//下次保存不再上传已传文件
|
||||||
|
this.test.scenarioDefinition.forEach(scenario => {
|
||||||
|
scenario.requests.forEach(request => {
|
||||||
|
if (request.body) {
|
||||||
|
request.body.kvs.forEach( param => {
|
||||||
|
if (param.files) {
|
||||||
|
param.files.forEach(item => {
|
||||||
|
if (item.file) {
|
||||||
|
item.file = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
cancel() {
|
cancel() {
|
||||||
this.$router.push('/api/test/list/all');
|
this.$router.push('/api/test/list/all');
|
||||||
},
|
},
|
||||||
|
@ -256,7 +302,7 @@
|
||||||
let jmx = runningTest.toJMX();
|
let jmx = runningTest.toJMX();
|
||||||
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
let blob = new Blob([jmx.xml], {type: "application/octet-stream"});
|
||||||
let file = new File([blob], jmx.name);
|
let file = new File([blob], jmx.name);
|
||||||
this.$fileUpload(url, file, this.test,response => {
|
this.$fileUpload(url, file, null, this.test,response => {
|
||||||
this.debugReportId = response.data;
|
this.debugReportId = response.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,21 @@
|
||||||
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
<div class="kv-row" v-for="(item, index) in parameters" :key="index">
|
||||||
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
<el-row type="flex" :gutter="20" justify="space-between" align="middle">
|
||||||
<el-col>
|
<el-col>
|
||||||
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200"
|
|
||||||
@change="change"
|
<el-input v-if="!suggestions" :disabled="isReadOnly" v-model="item.name" size="small" maxlength="200" @change="change" :placeholder="keyText" show-word-limit>
|
||||||
:placeholder="keyText" show-word-limit/>
|
<template v-slot:prepend>
|
||||||
|
<el-select v-if="type === 'body'" :disabled="isReadOnly" class="kv-type" v-model="item.type">
|
||||||
|
<el-option value="text" />
|
||||||
|
<el-option value="file"/>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
|
||||||
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
|
<el-autocomplete :disabled="isReadOnly" :maxlength="200" v-if="suggestions" v-model="item.name" size="small"
|
||||||
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText"
|
:fetch-suggestions="querySearch" @change="change" :placeholder="keyText" show-word-limit/>
|
||||||
show-word-limit/>
|
|
||||||
|
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col>
|
<el-col v-if="item.type !== 'file'">
|
||||||
<el-autocomplete
|
<el-autocomplete
|
||||||
:disabled="isReadOnly"
|
:disabled="isReadOnly"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -28,6 +34,10 @@
|
||||||
<i slot="suffix" class="el-input__icon el-icon-edit" style="cursor: pointer;" @click="advanced(item)"></i>
|
<i slot="suffix" class="el-input__icon el-icon-edit" style="cursor: pointer;" @click="advanced(item)"></i>
|
||||||
</el-autocomplete>
|
</el-autocomplete>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
|
||||||
|
<el-col v-if="item.type === 'file'">
|
||||||
|
<ms-api-body-file-upload :parameter="item"/>
|
||||||
|
</el-col>
|
||||||
<el-col class="kv-delete">
|
<el-col class="kv-delete">
|
||||||
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
<el-button size="mini" class="el-icon-delete-solid" circle @click="remove(index)"
|
||||||
:disabled="isDisable(index) || isReadOnly"/>
|
:disabled="isDisable(index) || isReadOnly"/>
|
||||||
|
@ -44,10 +54,11 @@
|
||||||
import {KeyValue, Scenario} from "../model/ScenarioModel";
|
import {KeyValue, Scenario} from "../model/ScenarioModel";
|
||||||
import {MOCKJS_FUNC} from "@/common/js/constants";
|
import {MOCKJS_FUNC} from "@/common/js/constants";
|
||||||
import MsApiVariableAdvance from "@/business/components/api/test/components/ApiVariableAdvance";
|
import MsApiVariableAdvance from "@/business/components/api/test/components/ApiVariableAdvance";
|
||||||
|
import MsApiBodyFileUpload from "./body/ApiBodyFileUpload";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "MsApiVariable",
|
name: "MsApiVariable",
|
||||||
components: {MsApiVariableAdvance},
|
components: {MsApiBodyFileUpload, MsApiVariableAdvance},
|
||||||
props: {
|
props: {
|
||||||
keyPlaceholder: String,
|
keyPlaceholder: String,
|
||||||
valuePlaceholder: String,
|
valuePlaceholder: String,
|
||||||
|
@ -55,6 +66,10 @@ export default {
|
||||||
parameters: Array,
|
parameters: Array,
|
||||||
environment: Object,
|
environment: Object,
|
||||||
scenario: Scenario,
|
scenario: Scenario,
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
isReadOnly: {
|
isReadOnly: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
@ -93,7 +108,7 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (isNeedCreate) {
|
if (isNeedCreate) {
|
||||||
this.parameters.push(new KeyValue());
|
this.parameters.push(new KeyValue(null, null, 'text'));
|
||||||
}
|
}
|
||||||
this.$emit('change', this.parameters);
|
this.$emit('change', this.parameters);
|
||||||
// TODO 检查key重复
|
// TODO 检查key重复
|
||||||
|
@ -133,35 +148,38 @@ export default {
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (this.parameters.length === 0) {
|
if (this.parameters.length === 0) {
|
||||||
this.parameters.push(new KeyValue());
|
this.parameters.push(new KeyValue(null, null, 'text'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.kv-description {
|
.kv-description {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kv-row {
|
.kv-row {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kv-delete {
|
.kv-delete {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-autocomplete {
|
.el-autocomplete {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.advanced-item-value >>> .el-dialog__body {
|
.advanced-item-value >>> .el-dialog__body {
|
||||||
padding: 15px 25px;
|
padding: 15px 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-row {
|
.el-row {
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kv-type {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
:environment="environment"
|
:environment="environment"
|
||||||
:scenario="scenario"
|
:scenario="scenario"
|
||||||
:extract="extract"
|
:extract="extract"
|
||||||
|
type="body"
|
||||||
:description="$t('api_test.request.parameters_desc')"
|
:description="$t('api_test.request.parameters_desc')"
|
||||||
v-if="body.isKV()"/>
|
v-if="body.isKV()"/>
|
||||||
<div class="body-raw" v-if="body.type == 'Raw'">
|
<div class="body-raw" v-if="body.type == 'Raw'">
|
||||||
|
@ -26,10 +27,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiKeyValue from "./ApiKeyValue";
|
import MsApiKeyValue from "../ApiKeyValue";
|
||||||
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../model/ScenarioModel";
|
import {Body, BODY_FORMAT, BODY_TYPE, Scenario} from "../../model/ScenarioModel";
|
||||||
import MsCodeEdit from "../../../common/components/MsCodeEdit";
|
import MsCodeEdit from "../../../../common/components/MsCodeEdit";
|
||||||
import MsDropdown from "../../../common/components/MsDropdown";
|
import MsDropdown from "../../../../common/components/MsDropdown";
|
||||||
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
|
import MsApiVariable from "@/business/components/api/test/components/ApiVariable";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -66,6 +67,11 @@ export default {
|
||||||
if (!this.body.format) {
|
if (!this.body.format) {
|
||||||
this.body.format = BODY_FORMAT.TEXT;
|
this.body.format = BODY_FORMAT.TEXT;
|
||||||
}
|
}
|
||||||
|
this.body.kvs.forEach(param => {
|
||||||
|
if (!param.type) {
|
||||||
|
param.type = 'text';
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -0,0 +1,116 @@
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<el-upload
|
||||||
|
action="#"
|
||||||
|
class="api-body-upload"
|
||||||
|
list-type="picture-card"
|
||||||
|
:http-request="upload"
|
||||||
|
:beforeUpload="uploadValidate"
|
||||||
|
:file-list="parameter.files"
|
||||||
|
ref="upload">
|
||||||
|
|
||||||
|
<div class="upload-default">
|
||||||
|
<i class="el-icon-plus"/>
|
||||||
|
</div>
|
||||||
|
<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>-->
|
||||||
|
<span v-if="!disabled" class="el-upload-list__item-delete" @click="handleRemove(file)">
|
||||||
|
<i class="el-icon-delete"/>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</el-upload>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
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++) {
|
||||||
|
if (file.name === this.parameter.files[i].name) {
|
||||||
|
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 > 20) {
|
||||||
|
// this.$warning(this.$t('test_track.case.import.upload_limit_size'));
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
if (!this.parameter.files) {
|
||||||
|
this.parameter.files = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
.el-upload {
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-body-upload >>> .el-upload {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-default {
|
||||||
|
min-height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon-plus {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-body-upload >>> .el-upload-list__item {
|
||||||
|
height: 32px;
|
||||||
|
width: auto;
|
||||||
|
padding: 6px;
|
||||||
|
margin-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-body-upload >>> .el-upload-list--picture-card {
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-body-upload {
|
||||||
|
min-height: 32px;
|
||||||
|
/*border: 1px solid #EBEEF5;*/
|
||||||
|
/*padding: 2px;*/
|
||||||
|
/*border-radius: 4px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-item {
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
|
@ -237,7 +237,7 @@
|
||||||
if (!this.swaggerUrlEable) {
|
if (!this.swaggerUrlEable) {
|
||||||
param.swaggerUrl = undefined;
|
param.swaggerUrl = undefined;
|
||||||
}
|
}
|
||||||
this.result = this.$fileUpload('/api/import', param.file, param,response => {
|
this.result = this.$fileUpload('/api/import', param.file, null, param,response => {
|
||||||
let res = response.data;
|
let res = response.data;
|
||||||
this.$success(this.$t('test_track.case.import.success'));
|
this.$success(this.$t('test_track.case.import.success'));
|
||||||
this.visible = false;
|
this.visible = false;
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiKeyValue from "../ApiKeyValue";
|
import MsApiKeyValue from "../ApiKeyValue";
|
||||||
import MsApiBody from "../ApiBody";
|
import MsApiBody from "../body/ApiBody";
|
||||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||||
import {DubboRequest, Scenario} from "../../model/ScenarioModel";
|
import {DubboRequest, Scenario} from "../../model/ScenarioModel";
|
||||||
import MsApiExtract from "../extract/ApiExtract";
|
import MsApiExtract from "../extract/ApiExtract";
|
||||||
|
|
|
@ -82,7 +82,7 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import MsApiKeyValue from "../ApiKeyValue";
|
import MsApiKeyValue from "../ApiKeyValue";
|
||||||
import MsApiBody from "../ApiBody";
|
import MsApiBody from "../body/ApiBody";
|
||||||
import MsApiAssertions from "../assertion/ApiAssertions";
|
import MsApiAssertions from "../assertion/ApiAssertions";
|
||||||
import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel";
|
import {HttpRequest, KeyValue, Scenario} from "../../model/ScenarioModel";
|
||||||
import MsApiExtract from "../extract/ApiExtract";
|
import MsApiExtract from "../extract/ApiExtract";
|
||||||
|
|
|
@ -328,6 +328,25 @@ export class HTTPSamplerArguments extends Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class HTTPsamplerFiles extends Element {
|
||||||
|
constructor(args) {
|
||||||
|
super('elementProp', {
|
||||||
|
name: "HTTPsampler.Files",
|
||||||
|
elementType: "HTTPFileArgs",
|
||||||
|
});
|
||||||
|
|
||||||
|
this.args = args || {};
|
||||||
|
|
||||||
|
let collectionProp = this.collectionProp('HTTPFileArgs.files');
|
||||||
|
this.args.forEach(arg => {
|
||||||
|
let elementProp = collectionProp.elementProp(arg.value, 'HTTPFileArg');
|
||||||
|
elementProp.stringProp('File.path', arg.value);
|
||||||
|
elementProp.stringProp('File.paramname', arg.name);
|
||||||
|
elementProp.stringProp('File.mimetype', arg.metadata || "application/octet-stream");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class CookieManager extends DefaultTestElement {
|
export class CookieManager extends DefaultTestElement {
|
||||||
constructor(testName) {
|
constructor(testName) {
|
||||||
super('CookieManager', 'CookiePanel', 'CookieManager', testName);
|
super('CookieManager', 'CookiePanel', 'CookieManager', testName);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
Element,
|
Element,
|
||||||
HashTree,
|
HashTree,
|
||||||
HeaderManager,
|
HeaderManager,
|
||||||
HTTPSamplerArguments,
|
HTTPSamplerArguments, HTTPsamplerFiles,
|
||||||
HTTPSamplerProxy,
|
HTTPSamplerProxy,
|
||||||
JSONPathAssertion,
|
JSONPathAssertion,
|
||||||
JSONPostProcessor,
|
JSONPostProcessor,
|
||||||
|
@ -38,6 +38,8 @@ export const uuid = function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BODY_FILE_DIR = "/opt/metersphere/data/body"; //存放body文件上传目录
|
||||||
|
|
||||||
export const calculate = function (itemValue) {
|
export const calculate = function (itemValue) {
|
||||||
if (!itemValue) {
|
if (!itemValue) {
|
||||||
return;
|
return;
|
||||||
|
@ -528,7 +530,7 @@ export class Body extends BaseConfig {
|
||||||
|
|
||||||
export class KeyValue extends BaseConfig {
|
export class KeyValue extends BaseConfig {
|
||||||
constructor() {
|
constructor() {
|
||||||
let options, key, value;
|
let options, key, value, type;
|
||||||
if (arguments.length === 1) {
|
if (arguments.length === 1) {
|
||||||
options = arguments[0];
|
options = arguments[0];
|
||||||
}
|
}
|
||||||
|
@ -537,16 +539,27 @@ export class KeyValue extends BaseConfig {
|
||||||
key = arguments[0];
|
key = arguments[0];
|
||||||
value = arguments[1];
|
value = arguments[1];
|
||||||
}
|
}
|
||||||
|
if (arguments.length === 3) {
|
||||||
|
key = arguments[0];
|
||||||
|
value = arguments[1];
|
||||||
|
type = arguments[2];
|
||||||
|
}
|
||||||
|
|
||||||
super();
|
super();
|
||||||
this.name = key;
|
this.name = key;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
this.type = type;
|
||||||
|
this.files = undefined;
|
||||||
|
|
||||||
this.set(options);
|
this.set(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid() {
|
isValid() {
|
||||||
return !!this.name || !!this.value;
|
return (!!this.name || !!this.value) && this.type !== 'file';
|
||||||
|
}
|
||||||
|
|
||||||
|
isFile() {
|
||||||
|
return (!!this.name || !!this.value) && this.type === 'file';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -807,13 +820,13 @@ class JMXGenerator {
|
||||||
if (!test || !test.id || !(test instanceof Test)) return undefined;
|
if (!test || !test.id || !(test instanceof Test)) return undefined;
|
||||||
|
|
||||||
let testPlan = new TestPlan(test.name);
|
let testPlan = new TestPlan(test.name);
|
||||||
this.addScenarios(testPlan, test.scenarioDefinition);
|
this.addScenarios(testPlan, test.id, test.scenarioDefinition);
|
||||||
|
|
||||||
this.jmeterTestPlan = new JMeterTestPlan();
|
this.jmeterTestPlan = new JMeterTestPlan();
|
||||||
this.jmeterTestPlan.put(testPlan);
|
this.jmeterTestPlan.put(testPlan);
|
||||||
}
|
}
|
||||||
|
|
||||||
addScenarios(testPlan, scenarios) {
|
addScenarios(testPlan, testId, scenarios) {
|
||||||
scenarios.forEach(s => {
|
scenarios.forEach(s => {
|
||||||
|
|
||||||
if (s.enable) {
|
if (s.enable) {
|
||||||
|
@ -842,7 +855,7 @@ class JMXGenerator {
|
||||||
if (request.method.toUpperCase() === 'GET') {
|
if (request.method.toUpperCase() === 'GET') {
|
||||||
this.addRequestArguments(sampler, request);
|
this.addRequestArguments(sampler, request);
|
||||||
} else {
|
} else {
|
||||||
this.addRequestBody(sampler, request);
|
this.addRequestBody(sampler, request, testId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -965,10 +978,11 @@ class JMXGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addRequestBody(httpSamplerProxy, request) {
|
addRequestBody(httpSamplerProxy, request, testId) {
|
||||||
let body = [];
|
let body = [];
|
||||||
if (request.body.isKV()) {
|
if (request.body.isKV()) {
|
||||||
body = this.filterKV(request.body.kvs);
|
body = this.filterKV(request.body.kvs);
|
||||||
|
this.addRequestBodyFile(httpSamplerProxy, request, testId);
|
||||||
} else {
|
} else {
|
||||||
httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
|
httpSamplerProxy.boolProp('HTTPSampler.postBodyRaw', true);
|
||||||
body.push({name: '', value: request.body.raw, encode: false});
|
body.push({name: '', value: request.body.raw, encode: false});
|
||||||
|
@ -977,6 +991,22 @@ class JMXGenerator {
|
||||||
httpSamplerProxy.add(new HTTPSamplerArguments(body));
|
httpSamplerProxy.add(new HTTPSamplerArguments(body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addRequestBodyFile(httpSamplerProxy, request, testId) {
|
||||||
|
let files = [];
|
||||||
|
let kvs = this.filterKVFile(request.body.kvs);
|
||||||
|
kvs.forEach(kv => {
|
||||||
|
if (kv.files) {
|
||||||
|
kv.files.forEach(file => {
|
||||||
|
let arg = {};
|
||||||
|
arg.name = kv.name;
|
||||||
|
arg.value = BODY_FILE_DIR + '/' + testId + '/' + file.id + '_' + file.name;
|
||||||
|
files.push(arg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
httpSamplerProxy.add(new HTTPsamplerFiles(files));
|
||||||
|
}
|
||||||
|
|
||||||
addRequestAssertion(httpSamplerProxy, request) {
|
addRequestAssertion(httpSamplerProxy, request) {
|
||||||
let assertions = request.assertions;
|
let assertions = request.assertions;
|
||||||
if (assertions.regex.length > 0) {
|
if (assertions.regex.length > 0) {
|
||||||
|
@ -1066,6 +1096,12 @@ class JMXGenerator {
|
||||||
return kvs.filter(this.filter);
|
return kvs.filter(this.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filterKVFile(kvs) {
|
||||||
|
return kvs.filter(kv => {
|
||||||
|
return kv.isFile();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toXML() {
|
toXML() {
|
||||||
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
||||||
xml += this.jmeterTestPlan.toXML();
|
xml += this.jmeterTestPlan.toXML();
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
upload(file) {
|
upload(file) {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.fileList.push(file.file);
|
this.fileList.push(file.file);
|
||||||
this.result = this.$fileUpload('/test/case/import/' + this.projectId, file.file, {}, response => {
|
this.result = this.$fileUpload('/test/case/import/' + this.projectId, file.file, null, {}, response => {
|
||||||
let res = response.data;
|
let res = response.data;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
this.$success(this.$t('test_track.case.import.success'));
|
this.$success(this.$t('test_track.case.import.success'));
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 06fc0a321a9886419be5c607ddaa6b40efb5179b
|
Subproject commit 7e4d80cc2b870a8cac6dbb9fe6711ab6041faf6d
|
|
@ -142,9 +142,16 @@ export default {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Vue.prototype.$fileUpload = function (url, file, param, success, failure) {
|
Vue.prototype.$fileUpload = function (url, file, files, param, success, failure) {
|
||||||
let formData = new FormData();
|
let formData = new FormData();
|
||||||
formData.append("file", file);
|
if (file) {
|
||||||
|
formData.append("file", file);
|
||||||
|
}
|
||||||
|
if (files) {
|
||||||
|
files.forEach(f => {
|
||||||
|
formData.append("files", f);
|
||||||
|
})
|
||||||
|
}
|
||||||
formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
|
formData.append('request', new Blob([JSON.stringify(param)], {type: "application/json"}));
|
||||||
let axiosRequestConfig = {
|
let axiosRequestConfig = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|
|
@ -187,4 +187,11 @@ export function removeGoBackListener(callback) {
|
||||||
window.removeEventListener('popstate', callback);
|
window.removeEventListener('popstate', callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUUID() {
|
||||||
|
function S4() {
|
||||||
|
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
|
||||||
|
}
|
||||||
|
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue