feat(功能用例): 新增功能用例获取接口用例模块统计接口

This commit is contained in:
guoyuqi 2023-12-28 16:34:40 +08:00 committed by Craftsman
parent 7fc32cbc7e
commit c85e5b8a0f
15 changed files with 267 additions and 12 deletions

View File

@ -1,9 +1,11 @@
package io.metersphere.provider;
import io.metersphere.dto.ApiTestCaseProviderDTO;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.request.ApiTestCasePageProviderRequest;
import java.util.List;
import java.util.Map;
/**
* @author guoyuqi
@ -11,11 +13,23 @@ import java.util.List;
public interface BaseAssociateApiProvider {
/**
* 获取尚未关联的接口列表
* @param sourceType 关联关系表表名
* @param sourceName 关联关系表主动关联方字段名称
* @param apiCaseColumnName 接口用例id 在关联关系表的字段名称
*
* @param sourceType 关联关系表表名
* @param sourceName 关联关系表主动关联方字段名称
* @param apiCaseColumnName 接口用例id 在关联关系表的字段名称
* @param apiTestCasePageProviderRequest 接口用例高级搜索条件
* @return List<ApiTestCaseProviderDTO>
*/
List<ApiTestCaseProviderDTO> getApiTestCaseList(String sourceType,String sourceName, String apiCaseColumnName, ApiTestCasePageProviderRequest apiTestCasePageProviderRequest);
List<ApiTestCaseProviderDTO> getApiTestCaseList(String sourceType, String sourceName, String apiCaseColumnName, ApiTestCasePageProviderRequest apiTestCasePageProviderRequest);
/**
* 根据接口用例的搜索条件获取符合条件的接口定义的模块统计数量
*
* @param request 接口用例高级搜索条件
* @param deleted 接口定义是否删除
* @return 接口模块统计数量
*/
Map<String, Long> moduleCount(String sourceType, String sourceName, String apiCaseColumnName, ApiModuleProviderRequest request, boolean deleted);
}

View File

@ -0,0 +1,37 @@
package io.metersphere.request;
import io.metersphere.sdk.constants.ModuleConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Data
public class ApiModuleProviderRequest {
@Schema(description = "关联关系表里主ID eg:功能用例关联接口用例时为功能用例id")
@NotBlank(message = "{api_definition.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_definition.project_id.length_range}")
private String sourceId;
@Schema(description = "模块ID(根据模块树查询时要把当前节点以及子节点都放在这里。)")
private List<@NotBlank String> moduleIds;
@Schema(description = "协议", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_module.protocol.not_blank}")
@Size(min = 1, max = 20, message = "{api_definition_module.protocol.length_range}")
private String protocol = ModuleConstants.NODE_PROTOCOL_HTTP;
@Schema(description = "项目ID", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "{api_definition_module.project_id.not_blank}")
@Size(min = 1, max = 50, message = "{api_definition_module.project_id.length_range}")
private String projectId;
@Schema(description = "用例关键字")
private String keyword;
@Schema(description = "模块关键字")
private String moduleKeyword;
}

View File

@ -4,6 +4,7 @@ import io.metersphere.api.dto.debug.ApiTreeNode;
import io.metersphere.api.dto.definition.ApiModuleRequest;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.project.dto.NodeSortQueryParam;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.system.dto.sdk.BaseModule;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import org.apache.ibatis.annotations.Param;
@ -34,4 +35,8 @@ public interface ExtApiDefinitionModuleMapper {
List<BaseTreeNode> selectNodeByIds(@Param("ids") List<String> ids);
List<BaseTreeNode> selectBaseByIds(@Param("ids") List<String> ids);
List<ModuleCountDTO> countModuleIdByProviderRequest(@Param("table") String resourceType, @Param("sourceName") String sourceName, @Param("apiCaseColumnName") String apiCaseColumnName, @Param("request") ApiModuleProviderRequest request, @Param("deleted") boolean deleted);
List<BaseTreeNode> selectIdAndParentIdByProviderRequest(@Param("request") ApiModuleProviderRequest request);
}

View File

@ -114,6 +114,58 @@
ORDER BY pos
</select>
<select id="countModuleIdByProviderRequest" resultType="io.metersphere.project.dto.ModuleCountDTO">
SELECT ad.module_id AS moduleId, count(ad.id) AS dataCount
FROM api_test_case atc inner join api_definition ad on atc.api_definition_id = ad.id
<include refid="api_case_module_request"/>
and atc.id not in
(
select associate.${apiCaseColumnName} from ${table} associate where associate.${sourceName} = #{request.sourceId}
)
GROUP BY ad.module_id
</select>
<select id="selectIdAndParentIdByProviderRequest" resultType="io.metersphere.system.dto.sdk.BaseTreeNode">
SELECT m.id,
m.parent_id AS parentId
FROM api_definition_module m
<include refid="provider_module_request"/>
ORDER BY pos
</select>
<sql id="api_case_module_request">
<where>
ad.deleted = #{deleted}
<if test="request.projectId != null and request.projectId != ''">
AND ad.project_id = #{request.projectId}
</if>
<if test="request.keyword != null and request.keyword != ''">
AND atc.name like CONCAT('%', #{request.keyword},'%')
</if>
<if test="request.moduleIds != null and request.moduleIds.size() != 0">
AND ad.module_id IN
<foreach collection="request.moduleIds" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="request.protocol != null and request.protocol != ''">
AND ad.protocol = #{request.protocol}
</if>
AND ad.latest = 1
<!--<if test="request.versionId != null and request.versionId != ''">
and api_definition.version_id = #{request.versionId}
</if>
<if test="request.refId != null and request.refId != ''">
and api_definition.ref_id = #{request.refId}
</if>
<if test="request.versionId == null and request.refId == null and request.id == null">
AND api_definition.latest = 1
</if>-->
</where>
</sql>
<sql id="api_request">
<where>
api_definition.deleted = #{deleted}
@ -163,4 +215,22 @@
</where>
</sql>
<sql id="provider_module_request">
<where>
<if test="request.projectId != null and request.projectId != ''">
AND m.project_id = #{request.projectId}
</if>
<if test="request.moduleKeyword != null and request.moduleKeyword != ''">
AND m.name like CONCAT('%', #{request.moduleKeyword},'%')
</if>
<if test="request.moduleIds != null and request.moduleIds.size() != 0">
AND m.module_id IN
<foreach collection="request.moduleIds" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
</where>
</sql>
</mapper>

View File

@ -42,6 +42,6 @@ public interface ExtApiTestCaseMapper {
List<String> getCaseIds(@Param("ids")List<String> ids, @Param("deleted")boolean deleted);
List<ApiTestCaseProviderDTO> listByProviderRequest(@Param("table") String resourceType, @Param("sourceName") String sourceName, @Param("targetName") String targetName,@Param("request") ApiTestCasePageProviderRequest request, @Param("deleted") boolean deleted);
List<ApiTestCaseProviderDTO> listByProviderRequest(@Param("table") String resourceType, @Param("sourceName") String sourceName, @Param("apiCaseColumnName") String apiCaseColumnName,@Param("request") ApiTestCasePageProviderRequest request, @Param("deleted") boolean deleted);
}

View File

@ -157,7 +157,7 @@
WHERE t1.deleted =#{deleted}
and t1.id not in
(
select associate.${targetName} from ${table} associate where associate.${sourceName} = #{request.sourceId}
select associate.${apiCaseColumnName} from ${table} associate where associate.${sourceName} = #{request.sourceId}
)
<include refid="queryWhereCondition"/>
</select>

View File

@ -1,13 +1,20 @@
package io.metersphere.api.provider;
import io.metersphere.api.mapper.ExtApiDefinitionModuleMapper;
import io.metersphere.api.mapper.ExtApiTestCaseMapper;
import io.metersphere.api.service.definition.ApiDefinitionModuleService;
import io.metersphere.dto.ApiTestCaseProviderDTO;
import io.metersphere.project.dto.ModuleCountDTO;
import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.request.ApiTestCasePageProviderRequest;
import io.metersphere.sdk.util.Translator;
import io.metersphere.system.dto.sdk.BaseTreeNode;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
@ -15,8 +22,52 @@ public class AssociateApiProvider implements BaseAssociateApiProvider {
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private ExtApiDefinitionModuleMapper extApiDefinitionModuleMapper;
@Resource
private ApiDefinitionModuleService moduleTreeService;
private static final String DEBUG_MODULE_COUNT_ALL = "all";
private static final String UNPLANNED_API = "api_unplanned_request";
@Override
public List<ApiTestCaseProviderDTO> getApiTestCaseList(String sourceType, String sourceName, String apiCaseColumnName, ApiTestCasePageProviderRequest apiTestCasePageProviderRequest) {
return extApiTestCaseMapper.listByProviderRequest(sourceType, sourceName, apiCaseColumnName, apiTestCasePageProviderRequest, false);
}
@Override
public Map<String, Long> moduleCount(String sourceType, String sourceName, String apiCaseColumnName, ApiModuleProviderRequest request, boolean deleted) {
request.setModuleIds(null);
//查找根据moduleIds查找模块下的接口数量 查非delete状态的
List<ModuleCountDTO> moduleCountDTOList = extApiDefinitionModuleMapper.countModuleIdByProviderRequest(sourceType, sourceName, apiCaseColumnName,request, deleted);
long allCount = getAllCount(moduleCountDTOList);
Map<String, Long> moduleCountMap = getModuleCountMap(request, moduleCountDTOList);
moduleCountMap.put(DEBUG_MODULE_COUNT_ALL, allCount);
return moduleCountMap;
}
public long getAllCount(List<ModuleCountDTO> moduleCountDTOList) {
long count = 0;
for (ModuleCountDTO countDTO : moduleCountDTOList) {
count += countDTO.getDataCount();
}
return count;
}
/**
* 查找当前项目下模块每个节点对应的资源统计
*/
public Map<String, Long> getModuleCountMap(ApiModuleProviderRequest request, List<ModuleCountDTO> moduleCountDTOList) {
//构建模块树并计算每个节点下的所有数量包含子节点
List<BaseTreeNode> treeNodeList = this.getTreeOnlyIdsAndResourceCount(request, moduleCountDTOList);
return moduleTreeService.getIdCountMapByBreadth(treeNodeList);
}
public List<BaseTreeNode> getTreeOnlyIdsAndResourceCount(ApiModuleProviderRequest request, List<ModuleCountDTO> moduleCountDTOList) {
//节点内容只有Id和parentId
List<BaseTreeNode> fileModuleList = extApiDefinitionModuleMapper.selectIdAndParentIdByProviderRequest(request);
return moduleTreeService.buildTreeAndCountResource(fileModuleList, moduleCountDTOList, true, Translator.get(UNPLANNED_API));
}
}

View File

@ -2,6 +2,7 @@ package io.metersphere.api.controller;
import io.metersphere.api.provider.AssociateApiProvider;
import io.metersphere.dto.ApiTestCaseProviderDTO;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.request.ApiTestCasePageProviderRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
@ -17,6 +18,7 @@ import org.springframework.test.context.jdbc.SqlConfig;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -42,5 +44,18 @@ public class AssociateApiProviderTest extends BaseTest {
System.out.println(jsonString);
}
@Test
@Order(2)
public void moduleCountSuccess() throws Exception {
ApiModuleProviderRequest request = new ApiModuleProviderRequest();
request.setSourceId("gyq_associate_case_id_1");
request.setProjectId("project-associate-case-test");
request.setKeyword("测试查询模块用");
Map<String, Long> stringLongMap = provider.moduleCount("functional_case_test", "case_id", "source_id", request, false);
String jsonString = JSON.toJSONString(stringLongMap);
System.out.println(jsonString);
}
}

View File

@ -16,9 +16,18 @@ INSERT INTO functional_case_blob(id, steps, text_description, expected_result, p
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 ('gyq_associate_api_case_id_1','gyq_associate_api_case_id_1','P0',1001, null, 'Underway', null, null, 100, 'project-associate-case-test', 'gyq_associate_api_definition_id_1', 'gyq_associate_version_id', 'gyq_associate_env_id', 1698058347559, 'admin',1698058347559,'admin',null,null,false);
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 ('gyq_associate_api_definition_id_1', 'gyq_associate_api_definition_id_1', 'HTTP', 'POST','api/test','test-api-status', 1000001, null, 1, '100001100001' , 'test_module', true, 'v1.10','aspect_gyq_api_one', null, UNIX_TIMESTAMP() * 1000,'admin', UNIX_TIMESTAMP() * 1000,'admin', null,null,false);
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 ('gyq_associate_api_case_id_2','测试查询模块用','P0',1002, null, 'Underway', null, null, 200, 'project-associate-case-test', 'gyq_associate_api_definition_id_1', 'gyq_associate_version_id', 'gyq_associate_env_id', 1698058347559, 'admin',1698058347559,'admin',null,null,false);
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 ('gyq_associate_api_definition_id_1', 'gyq_associate_api_definition_id_1', 'HTTP', 'POST','api/test','test-api-status', 1000001, null, 1, 'project-associate-case-test' , 'gyq_associate_test_module', true, 'v1.10','gyq_associate_api_definition_id_1', null, UNIX_TIMESTAMP() * 1000,'admin', UNIX_TIMESTAMP() * 1000,'admin', null,null,false);
INSERT INTO api_definition_module(id, name,parent_id, project_id, pos, create_time, update_time, update_user, create_user)
VALUES ('gyq_associate_test_module', 'gyq_associate_test_module', 'NONE', 'project-associate-case-test', 100, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin','admin');
INSERT INTO project_version(id, project_id, name, description, status, latest, publish_time, start_time, end_time, create_time, create_user)
values ('gyq_associate_version_id','project-associate-case-test','v1.0.0', null, 'open', 1, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, UNIX_TIMESTAMP() * 1000, 'admin');

View File

@ -4,6 +4,7 @@ import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.dto.ApiTestCaseProviderDTO;
import io.metersphere.functional.service.FunctionalTestCaseService;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.request.ApiTestCasePageProviderRequest;
import io.metersphere.sdk.constants.PermissionConstants;
import io.metersphere.system.security.CheckOwner;
@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@Tag(name = "用例管理-功能用例-关联其他用例")
@RestController
@ -31,15 +33,27 @@ public class FunctionalTestCaseController {
@Resource
private FunctionalTestCaseService functionalTestCaseService;
@PostMapping("/associate/page")
@PostMapping("/associate/api/page")
@Operation(summary = "用例管理-功能用例-关联其他用例-获取接口用例列表")
@RequiresPermissions(value = {PermissionConstants.FUNCTIONAL_CASE_READ_ADD, PermissionConstants.FUNCTIONAL_CASE_READ_UPDATE, PermissionConstants.FUNCTIONAL_CASE_READ_DELETE}, logical = Logical.OR)
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
public Pager<List<ApiTestCaseProviderDTO>> associateCase(@Validated @RequestBody ApiTestCasePageProviderRequest request) {
public Pager<List<ApiTestCaseProviderDTO>> associateApiCaseList(@Validated @RequestBody ApiTestCasePageProviderRequest request) {
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "create_time desc");
return PageUtils.setPageInfo(page, functionalTestCaseService.page(request));
}
@PostMapping("/associate/api/module/count")
@Operation(summary = "接口测试-接口管理-模块-统计模块数量")
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
@CheckOwner(resourceId = "#request.projectId", resourceType = "project")
public Map<String, Long> moduleCount(@Validated @RequestBody ApiModuleProviderRequest request) {
return functionalTestCaseService.moduleCount(request, false);
}
}

View File

@ -572,6 +572,7 @@ public class CaseReviewService {
public Map<String, Long> moduleCount(CaseReviewPageRequest request) {
//查出每个模块节点下的资源数量 不需要按照模块进行筛选
request.setModuleIds(null);
List<ModuleCountDTO> moduleCountDTOList = extCaseReviewMapper.countModuleIdByKeywordAndFileType(request);
Map<String, Long> moduleCountMap = caseReviewModuleService.getModuleCountMap(request.getProjectId(), moduleCountDTOList);
//查出全部用例数量

View File

@ -673,6 +673,7 @@ public class FunctionalCaseService {
public Map<String, Long> moduleCount(FunctionalCasePageRequest request, boolean delete) {
//查出每个模块节点下的资源数量 不需要按照模块进行筛选
request.setModuleIds(null);
List<ModuleCountDTO> moduleCountDTOList = extFunctionalCaseMapper.countModuleIdByKeywordAndFileType(request, delete);
Map<String, Long> moduleCountMap = functionalCaseModuleService.getModuleCountMap(request.getProjectId(), moduleCountDTOList);
//查出全部用例数量

View File

@ -2,12 +2,14 @@ package io.metersphere.functional.service;
import io.metersphere.dto.ApiTestCaseProviderDTO;
import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.request.ApiTestCasePageProviderRequest;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
/**
* @author guoyuqi
@ -20,7 +22,24 @@ public class FunctionalTestCaseService {
@Resource
private BaseAssociateApiProvider provider;
/**
* 获取功能用例未关联的接口用例列表
* @param request request
* @return List<ApiTestCaseProviderDTO>
*/
public List<ApiTestCaseProviderDTO> page(ApiTestCasePageProviderRequest request) {
return provider.getApiTestCaseList("functional_case_test", "case_id", "source_id", request);
}
/**
* 根据接口用例的搜索条件获取符合条件的接口定义的模块统计数量
*
* @param request 接口用例高级搜索条件
* @param deleted 接口定义是否删除
* @return 接口模块统计数量
*/
public Map<String, Long> moduleCount(ApiModuleProviderRequest request, boolean deleted) {
return provider.moduleCount("functional_case_test", "case_id", "source_id", request, deleted);
}
}

View File

@ -2,6 +2,7 @@ package io.metersphere.functional.controller;
import io.metersphere.dto.ApiTestCaseProviderDTO;
import io.metersphere.provider.BaseAssociateApiProvider;
import io.metersphere.request.ApiModuleProviderRequest;
import io.metersphere.request.ApiTestCasePageProviderRequest;
import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
@ -24,7 +25,10 @@ import java.util.List;
public class FunctionalTestCaseControllerTests extends BaseTest {
private static final String URL_CASE_PAGE = "/functional/case/test/associate/page";
private static final String URL_CASE_PAGE = "/functional/case/test/associate/api/page";
private static final String URL_CASE_PAGE_MODULE_COUNT = "/functional/case/test/associate/api/module/count";
@Resource
BaseAssociateApiProvider provider;
@ -72,4 +76,19 @@ public class FunctionalTestCaseControllerTests extends BaseTest {
System.out.println(JSON.toJSONString(apiTestCaseList));
}
@Test
@Order(3)
public void getModuleCountSuccess() throws Exception {
ApiModuleProviderRequest request = new ApiModuleProviderRequest();
request.setSourceId("gyq_associate_case_id_1");
request.setProjectId("project_gyq_associate_test");
request.setKeyword("测试查询模块用");
MvcResult mvcResult = this.requestPostWithOkAndReturn(URL_CASE_PAGE_MODULE_COUNT, request);
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
Assertions.assertNotNull(resultHolder);
}
}

View File

@ -234,7 +234,7 @@ public abstract class ModuleTreeService {
return count;
}
protected Map<String, Long> getIdCountMapByBreadth(List<BaseTreeNode> treeNodeList) {
public Map<String, Long> getIdCountMapByBreadth(List<BaseTreeNode> treeNodeList) {
Map<String, Long> returnMap = new HashMap<>();
List<BaseTreeNode> whileList = new ArrayList<>(treeNodeList);
while (CollectionUtils.isNotEmpty(whileList)) {