diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/MetersphereApiScenarioExportResponse.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/MetersphereApiScenarioExportResponse.java index ad4cabf912..0db43aebbb 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/MetersphereApiScenarioExportResponse.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/export/MetersphereApiScenarioExportResponse.java @@ -1,7 +1,5 @@ package io.metersphere.api.dto.export; -import io.metersphere.api.constants.ApiScenarioStepRefType; -import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.domain.ApiScenarioCsv; import io.metersphere.api.dto.converter.ApiDefinitionDetail; import io.metersphere.api.dto.definition.ApiTestCaseDTO; @@ -9,7 +7,6 @@ import io.metersphere.api.dto.scenario.ApiScenarioDetail; import io.metersphere.api.dto.scenario.ApiScenarioStepDTO; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.HashMap; @@ -55,20 +52,4 @@ public class MetersphereApiScenarioExportResponse extends ApiScenarioExportRespo public void addExportScenario(ApiScenarioDetail apiScenarioDetail) { exportScenarioList.add(apiScenarioDetail); } - - public void setStepTypeToCustomRequest() { - scenarioStepList.forEach(step -> { - if (StringUtils.equalsAnyIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name(), ApiScenarioStepType.API_CASE.name())) { - step.setStepType(ApiScenarioStepType.CUSTOM_REQUEST.name()); - } - }); - } - - public void setRefTypeToCopy() { - scenarioStepList.forEach(step -> { - if (StringUtils.equalsAnyIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name(), ApiScenarioStepType.API_SCENARIO.name(), ApiScenarioStepType.API_CASE.name())) { - step.setRefType(ApiScenarioStepRefType.COPY.name()); - } - }); - } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java index 1440918997..35e96e13cf 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/ApiScenarioDataTransferService.java @@ -1,6 +1,7 @@ package io.metersphere.api.service; import io.metersphere.api.constants.ApiDefinitionStatus; +import io.metersphere.api.constants.ApiScenarioStepRefType; import io.metersphere.api.constants.ApiScenarioStepType; import io.metersphere.api.domain.*; import io.metersphere.api.dto.ApiFile; @@ -179,7 +180,7 @@ public class ApiScenarioDataTransferService { } //解析 ApiScenarioPreImportAnalysisResult preImportAnalysisResult = this.importAnalysis( - parseResult, request.getOperator(), request.getProjectId(), request.getModuleId(), apiScenarioModuleService.getTree(request.getProjectId())); + parseResult, request.getOperator(), request.getProjectId(), request.getModuleId(), apiScenarioModuleService.getImportTreeNodeList(request.getProjectId())); //存储 this.save(preImportAnalysisResult, request.getProjectId(), request.getOperator(), request.isCoverData()); } @@ -938,7 +939,7 @@ public class ApiScenarioDataTransferService { { for (Map.Entry> entry : projectScenarioMap.entrySet()) { String targetProjectId = entry.getKey(); - List apiScenarioModules = apiScenarioModuleService.getTree(targetProjectId); + List apiScenarioModules = apiScenarioModuleService.getImportTreeNodeList(targetProjectId); Map moduleIdPathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); Map modulePathMap = apiScenarioModules.stream().collect(Collectors.toMap(BaseTreeNode::getPath, k -> k, (k1, k2) -> k1)); @@ -1020,7 +1021,7 @@ public class ApiScenarioDataTransferService { if (CollectionUtils.isEmpty(ids)) { return null; } - Map moduleMap = this.apiScenarioModuleService.getTree(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); + Map moduleMap = this.apiScenarioModuleService.getImportTreeNodeList(request.getProjectId()).stream().collect(Collectors.toMap(BaseTreeNode::getId, BaseTreeNode::getPath)); String fileFolder = tmpDir.getPath() + File.separatorChar + request.getFileId(); AtomicInteger fileIndex = new AtomicInteger(1); @@ -1150,9 +1151,47 @@ public class ApiScenarioDataTransferService { } } else { + // 普通导出,所有的引用都改为复制,并且Api、ApiCase改为CUSTOM_REQUEST - response.setRefTypeToCopy(); - response.setStepTypeToCustomRequest(); + Map stepApiDefinitionMap = new HashMap<>(); + Map stepApiCaseMap = new HashMap<>(); + response.getScenarioStepList().forEach(step -> { + if (StringUtils.equalsAnyIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name(), ApiScenarioStepType.API_SCENARIO.name(), ApiScenarioStepType.API_CASE.name())) { + // 引用的api、case转换为自定义步骤时,要对应的api、case也一并导出 + if (!response.getScenarioStepBlobMap().containsKey(step.getId())) { + if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API.name())) { + stepApiDefinitionMap.put(step.getId(), step.getResourceId()); + } else if (StringUtils.equalsIgnoreCase(step.getStepType(), ApiScenarioStepType.API_CASE.name())) { + stepApiCaseMap.put(step.getId(), step.getResourceId()); + } + } + step.setRefType(ApiScenarioStepRefType.COPY.name()); + step.setStepType(ApiScenarioStepType.CUSTOM_REQUEST.name()); + } + }); + Map appendBlobMap = new HashMap<>(); + if (MapUtils.isNotEmpty(stepApiDefinitionMap)) { + List apiDefinitionWithBlobs = extApiDefinitionMapper.selectApiDefinitionWithBlob(new ArrayList<>(stepApiDefinitionMap.values())); + Map idMap = apiDefinitionWithBlobs.stream().collect(Collectors.toMap(ApiDefinitionWithBlob::getId, Function.identity())); + stepApiDefinitionMap.forEach((stepId, apiId) -> { + ApiDefinitionWithBlob api = idMap.get(apiId); + if (api != null) { + appendBlobMap.put(stepId, new String(api.getRequest(), StandardCharsets.UTF_8)); + } + }); + } + if (MapUtils.isNotEmpty(stepApiCaseMap)) { + List apiTestCaseList = extApiTestCaseMapper.selectAllDetailByIds(new ArrayList<>(stepApiCaseMap.values())); + Map idMap = apiTestCaseList.stream().collect(Collectors.toMap(ApiTestCaseWithBlob::getId, Function.identity())); + stepApiCaseMap.forEach((stepId, apiCaseId) -> { + ApiTestCaseWithBlob apiTestCase = idMap.get(apiCaseId); + if (apiTestCase != null) { + appendBlobMap.put(stepId, new String(apiTestCase.getRequest(), StandardCharsets.UTF_8)); + } + }); + } + + response.getScenarioStepBlobMap().putAll(appendBlobMap); } return response; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java index d23634fc73..0661869682 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/scenario/ApiScenarioModuleService.java @@ -35,6 +35,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -65,12 +66,36 @@ public class ApiScenarioModuleService extends ModuleTreeService { return super.buildTreeAndCountResource(fileModuleList, true, Translator.get(UNPLANNED_SCENARIO)); } - public List getTree(String projectId) { + public List getImportTreeNodeList(String projectId) { + //接口的树结构是 模块:子模块+接口 接口为非delete状态的 - List fileModuleList = extApiScenarioModuleMapper.selectBaseByRequest(new ApiScenarioModuleRequest() {{ + List traverseList = extApiScenarioModuleMapper.selectBaseByRequest(new ApiScenarioModuleRequest() {{ this.setProjectId(projectId); }}); - return super.buildTreeAndCountResource(fileModuleList, true, Translator.get(UNPLANNED_SCENARIO)); + + List baseTreeNodeList = new ArrayList<>(); + BaseTreeNode defaultNode = new BaseTreeNode(ModuleConstants.DEFAULT_NODE_ID, Translator.get(UNPLANNED_SCENARIO), ModuleConstants.NODE_TYPE_DEFAULT, ModuleConstants.ROOT_NODE_PARENT_ID); + defaultNode.setPath(StringUtils.join("/", defaultNode.getName())); + baseTreeNodeList.add(defaultNode); + int lastSize = 0; + Map baseTreeNodeMap = new HashMap<>(); + while (CollectionUtils.isNotEmpty(traverseList) && traverseList.size() != lastSize) { + lastSize = traverseList.size(); + List notMatchedList = new ArrayList<>(); + for (BaseTreeNode treeNode : traverseList) { + if (!baseTreeNodeMap.containsKey(treeNode.getParentId()) && !StringUtils.equalsIgnoreCase(treeNode.getParentId(), ModuleConstants.ROOT_NODE_PARENT_ID)) { + notMatchedList.add(treeNode); + continue; + } + BaseTreeNode node = new BaseTreeNode(treeNode.getId(), treeNode.getName(), treeNode.getType(), treeNode.getParentId()); + node.genModulePath(baseTreeNodeMap.get(treeNode.getParentId())); + baseTreeNodeMap.put(treeNode.getId(), node); + + baseTreeNodeList.add(node); + } + traverseList = notMatchedList; + } + return baseTreeNodeList; } public List getTreeOnlyIdsAndResourceCount(ApiScenarioModuleRequest request, List moduleCountDTOList) { diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java index 0a810660fa..f3b15ddb11 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiScenarioControllerImportAndExportTests.java @@ -1,6 +1,6 @@ package io.metersphere.api.controller; -import io.metersphere.api.dto.definition.ApiDefinitionBatchExportRequest; +import io.metersphere.api.dto.definition.ApiScenarioBatchExportRequest; import io.metersphere.api.dto.export.MetersphereApiScenarioExportResponse; import io.metersphere.api.dto.scenario.ApiScenarioImportRequest; import io.metersphere.api.utils.ApiDataUtils; @@ -34,6 +34,7 @@ import org.springframework.util.MultiValueMap; import java.io.File; import java.io.FileInputStream; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -62,11 +63,6 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest { initProject.setEnable(true); initProject.setUserIds(List.of("admin")); project = commonProjectService.add(initProject, "admin", "/organization-project/add", OperationLogModule.SETTING_ORGANIZATION_PROJECT); - // ArrayList moduleList = new ArrayList<>(List.of("workstation", "testPlan", "bugManagement", "caseManagement", "apiTest", "uiTest", "loadTest")); - // Project updateProject = new Project(); - // updateProject.setId(importProject.getId()); - // updateProject.setModuleSetting(JSON.toJSONString(moduleList)); - // projectMapper.updateByPrimaryKeySelective(updateProject); } } @@ -101,47 +97,52 @@ public class ApiScenarioControllerImportAndExportTests extends BaseTest { public void testExport() throws Exception { MsFileUtils.deleteDir("/tmp/api-scenario-export/"); - ApiDefinitionBatchExportRequest exportRequest = new ApiDefinitionBatchExportRequest(); - String fileId = IDGenerator.nextStr(); - exportRequest.setProjectId(project.getId()); - exportRequest.setFileId(fileId); - exportRequest.setSelectAll(true); - exportRequest.setExportApiCase(true); - exportRequest.setExportApiMock(true); - MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "metersphere", exportRequest); - String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); + List exportAllRelatedData = new ArrayList<>() {{ + this.add(true); + this.add(false); + }}; + for (Boolean isAllRelatedData : exportAllRelatedData) { + ApiScenarioBatchExportRequest exportRequest = new ApiScenarioBatchExportRequest(); + String fileId = IDGenerator.nextStr(); + exportRequest.setProjectId(project.getId()); + exportRequest.setFileId(fileId); + exportRequest.setSelectAll(true); + exportRequest.setExportAllRelatedData(isAllRelatedData); + MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_POST_EXPORT + "metersphere", exportRequest); + String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8); - JSON.parseObject(returnData, ResultHolder.class).getData().toString(); - Assertions.assertTrue(StringUtils.isNotBlank(fileId)); - List 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); + JSON.parseObject(returnData, ResultHolder.class).getData().toString(); + Assertions.assertTrue(StringUtils.isNotBlank(fileId)); + List 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, 1); + String fileContent = FileUtils.readFileToString(files[0], StandardCharsets.UTF_8); + + MetersphereApiScenarioExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiScenarioExportResponse.class); + + Assertions.assertEquals(exportResponse.getExportScenarioList().size(), 8); + + MsFileUtils.deleteDir("/tmp/api-scenario-export/"); } - - 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, 1); - String fileContent = FileUtils.readFileToString(files[0], StandardCharsets.UTF_8); - - MetersphereApiScenarioExportResponse exportResponse = ApiDataUtils.parseObject(fileContent, MetersphereApiScenarioExportResponse.class); - - Assertions.assertEquals(exportResponse.getExportScenarioList().size(), 8); - - MsFileUtils.deleteDir("/tmp/api-scenario-export/"); } private MvcResult download(String projectId, String fileId) throws Exception {