diff --git a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/DefaultRepositoryDir.java b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/DefaultRepositoryDir.java index 6220f5f800..49bab598dd 100644 --- a/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/DefaultRepositoryDir.java +++ b/backend/framework/sdk/src/main/java/io/metersphere/sdk/constants/DefaultRepositoryDir.java @@ -48,6 +48,12 @@ public class DefaultRepositoryDir { private static final String PROJECT_API_DEBUG_DIR = PROJECT_DIR + "/api-debug/%s"; private static final String PROJECT_BUG_DIR = PROJECT_DIR + "/bug/%s"; + /** + * 接口定义相关文件的存储目录 + * project/{projectId}/api-definition/{apiId} + */ + private static final String PROJECT_API_DEFINITION_DIR = PROJECT_DIR + "/api-definition/%s"; + /*------ end: 项目下资源目录 --------*/ @@ -82,4 +88,8 @@ public class DefaultRepositoryDir { public static String getApiDebugDir(String projectId, String apiDebugId) { return String.format(PROJECT_API_DEBUG_DIR, projectId, apiDebugId); } + + public static String getApiDefinitionDir(String projectId, String apiId) { + return String.format(PROJECT_API_DEFINITION_DIR, projectId, apiId); + } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java index 9dcbb25b95..c4efd0327a 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/controller/definition/ApiDefinitionController.java @@ -116,12 +116,41 @@ public class ApiDefinitionController { } @PostMapping("/page") - @Operation(summary = "接口测试-接口管理-接口列表") + @Operation(summary = "接口测试-接口管理-接口列表(deleted 状态为 0 时为回收站数据)") @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ) public Pager> getPage(@Validated @RequestBody ApiDefinitionPageRequest request) { Page page = PageHelper.startPage(request.getCurrent(), request.getPageSize(), StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc"); - return PageUtils.setPageInfo(page, apiDefinitionService.getApiDefinitionPage(request, false)); + return PageUtils.setPageInfo(page, apiDefinitionService.getApiDefinitionPage(request)); + } + + @PostMapping(value = "/restore") + @Operation(summary = "接口测试-接口管理-恢复回收站接口定义") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) + @Log(type = OperationLogType.UPDATE, expression = "#msClass.restoreLog(#request)", msClass = ApiDefinitionLogService.class) + public void restore(@Validated @RequestBody ApiDefinitionDeleteRequest request) { + apiDefinitionService.restore(request, SessionUtils.getUserId()); + } + @PostMapping(value = "/recycle-del") + @Operation(summary = "接口测试-接口管理-删除回收站接口定义") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE) + @Log(type = OperationLogType.DELETE, expression = "#msClass.recycleDelLog(#request)", msClass = ApiDefinitionLogService.class) + public void recycleDel(@Validated @RequestBody ApiDefinitionDeleteRequest request) { + apiDefinitionService.recycleDel(request, SessionUtils.getUserId()); + } + @PostMapping(value = "/batch-restore") + @Operation(summary = "接口测试-接口管理-批量从回收站恢复接口定义") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_UPDATE) + @Log(type = OperationLogType.UPDATE, expression = "#msClass.batchRestoreLog(#request)", msClass = ApiDefinitionLogService.class) + public void batchRestore(@Validated @RequestBody ApiDefinitionBatchRequest request) { + apiDefinitionService.batchRestore(request, SessionUtils.getUserId()); + } + + @PostMapping(value = "/batch-recycle-del") + @Operation(summary = "接口测试-接口管理-批量从回收站删除接口定义") + @RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_DELETE) + public void batchRecycleDel(@Validated @RequestBody ApiDefinitionBatchRequest request) { + apiDefinitionService.batchRecycleDel(request, SessionUtils.getUserId()); } } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java index aee54576e3..e3d165ae09 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionAddRequest.java @@ -9,6 +9,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; import java.util.LinkedHashSet; +import java.util.List; /** * @author lan @@ -63,4 +64,17 @@ public class ApiDefinitionAddRequest implements Serializable { @Schema(description = "请求内容") @NotBlank private String request; + + @Schema(description = "请求内容") + @NotBlank + private String response; + + /** + * 文件ID列表 + * 需要和上传的文件顺序保持一致 + * 为了解决文件名称重复的问题,需要把文件和ID一一对应 + * 创建时先按ID创建目录,再把文件放入目录 + */ + @Schema(description = "接口所需的所有文件资源ID,与上传的文件顺序保持一致") + private List fileIds; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java index ba66441e28..701017f542 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDTO.java @@ -5,21 +5,21 @@ import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.experimental.Accessors; + +import java.util.List; /** * @author lan */ @Data @EqualsAndHashCode(callSuper = false) -@Accessors(chain = true) public class ApiDefinitionDTO extends ApiDefinition{ @Schema(description = "请求内容") private AbstractMsTestElement request; @Schema(description = "响应内容") - private String response; + private List response; @Schema(description = "创建人名称") private String createUserName; diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDeleteRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDeleteRequest.java index 289ae9bc79..b48aecb2e9 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDeleteRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionDeleteRequest.java @@ -1,5 +1,6 @@ package io.metersphere.api.dto.definition; +import io.metersphere.sdk.constants.ModuleConstants; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; @@ -27,8 +28,13 @@ public class ApiDefinitionDeleteRequest implements Serializable { @Size(min = 1, max = 50, message = "{api_definition.project_id.length_range}") private String projectId; + @Schema(description = "接口协议", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank(message = "{api_debug.protocol.not_blank}") + @Size(min = 1, max = 20, message = "{api_debug.protocol.length_range}") + private String protocol = ModuleConstants.NODE_PROTOCOL_HTTP; + @Schema(description = "删除列表版本/删除全部版本") - private Boolean deleteAll; + private Boolean deleteAll = false; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionPageRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionPageRequest.java index 568d6764d5..67559451cc 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionPageRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionPageRequest.java @@ -45,4 +45,7 @@ public class ApiDefinitionPageRequest extends BasePageRequest { @Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)") private List moduleIds; + + @Schema(description = "删除状态(状态为 0 时为回收站数据)") + private Boolean deleted = false; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionUpdateRequest.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionUpdateRequest.java index f567a41c7b..2b4bf28ab0 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionUpdateRequest.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/ApiDefinitionUpdateRequest.java @@ -7,6 +7,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serial; +import java.util.List; /** * @author lan @@ -23,4 +24,11 @@ public class ApiDefinitionUpdateRequest extends ApiDefinitionAddRequest { @Size(min = 1, max = 50, message = "{api_definition.id.length_range}") private String id; + /** + * 新上传的文件ID + * 为了解决文件名称重复的问题,需要把文件和ID一一对应 + * 创建时先按ID创建目录,再把文件放入目录 + */ + @Schema(description = "新上传的文件ID,与上传的文件顺序保持一致") + private List addFileIds; } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java new file mode 100644 index 0000000000..e6c1211705 --- /dev/null +++ b/backend/services/api-test/src/main/java/io/metersphere/api/dto/definition/HttpResponse.java @@ -0,0 +1,51 @@ +package io.metersphere.api.dto.definition; + +import io.metersphere.sdk.constants.ModuleConstants; +import io.metersphere.sdk.dto.api.request.http.Header; +import io.metersphere.sdk.dto.api.request.http.body.Body; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * @author: LAN + * @date: 2023/11/21 18:12 + * @version: 1.0 + */ +@Data +public class HttpResponse { + + @Schema(description = "响应编号") + private Integer id; + + @Schema(description = "响应类型") + private String type = ModuleConstants.NODE_PROTOCOL_HTTP; + + @Schema(description = "响应名称") + private String name; + + /** + * 请求头 + */ + @Schema(description = "响应请求头") + private List
headers; + + /** + * 请求体 + */ + @Schema(description = "响应请求体") + private Body body; + + /** + * 请求方法 + */ + @Schema(description = "响应请求方法") + private String statusCode; + + /** + * 默认标识 + */ + @Schema(description = "默认响应标识") + private Boolean defaultFlag; +} diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java index e66a99cb02..236d2eb470 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.java @@ -10,7 +10,7 @@ import java.util.List; public interface ExtApiDefinitionMapper { void deleteApiToGc(@Param("ids") List ids, @Param("userId") String userId, @Param("time") long time); - List list(@Param("request") ApiDefinitionPageRequest request, @Param("deleted") boolean deleted); + List list(@Param("request") ApiDefinitionPageRequest request); List selectApiCaseByIdsAndStatusIsNotTrash(@Param("ids") List ids, @Param("projectId") String projectId); @@ -18,15 +18,21 @@ public interface ExtApiDefinitionMapper { List getIds(@Param("request") TableBatchProcessDTO request, @Param("projectId") String projectId, @Param("protocol") String protocol, @Param("deleted") boolean deleted); - List getRefIds(@Param("ids") List ids); + List getRefIds(@Param("ids") List ids, @Param("deleted") boolean deleted); - List getTagsByIds(@Param("ids") List ids); + List getIdsByRefId(@Param("refIds") List refIds, @Param("deleted") boolean deleted); + + List getTagsByIds(@Param("ids") List ids, @Param("deleted") boolean deleted); List getApiDefinitionByRefId(@Param("refId") String refId); void batchMove(@Param("request") ApiDefinitionBatchMoveRequest request, @Param("ids") List ids, @Param("userId") String userId); - void batchDelete(@Param("ids") List ids, @Param("userId") String userId); + void batchDeleteByRefId(@Param("refIds") List refIds, @Param("userId") String userId, @Param("projectId") String projectId); + + void batchDeleteById(@Param("ids") List ids, @Param("userId") String userId, @Param("projectId") String projectId); + + void batchRestoreById(@Param("ids") List ids, @Param("userId") String userId, @Param("projectId") String projectId); void clearLatestVersion(@Param("refId") String refId, @Param("projectId") String projectId); diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml index 06e164f6f7..6bb3e4f263 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml +++ b/backend/services/api-test/src/main/java/io/metersphere/api/mapper/ExtApiDefinitionMapper.xml @@ -21,7 +21,7 @@ api_definition.deleted, project_version.name as version_name from api_definition LEFT JOIN project_version ON project_version.id = api_definition.version_id - where deleted = #{deleted} + where deleted = #{request.deleted} @@ -62,6 +62,12 @@ SELECT id FROM api_definition where project_id = #{projectId} AND protocol = #{protocol} and deleted = #{deleted} + + AND module_id IN + + #{item} + + @@ -75,9 +81,22 @@ #{id} - and deleted = false + and deleted = #{deleted} group by ref_id + + + + - + update api_definition set deleted = 1, delete_user = #{userId}, delete_time = UNIX_TIMESTAMP()*1000 where ref_id in + + #{id} + + and deleted = false and project_id = #{projectId} + + + + update api_definition + set deleted = 1, + delete_user = #{userId}, + delete_time = UNIX_TIMESTAMP()*1000 + where id in #{id} - and deleted = false + and deleted = false and project_id = #{projectId} + + update api_definition + set deleted = 0, + delete_user = null, + delete_time = null, + update_user = #{userId}, + update_time = UNIX_TIMESTAMP()*1000 + where id in + + #{id} + + and deleted = true and project_id = #{projectId} + + + update api_definition set module_id = #{request.moduleId}, diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java index 944099893b..adae43e8da 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionLogService.java @@ -4,29 +4,33 @@ import io.metersphere.api.domain.ApiDefinition; import io.metersphere.api.domain.ApiDefinitionExample; import io.metersphere.api.dto.definition.*; import io.metersphere.api.mapper.ApiDefinitionMapper; +import io.metersphere.api.mapper.ExtApiDefinitionMapper; import io.metersphere.project.domain.Project; import io.metersphere.project.mapper.ProjectMapper; import io.metersphere.sdk.constants.HttpMethodConstants; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.Translator; +import io.metersphere.system.dto.table.TableBatchProcessDTO; import io.metersphere.system.log.constants.OperationLogModule; import io.metersphere.system.log.constants.OperationLogType; import io.metersphere.system.log.dto.LogDTO; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; @Service +@Transactional(rollbackFor = Exception.class) public class ApiDefinitionLogService { @Resource private ApiDefinitionMapper apiDefinitionMapper; @Resource - private ApiDefinitionService apiDefinitionService; + private ExtApiDefinitionMapper extApiDefinitionMapper; @Resource private ProjectMapper projectMapper; @@ -112,7 +116,7 @@ public class ApiDefinitionLogService { * @return */ public List batchDelLog(ApiDefinitionBatchRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol()); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); List dtoList = new ArrayList<>(); if (CollectionUtils.isNotEmpty(ids)) { ApiDefinitionExample example = new ApiDefinitionExample(); @@ -144,7 +148,7 @@ public class ApiDefinitionLogService { * @return */ public List batchUpdateLog(ApiDefinitionBatchUpdateRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol()); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); List dtoList = new ArrayList<>(); if (CollectionUtils.isNotEmpty(ids)) { ApiDefinitionExample example = new ApiDefinitionExample(); @@ -190,7 +194,7 @@ public class ApiDefinitionLogService { } public List batchMoveLog(ApiDefinitionBatchMoveRequest request) { - List ids = apiDefinitionService.getBatchApiIds(request, request.getProjectId(), request.getProtocol()); + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); List dtoList = new ArrayList<>(); if (CollectionUtils.isNotEmpty(ids)) { ApiDefinitionExample example = new ApiDefinitionExample(); @@ -235,4 +239,108 @@ public class ApiDefinitionLogService { } return null; } + + /** + * 恢复回收站接口定义接口日志 + * + * @param request + * @return + */ + public LogDTO restoreLog(ApiDefinitionDeleteRequest request) { + ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(request.getId()); + if(apiDefinition != null){ + LogDTO dto = new LogDTO( + request.getProjectId(), + null, + request.getId(), + null, + OperationLogType.UPDATE.name(), + OperationLogModule.API_DEFINITION, + apiDefinition.getName()); + + dto.setPath("/api/definition/restore"); + dto.setMethod(HttpMethodConstants.POST.name()); + dto.setOriginalValue(JSON.toJSONBytes(apiDefinition)); + return dto; + } + + return null; + } + + + /** + * 批量恢复回收站接口定义接口日志 + * + * @return + */ + public List batchRestoreLog(ApiDefinitionBatchRequest request) { + List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); + List dtoList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(ids)) { + ApiDefinitionExample example = new ApiDefinitionExample(); + example.createCriteria().andIdIn(ids).andDeletedEqualTo(false); + List apiDefinitions = apiDefinitionMapper.selectByExample(example); + apiDefinitions.forEach(item -> { + LogDTO dto = new LogDTO( + item.getProjectId(), + "", + item.getId(), + item.getCreateUser(), + OperationLogType.UPDATE.name(), + OperationLogModule.API_DEFINITION, + item.getName()); + + dto.setPath("/api/definition/batch-restore"); + dto.setMethod(HttpMethodConstants.POST.name()); + dto.setOriginalValue(JSON.toJSONBytes(item)); + dtoList.add(dto); + }); + } + + return dtoList; + } + + + /** + * 删除回收站接口定义接口日志 + * + * @return + */ + public List recycleDelLog(List apiDefinitions, String operator, Boolean isBatch) { + List dtoList = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(apiDefinitions)) { + apiDefinitions.forEach(item -> { + LogDTO dto = new LogDTO( + item.getProjectId(), + "", + item.getId(), + operator, + OperationLogType.DELETE.name(), + OperationLogModule.API_DEFINITION, + item.getName()); + String path = isBatch ? "/api/definition/batch-recycle-del" : "/api/definition/recycle-del"; + dto.setPath(path); + dto.setMethod(HttpMethodConstants.POST.name()); + dto.setOriginalValue(JSON.toJSONBytes(item)); + dtoList.add(dto); + }); + } + + return dtoList; + } + + // 获取批量操作选中的ID + public List getBatchApiIds(T dto, String projectId, String protocol, boolean deleted) { + TableBatchProcessDTO request = (TableBatchProcessDTO) dto; + if (request.isSelectAll()) { + List ids = extApiDefinitionMapper.getIds(request, projectId, protocol, deleted); + if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { + ids.removeAll(request.getExcludeIds()); + } + return ids; + } else { + return request.getSelectIds(); + } + } + } diff --git a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java index f7c2210662..8234be1e4c 100644 --- a/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java +++ b/backend/services/api-test/src/main/java/io/metersphere/api/service/definition/ApiDefinitionService.java @@ -1,27 +1,24 @@ package io.metersphere.api.service.definition; +import io.metersphere.api.constants.ApiResourceType; import io.metersphere.api.controller.result.ApiResultCode; import io.metersphere.api.domain.*; +import io.metersphere.api.dto.debug.ApiFileResourceUpdateRequest; import io.metersphere.api.dto.definition.*; import io.metersphere.api.enums.ApiReportStatus; -import io.metersphere.api.mapper.ApiDefinitionBlobMapper; -import io.metersphere.api.mapper.ApiDefinitionFollowerMapper; -import io.metersphere.api.mapper.ApiDefinitionMapper; -import io.metersphere.api.mapper.ExtApiDefinitionMapper; +import io.metersphere.api.mapper.*; +import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.util.ApiDataUtils; import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; import io.metersphere.project.service.ProjectService; import io.metersphere.sdk.constants.ApplicationNumScope; -import io.metersphere.sdk.constants.StorageType; +import io.metersphere.sdk.constants.DefaultRepositoryDir; +import io.metersphere.sdk.constants.ModuleConstants; import io.metersphere.sdk.exception.MSException; import io.metersphere.sdk.util.BeanUtils; import io.metersphere.sdk.util.JSON; import io.metersphere.sdk.util.LogUtils; -import io.metersphere.sdk.util.Translator; -import io.metersphere.system.dto.table.TableBatchProcessDTO; -import io.metersphere.system.file.FileRequest; -import io.metersphere.system.file.MinioRepository; import io.metersphere.system.service.UserLoginService; import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.uid.NumGenerator; @@ -33,6 +30,7 @@ import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionUtils; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.util.*; @@ -41,13 +39,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; @Service +@Transactional(rollbackFor = Exception.class) public class ApiDefinitionService { public static final Long ORDER_STEP = 5000L; - private static final String MAIN_FOLDER_PROJECT = "project"; - private static final String APP_NAME_API_DEFINITION = "apiDefinition"; - @Resource private ApiDefinitionMapper apiDefinitionMapper; @@ -64,17 +60,25 @@ public class ApiDefinitionService { private ApiDefinitionBlobMapper apiDefinitionBlobMapper; @Resource - private UserLoginService userLoginService; + private ExtApiTestCaseMapper extApiTestCaseMapper; @Resource - private MinioRepository minioRepository; + private UserLoginService userLoginService; @Resource private SqlSessionFactory sqlSessionFactory; + @Resource + private ApiTestCaseService apiTestCaseService; - public List getApiDefinitionPage(ApiDefinitionPageRequest request, Boolean deleted){ - List list = extApiDefinitionMapper.list(request, deleted); + @Resource + private ApiDefinitionLogService apiDefinitionLogService; + + @Resource + private ApiFileResourceService apiFileResourceService; + + public List getApiDefinitionPage(ApiDefinitionPageRequest request){ + List list = extApiDefinitionMapper.list(request); if (!CollectionUtils.isEmpty(list)) { processApiDefinitions(list, request.getProjectId()); } @@ -83,16 +87,22 @@ public class ApiDefinitionService { public ApiDefinitionDTO get(String id, String userId){ ApiDefinitionDTO apiDefinitionDTO = new ApiDefinitionDTO(); + // 1. 避免重复查询数据库,将查询结果传递给get方法 ApiDefinition apiDefinition = checkApiDefinition(id); - ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(id); - BeanUtils.copyBean(apiDefinitionDTO,apiDefinition); + // 2. 使用Optional避免空指针异常 + Optional apiDefinitionBlobOptional = Optional.ofNullable(apiDefinitionBlobMapper.selectByPrimaryKey(id)); + apiDefinitionBlobOptional.ifPresent(blob -> { + apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class)); + // blob.getResponse() 为 null 时不进行转换 + if (blob.getResponse() != null) { + apiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class)); + } + }); + // 3. 使用Stream简化集合操作 ApiDefinitionFollowerExample example = new ApiDefinitionFollowerExample(); example.createCriteria().andApiDefinitionIdEqualTo(id).andUserIdEqualTo(userId); - List followers = apiDefinitionFollowerMapper.selectByExample(example); - apiDefinitionDTO.setFollow(CollectionUtils.isNotEmpty(followers)); - if(apiDefinitionBlob != null){ - apiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class)); - } + apiDefinitionDTO.setFollow(apiDefinitionFollowerMapper.countByExample(example) > 0); + BeanUtils.copyBean(apiDefinitionDTO, apiDefinition); return apiDefinitionDTO; } @@ -119,9 +129,21 @@ public class ApiDefinitionService { ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob(); apiDefinitionBlob.setId(apiDefinition.getId()); apiDefinitionBlob.setRequest(request.getRequest().getBytes()); + apiDefinitionBlob.setResponse(request.getResponse().getBytes()); apiDefinitionBlobMapper.insertSelective(apiDefinitionBlob); - // TODO 文件只是上传到minio,不需要入库吗 - uploadBodyFile(bodyFiles, apiDefinition.getId(), request.getProjectId()); + + // 处理文件 + if (CollectionUtils.isNotEmpty(request.getFileIds()) && CollectionUtils.isNotEmpty(bodyFiles)) { + String apiDefinitionDir = DefaultRepositoryDir.getApiDefinitionDir(request.getProjectId(), apiDefinition.getId()); + ApiFileResourceUpdateRequest resourceUpdateRequest = new ApiFileResourceUpdateRequest(); + resourceUpdateRequest.setProjectId(apiDefinition.getProjectId()); + resourceUpdateRequest.setFileIds(request.getFileIds()); + resourceUpdateRequest.setAddFileIds(request.getFileIds()); + resourceUpdateRequest.setFolder(apiDefinitionDir); + resourceUpdateRequest.setResourceId(apiDefinition.getId()); + resourceUpdateRequest.setApiResourceType(ApiResourceType.API); + apiFileResourceService.addFileResource(resourceUpdateRequest, bodyFiles); + } return apiDefinition; } @@ -130,7 +152,9 @@ public class ApiDefinitionService { ApiDefinition originApiDefinition = checkApiDefinition(request.getId()); ApiDefinition apiDefinition = new ApiDefinition(); BeanUtils.copyBean(apiDefinition, request); - checkUpdateExist(apiDefinition, originApiDefinition); + if(request.getProtocol().equals(ModuleConstants.NODE_PROTOCOL_HTTP)){ + checkUpdateExist(apiDefinition, originApiDefinition); + } apiDefinition.setStatus(request.getStatus()); apiDefinition.setUpdateUser(userId); apiDefinition.setUpdateTime(System.currentTimeMillis()); @@ -142,16 +166,26 @@ public class ApiDefinitionService { ApiDefinitionBlob apiDefinitionBlob = new ApiDefinitionBlob(); apiDefinitionBlob.setId(apiDefinition.getId()); apiDefinitionBlob.setRequest(request.getRequest().getBytes()); + apiDefinitionBlob.setResponse(request.getResponse().getBytes()); apiDefinitionBlobMapper.updateByPrimaryKeySelective(apiDefinitionBlob); - // TODO 需要处理之前的文件 - uploadBodyFile(bodyFiles, request.getId(), request.getProjectId()); + // 处理文件 + String apiDefinitionDir = DefaultRepositoryDir.getApiDefinitionDir(originApiDefinition.getProjectId(), apiDefinition.getId()); + ApiFileResourceUpdateRequest resourceUpdateRequest = new ApiFileResourceUpdateRequest(); + resourceUpdateRequest.setProjectId(originApiDefinition.getProjectId()); + resourceUpdateRequest.setFileIds(request.getFileIds()); + resourceUpdateRequest.setAddFileIds(request.getAddFileIds()); + resourceUpdateRequest.setFolder(apiDefinitionDir); + resourceUpdateRequest.setResourceId(apiDefinition.getId()); + resourceUpdateRequest.setApiResourceType(ApiResourceType.API); + apiFileResourceService.updateFileResource(resourceUpdateRequest, bodyFiles); + return apiDefinition; } public void batchUpdate(ApiDefinitionBatchUpdateRequest request, String userId) { ProjectService.checkResourceExist(request.getProjectId()); - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol()); + List ids = apiDefinitionLogService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); if (CollectionUtils.isNotEmpty(ids)) { if (request.getType().equals("tags")) { handleTags(request, userId, ids); @@ -191,27 +225,27 @@ public class ApiDefinitionService { apiDefinitionBlob.setResponse(copyApiDefinitionBlob.getResponse()); apiDefinitionBlobMapper.insertSelective(apiDefinitionBlob); } - // TODO 复制的时候文件需要复制一份 + // TODO 复制的时候文件需要复制一份 仅复制接口内容, 不包含用例、mock信息 return apiDefinition; } public void delete(ApiDefinitionDeleteRequest request, String userId) { checkApiDefinition(request.getId()); - handleDeleteApiDefinition(Collections.singletonList(request.getId()),request.getDeleteAll(), userId); + handleDeleteApiDefinition(Collections.singletonList(request.getId()),request.getDeleteAll(), request.getProjectId(), userId); } public void batchDelete(ApiDefinitionBatchRequest request, String userId) { - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol()); + List ids = apiDefinitionLogService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); if (CollectionUtils.isNotEmpty(ids)) { - handleDeleteApiDefinition(ids, request.getDeleteAll(), userId); + handleDeleteApiDefinition(ids, request.getDeleteAll(), request.getProjectId(), userId); } } public void batchMove(ApiDefinitionBatchMoveRequest request, String userId) { - List ids = getBatchApiIds(request, request.getProjectId(), request.getProtocol()); + List ids = apiDefinitionLogService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), false); if (!ids.isEmpty()) { - List refIds = extApiDefinitionMapper.getRefIds(ids); + List refIds = extApiDefinitionMapper.getRefIds(ids, false); if (!refIds.isEmpty()) { extApiDefinitionMapper.batchMove(request, refIds, userId); } @@ -231,8 +265,15 @@ public class ApiDefinitionService { item.setDeleteUserName(userMap.get(item.getDeleteUser())); item.setUpdateUserName(userMap.get(item.getUpdateUser())); - // Convert tags - + // Convert Blob + Optional apiDefinitionBlobOptional = Optional.ofNullable(apiDefinitionBlobMapper.selectByPrimaryKey(item.getId())); + apiDefinitionBlobOptional.ifPresent(blob -> { + item.setRequest(ApiDataUtils.parseObject(new String(blob.getRequest()), AbstractMsTestElement.class)); + // blob.getResponse() 为 null 时不进行转换 + if (blob.getResponse() != null) { + item.setResponse(ApiDataUtils.parseArray(new String(blob.getResponse()), HttpResponse.class)); + } + }); // Calculate API Case Metrics ApiCaseComputeDTO apiCaseComputeDTO = resultMap.get(item.getId()); if (apiCaseComputeDTO != null) { @@ -271,23 +312,6 @@ public class ApiDefinitionService { return NumGenerator.nextNum(projectId, ApplicationNumScope.API_DEFINITION); } - public void uploadBodyFile(List files, String apiId, String projectId) { - if (CollectionUtils.isNotEmpty(files)) { - files.forEach(file -> { - FileRequest fileRequest = new FileRequest(); - fileRequest.setFileName(file.getName()); - fileRequest.setFolder(minioPath(projectId)); - fileRequest.setStorage(StorageType.MINIO.name()); - try { - minioRepository.saveFile(file, fileRequest); - } catch (Exception e) { - LogUtils.info("上传body文件失败: 文件名称:" + file.getName(), e); - throw new MSException(Translator.get("file_upload_fail")); - } - }); - } - } - /** * 校验接口是否存在 * @@ -304,23 +328,23 @@ public class ApiDefinitionService { private void checkAddExist(ApiDefinition apiDefinition) { ApiDefinitionExample example = new ApiDefinitionExample(); example.createCriteria() - .andNameEqualTo(apiDefinition.getName()) - .andModuleIdEqualTo(apiDefinition.getModuleId()); + .andPathEqualTo(apiDefinition.getPath()).andMethodEqualTo(apiDefinition.getMethod()) + .andModuleIdEqualTo(apiDefinition.getModuleId()).andProtocolEqualTo(apiDefinition.getProtocol()); if (CollectionUtils.isNotEmpty(apiDefinitionMapper.selectByExample(example))) { throw new MSException(ApiResultCode.API_DEFINITION_EXIST); } } private void checkUpdateExist(ApiDefinition apiDefinition, ApiDefinition originApiDefinition) { - if (StringUtils.isBlank(apiDefinition.getName())) { + if (StringUtils.isBlank(apiDefinition.getPath()) || StringUtils.isBlank(apiDefinition.getMethod())) { return; } ApiDefinitionExample example = new ApiDefinitionExample(); example.createCriteria() - .andIdNotEqualTo(apiDefinition.getId()) + .andIdNotEqualTo(apiDefinition.getId()).andProtocolEqualTo(apiDefinition.getProtocol()) .andModuleIdEqualTo(apiDefinition.getModuleId() == null ? originApiDefinition.getModuleId() : apiDefinition.getModuleId()) - .andNameEqualTo(apiDefinition.getName()); - if (CollectionUtils.isNotEmpty(apiDefinitionMapper.selectByExample(example))) { + .andPathEqualTo(apiDefinition.getPath()).andMethodEqualTo(apiDefinition.getMethod()); + if (apiDefinitionMapper.countByExample(example) > 0) { throw new MSException(ApiResultCode.API_DEFINITION_EXIST); } } @@ -331,19 +355,6 @@ public class ApiDefinitionService { return apiDefinitionBlobMapper.selectByPrimaryKey(apiId); } - public List getBatchApiIds(T dto, String projectId, String protocol) { - TableBatchProcessDTO request = (TableBatchProcessDTO) dto; - if (request.isSelectAll()) { - List ids = extApiDefinitionMapper.getIds(request, projectId, protocol, false); - if (CollectionUtils.isNotEmpty(request.getExcludeIds())) { - ids.removeAll(request.getExcludeIds()); - } - return ids; - } else { - return request.getSelectIds(); - } - } - /** * 根据接口id 获取接口是否存在多个版本 * @@ -372,7 +383,7 @@ public class ApiDefinitionService { private void handleTags(ApiDefinitionBatchUpdateRequest request, String userId, List ids) { if (request.isAppend()) { //追加标签 - Map collect = extApiDefinitionMapper.getTagsByIds(ids) + Map collect = extApiDefinitionMapper.getTagsByIds(ids, false) .stream() .collect(Collectors.toMap(ApiDefinition::getId, Function.identity())); try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) { @@ -415,27 +426,30 @@ public class ApiDefinitionService { return copyName; } - private void handleDeleteApiDefinition(List ids, Boolean deleteAll, String userId) { + private void handleDeleteApiDefinition(List ids, Boolean deleteAll, String projectId, String userId) { if (deleteAll) { //全部删除 进入回收站 - List refId = extApiDefinitionMapper.getRefIds(ids); - if(CollectionUtils.isNotEmpty(refId)){ - extApiDefinitionMapper.batchDelete(refId, userId); + List refIds = extApiDefinitionMapper.getRefIds(ids, false); + if(CollectionUtils.isNotEmpty(refIds)){ + extApiDefinitionMapper.batchDeleteByRefId(refIds, userId, projectId); + List delApiIds = extApiDefinitionMapper.getIdsByRefId(refIds, false); + if(CollectionUtils.isNotEmpty(delApiIds)){ + // 删除接口相关数据到回收站 + deleteApiRelatedData(delApiIds, userId, projectId); + } } } else { - //列表删除 需要判断是否存在多个版本问题 - ids.forEach(id -> { - ApiDefinition apiDefinition = checkApiDefinition(id); - // 选中的数据直接放入回收站 - doDelete(id, userId); - // 删除的数据是否为最新版本的数据,如果是则需要查询是否有多版本数据存在,需要去除当前删除的数据,更新剩余版本数据中最近的一条数据为最新的数据 - if(apiDefinition.getLatest()){ - List apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId()); - if (apiDefinitionVersions.size() > 1) { - deleteAfterAction(apiDefinitionVersions); - } + // 列表删除 + // 选中的数据直接放入回收站 + while (!ids.isEmpty()) { + if (ids.size() <= 2000) { + doDelete(ids, userId, projectId); + break; } - }); + List subList = ids.subList(0, 2000); + doDelete(subList, userId, projectId); + ids.removeAll(subList); + } } } @@ -469,21 +483,165 @@ public class ApiDefinitionService { extApiDefinitionMapper.updateLatestVersion(id, projectId); } - private void doDelete(String id, String userId) { - ApiDefinition apiDefinition = new ApiDefinition(); - apiDefinition.setDeleted(true); - apiDefinition.setId(id); - apiDefinition.setDeleteUser(userId); - apiDefinition.setDeleteTime(System.currentTimeMillis()); - apiDefinitionMapper.updateByPrimaryKeySelective(apiDefinition); + private void doDelete(List ids, String userId, String projectId) { + if(CollectionUtils.isNotEmpty(ids)){ + extApiDefinitionMapper.batchDeleteById(ids, userId, projectId); + // 需要判断是否存在多个版本问题 + ids.forEach(id -> { + ApiDefinition apiDefinition = checkApiDefinition(id); + // 删除的数据是否为最新版本的数据,如果是则需要查询是否有多版本数据存在,需要去除当前删除的数据,更新剩余版本数据中最近的一条数据为最新的数据 + if(apiDefinition.getLatest()){ + List apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId()); + if (apiDefinitionVersions.size() > 1) { + deleteAfterAction(apiDefinitionVersions); + } + } + }); + // 删除 case + deleteApiRelatedData(ids, userId, projectId); + } + + } + + private void deleteApiRelatedData(List apiIds, String userId, String projectId){ + // 是否存在 case 删除 case + List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(apiIds, false); + if(CollectionUtils.isNotEmpty(caseLists)){ + List caseIds = caseLists.stream().map(ApiTestCase::getId).distinct().toList(); + apiTestCaseService.batchDeleteToGc(caseIds, userId, projectId, false); + } + // todo 删除文档到回收站? } private void deleteFollower(String apiId, String userId) { apiDefinitionFollowerMapper.deleteByPrimaryKey(apiId, userId); } + public void restore(ApiDefinitionDeleteRequest request, String userId) { + // 恢复接口到接口列表 + handleRestoreApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId()); + } + private void handleRestoreApiDefinition(List ids, String userId, String projectId){ + if (CollectionUtils.isNotEmpty(ids)) { + while (!ids.isEmpty()) { + if (ids.size() <= 2000) { + doRestore(ids, userId, projectId); + break; + } + List subList = ids.subList(0, 2000); + doRestore(ids, userId, projectId); + ids.removeAll(subList); + } + } + } + private void doRestore(List apiIds, String userId, String projectId) { + // 恢复接口 + if(CollectionUtils.isNotEmpty(apiIds)){ + extApiDefinitionMapper.batchRestoreById(apiIds, userId, projectId); + apiIds.forEach(id -> { + // 恢复数据恢复最新标识 + ApiDefinition apiDefinition = checkApiDefinition(id); + // 判断是否存在多个版本 + List apiDefinitionVersions = extApiDefinitionMapper.getApiDefinitionByRefId(apiDefinition.getRefId()); + if (apiDefinitionVersions != null) { + if (apiDefinitionVersions.size() > 1) { + String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId()); + // 清除所有最新标识 + clearLatestVersion(apiDefinition.getRefId(), apiDefinition.getProjectId()); - private String minioPath(String projectId) { - return StringUtils.join(MAIN_FOLDER_PROJECT, "/", projectId, "/", APP_NAME_API_DEFINITION); + // 获取最新数据,恢复的数据最新标识,则最新数据,反之获取最新一条数据 + ApiDefinition latestData = apiDefinition.getLatest() ? + apiDefinition : getLatestData(apiDefinition.getRefId(), apiDefinition.getProjectId()); + + // 恢复的数据不为最新标识,同时接口版本为默认版本,则更新此数据为最新标识 + if (!latestData.getLatest()) { + updateLatestVersion(latestData.getVersionId().equals(defaultVersion) ? + apiDefinition.getId() : latestData.getId(), latestData.getProjectId()); + } + } + } + }); + // 恢复接口关联用例 + recoverApiRelatedData(apiIds, userId, projectId); + } + } + + private void recoverApiRelatedData(List apiIds, String userId, String projectId){ + // 是否存在 case 恢复 case + List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(apiIds, false); + if(CollectionUtils.isNotEmpty(caseLists)) { + List caseIds = caseLists.stream().map(ApiTestCase::getId).distinct().toList(); + // todo case 批量恢复方法 + LogUtils.info("caseIds" + JSON.toJSONString(caseIds)); + // todo 恢复文档? + } + } + public void recycleDel(ApiDefinitionDeleteRequest request, String userId) { + handleRecycleDelApiDefinition(Collections.singletonList(request.getId()), userId, request.getProjectId(), false); + } + + public void batchRestore(ApiDefinitionBatchRequest request, String userId) { + List ids = apiDefinitionLogService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true); + if (CollectionUtils.isNotEmpty(ids)) { + handleRestoreApiDefinition(ids, userId, request.getProjectId()); + } + } + + public void batchRecycleDel(ApiDefinitionBatchRequest request, String userId) { + List ids = apiDefinitionLogService.getBatchApiIds(request, request.getProjectId(), request.getProtocol(), true); + if (CollectionUtils.isNotEmpty(ids)) { + handleRecycleDelApiDefinition(ids, userId, request.getProjectId(), true); + } + } + + private void handleRecycleDelApiDefinition(List ids, String userId, String projectId, Boolean isBatch){ + if (CollectionUtils.isNotEmpty(ids)) { + while (!ids.isEmpty()) { + if (ids.size() <= 2000) { + doRecycleDel(ids, userId, projectId, isBatch); + break; + } + List subList = ids.subList(0, 2000); + doRecycleDel(ids, userId, projectId, isBatch); + ids.removeAll(subList); + } + } + } + + private void doRecycleDel(List ids, String userId, String projectId, Boolean isBatch){ + if(CollectionUtils.isNotEmpty(ids)){ + ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample(); + apiDefinitionExample.createCriteria().andIdIn(ids).andDeletedEqualTo(true).andProjectIdEqualTo(projectId); + List apiDefinitions = apiDefinitionMapper.selectByExample(apiDefinitionExample); + // 删除接口 + apiDefinitionMapper.deleteByExample(apiDefinitionExample); + // 删除接口关注人 + ApiDefinitionFollowerExample apiDefinitionFollowerExample = new ApiDefinitionFollowerExample(); + apiDefinitionFollowerExample.createCriteria().andApiDefinitionIdIn(ids).andUserIdEqualTo(userId); + apiDefinitionFollowerMapper.deleteByExample(apiDefinitionFollowerExample); + // 删除上传的文件 + ids.forEach(id -> { + String apiDefinitionDir = DefaultRepositoryDir.getApiDefinitionDir(projectId, id); + apiFileResourceService.deleteByResourceId(apiDefinitionDir, id); + }); + + // 删除接口关联数据 + recycleDelApiRelatedData(ids, userId, projectId); + + // 写入删除日志 + apiDefinitionLogService.recycleDelLog(apiDefinitions, userId, isBatch); + } + } + + private void recycleDelApiRelatedData(List apiIds, String userId, String projectId){ + // 是否存在 case 删除 case + List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(apiIds, true); + if(CollectionUtils.isNotEmpty(caseLists)) { + List caseIds = caseLists.stream().map(ApiTestCase::getId).distinct().toList(); + // case 批量删除回收站 + apiTestCaseService.deleteResourceByIds(caseIds, projectId, userId); + // todo 删除文档? + + } } } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java index a5ede401c0..041251f528 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/ApiDefinitionControllerTests.java @@ -5,9 +5,11 @@ import io.metersphere.api.domain.*; import io.metersphere.api.dto.definition.*; import io.metersphere.api.enums.ApiDefinitionStatus; import io.metersphere.api.mapper.*; +import io.metersphere.api.service.ApiFileResourceService; import io.metersphere.api.util.ApiDataUtils; import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.project.mapper.ExtBaseProjectVersionMapper; +import io.metersphere.sdk.constants.DefaultRepositoryDir; import io.metersphere.sdk.constants.PermissionConstants; import io.metersphere.sdk.dto.api.request.http.MsHTTPElement; import io.metersphere.sdk.util.BeanUtils; @@ -16,15 +18,16 @@ import io.metersphere.sdk.util.LogUtils; import io.metersphere.system.base.BaseTest; import io.metersphere.system.controller.handler.ResultHolder; import io.metersphere.system.dto.sdk.BaseCondition; +import io.metersphere.system.file.FileCenter; +import io.metersphere.system.file.FileRequest; import io.metersphere.system.log.constants.OperationLogType; +import io.metersphere.system.uid.IDGenerator; import io.metersphere.system.utils.Pager; import jakarta.annotation.Resource; import org.apache.commons.collections.CollectionUtils; import org.junit.jupiter.api.*; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.SqlConfig; import org.springframework.test.web.servlet.MvcResult; @@ -32,7 +35,6 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.io.File; -import java.io.FileInputStream; import java.nio.charset.StandardCharsets; import java.util.*; @@ -49,16 +51,23 @@ public class ApiDefinitionControllerTests extends BaseTest { private final static String UPDATE = BASE_PATH + "update"; private final static String BATCH_UPDATE = BASE_PATH + "batch-update"; private final static String DELETE = BASE_PATH + "delete"; - private final static String BATCH_DELETE= BASE_PATH + "batch-del"; - private final static String COPY= BASE_PATH + "copy"; - private final static String BATCH_MOVE= BASE_PATH + "batch-move"; + private final static String BATCH_DELETE = BASE_PATH + "batch-del"; + private final static String COPY = BASE_PATH + "copy"; + private final static String BATCH_MOVE = BASE_PATH + "batch-move"; - private final static String PAGE= BASE_PATH + "page"; + private final static String RESTORE = BASE_PATH + "restore"; + private final static String BATCH_RESTORE = BASE_PATH + "batch-restore"; + + private final static String RECYCLE_DEL = BASE_PATH + "recycle-del"; + private final static String BATCH_RECYCLE_DEL = BASE_PATH + "batch-recycle-del"; + + private final static String PAGE = BASE_PATH + "page"; private static final String GET = BASE_PATH + "get-detail/"; private static final String FOLLOW = BASE_PATH + "follow/"; private static final String VERSION = BASE_PATH + "version/"; private static final String DEFAULT_MODULE_ID = "10001"; + private static ApiDefinition apiDefinition; @Resource private ApiDefinitionMapper apiDefinitionMapper; @@ -74,6 +83,12 @@ public class ApiDefinitionControllerTests extends BaseTest { @Resource private ExtBaseProjectVersionMapper extBaseProjectVersionMapper; + @Resource + private ApiFileResourceService apiFileResourceService; + + @Resource + private ExtApiTestCaseMapper extApiTestCaseMapper; + @Test @Order(1) @@ -83,13 +98,14 @@ public class ApiDefinitionControllerTests extends BaseTest { ApiDefinitionAddRequest request = createApiDefinitionAddRequest(); MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); request.setRequest(ApiDataUtils.toJSONString(msHttpElement)); + List msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); + request.setResponse(ApiDataUtils.toJSONString(msHttpResponse)); // 构造请求参数 MultiValueMap paramMap = new LinkedMultiValueMap<>(); - paramMap.add("request", JSON.toJSONString(request)); - FileInputStream inputStream = new FileInputStream(new File( - Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/file_upload.JPG")) - .getPath())); - MockMultipartFile file = new MockMultipartFile("file_upload.JPG", "file_upload.JPG", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream); + File file = new File( + Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/file_upload.JPG")).getPath() + ); + request.setFileIds(List.of(IDGenerator.nextStr())); paramMap.add("files", List.of(file)); paramMap.add("request", JSON.toJSONString(request)); @@ -97,14 +113,16 @@ public class ApiDefinitionControllerTests extends BaseTest { MvcResult mvcResult = this.requestMultipartWithOkAndReturn(ADD, paramMap); // 校验请求成功数据 ApiDefinition resultData = getResultData(mvcResult, ApiDefinition.class); - ApiDefinition apiDefinition = assertAddApiDefinition(request, msHttpElement, resultData.getId()); + apiDefinition = assertAddApiDefinition(request, msHttpElement, resultData.getId(), request.getFileIds()); // 再插入一条数据,便于修改时重名校验 - request.setName("接口定义test-6"); + request.setMethod("GET"); + request.setPath("/api/admin/posts"); + request.setFileIds(null); paramMap.clear(); paramMap.add("request", JSON.toJSONString(request)); mvcResult = this.requestMultipartWithOkAndReturn(ADD, paramMap); resultData = getResultData(mvcResult, ApiDefinition.class); - assertAddApiDefinition(request, msHttpElement, resultData.getId()); + assertAddApiDefinition(request, msHttpElement, resultData.getId(), request.getFileIds()); // @@重名校验异常 assertErrorCode(this.requestMultipart(ADD, paramMap), ApiResultCode.API_DEFINITION_EXIST); @@ -122,7 +140,9 @@ public class ApiDefinitionControllerTests extends BaseTest { // @@校验权限 request.setProjectId(DEFAULT_PROJECT_ID); paramMap.clear(); - request.setName("permission"); + request.setName("permission-st-6"); + request.setMethod("DELETE"); + request.setPath("/api/admin/posts"); paramMap.add("request", JSON.toJSONString(request)); requestMultipartPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_ADD, ADD, paramMap); } @@ -137,14 +157,14 @@ public class ApiDefinitionControllerTests extends BaseTest { request.setMethod("POST"); request.setPath("/api/admin/posts"); request.setStatus(ApiDefinitionStatus.PREPARE.getValue()); - request.setModuleId("root"); + request.setModuleId("default"); request.setVersionId(defaultVersion); request.setDescription("描述内容"); request.setTags(new LinkedHashSet<>(List.of("tag1", "tag2"))); return request; } - private ApiDefinition assertAddApiDefinition(Object request, MsHTTPElement msHttpElement, String id) { + private ApiDefinition assertAddApiDefinition(Object request, MsHTTPElement msHttpElement, String id, List fileIds) throws Exception { ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(id); ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(id); ApiDefinition copyApiDefinition = BeanUtils.copyBean(new ApiDefinition(), apiDefinition); @@ -154,6 +174,26 @@ public class ApiDefinitionControllerTests extends BaseTest { if(apiDefinitionBlob != null){ Assertions.assertEquals(msHttpElement, ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class)); } + if (fileIds != null) { + // 验证文件的关联关系,以及是否存入对象存储 + List apiFileResources = apiFileResourceService.getByResourceId(id); + Assertions.assertEquals(apiFileResources.size(), fileIds.size()); + + String apiDefinitionDir = DefaultRepositoryDir.getApiDefinitionDir(apiDefinition.getProjectId(), id); + FileRequest fileRequest = new FileRequest(); + if (fileIds.size() > 0) { + for (ApiFileResource apiFileResource : apiFileResources) { + Assertions.assertEquals(apiFileResource.getProjectId(), apiDefinition.getProjectId()); + fileRequest.setFolder(apiDefinitionDir + "/" + apiFileResource.getFileId()); + fileRequest.setFileName(apiFileResource.getFileName()); + Assertions.assertNotNull(FileCenter.getDefaultRepository().getFile(fileRequest)); + } + fileRequest.setFolder(apiDefinitionDir); + } else { + fileRequest.setFolder(apiDefinitionDir); + Assertions.assertTrue(CollectionUtils.isEmpty(FileCenter.getDefaultRepository().getFolderFileNames(fileRequest))); + } + } return apiDefinition; } @@ -161,13 +201,15 @@ public class ApiDefinitionControllerTests extends BaseTest { @Order(2) @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void get() throws Exception { - ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + if(apiDefinition == null){ + apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + } // @@请求成功 MvcResult mvcResult = this.requestGetWithOkAndReturn(GET + apiDefinition.getId()); ApiDataUtils.setResolver(MsHTTPElement.class); - ApiDefinitionDTO apiDefinitionDTO = getResultData(mvcResult, ApiDefinitionDTO.class); + ApiDefinitionDTO apiDefinitionDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResult).get("data")), ApiDefinitionDTO.class); // 校验数据是否正确 - ApiDefinitionDTO copyApiDefinitionDTO = BeanUtils.copyBean(new ApiDefinitionDTO(), apiDefinitionMapper.selectByPrimaryKey(apiDefinition.getId())); + ApiDefinitionDTO copyApiDefinitionDTO = BeanUtils.copyBean(new ApiDefinitionDTO(), apiDefinition); ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(apiDefinition.getId()); ApiDefinitionFollowerExample example = new ApiDefinitionFollowerExample(); example.createCriteria().andApiDefinitionIdEqualTo(apiDefinition.getId()).andUserIdEqualTo("admin"); @@ -175,6 +217,7 @@ public class ApiDefinitionControllerTests extends BaseTest { copyApiDefinitionDTO.setFollow(CollectionUtils.isNotEmpty(followers)); if(apiDefinitionBlob != null){ copyApiDefinitionDTO.setRequest(ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class)); + copyApiDefinitionDTO.setResponse(ApiDataUtils.parseArray(new String(apiDefinitionBlob.getResponse()), HttpResponse.class)); } Assertions.assertEquals(apiDefinitionDTO, copyApiDefinitionDTO); @@ -189,7 +232,9 @@ public class ApiDefinitionControllerTests extends BaseTest { @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void testUpdate() throws Exception { LogUtils.info("update api test"); - ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + if(apiDefinition == null){ + apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + } ApiDefinitionUpdateRequest request = new ApiDefinitionUpdateRequest(); BeanUtils.copyBean(request, apiDefinition); request.setPath("test.com/admin/test"); @@ -198,14 +243,17 @@ public class ApiDefinitionControllerTests extends BaseTest { request.setModuleId("default1"); MsHTTPElement msHttpElement = MsHTTPElementTest.getMsHttpElement(); request.setRequest(ApiDataUtils.toJSONString(msHttpElement)); + List msHttpResponse = MsHTTPElementTest.getMsHttpResponse(); + request.setResponse(ApiDataUtils.toJSONString(msHttpResponse)); // 构造请求参数 MultiValueMap paramMap = new LinkedMultiValueMap<>(); - paramMap.add("request", JSON.toJSONString(request)); - FileInputStream inputStream = new FileInputStream(new File( - Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/file_update_upload.JPG")) - .getPath())); - MockMultipartFile file = new MockMultipartFile("file_update_upload.JPG", "file_update_upload.JPG", MediaType.APPLICATION_OCTET_STREAM_VALUE, inputStream); + File file = new File( + Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/file_update_upload.JPG")).getPath() + ); + // 带文件的更新 + request.setAddFileIds(List.of(IDGenerator.nextStr())); + request.setFileIds(request.getAddFileIds()); paramMap.add("files", List.of(file)); paramMap.add("request", JSON.toJSONString(request)); @@ -213,7 +261,36 @@ public class ApiDefinitionControllerTests extends BaseTest { MvcResult mvcResult = this.requestMultipartWithOkAndReturn(UPDATE, paramMap); // 校验请求成功数据 ApiDefinition resultData = getResultData(mvcResult, ApiDefinition.class); - apiDefinition = assertAddApiDefinition(request, msHttpElement, resultData.getId()); + apiDefinition = assertAddApiDefinition(request, msHttpElement, resultData.getId(), request.getFileIds()); + + // 删除了上一次上传的文件,重新上传一个文件 + request.setAddFileIds(List.of(IDGenerator.nextStr())); + request.setFileIds(request.getAddFileIds()); + paramMap.clear(); + paramMap.add("files", List.of(file)); + paramMap.add("request", JSON.toJSONString(request)); + this.requestMultipartWithOk(UPDATE, paramMap); + assertAddApiDefinition(request, msHttpElement, request.getId(), request.getFileIds()); + + // 已有一个文件,再上传一个文件 + request.setAddFileIds(List.of(IDGenerator.nextStr())); + List fileIds = apiFileResourceService.getFileIdsByResourceId(request.getId()); + fileIds.addAll(request.getAddFileIds()); + request.setFileIds(fileIds); + paramMap.clear(); + paramMap.add("files", List.of(file)); + paramMap.add("request", JSON.toJSONString(request)); + this.requestMultipartWithOk(UPDATE, paramMap); + assertAddApiDefinition(request, msHttpElement, request.getId(), request.getFileIds()); + + // @@重名校验异常 + request.setModuleId("default"); + request.setPath("/api/admin/posts"); + request.setMethod("GET"); + paramMap.clear(); + paramMap.add("request", JSON.toJSONString(request)); + assertErrorCode(this.requestMultipart(UPDATE, paramMap), ApiResultCode.API_DEFINITION_EXIST); + // 校验数据是否存在 request.setId("111"); request.setName("test123"); @@ -235,7 +312,7 @@ public class ApiDefinitionControllerTests extends BaseTest { // @@校验权限 request.setProjectId(DEFAULT_PROJECT_ID); paramMap.clear(); - request.setName("permission"); + request.setName("permission-st-6"); paramMap.add("request", JSON.toJSONString(request)); requestMultipartPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_UPDATE, UPDATE, paramMap); } @@ -334,6 +411,8 @@ public class ApiDefinitionControllerTests extends BaseTest { request.setId(apiDefinition.getId()); MvcResult mvcResult = this.requestPostWithOkAndReturn(COPY, request); ApiDefinition resultData = getResultData(mvcResult, ApiDefinition.class); + // @数据验证 + Assertions.assertEquals("copy_" + apiDefinition.getName(), resultData.getName()); // @@校验日志 checkLog(resultData.getId(), OperationLogType.UPDATE); request.setId("121"); @@ -428,7 +507,9 @@ public class ApiDefinitionControllerTests extends BaseTest { @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void testDel() throws Exception { LogUtils.info("delete api test"); - ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + if(apiDefinition == null){ + apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + } // @只存在一个版本 ApiDefinitionDeleteRequest apiDefinitionDeleteRequest = new ApiDefinitionDeleteRequest(); apiDefinitionDeleteRequest.setId(apiDefinition.getId()); @@ -504,17 +585,16 @@ public class ApiDefinitionControllerTests extends BaseTest { request.setProjectId(DEFAULT_PROJECT_ID); // 删除选中 - request.setSelectIds(List.of("1001","1002","1005")); - request.setExcludeIds(List.of("1005")); + request.setSelectIds(List.of("1002","1004")); request.setDeleteAll(false); request.setSelectAll(false); this.requestPostWithOkAndReturn(BATCH_DELETE, request); // @@校验日志 - checkLog("1001", OperationLogType.DELETE); checkLog("1002", OperationLogType.DELETE); - checkLog("1005", OperationLogType.DELETE); + checkLog("1004", OperationLogType.DELETE); // 删除全部 条件为关键字为st-6的数据 request.setDeleteAll(true); + request.setExcludeIds(List.of("1005")); request.setSelectAll(true); BaseCondition baseCondition = new BaseCondition(); baseCondition.setKeyword("st-6"); @@ -531,54 +611,7 @@ public class ApiDefinitionControllerTests extends BaseTest { @Order(11) @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) public void getPage() throws Exception { - ApiDefinitionPageRequest request = new ApiDefinitionPageRequest(); - request.setProjectId(DEFAULT_PROJECT_ID); - request.setCurrent(1); - request.setPageSize(10); - this.requestPost(PAGE, request); - request.setSort(new HashMap<>() {{ - put("createTime", "asc"); - }}); - - // ALL 全部 KEYWORD 关键字 FILTER 筛选 COMBINE 自定义 - String search = "KEYWORD"; - Map> filters = new HashMap<>(); - Map map = new HashMap<>(); - switch (search) { - case "ALL": - // Perform all search types - request.setKeyword("100"); - filters.put("status", Arrays.asList("Underway", "Completed")); - filters.put("method", List.of("GET")); - filters.put("version_id", List.of("1005704995741369851")); - request.setFilter(filters); - - map.put("name", Map.of("operator", "like", "value", "test-1")); - map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST"))); - request.setCombine(map); - break; - case "KEYWORD": - // 基础查询 - request.setKeyword("100"); - // 版本查询 - request.setVersionId("100570499574136985"); - break; - case "FILTER": - // 筛选 - filters.put("status", Arrays.asList("Underway", "Completed")); - filters.put("method", List.of("GET")); - filters.put("version_id", List.of("1005704995741369851")); - request.setFilter(filters); - break; - case "COMBINE": - // 自定义字段 测试 - map.put("name", Map.of("operator", "like", "value", "test-1")); - map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST"))); - request.setCombine(map); - break; - default: - break; - } + ApiDefinitionPageRequest request = createApiDefinitionPageRequest(); MvcResult mvcResult = this.requestPostWithOkAndReturn(PAGE, request); // 获取返回值 @@ -595,4 +628,236 @@ public class ApiDefinitionControllerTests extends BaseTest { Assertions.assertTrue(JSON.parseArray(JSON.toJSONString(pageData.getList())).size() <= request.getPageSize()); } + private ApiDefinitionPageRequest createApiDefinitionPageRequest() { + ApiDefinitionPageRequest request = new ApiDefinitionPageRequest(); + request.setProjectId(DEFAULT_PROJECT_ID); + request.setCurrent(1); + request.setPageSize(10); + request.setSort(Map.of("createTime", "asc")); + + String search = getRandomSearchType(); + switch (search) { + case "ALL" -> configureAllSearch(request); + case "KEYWORD" -> configureKeywordSearch(request); + case "FILTER" -> configureFilterSearch(request); + case "COMBINE" -> configureCombineSearch(request); + default -> {} + } + + return request; + } + + private String getRandomSearchType() { + List searchTypes = Arrays.asList("ALL", "KEYWORD", "FILTER", "COMBINE"); + return searchTypes.get(new Random().nextInt(searchTypes.size())); + } + + private void configureAllSearch(ApiDefinitionPageRequest request) { + request.setKeyword("100"); + Map> filters = new HashMap<>(); + filters.put("status", Arrays.asList("Underway", "Completed")); + filters.put("method", List.of("GET")); + filters.put("version_id", List.of("1005704995741369851")); + request.setFilter(filters); + + Map map = new HashMap<>(); + map.put("name", Map.of("operator", "like", "value", "test-1")); + map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST"))); + request.setCombine(map); + } + + private void configureKeywordSearch(ApiDefinitionPageRequest request) { + request.setKeyword("100"); + request.setVersionId("100570499574136985"); + } + + private void configureFilterSearch(ApiDefinitionPageRequest request) { + Map> filters = new HashMap<>(); + filters.put("status", Arrays.asList("Underway", "Completed")); + filters.put("method", List.of("GET")); + filters.put("version_id", List.of("1005704995741369851")); + request.setFilter(filters); + } + + private void configureCombineSearch(ApiDefinitionPageRequest request) { + Map map = new HashMap<>(); + map.put("name", Map.of("operator", "like", "value", "test-1")); + map.put("method", Map.of("operator", "in", "value", Arrays.asList("GET", "POST"))); + request.setCombine(map); + } + + @Test + @Order(12) + @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void testRestore() throws Exception { + LogUtils.info("restore api test"); + if(apiDefinition == null){ + apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + } + // @恢复一条数据 + ApiDefinitionDeleteRequest apiDefinitionDeleteRequest = new ApiDefinitionDeleteRequest(); + apiDefinitionDeleteRequest.setId(apiDefinition.getId()); + apiDefinitionDeleteRequest.setProjectId(DEFAULT_PROJECT_ID); + // @@请求成功 + this.requestPostWithOkAndReturn(RESTORE, apiDefinitionDeleteRequest); + checkLog(apiDefinition.getId(), OperationLogType.UPDATE); + ApiDefinition apiDefinitionInfo = apiDefinitionMapper.selectByPrimaryKey(apiDefinition.getId()); + Assertions.assertFalse(apiDefinitionInfo.getDeleted()); + Assertions.assertNull(apiDefinitionInfo.getDeleteUser()); + Assertions.assertNull(apiDefinitionInfo.getDeleteTime()); + + // @查询恢复的数据版本是否为默认版本 + String defaultVersion = extBaseProjectVersionMapper.getDefaultVersion(apiDefinition.getProjectId()); + if(defaultVersion.equals(apiDefinition.getVersionId())){ + // 需要处理此数据为最新标识 + // 此数据不为最新版本,验证是否更新为最新版本 + if(!apiDefinition.getLatest()){ + Assertions.assertTrue(apiDefinitionInfo.getLatest()); + } + } + // todo 效验 关联数据 +// List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(Collections.singletonList(apiDefinition.getId()), false); +// if(!caseLists.isEmpty()) { +// caseLists.forEach(item -> { +// Assertions.assertFalse(item.getDeleted()); +// Assertions.assertNull(item.getDeleteUser()); +// Assertions.assertNull(item.getDeleteTime()); +// }); +// } + // @@校验权限 + requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_UPDATE, RESTORE, apiDefinitionDeleteRequest); + } + + @Test + @Order(13) + @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void testRecycleDel() throws Exception { + LogUtils.info("recycleDel api test"); + if(apiDefinition == null){ + apiDefinition = apiDefinitionMapper.selectByPrimaryKey("1001"); + } + if(!apiDefinition.getDeleted()){ + testDel(); + } + // @只存在一个版本 + ApiDefinitionDeleteRequest apiDefinitionDeleteRequest = new ApiDefinitionDeleteRequest(); + apiDefinitionDeleteRequest.setId(apiDefinition.getId()); + apiDefinitionDeleteRequest.setProjectId(DEFAULT_PROJECT_ID); + apiDefinitionDeleteRequest.setDeleteAll(false); + // @@请求成功 + this.requestPostWithOkAndReturn(RECYCLE_DEL, apiDefinitionDeleteRequest); + checkLog(apiDefinition.getId(), OperationLogType.DELETE); + // 验证数据 + ApiDefinition apiDefinitionInfo = apiDefinitionMapper.selectByPrimaryKey(apiDefinition.getId()); + Assertions.assertNull(apiDefinitionInfo); + + // 文件是否删除 + List apiFileResources = apiFileResourceService.getByResourceId(apiDefinition.getId()); + Assertions.assertEquals(0, apiFileResources.size()); + + // 效验 关联数据 + List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(Collections.singletonList(apiDefinition.getId()), false); + Assertions.assertEquals(0, caseLists.size()); + + // @@校验权限 + requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_DELETE, RECYCLE_DEL, apiDefinitionDeleteRequest); + } + + @Test + @Order(14) + @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void testBatchRestore() throws Exception { + LogUtils.info("batch restore api test"); + ApiDefinitionBatchRequest request = new ApiDefinitionBatchRequest(); + request.setProjectId(DEFAULT_PROJECT_ID); + // 恢复选中 + request.setSelectIds(List.of("1002","1004","1005")); + request.setExcludeIds(List.of("1005")); + request.setSelectAll(false); + this.requestPostWithOkAndReturn(BATCH_RESTORE, request); + + // 效验数据结果 + ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample(); + apiDefinitionExample.createCriteria().andIdIn(request.getSelectIds()); + List apiDefinitions = apiDefinitionMapper.selectByExample(apiDefinitionExample); + if(!apiDefinitions.isEmpty()){ + apiDefinitions.forEach(item -> { + Assertions.assertFalse(item.getDeleted()); + Assertions.assertNull(item.getDeleteUser()); + Assertions.assertNull(item.getDeleteTime()); + // todo 效验 关联数据 +// List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(Collections.singletonList(item.getId()), false); +// if(!caseLists.isEmpty()) { +// caseLists.forEach(test -> { +// Assertions.assertFalse(test.getDeleted()); +// Assertions.assertNull(test.getDeleteUser()); +// Assertions.assertNull(test.getDeleteTime()); +// }); +// } + }); + } + + + // @@校验日志 + checkLog("1002", OperationLogType.UPDATE); + checkLog("1004", OperationLogType.UPDATE); + checkLog("1005", OperationLogType.UPDATE); + + // 恢复全部 条件为关键字为st-6的数据 + request.setSelectAll(true); + BaseCondition baseCondition = new BaseCondition(); + baseCondition.setKeyword("st-6"); + request.setCondition(baseCondition); + this.requestPostWithOkAndReturn(BATCH_RESTORE, request); + + // @@校验日志 + checkLog("1006", OperationLogType.UPDATE); + // @@校验权限 + requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_UPDATE, BATCH_RESTORE, request); + } + + @Test + @Order(15) + @Sql(scripts = {"/dml/init_api_definition.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED)) + public void testBatchRecycleDel() throws Exception { + LogUtils.info("batch recycle delete api test"); + testBatchDel(); + ApiDefinitionBatchRequest request = new ApiDefinitionBatchRequest(); + request.setProjectId(DEFAULT_PROJECT_ID); + + // 删除选中 + request.setSelectIds(List.of("1002","1004")); + request.setSelectAll(false); + this.requestPostWithOkAndReturn(BATCH_RECYCLE_DEL, request); + // 效验数据结果 + ApiDefinitionExample apiDefinitionExample = new ApiDefinitionExample(); + apiDefinitionExample.createCriteria().andIdIn(request.getSelectIds()); + List apiDefinitions = apiDefinitionMapper.selectByExample(apiDefinitionExample); + Assertions.assertEquals(0, apiDefinitions.size()); + + request.getSelectIds().forEach(item -> { + // 文件是否删除 + List apiFileResources = apiFileResourceService.getByResourceId(item); + Assertions.assertEquals(0, apiFileResources.size()); + }); + // 效验 关联数据 + List caseLists = extApiTestCaseMapper.getCaseInfoByApiIds(request.getSelectIds(), false); + Assertions.assertEquals(0, caseLists.size()); + + // @@校验日志 + checkLog("1002", OperationLogType.DELETE); + checkLog("1004", OperationLogType.DELETE); + // 删除全部 条件为关键字为st-6的数据 + request.setSelectAll(true); + request.setExcludeIds(List.of("1005")); + BaseCondition baseCondition = new BaseCondition(); + baseCondition.setKeyword("st-6"); + request.setCondition(baseCondition); + this.requestPostWithOkAndReturn(BATCH_RECYCLE_DEL, request); + // @@校验日志 + checkLog("1006", OperationLogType.DELETE); + // @@校验权限 + requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_DELETE, BATCH_RECYCLE_DEL, request); + } + } diff --git a/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java b/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java index 8680dd8345..a3e10c7b47 100644 --- a/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java +++ b/backend/services/api-test/src/test/java/io/metersphere/api/controller/MsHTTPElementTest.java @@ -1,5 +1,7 @@ package io.metersphere.api.controller; +import io.metersphere.sdk.dto.api.request.http.body.Body; +import io.metersphere.api.dto.definition.HttpResponse; import io.metersphere.api.util.ApiDataUtils; import io.metersphere.plugin.api.spi.AbstractMsTestElement; import io.metersphere.sdk.dto.api.request.http.*; @@ -187,4 +189,38 @@ public class MsHTTPElementTest { return msHTTPElement; } + + public static List getMsHttpResponse() { + List httpResponses = new ArrayList<>(); + HttpResponse httpResponse = new HttpResponse(); + httpResponse.setId(1); + httpResponse.setName("Response1"); + httpResponse.setStatusCode("200"); + httpResponse.setDefaultFlag(true); + + Header header = new Header(); + header.setEnable(false); + header.setValue("value"); + header.setKey("key"); + header.setDescription("desc"); + httpResponse.setHeaders(List.of(header)); + + FormDataBody formDataBody = new FormDataBody(); + FormDataKV formDataKV = new FormDataKV(); + formDataKV.setEnable(false); + formDataKV.setContentType("text/plain"); + formDataKV.setEncode(true); + formDataKV.setMaxLength(10); + formDataKV.setMinLength(8); + formDataKV.setParamType("text"); + formDataKV.setDescription("test"); + formDataKV.setRequired(true); + formDataKV.setValue("value"); + formDataKV.setKey("key"); + formDataBody.setFromValues(List.of(formDataKV)); + httpResponse.setBody(formDataBody); + + httpResponses.add(httpResponse); + return httpResponses; + } } diff --git a/backend/services/api-test/src/test/resources/dml/init_api_definition.sql b/backend/services/api-test/src/test/resources/dml/init_api_definition.sql index bfed175c07..faa956b27a 100644 --- a/backend/services/api-test/src/test/resources/dml/init_api_definition.sql +++ b/backend/services/api-test/src/test/resources/dml/init_api_definition.sql @@ -5,7 +5,7 @@ INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `statu INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`) VALUES ('1002', 'test-2', 'HTTP', 'GET', '/api/admin/2', 'Underway', 1002, '[\"test2\",\"te\"]', 1, '100001100001', 'root', b'1', '1005704995741369851', '1002', NULL, 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`) VALUES ('1003', 'test-3', 'HTTP', 'POST', '/api/admin/3', 'Completed', 1003, '[\"test3\",\"te\"]', 1, '100001100001', 'root', b'1', '100570499574136985', '1002', NULL, 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`) VALUES ('1004', 'test-4', 'HTTP', 'GET', '/api/admin/4', 'Prepare', 1004, '[\"test4\",\"te\"]', 1, '100001100001', 'root', b'1', '1005704995741369851', '1004', NULL, 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); -INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`) VALUES ('1005', 'test-5', 'HTTP', 'POST', '/api/admin/5', 'Underway', 1005, '[\"test5\",\"te\"]', 1, '100001100001', 'root', b'0', '1005704995741369851', '1004', NULL, 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); +INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`) VALUES ('1005', 'test-65', 'HTTP', 'POST', '/api/admin/5', 'Underway', 1005, '[\"test5\",\"te\"]', 1, '100001100001', 'root', b'0', '1005704995741369851', '1004', NULL, 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); INSERT INTO `api_definition` (`id`, `name`, `protocol`, `method`, `path`, `status`, `num`, `tags`, `pos`, `project_id`, `module_id`, `latest`, `version_id`, `ref_id`, `description`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_user`, `delete_time`, `deleted`) VALUES ('1006', 'test-6', 'HTTP', 'GET', '/api/admin/6', 'Completed', 1006, '[\"test6\",\"te\"]', 1, '100001100001', 'root', b'1', '100570499574136985', '1006', NULL, 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); DELETE FROM `project_version` WHERE `id` = '100570499574136985'; @@ -14,10 +14,10 @@ INSERT INTO project_version (id, project_id, name, description, status, latest, DELETE FROM `api_test_case` WHERE `id` in ('12df5721-c5e6-a38b-e999-3eafcb992094','12df5721-c5e6-a38b-e999-3eafcb992100','12df5721-c5e6-a38b-e999-3eafcb992233','3ee2ae9c-a680-4ed6-b115-1f6ab8980973','3ee2ae9c-a680-4ed6-b115-1f6ab8980100','3ee2ae9c-a680-4ed6-b115-1f6ab8980589'); INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('12df5721-c5e6-a38b-e999-3eafcb992094', '查询windows主机', 'P0', 100002001, NULL, 'PENDING', NULL, '10001', 10000, '100001100001', '1001', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('12df5721-c5e6-a38b-e999-3eafcb992100', '查询windows主机', 'P0', 100002002, NULL, 'PENDING', NULL, '10002', 10000, '100001100001', '1001', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); -INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('12df5721-c5e6-a38b-e999-3eafcb992233', '查询windows主机', 'P0', 100002003, NULL, 'PENDING', NULL, '10003', 10000, '100001100001', '1001', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); +INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('12df5721-c5e6-a38b-e999-3eafcb992233', '查询windows主机', 'P0', 100002003, NULL, 'PENDING', NULL, '10003', 10000, '100001100001', '1002', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('3ee2ae9c-a680-4ed6-b115-1f6ab8980973', '查询Linux主机', 'P0', 100002004, NULL, 'PENDING', NULL, '10004', 10000, '100001100001', '1002', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); -INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('3ee2ae9c-a680-4ed6-b115-1f6ab8980100', '查询Linux主机', 'P0', 100002005, NULL, 'PENDING', NULL, '10005', 10000, '100001100001', '1002', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); -INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('3ee2ae9c-a680-4ed6-b115-1f6ab8980589', '查询Linux主机', 'P0', 100002006, NULL, 'PENDING', NULL, '10006', 10000, '100001100001', '1002', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); +INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('3ee2ae9c-a680-4ed6-b115-1f6ab8980100', '查询Linux主机', 'P0', 100002005, NULL, 'PENDING', NULL, '10005', 10000, '100001100001', '1003', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); +INSERT INTO `api_test_case` (`id`, `name`, `priority`, `num`, `tags`, `status`, `last_report_status`, `last_report_id`, `pos`, `project_id`, `api_definition_id`, `version_id`, `environment_id`, `create_time`, `create_user`, `update_time`, `update_user`, `delete_time`, `delete_user`, `deleted`) VALUES ('3ee2ae9c-a680-4ed6-b115-1f6ab8980589', '查询Linux主机', 'P0', 100002006, NULL, 'PENDING', NULL, '10006', 10000, '100001100001', '1004', '100570499574136985', 'admin', 1699500298164, 'admin', 1699500298164, 'admin', NULL, NULL, b'0'); DELETE FROM `api_report` WHERE `id` in ('10001','10002','10003','10004','10005','10006'); INSERT INTO `api_report` (`id`, `name`, `resource_id`, `create_time`, `update_time`, `create_user`, `update_user`, `deleted`, `status`, `start_time`, `end_time`, `run_mode`, `pool_id`, `trigger_mode`, `version_id`, `project_id`, `integrated_report_id`, `integrated`) VALUES ('10001', '报告001', 'resource_id_10001', 1680624405386, 1680624405386, 'admin', 'admin', b'0', 'SUCCESS', 1680624405386, 1680624405386, 'API', 'pol_id_100001', 'hand', NULL, '100001100001', 'NONE', b'0');