feat(测试计划): 关联抽屉接口用例列表
This commit is contained in:
parent
5f769d7f3e
commit
0f2ba1d329
|
@ -160,7 +160,7 @@ public class ApiTestCaseController {
|
||||||
public Pager<List<ApiTestCaseDTO>> page(@Validated @RequestBody ApiTestCasePageRequest request) {
|
public Pager<List<ApiTestCaseDTO>> page(@Validated @RequestBody ApiTestCasePageRequest request) {
|
||||||
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
||||||
StringUtils.isNotBlank(request.getSortString("id")) ? request.getSortString("id") : "pos desc, id desc");
|
StringUtils.isNotBlank(request.getSortString("id")) ? request.getSortString("id") : "pos desc, id desc");
|
||||||
return PageUtils.setPageInfo(page, apiTestCaseService.page(request, false));
|
return PageUtils.setPageInfo(page, apiTestCaseService.page(request, false, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/batch/delete")
|
@PostMapping("/batch/delete")
|
||||||
|
@ -202,7 +202,7 @@ public class ApiTestCaseController {
|
||||||
public Pager<List<ApiTestCaseDTO>> pageTrash(@Validated @RequestBody ApiTestCasePageRequest request) {
|
public Pager<List<ApiTestCaseDTO>> pageTrash(@Validated @RequestBody ApiTestCasePageRequest request) {
|
||||||
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
||||||
StringUtils.isNotBlank(request.getSortString("id")) ? request.getSortString("id") : "delete_time desc, id desc");
|
StringUtils.isNotBlank(request.getSortString("id")) ? request.getSortString("id") : "delete_time desc, id desc");
|
||||||
return PageUtils.setPageInfo(page, apiTestCaseService.page(request, true));
|
return PageUtils.setPageInfo(page, apiTestCaseService.page(request, true, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/edit/pos")
|
@PostMapping("/edit/pos")
|
||||||
|
|
|
@ -33,4 +33,7 @@ public class ApiModuleRequest extends BaseCondition {
|
||||||
@Schema(description = "版本引用fk")
|
@Schema(description = "版本引用fk")
|
||||||
@Size(max = 50, message = "{api_definition.ref_id.length_range}")
|
@Size(max = 50, message = "{api_definition.ref_id.length_range}")
|
||||||
private String refId;
|
private String refId;
|
||||||
|
|
||||||
|
@Schema(description = "测试计划id")
|
||||||
|
private String testPlanId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ public interface ExtApiDefinitionModuleMapper {
|
||||||
|
|
||||||
List<ApiTreeNode> selectApiDataByRequest(@Param("request") ApiModuleRequest request, @Param("deleted") boolean deleted);
|
List<ApiTreeNode> selectApiDataByRequest(@Param("request") ApiModuleRequest request, @Param("deleted") boolean deleted);
|
||||||
|
|
||||||
List<ModuleCountDTO> countModuleIdByRequest(@Param("request") ApiModuleRequest request, @Param("deleted") boolean deleted);
|
List<ModuleCountDTO> countModuleIdByRequest(@Param("request") ApiModuleRequest request, @Param("deleted") boolean deleted, @Param("isRepeat") boolean isRepeat);
|
||||||
|
|
||||||
List<BaseTreeNode> selectNodeByIds(@Param("ids") List<String> ids);
|
List<BaseTreeNode> selectNodeByIds(@Param("ids") List<String> ids);
|
||||||
|
|
||||||
|
|
|
@ -53,6 +53,21 @@
|
||||||
FROM api_definition
|
FROM api_definition
|
||||||
where api_definition.deleted = #{deleted}
|
where api_definition.deleted = #{deleted}
|
||||||
<include refid="queryWhereCondition"/>
|
<include refid="queryWhereCondition"/>
|
||||||
|
<if test="request.testPlanId != null and request.testPlanId != ''">
|
||||||
|
and exists (
|
||||||
|
select id
|
||||||
|
from api_test_case c
|
||||||
|
where c.api_definition_id = api_definition.id and c.deleted = false
|
||||||
|
<if test="!isRepeat">
|
||||||
|
and not exists (
|
||||||
|
select id
|
||||||
|
from test_plan_api_case t
|
||||||
|
where t.api_case_id = c.id
|
||||||
|
and t.test_plan_id = #{request.testPlanId}
|
||||||
|
)
|
||||||
|
</if>
|
||||||
|
)
|
||||||
|
</if>
|
||||||
GROUP BY api_definition.module_id
|
GROUP BY api_definition.module_id
|
||||||
</select>
|
</select>
|
||||||
<select id="selectBaseModuleById" resultType="io.metersphere.system.dto.sdk.BaseModule">
|
<select id="selectBaseModuleById" resultType="io.metersphere.system.dto.sdk.BaseModule">
|
||||||
|
|
|
@ -26,7 +26,7 @@ public interface ExtApiTestCaseMapper {
|
||||||
|
|
||||||
Long getPos(@Param("projectId") String projectId);
|
Long getPos(@Param("projectId") String projectId);
|
||||||
|
|
||||||
List<ApiTestCaseDTO> listByRequest(@Param("request") ApiTestCasePageRequest request, @Param("deleted") boolean deleted);
|
List<ApiTestCaseDTO> listByRequest(@Param("request") ApiTestCasePageRequest request, @Param("deleted") boolean deleted, @Param("isRepeat") boolean isRepeat);
|
||||||
|
|
||||||
List<String> getIds(@Param("request") ApiTestCaseBatchRequest request, @Param("deleted") boolean deleted);
|
List<String> getIds(@Param("request") ApiTestCaseBatchRequest request, @Param("deleted") boolean deleted);
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,11 @@
|
||||||
INNER JOIN api_definition a ON atc.api_definition_id = a.id
|
INNER JOIN api_definition a ON atc.api_definition_id = a.id
|
||||||
WHERE atc.deleted =#{deleted}
|
WHERE atc.deleted =#{deleted}
|
||||||
<include refid="queryWhereCondition"/>
|
<include refid="queryWhereCondition"/>
|
||||||
|
<if test="!isRepeat">
|
||||||
|
AND atc.id not in (
|
||||||
|
select test_plan_api_case.api_case_id from test_plan_api_case where test_plan_api_case.test_plan_id = #{request.testPlanId}
|
||||||
|
)
|
||||||
|
</if>
|
||||||
</select>
|
</select>
|
||||||
<select id="getIds" resultType="java.lang.String">
|
<select id="getIds" resultType="java.lang.String">
|
||||||
SELECT
|
SELECT
|
||||||
|
|
|
@ -11,6 +11,8 @@ import io.metersphere.api.dto.definition.EnvApiModuleRequest;
|
||||||
import io.metersphere.api.dto.definition.EnvApiTreeDTO;
|
import io.metersphere.api.dto.definition.EnvApiTreeDTO;
|
||||||
import io.metersphere.api.mapper.*;
|
import io.metersphere.api.mapper.*;
|
||||||
import io.metersphere.api.service.debug.ApiDebugModuleService;
|
import io.metersphere.api.service.debug.ApiDebugModuleService;
|
||||||
|
import io.metersphere.plan.domain.TestPlanConfig;
|
||||||
|
import io.metersphere.plan.mapper.TestPlanConfigMapper;
|
||||||
import io.metersphere.project.dto.ModuleCountDTO;
|
import io.metersphere.project.dto.ModuleCountDTO;
|
||||||
import io.metersphere.project.dto.NodeSortDTO;
|
import io.metersphere.project.dto.NodeSortDTO;
|
||||||
import io.metersphere.project.service.ModuleTreeService;
|
import io.metersphere.project.service.ModuleTreeService;
|
||||||
|
@ -61,6 +63,8 @@ public class ApiDefinitionModuleService extends ModuleTreeService {
|
||||||
private ExtApiTestCaseMapper extApiTestCaseMapper;
|
private ExtApiTestCaseMapper extApiTestCaseMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ApiTestCaseService apiTestCaseService;
|
private ApiTestCaseService apiTestCaseService;
|
||||||
|
@Resource
|
||||||
|
private TestPlanConfigMapper testPlanConfigMapper;
|
||||||
|
|
||||||
public List<BaseTreeNode> getTree(ApiModuleRequest request, boolean deleted, boolean containRequest) {
|
public List<BaseTreeNode> getTree(ApiModuleRequest request, boolean deleted, boolean containRequest) {
|
||||||
//接口的树结构是 模块:子模块+接口 接口为非delete状态的
|
//接口的树结构是 模块:子模块+接口 接口为非delete状态的
|
||||||
|
@ -259,15 +263,24 @@ public class ApiDefinitionModuleService extends ModuleTreeService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Long> moduleCount(ApiModuleRequest request, boolean deleted) {
|
public Map<String, Long> moduleCount(ApiModuleRequest request, boolean deleted) {
|
||||||
|
boolean isRepeat = true;
|
||||||
|
if (StringUtils.isNotEmpty(request.getTestPlanId())) {
|
||||||
|
isRepeat = this.checkTestPlanRepeatCase(request);
|
||||||
|
}
|
||||||
request.setModuleIds(null);
|
request.setModuleIds(null);
|
||||||
//查找根据moduleIds查找模块下的接口数量 查非delete状态的
|
//查找根据moduleIds查找模块下的接口数量 查非delete状态的
|
||||||
List<ModuleCountDTO> moduleCountDTOList = extApiDefinitionModuleMapper.countModuleIdByRequest(request, deleted);
|
List<ModuleCountDTO> moduleCountDTOList = extApiDefinitionModuleMapper.countModuleIdByRequest(request, deleted, isRepeat);
|
||||||
long allCount = getAllCount(moduleCountDTOList);
|
long allCount = getAllCount(moduleCountDTOList);
|
||||||
Map<String, Long> moduleCountMap = getModuleCountMap(request, moduleCountDTOList);
|
Map<String, Long> moduleCountMap = getModuleCountMap(request, moduleCountDTOList);
|
||||||
moduleCountMap.put(DEBUG_MODULE_COUNT_ALL, allCount);
|
moduleCountMap.put(DEBUG_MODULE_COUNT_ALL, allCount);
|
||||||
return moduleCountMap;
|
return moduleCountMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkTestPlanRepeatCase(ApiModuleRequest request) {
|
||||||
|
TestPlanConfig testPlanConfig = testPlanConfigMapper.selectByPrimaryKey(request.getTestPlanId());
|
||||||
|
return BooleanUtils.isTrue(testPlanConfig.getRepeatCase());
|
||||||
|
}
|
||||||
|
|
||||||
public List<BaseTreeNode> getTrashTree(ApiModuleRequest request) {
|
public List<BaseTreeNode> getTrashTree(ApiModuleRequest request) {
|
||||||
ApiDefinitionExample example = new ApiDefinitionExample();
|
ApiDefinitionExample example = new ApiDefinitionExample();
|
||||||
example.createCriteria()
|
example.createCriteria()
|
||||||
|
|
|
@ -315,8 +315,8 @@ public class ApiTestCaseService extends MoveNodeService {
|
||||||
apiTestCaseMapper.updateByPrimaryKeySelective(update);
|
apiTestCaseMapper.updateByPrimaryKeySelective(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ApiTestCaseDTO> page(ApiTestCasePageRequest request, boolean deleted) {
|
public List<ApiTestCaseDTO> page(ApiTestCasePageRequest request, boolean deleted, boolean isRepeat) {
|
||||||
List<ApiTestCaseDTO> apiCaseLists = extApiTestCaseMapper.listByRequest(request, deleted);
|
List<ApiTestCaseDTO> apiCaseLists = extApiTestCaseMapper.listByRequest(request, deleted, isRepeat);
|
||||||
buildApiTestCaseDTO(apiCaseLists);
|
buildApiTestCaseDTO(apiCaseLists);
|
||||||
return apiCaseLists;
|
return apiCaseLists;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ import io.metersphere.api.dto.definition.EnvApiTreeDTO;
|
||||||
import io.metersphere.api.dto.request.http.MsHTTPElement;
|
import io.metersphere.api.dto.request.http.MsHTTPElement;
|
||||||
import io.metersphere.api.mapper.*;
|
import io.metersphere.api.mapper.*;
|
||||||
import io.metersphere.api.service.definition.ApiDefinitionModuleService;
|
import io.metersphere.api.service.definition.ApiDefinitionModuleService;
|
||||||
|
import io.metersphere.plan.domain.TestPlanConfig;
|
||||||
|
import io.metersphere.plan.mapper.TestPlanConfigMapper;
|
||||||
import io.metersphere.project.domain.Project;
|
import io.metersphere.project.domain.Project;
|
||||||
import io.metersphere.project.mapper.ProjectMapper;
|
import io.metersphere.project.mapper.ProjectMapper;
|
||||||
import io.metersphere.sdk.constants.ApplicationNumScope;
|
import io.metersphere.sdk.constants.ApplicationNumScope;
|
||||||
|
@ -27,6 +29,7 @@ import io.metersphere.system.uid.IDGenerator;
|
||||||
import io.metersphere.system.uid.NumGenerator;
|
import io.metersphere.system.uid.NumGenerator;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.apache.commons.collections4.CollectionUtils;
|
import org.apache.commons.collections4.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.ibatis.session.ExecutorType;
|
import org.apache.ibatis.session.ExecutorType;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
import org.apache.ibatis.session.SqlSessionFactory;
|
import org.apache.ibatis.session.SqlSessionFactory;
|
||||||
|
@ -37,7 +40,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
import org.springframework.test.web.servlet.ResultMatcher;
|
import org.springframework.test.web.servlet.ResultMatcher;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -77,6 +79,8 @@ public class ApiDefinitionModuleControllerTests extends BaseTest {
|
||||||
private ApiDefinitionModuleService apiDefinitionModuleService;
|
private ApiDefinitionModuleService apiDefinitionModuleService;
|
||||||
@Resource
|
@Resource
|
||||||
private SqlSessionFactory sqlSessionFactory;
|
private SqlSessionFactory sqlSessionFactory;
|
||||||
|
@Resource
|
||||||
|
private TestPlanConfigMapper testPlanConfigMapper;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public ApiDefinitionModuleControllerTests(ProjectServiceInvoker serviceInvoker) {
|
public ApiDefinitionModuleControllerTests(ProjectServiceInvoker serviceInvoker) {
|
||||||
|
@ -822,6 +826,14 @@ public class ApiDefinitionModuleControllerTests extends BaseTest {
|
||||||
request.setProjectId(DEFAULT_PROJECT_ID);
|
request.setProjectId(DEFAULT_PROJECT_ID);
|
||||||
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_READ, URL_FILE_MODULE_COUNT, request);
|
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_READ, URL_FILE_MODULE_COUNT, request);
|
||||||
|
|
||||||
|
TestPlanConfig planConfig = new TestPlanConfig();
|
||||||
|
planConfig.setTestPlanId("wx_123");
|
||||||
|
planConfig.setPassThreshold(0.8);
|
||||||
|
planConfig.setRepeatCase(false);
|
||||||
|
planConfig.setAutomaticStatusUpdate(false);
|
||||||
|
request.setTestPlanId("wx_123");
|
||||||
|
testPlanConfigMapper.insert(planConfig);
|
||||||
|
this.requestPostWithOkAndReturn(URL_FILE_MODULE_COUNT, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -3,12 +3,15 @@ package io.metersphere.plan.controller;
|
||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
|
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
|
||||||
|
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
|
||||||
import io.metersphere.functional.dto.FunctionalCasePageDTO;
|
import io.metersphere.functional.dto.FunctionalCasePageDTO;
|
||||||
import io.metersphere.functional.request.FunctionalCasePageRequest;
|
import io.metersphere.functional.request.FunctionalCasePageRequest;
|
||||||
import io.metersphere.functional.service.FunctionalCaseService;
|
import io.metersphere.functional.service.FunctionalCaseService;
|
||||||
|
import io.metersphere.plan.dto.request.TestPlanApiCaseRequest;
|
||||||
import io.metersphere.plan.dto.request.TestPlanApiRequest;
|
import io.metersphere.plan.dto.request.TestPlanApiRequest;
|
||||||
import io.metersphere.plan.service.TestPlanApiCaseService;
|
import io.metersphere.plan.service.TestPlanApiCaseService;
|
||||||
import io.metersphere.plan.service.TestPlanConfigService;
|
import io.metersphere.plan.service.TestPlanConfigService;
|
||||||
|
import io.metersphere.plan.service.TestPlanManagementService;
|
||||||
import io.metersphere.sdk.constants.PermissionConstants;
|
import io.metersphere.sdk.constants.PermissionConstants;
|
||||||
import io.metersphere.system.security.CheckOwner;
|
import io.metersphere.system.security.CheckOwner;
|
||||||
import io.metersphere.system.utils.PageUtils;
|
import io.metersphere.system.utils.PageUtils;
|
||||||
|
@ -37,12 +40,18 @@ public class TestPlanAssociateController {
|
||||||
private FunctionalCaseService functionalCaseService;
|
private FunctionalCaseService functionalCaseService;
|
||||||
@Resource
|
@Resource
|
||||||
private TestPlanApiCaseService testPlanApiCaseService;
|
private TestPlanApiCaseService testPlanApiCaseService;
|
||||||
|
@Resource
|
||||||
|
private TestPlanManagementService testPlanManagementService;
|
||||||
|
|
||||||
@PostMapping("/page")
|
@PostMapping("/page")
|
||||||
@Operation(summary = "测试计划-关联用例弹窗-功能用例列表查询(项目)")
|
@Operation(summary = "测试计划-关联用例弹窗-功能用例列表查询(项目)")
|
||||||
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
|
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
|
||||||
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
|
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
|
||||||
public Pager<List<FunctionalCasePageDTO>> getFunctionalCasePage(@Validated @RequestBody FunctionalCasePageRequest request) {
|
public Pager<List<FunctionalCasePageDTO>> getFunctionalCasePage(@Validated @RequestBody FunctionalCasePageRequest request) {
|
||||||
|
boolean moduleIsOpen = testPlanManagementService.checkModuleIsOpenByProjectId(request.getProjectId());
|
||||||
|
if (!moduleIsOpen) {
|
||||||
|
return new Pager<>();
|
||||||
|
}
|
||||||
boolean isRepeat = false;
|
boolean isRepeat = false;
|
||||||
if (StringUtils.isNotBlank(request.getTestPlanId())) {
|
if (StringUtils.isNotBlank(request.getTestPlanId())) {
|
||||||
isRepeat = testPlanConfigService.isRepeatCase(request.getTestPlanId());
|
isRepeat = testPlanConfigService.isRepeatCase(request.getTestPlanId());
|
||||||
|
@ -57,10 +66,30 @@ public class TestPlanAssociateController {
|
||||||
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
|
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_READ)
|
||||||
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
|
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
|
||||||
public Pager<List<ApiDefinitionDTO>> getApiPage(@Validated @RequestBody TestPlanApiRequest request) {
|
public Pager<List<ApiDefinitionDTO>> getApiPage(@Validated @RequestBody TestPlanApiRequest request) {
|
||||||
|
boolean moduleIsOpen = testPlanManagementService.checkModuleIsOpenByProjectId(request.getProjectId());
|
||||||
|
if (!moduleIsOpen) {
|
||||||
|
return new Pager<>();
|
||||||
|
}
|
||||||
boolean isRepeat = testPlanConfigService.isRepeatCase(request.getTestPlanId());
|
boolean isRepeat = testPlanConfigService.isRepeatCase(request.getTestPlanId());
|
||||||
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
||||||
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "pos desc");
|
StringUtils.isNotBlank(request.getSortString()) ? request.getSortString() : "pos desc");
|
||||||
return PageUtils.setPageInfo(page, testPlanApiCaseService.getApiPage(request, isRepeat));
|
return PageUtils.setPageInfo(page, testPlanApiCaseService.getApiPage(request, isRepeat));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@PostMapping("/api/case/page")
|
||||||
|
@Operation(summary = "测试计划-关联用例弹窗-接口CASE列表查询(项目)")
|
||||||
|
@RequiresPermissions(PermissionConstants.PROJECT_API_DEFINITION_CASE_READ)
|
||||||
|
@CheckOwner(resourceId = "#request.getTestPlanId()", resourceType = "test_plan")
|
||||||
|
public Pager<List<ApiTestCaseDTO>> page(@Validated @RequestBody TestPlanApiCaseRequest request) {
|
||||||
|
boolean moduleIsOpen = testPlanManagementService.checkModuleIsOpenByProjectId(request.getProjectId());
|
||||||
|
if (!moduleIsOpen) {
|
||||||
|
return new Pager<>();
|
||||||
|
}
|
||||||
|
boolean isRepeat = testPlanConfigService.isRepeatCase(request.getTestPlanId());
|
||||||
|
Page<Object> page = PageHelper.startPage(request.getCurrent(), request.getPageSize(),
|
||||||
|
StringUtils.isNotBlank(request.getSortString("id")) ? request.getSortString("id") : "pos desc, id desc");
|
||||||
|
return PageUtils.setPageInfo(page, testPlanApiCaseService.getApiCasePage(request, isRepeat));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package io.metersphere.plan.dto.request;
|
||||||
|
|
||||||
|
import io.metersphere.api.dto.definition.ApiTestCasePageRequest;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wx
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TestPlanApiCaseRequest extends ApiTestCasePageRequest {
|
||||||
|
|
||||||
|
@Schema(description = "测试计划id", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotBlank(message = "{test_plan.id.not_blank}")
|
||||||
|
private String testPlanId;
|
||||||
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package io.metersphere.plan.service;
|
package io.metersphere.plan.service;
|
||||||
|
|
||||||
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
|
import io.metersphere.api.dto.definition.ApiDefinitionDTO;
|
||||||
|
import io.metersphere.api.dto.definition.ApiTestCaseDTO;
|
||||||
import io.metersphere.api.service.definition.ApiDefinitionService;
|
import io.metersphere.api.service.definition.ApiDefinitionService;
|
||||||
|
import io.metersphere.api.service.definition.ApiTestCaseService;
|
||||||
import io.metersphere.plan.domain.TestPlanApiCaseExample;
|
import io.metersphere.plan.domain.TestPlanApiCaseExample;
|
||||||
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
|
import io.metersphere.plan.dto.TestPlanCaseRunResultCount;
|
||||||
|
import io.metersphere.plan.dto.request.TestPlanApiCaseRequest;
|
||||||
import io.metersphere.plan.dto.request.TestPlanApiRequest;
|
import io.metersphere.plan.dto.request.TestPlanApiRequest;
|
||||||
import io.metersphere.plan.mapper.ExtTestPlanApiCaseMapper;
|
import io.metersphere.plan.mapper.ExtTestPlanApiCaseMapper;
|
||||||
import io.metersphere.plan.mapper.TestPlanApiCaseMapper;
|
import io.metersphere.plan.mapper.TestPlanApiCaseMapper;
|
||||||
|
@ -27,6 +30,8 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
|
||||||
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
|
private ExtTestPlanApiCaseMapper extTestPlanApiCaseMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private ApiDefinitionService apiDefinitionService;
|
private ApiDefinitionService apiDefinitionService;
|
||||||
|
@Resource
|
||||||
|
private ApiTestCaseService apiTestCaseService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteBatchByTestPlanId(List<String> testPlanIdList) {
|
public void deleteBatchByTestPlanId(List<String> testPlanIdList) {
|
||||||
|
@ -65,10 +70,30 @@ public class TestPlanApiCaseService extends TestPlanResourceService {
|
||||||
// SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
|
// SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取接口列表
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param isRepeat
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
public List<ApiDefinitionDTO> getApiPage(TestPlanApiRequest request, boolean isRepeat) {
|
public List<ApiDefinitionDTO> getApiPage(TestPlanApiRequest request, boolean isRepeat) {
|
||||||
List<ApiDefinitionDTO> list = extTestPlanApiCaseMapper.list(request, isRepeat);
|
List<ApiDefinitionDTO> list = extTestPlanApiCaseMapper.list(request, isRepeat);
|
||||||
apiDefinitionService.processApiDefinitions(list);
|
apiDefinitionService.processApiDefinitions(list);
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取接口用例列表
|
||||||
|
*
|
||||||
|
* @param request
|
||||||
|
* @param isRepeat
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<ApiTestCaseDTO> getApiCasePage(TestPlanApiCaseRequest request, boolean isRepeat) {
|
||||||
|
List<ApiTestCaseDTO> apiCaseLists = apiTestCaseService.page(request, isRepeat, false);
|
||||||
|
return apiCaseLists;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class TestPlanManagementService {
|
||||||
|
|
||||||
private List<TestPlanResponse> getTableList(TestPlanTableRequest request) {
|
private List<TestPlanResponse> getTableList(TestPlanTableRequest request) {
|
||||||
List<TestPlanResponse> testPlanResponses = extTestPlanMapper.selectByConditions(request, null);
|
List<TestPlanResponse> testPlanResponses = extTestPlanMapper.selectByConditions(request, null);
|
||||||
handChildren(testPlanResponses,request.getProjectId());
|
handChildren(testPlanResponses, request.getProjectId());
|
||||||
return testPlanResponses;
|
return testPlanResponses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ public class TestPlanManagementService {
|
||||||
*
|
*
|
||||||
* @param testPlanResponses
|
* @param testPlanResponses
|
||||||
*/
|
*/
|
||||||
private void handChildren(List<TestPlanResponse> testPlanResponses,String projectId) {
|
private void handChildren(List<TestPlanResponse> testPlanResponses, String projectId) {
|
||||||
List<String> groupIds = testPlanResponses.stream().filter(item -> StringUtils.equals(item.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)).map(TestPlanResponse::getId).toList();
|
List<String> groupIds = testPlanResponses.stream().filter(item -> StringUtils.equals(item.getType(), TestPlanConstants.TEST_PLAN_TYPE_GROUP)).map(TestPlanResponse::getId).toList();
|
||||||
TestPlanTableRequest request = new TestPlanTableRequest();
|
TestPlanTableRequest request = new TestPlanTableRequest();
|
||||||
request.setProjectId(projectId);
|
request.setProjectId(projectId);
|
||||||
|
@ -115,4 +115,23 @@ public class TestPlanManagementService {
|
||||||
throw new MSException(Translator.get("project.module_menu.check.error"));
|
throw new MSException(Translator.get("project.module_menu.check.error"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据项目id检查模块是否开启
|
||||||
|
*
|
||||||
|
* @param projectId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean checkModuleIsOpenByProjectId(String projectId) {
|
||||||
|
Project project = projectMapper.selectByPrimaryKey(projectId);
|
||||||
|
if (project == null || StringUtils.isEmpty(project.getModuleSetting())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
List<String> projectModuleMenus = JSON.parseArray(project.getModuleSetting(), String.class);
|
||||||
|
if (projectModuleMenus.contains(TestPlanResourceConfig.CONFIG_TEST_PLAN)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package io.metersphere.plan.controller;
|
package io.metersphere.plan.controller;
|
||||||
|
|
||||||
import io.metersphere.functional.request.FunctionalCasePageRequest;
|
import io.metersphere.functional.request.FunctionalCasePageRequest;
|
||||||
|
import io.metersphere.plan.dto.request.TestPlanApiCaseRequest;
|
||||||
import io.metersphere.plan.dto.request.TestPlanApiRequest;
|
import io.metersphere.plan.dto.request.TestPlanApiRequest;
|
||||||
import io.metersphere.sdk.util.JSON;
|
import io.metersphere.sdk.util.JSON;
|
||||||
import io.metersphere.system.base.BaseTest;
|
import io.metersphere.system.base.BaseTest;
|
||||||
|
@ -21,15 +22,18 @@ import java.util.HashMap;
|
||||||
public class TestPlanAssociateControllerTests extends BaseTest {
|
public class TestPlanAssociateControllerTests extends BaseTest {
|
||||||
public static final String FUNCTIONAL_CASE_ASSOCIATION_URL = "/test-plan/association/page";
|
public static final String FUNCTIONAL_CASE_ASSOCIATION_URL = "/test-plan/association/page";
|
||||||
public static final String API_ASSOCIATION_URL = "/test-plan/association/api/page";
|
public static final String API_ASSOCIATION_URL = "/test-plan/association/api/page";
|
||||||
|
public static final String API_CASE_ASSOCIATION_URL = "/test-plan/association/api/case/page";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
@Sql(scripts = {"/dml/init_test_plan_association.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
|
@Sql(scripts = {"/dml/init_test_plan_association.sql"}, config = @SqlConfig(encoding = "utf-8", transactionMode = SqlConfig.TransactionMode.ISOLATED))
|
||||||
public void testGetPageList() throws Exception {
|
public void testGetPageList() throws Exception {
|
||||||
FunctionalCasePageRequest request = new FunctionalCasePageRequest();
|
FunctionalCasePageRequest request = new FunctionalCasePageRequest();
|
||||||
request.setProjectId("123");
|
|
||||||
request.setCurrent(1);
|
request.setCurrent(1);
|
||||||
request.setPageSize(10);
|
request.setPageSize(10);
|
||||||
|
request.setProjectId("1234567");
|
||||||
|
this.requestPost(FUNCTIONAL_CASE_ASSOCIATION_URL, request);
|
||||||
|
request.setProjectId("wx_1234");
|
||||||
this.requestPost(FUNCTIONAL_CASE_ASSOCIATION_URL, request);
|
this.requestPost(FUNCTIONAL_CASE_ASSOCIATION_URL, request);
|
||||||
request.setSort(new HashMap<>() {{
|
request.setSort(new HashMap<>() {{
|
||||||
put("createTime", "desc");
|
put("createTime", "desc");
|
||||||
|
@ -46,10 +50,12 @@ public class TestPlanAssociateControllerTests extends BaseTest {
|
||||||
@Order(2)
|
@Order(2)
|
||||||
public void testApiPageList() throws Exception {
|
public void testApiPageList() throws Exception {
|
||||||
TestPlanApiRequest request = new TestPlanApiRequest();
|
TestPlanApiRequest request = new TestPlanApiRequest();
|
||||||
request.setProjectId("123");
|
|
||||||
request.setCurrent(1);
|
request.setCurrent(1);
|
||||||
request.setPageSize(10);
|
request.setPageSize(10);
|
||||||
request.setTestPlanId("wxx_1");
|
request.setTestPlanId("wxx_1");
|
||||||
|
request.setProjectId("1234567");
|
||||||
|
this.requestPost(API_ASSOCIATION_URL, request);
|
||||||
|
request.setProjectId("wx_1234");
|
||||||
this.requestPost(API_ASSOCIATION_URL, request);
|
this.requestPost(API_ASSOCIATION_URL, request);
|
||||||
request.setSort(new HashMap<>() {{
|
request.setSort(new HashMap<>() {{
|
||||||
put("createTime", "desc");
|
put("createTime", "desc");
|
||||||
|
@ -59,4 +65,25 @@ public class TestPlanAssociateControllerTests extends BaseTest {
|
||||||
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
|
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
|
||||||
Assertions.assertNotNull(resultHolder);
|
Assertions.assertNotNull(resultHolder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(3)
|
||||||
|
public void testApiCasePageList() throws Exception {
|
||||||
|
TestPlanApiCaseRequest request = new TestPlanApiCaseRequest();
|
||||||
|
request.setCurrent(1);
|
||||||
|
request.setPageSize(10);
|
||||||
|
request.setProjectId("1234567");
|
||||||
|
request.setTestPlanId("wxx_1");
|
||||||
|
this.requestPost(API_CASE_ASSOCIATION_URL, request);
|
||||||
|
request.setProjectId("wx_1234");
|
||||||
|
this.requestPost(API_CASE_ASSOCIATION_URL, request);
|
||||||
|
request.setSort(new HashMap<>() {{
|
||||||
|
put("createTime", "desc");
|
||||||
|
}});
|
||||||
|
MvcResult mvcResult = this.requestPostWithOkAndReturn(API_CASE_ASSOCIATION_URL, request);
|
||||||
|
String returnData = mvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
ResultHolder resultHolder = JSON.parseObject(returnData, ResultHolder.class);
|
||||||
|
Assertions.assertNotNull(resultHolder);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`)
|
INSERT INTO `test_plan`(`id`, `num`, `project_id`, `group_id`, `module_id`, `name`, `status`, `type`, `tags`, `create_time`, `create_user`, `update_time`, `update_user`, `planned_start_time`, `planned_end_time`, `actual_start_time`, `actual_end_time`, `description`)
|
||||||
VALUES
|
VALUES
|
||||||
('wxx_1', 5000, '123', 'NONE', '1', 'qwe', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
|
('wxx_1', 5000, 'wx_1234', 'NONE', '1', 'qwe', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11'),
|
||||||
('wxx_2', 10000, '123', 'NONE', '1', 'eeew', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
|
('wxx_2', 10000, 'wx_1234', 'NONE', '1', 'eeew', 'PREPARED', 'TEST_PLAN', NULL, 1714980158000, 'WX', 1714980158000, 'WX', 1714980158000, 1714980158000, 1714980158000, 1714980158000, '11');
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`)
|
INSERT INTO `test_plan_config`(`test_plan_id`, `automatic_status_update`, `repeat_case`, `pass_threshold`)
|
||||||
|
@ -15,19 +15,23 @@ VALUES ('wxx_tpfc_1', 'wxx_1', 'wxx_test_1', 1714980158000, 'admin', NULL, NULL,
|
||||||
|
|
||||||
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
|
INSERT INTO functional_case(id, num, module_id, project_id, template_id, name, review_status, tags, case_edit_type, pos, version_id, ref_id, last_execute_result, deleted, public_case, latest, create_user, update_user, delete_user, create_time, update_time, delete_time)
|
||||||
VALUES
|
VALUES
|
||||||
('wxx_test_1', 1, '1', '123', '100001', '1111', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
('wxx_test_1', 1, '1', 'wx_1234', '100001', '1111', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
||||||
('wxx_test_2', 2, '1', '123', '100001', '2222', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
('wxx_test_2', 2, '1', 'wx_1234', '100001', '2222', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL),
|
||||||
('wxx_test_3', 3, 'root', '123', '100001', '3333', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
('wxx_test_3', 3, 'root', 'wx_1234', '100001', '3333', 'UN_REVIEWED', NULL, 'TEXT', 55000, 'v3.0.0', 'TEST_FUNCTIONAL_MINDER_CASE_ID_7', 'UN_EXECUTED', b'0', b'0', b'1', 'admin', 'admin', '', 1698058347559, 1698058347559, NULL);
|
||||||
|
|
||||||
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`)
|
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
|
VALUES
|
||||||
('wxx_api_1', '2222', 'HTTP', 'GET', '/111', 'DEPRECATED', 100003, '[]', 192, '123', 'root', b'1', '100844458962059498', '1086025445195776', '', 1716370415311, 'admin', 1716455838628, 'admin', NULL, NULL, b'0'),
|
('wxx_api_1', '2222', 'HTTP', 'GET', '/111', 'DEPRECATED', 100003, '[]', 192, 'wx_1234', 'root', b'1', '100844458962059498', '1086025445195776', '', 1716370415311, 'admin', 1716455838628, 'admin', NULL, NULL, b'0'),
|
||||||
('wxx_api_2', '3333', 'HTTP', 'GET', '/111', 'DEPRECATED', 100003, '[]', 192, '123', 'root', b'1', '100844458962059498', '1086025445195776', '', 1716370415311, 'admin', 1716455838628, 'admin', NULL, NULL, b'0'),
|
('wxx_api_2', '3333', 'HTTP', 'GET', '/111', 'DEPRECATED', 100003, '[]', 192, 'wx_1234', 'root', b'1', '100844458962059498', '1086025445195776', '', 1716370415311, 'admin', 1716455838628, 'admin', NULL, NULL, b'0'),
|
||||||
('wxx_api_3', '4444', 'HTTP', 'GET', '/111', 'DEPRECATED', 100003, '[]', 192, '123', 'root', b'1', '100844458962059498', '1086025445195776', '', 1716370415311, 'admin', 1716455838628, 'admin', NULL, NULL, b'0');
|
('wxx_api_3', '4444', 'HTTP', 'GET', '/111', 'DEPRECATED', 100003, '[]', 192, 'wx_1234', 'root', b'1', '100844458962059498', '1086025445195776', '', 1716370415311, 'admin', 1716455838628, '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`)
|
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
|
VALUES
|
||||||
('wxx_api_case_1', '231', 'P0', 100055001, '[]', 'PROCESSING', 'SUCCESS', '1130899263537153', 64, '123', 'wxx_api_1', '899658209591296', '899606669983745', 1716199600948, 'admin', 1716199600948, 'admin', NULL, NULL, b'0'),
|
('wxx_api_case_1', '231', 'P0', 100055001, '[]', 'PROCESSING', 'SUCCESS', '1130899263537153', 64, 'wx_1234', 'wxx_api_1', '899658209591296', '899606669983745', 1716199600948, 'admin', 1716199600948, 'admin', NULL, NULL, b'0'),
|
||||||
('wxx_api_case_2', '232', 'P0', 100055001, '[]', 'PROCESSING', 'SUCCESS', '1130899263537153', 64, '123', 'wxx_api_1', '899658209591296', '899606669983745', 1716199600948, 'admin', 1716199600948, 'admin', NULL, NULL, b'0'),
|
('wxx_api_case_2', '232', 'P0', 100055001, '[]', 'PROCESSING', 'SUCCESS', '1130899263537153', 64, 'wx_1234', 'wxx_api_1', '899658209591296', '899606669983745', 1716199600948, 'admin', 1716199600948, 'admin', NULL, NULL, b'0'),
|
||||||
('wxx_api_case_3', '233', 'P0', 100055001, '[]', 'PROCESSING', 'SUCCESS', '1130899263537153', 64, '123', 'wxx_api_2', '899658209591296', '899606669983745', 1716199600948, 'admin', 1716199600948, 'admin', NULL, NULL, b'0');
|
('wxx_api_case_3', '233', 'P0', 100055001, '[]', 'PROCESSING', 'SUCCESS', '1130899263537153', 64, 'wx_1234', 'wxx_api_2', '899658209591296', '899606669983745', 1716199600948, 'admin', 1716199600948, 'admin', NULL, NULL, b'0');
|
||||||
|
|
||||||
|
INSERT INTO project (id, num, organization_id, name, description, create_user, update_user, create_time, update_time, module_setting)
|
||||||
|
VALUES
|
||||||
|
('wx_1234', 3, 1, 'wx', 'wx', 'admin', 'admin', unix_timestamp() * 1000, unix_timestamp() * 1000,'["bugManagement","caseManagement","apiTest","testPlan"]');
|
Loading…
Reference in New Issue