feat(接口测试): 场景导出JMX

This commit is contained in:
Jianguo-Genius 2024-11-04 14:51:31 +08:00 committed by Craftsman
parent d629d607ea
commit 225ceea3e4
6 changed files with 254 additions and 10 deletions

View File

@ -151,7 +151,11 @@ public class TempFileUtils {
}
}
try {
FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes());
if (exportResponse instanceof String exportResponseStr) {
FileUtils.writeByteArrayToFile(createFile, exportResponseStr.getBytes());
} else {
FileUtils.writeByteArrayToFile(createFile, JSON.toJSONString(exportResponse).getBytes());
}
} catch (Exception e) {
LogUtils.error(e);
}

View File

@ -0,0 +1,18 @@
package io.metersphere.api.dto.export;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* @author wx
*/
@Data
public class JMeterApiScenarioExportResponse extends ApiScenarioExportResponse {
Map<String, String> scenarioJmxMap = new HashMap<>();
public void addJmx(String key, String jmx) {
scenarioJmxMap.put(key, jmx);
}
}

View File

@ -6,14 +6,19 @@ import io.metersphere.api.constants.ApiScenarioStepRefType;
import io.metersphere.api.constants.ApiScenarioStepType;
import io.metersphere.api.domain.*;
import io.metersphere.api.dto.ApiFile;
import io.metersphere.api.dto.ApiScenarioParamConfig;
import io.metersphere.api.dto.ApiScenarioParseTmpParam;
import io.metersphere.api.dto.converter.ApiDefinitionDetail;
import io.metersphere.api.dto.converter.ApiDefinitionExportDetail;
import io.metersphere.api.dto.converter.ApiScenarioImportParseResult;
import io.metersphere.api.dto.converter.ApiScenarioPreImportAnalysisResult;
import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest;
import io.metersphere.api.dto.debug.ApiResourceRunRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.export.ApiScenarioExportResponse;
import io.metersphere.api.dto.export.JMeterApiScenarioExportResponse;
import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse;
import io.metersphere.api.dto.request.MsScenario;
import io.metersphere.api.dto.scenario.*;
import io.metersphere.api.mapper.*;
import io.metersphere.api.parser.ApiScenarioImportParser;
@ -24,6 +29,7 @@ import io.metersphere.api.service.definition.ApiDefinitionModuleService;
import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.api.service.scenario.ApiScenarioLogService;
import io.metersphere.api.service.scenario.ApiScenarioModuleService;
import io.metersphere.api.service.scenario.ApiScenarioRunService;
import io.metersphere.api.service.scenario.ApiScenarioService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.ApiDefinitionImportUtils;
@ -39,6 +45,7 @@ import io.metersphere.project.service.PermissionCheckService;
import io.metersphere.project.utils.FileDownloadUtils;
import io.metersphere.sdk.constants.*;
import io.metersphere.sdk.dto.ExportMsgDTO;
import io.metersphere.sdk.dto.api.task.ApiRunRetryConfig;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.file.FileRequest;
import io.metersphere.sdk.util.*;
@ -118,6 +125,12 @@ public class ApiScenarioDataTransferService {
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private ExtFileAssociationMapper extFileAssociationMapper;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
@Resource
private ApiTestCaseBlobMapper apiTestCaseBlobMapper;
@Resource
private ApiScenarioBlobMapper apiScenarioBlobMapper;
@Resource
private FileService fileService;
@ -1129,14 +1142,25 @@ public class ApiScenarioDataTransferService {
if (CollectionUtils.isEmpty(ids)) {
return null;
}
Map<String, String> moduleMap = this.apiScenarioModuleService.getImportTreeNodeList(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath));
Map<String, String> moduleMap;
if (StringUtils.equalsIgnoreCase(exportType, "metersphere")) {
moduleMap = this.apiScenarioModuleService.getImportTreeNodeList(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath));
} else {
moduleMap = null;
}
String fileFolder = tmpDir.getPath() + File.separatorChar + request.getFileId();
AtomicInteger fileIndex = new AtomicInteger(1);
SubListUtils.dealForSubList(ids, 500, subList -> {
request.setSelectIds(subList);
ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponse(request, moduleMap);
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_" + fileIndex.getAndIncrement() + ".ms", exportResponse);
if (StringUtils.equalsIgnoreCase(exportType, "metersphere")) {
ApiScenarioExportResponse exportResponse = this.genMetersphereExportResponseByMetersphere(request, moduleMap);
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + "scenario_" + fileIndex.getAndIncrement() + ".ms", exportResponse);
} else {
JMeterApiScenarioExportResponse exportResponse = this.genMetersphereExportResponseByJmx(request);
exportResponse.getScenarioJmxMap().forEach((k, v) -> {
TempFileUtils.writeExportFile(fileFolder + File.separatorChar + k + ".jmx", v);
});
}
});
File zipFile = MsFileUtils.zipFile(tmpDir.getPath(), request.getFileId());
if (zipFile == null) {
@ -1167,7 +1191,93 @@ public class ApiScenarioDataTransferService {
}
}
private ApiScenarioExportResponse genMetersphereExportResponse(ApiScenarioBatchExportRequest request, Map<String, String> moduleMap) {
@Resource
private ApiScenarioRunService apiScenarioRunService;
@Resource
private ApiExecuteService apiExecuteService;
private JMeterApiScenarioExportResponse genMetersphereExportResponseByJmx(ApiScenarioBatchExportRequest request) {
JMeterApiScenarioExportResponse response = new JMeterApiScenarioExportResponse();
List<String> apiIdList = new ArrayList<>();
List<String> apiCaseList = new ArrayList<>();
List<String> scenarioList = new ArrayList<>();
List<ApiScenarioDetail> apiScenarioDetailList = new ArrayList<>();
request.getSelectIds().forEach(id -> {
ApiScenarioDetail apiScenarioDetail = apiScenarioRunService.getForRun(id);
apiScenarioDetailList.add(apiScenarioDetail);
if (CollectionUtils.isNotEmpty(apiScenarioDetail.getSteps())) {
apiScenarioDetail.getSteps().forEach(step -> {
if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name())) {
apiIdList.add(step.getResourceId());
} else if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API_CASE.name())) {
apiCaseList.add(step.getResourceId());
} else if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API_SCENARIO.name())) {
scenarioList.add(step.getResourceId());
}
});
}
});
Map<String, ApiDefinitionBlob> apiBlobMap = new HashMap<>();
Map<String, ApiTestCaseBlob> apiTestCaseBlobMap = new HashMap<>();
Map<String, ApiScenarioBlob> scenarioBlobMap = new HashMap<>();
if (CollectionUtils.isNotEmpty(apiIdList)) {
ApiDefinitionBlobExample apiDefinitionBlobExample = new ApiDefinitionBlobExample();
apiDefinitionBlobExample.createCriteria().andIdIn(apiIdList);
apiBlobMap = apiDefinitionBlobMapper.selectByExampleWithBLOBs(apiDefinitionBlobExample)
.stream().collect(Collectors.toMap(ApiDefinitionBlob::getId, Function.identity()));
}
if (CollectionUtils.isNotEmpty(apiCaseList)) {
ApiTestCaseBlobExample example = new ApiTestCaseBlobExample();
example.createCriteria().andIdIn(apiCaseList);
apiTestCaseBlobMap = apiTestCaseBlobMapper.selectByExampleWithBLOBs(example)
.stream().collect(Collectors.toMap(ApiTestCaseBlob::getId, Function.identity()));
}
if (CollectionUtils.isNotEmpty(scenarioList)) {
ApiScenarioBlobExample example = new ApiScenarioBlobExample();
example.createCriteria().andIdIn(scenarioList);
scenarioBlobMap = apiScenarioBlobMapper.selectByExampleWithBLOBs(example)
.stream().collect(Collectors.toMap(ApiScenarioBlob::getId, Function.identity()));
}
for (ApiScenarioDetail apiScenarioDetail : apiScenarioDetailList) {
String jmx = getJmx(apiScenarioDetail, apiBlobMap, apiTestCaseBlobMap, scenarioBlobMap);
response.addJmx(apiScenarioDetail.getNum() + "_" + apiScenarioDetail.getName(), jmx);
}
return response;
}
private String getJmx(ApiScenarioDetail apiScenarioDetail, Map<String, ApiDefinitionBlob> apiBlobMap,
Map<String, ApiTestCaseBlob> apiTestCaseBlobMap, Map<String, ApiScenarioBlob> scenarioBlobMap) {
String envId = apiScenarioDetail.getEnvironmentId();
boolean envGroup = apiScenarioDetail.getGrouped();
// 解析生成待执行的场景树
MsScenario msScenario = apiScenarioRunService.getMsScenario(apiScenarioDetail);
ApiScenarioParseParam parseParam = apiScenarioRunService.getApiScenarioParseParam(apiScenarioDetail);
parseParam.setEnvironmentId(envId);
parseParam.setGrouped(envGroup);
msScenario.setResourceId(apiScenarioDetail.getId());
ApiScenarioParseTmpParam tmpParam = apiScenarioRunService.parse(msScenario, apiBlobMap, apiTestCaseBlobMap, scenarioBlobMap, apiScenarioDetail.getSteps(), parseParam);
ApiResourceRunRequest runRequest = apiScenarioRunService.getApiResourceRunRequest(msScenario, tmpParam);
ApiScenarioParamConfig parseConfig = apiScenarioRunService.getApiScenarioParamConfig(apiScenarioDetail.getProjectId(), parseParam, tmpParam.getScenarioParseEnvInfo());
parseConfig.setReportId(StringUtils.EMPTY);
parseConfig.setTaskItemId(StringUtils.EMPTY);
parseConfig.setRetryConfig(new ApiRunRetryConfig());
return apiExecuteService.parseExecuteScript(runRequest.getTestElement(), parseConfig);
}
private ApiScenarioExportResponse genMetersphereExportResponseByMetersphere(ApiScenarioBatchExportRequest request, Map<String, String> moduleMap) {
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
MetersphereApiScenarioExportResponse response = apiScenarioService.selectAndSortScenarioDetailWithIds(request.getSelectIds(), moduleMap);
response.setProjectId(project.getId());

View File

@ -646,6 +646,59 @@ public class ApiScenarioRunService {
return tmpParam;
}
public ApiScenarioParseTmpParam parse(MsScenario msScenario,
Map<String, ApiDefinitionBlob> apiBlobMap,
Map<String, ApiTestCaseBlob> apiTestCaseBlobMap,
Map<String, ApiScenarioBlob> scenarioBlobMap,
List<? extends ApiScenarioStepCommonDTO> steps,
ApiScenarioParseParam parseParam) {
ApiScenarioParseTmpParam tmpParam = new ApiScenarioParseTmpParam();
Map<String, List<String>> refResourceMap = new HashMap<>();
buildRefResourceIdMap(steps, refResourceMap);
Map<String, String> resourceBlobMap = new HashMap<>();
List<String> apiIds = refResourceMap.get(ApiScenarioStepType.API.name());
if (CollectionUtils.isNotEmpty(apiIds)) {
apiIds.forEach(apiId -> {
ApiDefinitionBlob blob = apiBlobMap.get(apiId);
if (blob != null) {
resourceBlobMap.put(blob.getId(), new String(blob.getRequest()));
}
});
}
List<String> apiCaseIds = refResourceMap.get(ApiScenarioStepType.API_CASE.name());
if (CollectionUtils.isNotEmpty(apiCaseIds)) {
apiCaseIds.forEach(apiCaseId -> {
ApiTestCaseBlob blob = apiTestCaseBlobMap.get(apiCaseId);
if (blob != null) {
resourceBlobMap.put(blob.getId(), new String(blob.getRequest()));
}
});
}
List<String> apiScenarioIds = refResourceMap.get(ApiScenarioStepType.API_SCENARIO.name());
if (CollectionUtils.isNotEmpty(apiScenarioIds)) {
apiScenarioIds.forEach(apiScenarioId -> {
ApiScenarioBlob blob = scenarioBlobMap.get(apiScenarioId);
if (blob != null) {
resourceBlobMap.put(blob.getId(), new String(blob.getConfig()));
}
});
}
tmpParam.setResourceDetailMap(resourceBlobMap);
// 查询复制的步骤详情
tmpParam.setStepDetailMap(getStepDetailMap(steps, parseParam.getStepDetails()));
// 获取场景环境相关配置
tmpParam.setScenarioParseEnvInfo(getScenarioParseEnvInfo(refResourceMap, parseParam.getEnvironmentId(), parseParam.getGrouped()));
parseStep2MsElement(msScenario, steps, tmpParam, msScenario.getResourceId());
// 设置 HttpElement 的模块信息
setApiDefinitionExecuteInfo(tmpParam.getUniqueIdStepMap(), tmpParam.getStepTypeHttpElementMap());
// 设置使用脚本前后置的公共脚本信息
apiCommonService.setCommonElementEnableCommonScriptInfo(tmpParam.getCommonElements());
apiCommonService.setScriptElementEnableCommonScriptInfo(tmpParam.getScriptElements());
return tmpParam;
}
private void buildRefResourceIdMap(List<? extends ApiScenarioStepCommonDTO> steps, Map<String, List<String>> refResourceIdMap) {
for (ApiScenarioStepCommonDTO step : steps) {
if (apiScenarioService.isRefOrPartialRef(step.getRefType()) && BooleanUtils.isTrue(step.getEnable())) {

View File

@ -169,6 +169,40 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest {
MsFileUtils.deleteDir("/tmp/api-scenario-export/");
}
// jmx export
ApiScenarioBatchExportRequest exportRequest = new ApiScenarioBatchExportRequest();
String fileId = IDGenerator.nextStr();
exportRequest.setProjectId(project.getId());
exportRequest.setFileId(fileId);
exportRequest.setSelectAll(true);
exportRequest.setExportAllRelatedData(false);
MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "jmeter", exportRequest);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
JSON.parseObject(returnData, ResultHolder.class).getData().toString();
Assertions.assertTrue(StringUtils.isNotBlank(fileId));
List<ExportTask> taskList = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId);
while (CollectionUtils.isEmpty(taskList)) {
Thread.sleep(1000);
taskList = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId);
}
ExportTask task = taskList.getFirst();
while (!StringUtils.equalsIgnoreCase(task.getState(), ExportConstants.ExportState.SUCCESS.name())) {
Thread.sleep(1000);
task = exportTaskManager.getExportTasks(exportRequest.getProjectId(), null, null, "admin", fileId).getFirst();
}
mvcResult = this.download(exportRequest.getProjectId(), fileId);
byte[] fileBytes = mvcResult.getResponse().getContentAsByteArray();
File zipFile = new File("/tmp/api-scenario-export/downloadFiles.zip");
FileUtils.writeByteArrayToFile(zipFile, fileBytes);
File[] files = MsFileUtils.unZipFile(zipFile, "/tmp/api-scenario-export/unzip/");
assert files != null;
Assertions.assertEquals(files.length, 6);
MsFileUtils.deleteDir("/tmp/api-scenario-export/");
}
private MvcResult download(String projectId, String fileId) throws Exception {

View File

@ -6,7 +6,18 @@
class="ms-modal-upload ms-modal-medium"
:width="400"
>
<div class="mb-[16px] flex items-center gap-[8px]">
<div class="mb-[16px] flex items-center gap-[16px]">
<div
v-for="item of platformList"
:key="item.value"
:class="`import-item ${exportPlatform === item.value ? 'import-item--active' : ''}`"
@click="() => setActiveImportFormat(item.value)"
>
<div class="text-[var(--color-text-1)]">{{ item.name }}</div>
</div>
</div>
<div v-show="exportPlatform === 'MeterSphere'" class="mb-[16px] flex items-center gap-[8px]">
<a-switch v-model:model-value="exportTypeRadio" size="small"></a-switch>
{{ t('apiScenario.export.type.all') }}
<a-tooltip :content="t('apiScenario.export.simple.tooltip')" position="tl">
@ -47,6 +58,18 @@
import useAppStore from '@/store/modules/app';
import { downloadByteFile, getGenerateId } from '@/utils';
import { RequestImportFormat } from '@/enums/apiEnum';
const platformList: { name: string; value: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter }[] = [
{
name: 'MeterSphere',
value: RequestImportFormat.MeterSphere,
},
{
name: 'Jmeter',
value: RequestImportFormat.Jmeter,
},
];
const appStore = useAppStore();
const { t } = useI18n();
@ -61,11 +84,13 @@
const exportLoading = ref(false);
const exportTypeRadio = ref(false);
const exportPlatform = ref(RequestImportFormat.MeterSphere);
function cancelExport() {
visible.value = false;
}
function setActiveImportFormat(format: RequestImportFormat.MeterSphere | RequestImportFormat.Jmeter) {
exportPlatform.value = format;
}
const websocket = ref<WebSocket>();
const reportId = ref('');
const isShowExportingMessage = ref(false); //
@ -192,7 +217,7 @@
sort: props.sorter || {},
fileId: reportId.value,
},
'METERSPHERE'
exportPlatform.value
);
showExportingMessage(res);
visible.value = false;