feat(接口测试): 接口测试支持文件上传

This commit is contained in:
chenjianxing 2020-08-25 13:42:31 +08:00
parent 9a9d702aca
commit d54b32ced3
20 changed files with 409 additions and 61 deletions

View File

@ -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")

View File

@ -24,4 +24,6 @@ public class SaveAPITestRequest {
private Schedule schedule; private Schedule schedule;
private String triggerMode; private String triggerMode;
private List<String> bodyUploadIds;
} }

View File

@ -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() {

View File

@ -0,0 +1,10 @@
package io.metersphere.api.dto.scenario.request;
import lombok.Data;
@Data
public class BodyFile {
private String id;
private String name;
}

View File

@ -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) {

View File

@ -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

View File

@ -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;
}); });
} }

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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";

View File

@ -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";

View File

@ -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);

View File

@ -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();

View File

@ -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

View File

@ -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',

View File

@ -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());
}