fix(测试跟踪): 修复接口调用没有关联可执行用例测试计划执行时没有提示的缺陷 (#16502)

--bug=1015449 --user=王孝刚
[测试计划]github#16371测试计划里只包含功能用例,在页面无法执行,但是调用API可以执行,且可正常生成报告,报告里无任何信息
https://www.tapd.cn/55049933/s/1214889

Co-authored-by: wxg0103 <727495428@qq.com>
This commit is contained in:
MeterSphere Bot 2022-08-01 09:47:03 +08:00 committed by GitHub
parent dc17466990
commit 08519bc6e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 440 additions and 353 deletions

View File

@ -20,7 +20,7 @@ public interface ExtTestPlanApiCaseMapper {
List<String> getIdsByPlanId(String planId); List<String> getIdsByPlanId(String planId);
List<String> getNotRelevanceCaseIds(@Param("planId")String planId, @Param("relevanceProjectIds")List<String> relevanceProjectIds); List<String> getNotRelevanceCaseIds(@Param("planId") String planId, @Param("relevanceProjectIds") List<String> relevanceProjectIds);
List<String> getStatusByTestPlanId(String id); List<String> getStatusByTestPlanId(String id);
@ -36,14 +36,17 @@ public interface ExtTestPlanApiCaseMapper {
List<TestPlanFailureApiDTO> getFailureList(@Param("planId") String planId, @Param("status") String status); List<TestPlanFailureApiDTO> getFailureList(@Param("planId") String planId, @Param("status") String status);
List<TestPlanFailureApiDTO> getFailureListByIds(@Param("ids") Collection<String> caseIdList,@Param("status") String status); List<TestPlanFailureApiDTO> getFailureListByIds(@Param("ids") Collection<String> caseIdList, @Param("status") String status);
List<String> selectPlanIds(); List<String> selectPlanIds();
List<String> getIdsOrderByUpdateTime(@Param("planId") String planId); List<String> getIdsOrderByUpdateTime(@Param("planId") String planId);
Long getPreOrder(@Param("planId")String planId, @Param("baseOrder") Long baseOrder); Long getPreOrder(@Param("planId") String planId, @Param("baseOrder") Long baseOrder);
Long getLastOrder(@Param("planId") String planId, @Param("baseOrder") Long baseOrder);
List<TestPlanApiCase> selectByIdsAndStatusIsNotTrash(@Param("ids") List<String> ids);
Long getLastOrder(@Param("planId")String planId, @Param("baseOrder") Long baseOrder);
} }

View File

@ -4,27 +4,45 @@
<insert id="insertIfNotExists" parameterType="io.metersphere.base.domain.TestPlanApiCase"> <insert id="insertIfNotExists" parameterType="io.metersphere.base.domain.TestPlanApiCase">
INSERT INTO test_plan_api_case(id, test_plan_id, api_case_id, environment_id, create_time, update_time, create_user, `order`) INSERT INTO test_plan_api_case(id, test_plan_id, api_case_id, environment_id, create_time, update_time,
SELECT #{request.id}, #{request.testPlanId}, #{request.apiCaseId}, #{request.environmentId}, #{request.createTime}, #{request.updateTime}, #{request.createUser}, #{request.order} create_user, `order`)
SELECT #{request.id},
#{request.testPlanId},
#{request.apiCaseId},
#{request.environmentId},
#{request.createTime},
#{request.updateTime},
#{request.createUser},
#{request.order}
FROM DUAL FROM DUAL
WHERE NOT EXISTS( WHERE NOT EXISTS(
SELECT id FROM SELECT id
test_plan_api_case FROM test_plan_api_case
WHERE test_plan_id = #{request.testPlanId} and api_case_id = #{request.apiCaseId} WHERE test_plan_id = #{request.testPlanId}
and api_case_id = #{request.apiCaseId}
) )
</insert> </insert>
<select id="getApiTestCaseById" resultType="io.metersphere.base.domain.ApiTestCaseWithBLOBs"> <select id="getApiTestCaseById" resultType="io.metersphere.base.domain.ApiTestCaseWithBLOBs">
SELECT t.* FROM api_test_case t SELECT t.*
FROM api_test_case t
INNER JOIN test_plan_api_case tpac ON t.id = tpac.api_case_id INNER JOIN test_plan_api_case tpac ON t.id = tpac.api_case_id
WHERE tpac.id = #{0} WHERE tpac.id = #{0}
</select> </select>
<select id="getApiTestCaseIdById" resultType="java.lang.String"> <select id="getApiTestCaseIdById" resultType="java.lang.String">
SELECT api_case_id FROM test_plan_api_case t WHERE id = #{0} SELECT api_case_id
FROM test_plan_api_case t
WHERE id = #{0}
</select> </select>
<select id="selectLegalDataByTestPlanId" resultType="io.metersphere.base.domain.TestPlanApiCase"> <select id="selectLegalDataByTestPlanId" resultType="io.metersphere.base.domain.TestPlanApiCase">
SELECT t.* FROM test_plan_api_case t WHERE t.test_plan_id = #{0} SELECT t.*
FROM test_plan_api_case t
WHERE t.test_plan_id = #{0}
AND t.api_case_id IN ( AND t.api_case_id IN (
SELECT id FROM api_test_case WHERE status IS NULL OR status != 'Trash' SELECT id
FROM api_test_case
WHERE status IS NULL
OR status
!= 'Trash'
) )
ORDER BY `order` DESC ORDER BY `order` DESC
</select> </select>
@ -244,7 +262,7 @@
order by order by
<foreach collection="request.orders" separator="," item="order"> <foreach collection="request.orders" separator="," item="order">
<choose> <choose>
<when test="order.name == 'update_time' or order.name == 'order'" > <when test="order.name == 'update_time' or order.name == 'order'">
t.${order.name} ${order.type} t.${order.name} ${order.type}
</when> </when>
<when test="order.name == 'create_user'"> <when test="order.name == 'create_user'">
@ -259,8 +277,7 @@
</select> </select>
<select id="getExecResultByPlanId" resultType="java.lang.String"> <select id="getExecResultByPlanId" resultType="java.lang.String">
select status select status
from from test_plan_api_case
test_plan_api_case
where test_plan_id = #{planId} where test_plan_id = #{planId}
AND api_case_id in (SELECT id FROM api_test_case WHERE (`status` is null or `status` != 'Trash')) AND api_case_id in (SELECT id FROM api_test_case WHERE (`status` is null or `status` != 'Trash'))
</select> </select>
@ -285,11 +302,20 @@
where t.test_plan_id = #{planId} where t.test_plan_id = #{planId}
</select> </select>
<select id="getStatusByTestPlanId" resultType="java.lang.String"> <select id="getStatusByTestPlanId" resultType="java.lang.String">
SELECT `status` FROM test_plan_api_case WHERE test_plan_id = #{0} SELECT `status`
FROM test_plan_api_case
WHERE test_plan_id = #{0}
</select> </select>
<select id="selectForPlanReport" resultType="io.metersphere.track.dto.PlanReportCaseDTO"> <select id="selectForPlanReport" resultType="io.metersphere.track.dto.PlanReportCaseDTO">
select id,status from test_plan_api_case where test_plan_id = #{planId} and api_case_id IN ( select id, status
SELECT id FROM api_test_case where status is null or status != 'Trash' from test_plan_api_case
where test_plan_id = #{planId}
and api_case_id IN (
SELECT id
FROM api_test_case
where status is null
or status
!= 'Trash'
) )
</select> </select>
<select id="getFailureList" resultType="io.metersphere.api.dto.automation.TestPlanFailureApiDTO"> <select id="getFailureList" resultType="io.metersphere.api.dto.automation.TestPlanFailureApiDTO">
@ -337,10 +363,14 @@
<select id="selectPlanIds" resultType="java.lang.String"> <select id="selectPlanIds" resultType="java.lang.String">
select DISTINCT test_plan_id from test_plan_api_case; select DISTINCT test_plan_id
from test_plan_api_case;
</select> </select>
<select id="getIdsOrderByUpdateTime" resultType="java.lang.String"> <select id="getIdsOrderByUpdateTime" resultType="java.lang.String">
select id from test_plan_api_case where test_plan_id = #{planId} order by update_time ASC; select id
from test_plan_api_case
where test_plan_id = #{planId}
order by update_time ASC;
</select> </select>
<select id="getLastOrder" resultType="java.lang.Long"> <select id="getLastOrder" resultType="java.lang.Long">
@ -358,6 +388,18 @@
</if> </if>
order by `order` desc limit 1; order by `order` desc limit 1;
</select> </select>
<select id="selectByIdsAndStatusIsNotTrash" resultType="io.metersphere.base.domain.TestPlanApiCase">
SELECT
plan.*
FROM
test_plan_api_case plan
INNER JOIN api_test_case api ON plan.api_case_id = api.id
WHERE
(api.`status` is null OR api.`status` != 'Trash') AND plan.test_plan_id IN
<foreach collection="ids" item="v" separator="," open="(" close=")">
#{v}
</foreach>
</select>
<sql id="queryVersionCondition"> <sql id="queryVersionCondition">
<if test="request.versionId != null"> <if test="request.versionId != null">

View File

@ -0,0 +1,12 @@
package io.metersphere.base.mapper.ext;
import io.metersphere.base.domain.TestPlanApiScenario;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface ExtTestPlanApiScenarioMapper {
List<TestPlanApiScenario> selectByIdsAndStatusIsNotTrash(@Param("ids") List<String> ids);
}

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="io.metersphere.base.mapper.ext.ExtTestPlanApiScenarioMapper">
<select id="selectByIdsAndStatusIsNotTrash" resultType="io.metersphere.base.domain.TestPlanApiScenario">
SELECT
plan.*
FROM
test_plan_api_scenario plan
INNER JOIN api_scenario api ON plan.api_scenario_id = api.id
WHERE
(api.`status` is null OR api.`status` != 'Trash') AND plan.test_plan_id IN
<foreach collection="ids" item="v" separator="," open="(" close=")">
#{v}
</foreach>
</select>
</mapper>

View File

@ -35,9 +35,9 @@ import io.metersphere.performance.service.MetricQueryService;
import io.metersphere.performance.service.PerformanceReportService; import io.metersphere.performance.service.PerformanceReportService;
import io.metersphere.performance.service.PerformanceTestService; import io.metersphere.performance.service.PerformanceTestService;
import io.metersphere.service.*; import io.metersphere.service.*;
import io.metersphere.track.factory.ReportComponentFactory;
import io.metersphere.track.domain.ReportComponent; import io.metersphere.track.domain.ReportComponent;
import io.metersphere.track.dto.*; import io.metersphere.track.dto.*;
import io.metersphere.track.factory.ReportComponentFactory;
import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest; import io.metersphere.track.request.testcase.PlanCaseRelevanceRequest;
import io.metersphere.track.request.testcase.QueryTestPlanRequest; import io.metersphere.track.request.testcase.QueryTestPlanRequest;
import io.metersphere.track.request.testplan.AddTestPlanRequest; import io.metersphere.track.request.testplan.AddTestPlanRequest;
@ -184,6 +184,8 @@ public class TestPlanService {
private ApiDefinitionExecResultMapper apiDefinitionExecResultMapper; private ApiDefinitionExecResultMapper apiDefinitionExecResultMapper;
@Resource @Resource
private ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper; private ExtApiDefinitionExecResultMapper extApiDefinitionExecResultMapper;
@Resource
private ExtTestPlanApiScenarioMapper extTestPlanApiScenarioMapper;
public synchronized TestPlan addTestPlan(AddTestPlanRequest testPlan) { public synchronized TestPlan addTestPlan(AddTestPlanRequest testPlan) {
if (getTestPlanByName(testPlan.getName()).size() > 0) { if (getTestPlanByName(testPlan.getName()).size() > 0) {
@ -1924,6 +1926,10 @@ public class TestPlanService {
} }
public String runPlan(TestplanRunRequest testplanRunRequest) { public String runPlan(TestplanRunRequest testplanRunRequest) {
//检查是否有可以执行的用例
if (!haveExecCase(testplanRunRequest.getTestPlanId())) {
MSException.throwException(Translator.get("plan_warning"));
}
String envType = testplanRunRequest.getEnvironmentType(); String envType = testplanRunRequest.getEnvironmentType();
Map<String, String> envMap = testplanRunRequest.getEnvMap(); Map<String, String> envMap = testplanRunRequest.getEnvMap();
String environmentGroupId = testplanRunRequest.getEnvironmentGroupId(); String environmentGroupId = testplanRunRequest.getEnvironmentGroupId();
@ -1999,16 +2005,14 @@ public class TestPlanService {
if (StringUtils.isBlank(id)) { if (StringUtils.isBlank(id)) {
return false; return false;
} }
TestPlanApiCaseExample apiCaseExample = new TestPlanApiCaseExample(); List<String> ids = new ArrayList<>();
apiCaseExample.createCriteria().andTestPlanIdEqualTo(id); ids.add(id);
List<TestPlanApiCase> testPlanApiCases = testPlanApiCaseMapper.selectByExample(apiCaseExample); List<TestPlanApiCase> testPlanApiCases = extTestPlanApiCaseMapper.selectByIdsAndStatusIsNotTrash(ids);
if (!CollectionUtils.isEmpty(testPlanApiCases)) { if (!CollectionUtils.isEmpty(testPlanApiCases)) {
return true; return true;
} }
TestPlanApiScenarioExample apiScenarioExample = new TestPlanApiScenarioExample(); List<TestPlanApiScenario> testPlanApiScenarios = extTestPlanApiScenarioMapper.selectByIdsAndStatusIsNotTrash(ids);
apiScenarioExample.createCriteria().andTestPlanIdEqualTo(id);
List<TestPlanApiScenario> testPlanApiScenarios = testPlanApiScenarioMapper.selectByExample(apiScenarioExample);
if (!CollectionUtils.isEmpty(testPlanApiScenarios)) { if (!CollectionUtils.isEmpty(testPlanApiScenarios)) {
return true; return true;
} }
@ -2153,6 +2157,7 @@ public class TestPlanService {
Map<String, String> executeQueue = new LinkedHashMap<>(); Map<String, String> executeQueue = new LinkedHashMap<>();
StringBuilder stringBuilder = new StringBuilder(); StringBuilder stringBuilder = new StringBuilder();
StringBuilder haveExecCaseBuilder = new StringBuilder();
for (int i = 0; i < planList.size(); i++) { for (int i = 0; i < planList.size(); i++) {
if (StringUtils.isBlank(planList.get(i).getRunModeConfig())) { if (StringUtils.isBlank(planList.get(i).getRunModeConfig())) {
StringBuilder append = stringBuilder.append("请保存[").append(planList.get(i).getName()).append("]的运行配置"); StringBuilder append = stringBuilder.append("请保存[").append(planList.get(i).getName()).append("]的运行配置");
@ -2160,8 +2165,14 @@ public class TestPlanService {
append.append("/"); append.append("/");
} }
} }
if (!haveExecCase(planList.get(i).getId())) {
haveExecCaseBuilder.append(planList.get(i).getName()).append("; ");
}
} }
if (StringUtils.isNotEmpty(haveExecCaseBuilder)) {
MSException.throwException(Translator.get("track_test_plan") + ": " + haveExecCaseBuilder.toString() + " :" + Translator.get("plan_warning"));
}
if (StringUtils.isNotEmpty(stringBuilder)) { if (StringUtils.isNotEmpty(stringBuilder)) {
MSException.throwException(stringBuilder.toString()); MSException.throwException(stringBuilder.toString());
} }

@ -1 +1 @@
Subproject commit 7f405105bf50af4ae2f1a240059c742f3ac7c729 Subproject commit 830bad0961b3ec2eaff21e9eac382d16f0521639

View File

@ -217,8 +217,8 @@ import_xmind_not_found=Test case not found
license_valid_license_error=Authorization authentication failed license_valid_license_error=Authorization authentication failed
test_review_task_notice=Test review task notice test_review_task_notice=Test review task notice
swagger_url_scheduled_import_notification=SwaggerUrl Scheduled import notification swagger_url_scheduled_import_notification=SwaggerUrl Scheduled import notification
Swagger_parse_error =Swagger parsing failed, please confirm file format is correct! Swagger_parse_error=Swagger parsing failed, please confirm file format is correct!
Swagger_parse_error_with_auth =Swagger parsing failed. Please check whether authentication information is correct or file format is correct! Swagger_parse_error_with_auth=Swagger parsing failed. Please check whether authentication information is correct or file format is correct!
test_track.length_less_than=The title is too long, the length must be less than test_track.length_less_than=The title is too long, the length must be less than
# check owner # check owner
check_owner_project=The current user does not have permission to operate this project check_owner_project=The current user does not have permission to operate this project
@ -388,3 +388,4 @@ cmdExtractElement=element extraction
tcp_mock_not_unique=This tcp port is be used tcp_mock_not_unique=This tcp port is be used
no_tcp_mock_port=No idle tcp port, please contact administrators. no_tcp_mock_port=No idle tcp port, please contact administrators.
name_already_exists_in_module=Name already exists in same module name_already_exists_in_module=Name already exists in same module
plan_warning=There is no associated executable use case under the test plan

View File

@ -387,3 +387,4 @@ cmdExtractElement=提取元素信息
tcp_mock_not_unique=该TCP端口号已被使用 tcp_mock_not_unique=该TCP端口号已被使用
no_tcp_mock_port=无可用的TCP端口号请联系管理员 no_tcp_mock_port=无可用的TCP端口号请联系管理员
name_already_exists_in_module=同层级下已经存在 name_already_exists_in_module=同层级下已经存在
plan_warning=测试计划下没有关联可以执行的用例

View File

@ -386,3 +386,4 @@ cmdExtractElement=提取元素信息
tcp_mock_not_unique=該TCP端口號已被使用 tcp_mock_not_unique=該TCP端口號已被使用
no_tcp_mock_port=無可用的TCP端口號請聯繫管理員 no_tcp_mock_port=無可用的TCP端口號請聯繫管理員
name_already_exists_in_module=同層級下已存在 name_already_exists_in_module=同層級下已存在
plan_warning=測試計劃下沒有關聯可以執行的用例

@ -1 +1 @@
Subproject commit 20b5563776f552ecd1b84bed3bcf50e5cbcaa9b0 Subproject commit 12370ba86adfdf31b11a30d48a59c3715490b351