feat(接口测试): 用例批量同步添加消息通知

--task=1015858 --user=陈建星 【接口测试】接口用例支持同步更新接口变更-后端-同步变更后消息通知与日志记录 https://www.tapd.cn/55049933/s/1560630
This commit is contained in:
AgAngle 2024-08-09 17:55:01 +08:00 committed by Craftsman
parent e625c94b76
commit 90f4f7ca4d
10 changed files with 143 additions and 15 deletions

View File

@ -19,8 +19,8 @@ public class ApiCaseBatchSyncRequest extends ApiTestCaseBatchRequest implements
@Data
public static class ApiCaseSyncNotificationRequest {
@Schema(description = "是否通知接口创建人", defaultValue = "true")
private Boolean apiCreator = true;
@Schema(description = "是否通知接口用例创建人", defaultValue = "true")
private Boolean apiCaseCreator = true;
@Schema(description = "是否通知引用该用例的场景创建人", defaultValue = "true")
private Boolean scenarioCreator = true;
}

View File

@ -35,6 +35,12 @@ public class ApiTestCaseDTO {
@Schema(description = "接口fk")
private String apiDefinitionId;
@Schema(description = "接口num")
private Long apiDefinitionNum;
@Schema(description = "接口名称")
private String apiDefinitionName;
@Schema(description = "环境fk")
private String environmentId;

View File

@ -111,4 +111,8 @@ public interface ExtApiTestCaseMapper {
List<ApiTestCase> getCaseListBySelectIds(@Param("isRepeat") boolean isRepeat, @Param("projectId") String projectId, @Param("ids") List<String> ids, @Param("testPlanId") String testPlanId, @Param("protocols") List<String> protocols);
void setApiChangeByApiDefinitionId(@Param("apiDefinitionId") String apiDefinitionId);
List<ApiTestCase> getRefApiScenarioCreator(@Param("ids") List<String> caseIds);
void clearApiChange(@Param("ids") List<String> ids);
}

View File

@ -35,6 +35,14 @@
and api_change is false
and ignore_api_change is false
</update>
<update id="clearApiChange">
update api_test_case
set api_change = false
where api_change = true and id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<select id="getPos" resultType="java.lang.Long">
SELECT pos
@ -727,4 +735,18 @@
)
</if>
</select>
<select id="getRefApiScenarioCreator" resultType="io.metersphere.api.domain.ApiTestCase">
select atc.id, api_scenario.create_user
from api_test_case atc
inner join api_scenario_step ass
on atc.id = ass.resource_id
and ass.ref_type = 'REF'
and atc.id in
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
inner join api_scenario
on ass.scenario_id = api_scenario.id
and api_scenario.deleted = 0;
</select>
</mapper>

View File

@ -236,14 +236,13 @@ public class ApiTestCaseLogService {
saveBatchLog(projectId, apiTestCases, operator, OperationLogType.RECOVER.name(), false, OperationLogModule.API_TEST_MANAGEMENT_RECYCLE);
}
public void batchSyncLog(Map<String, ApiTestCaseLogDTO> originMap, Map<String, ApiTestCaseLogDTO> modifiedMap) {
public void batchSyncLog(Map<String, ApiTestCaseLogDTO> originMap, Map<String, ApiTestCaseLogDTO> modifiedMap, Project project) {
List<LogDTO> logs = new ArrayList<>();
originMap.forEach((id, origin) -> {
ApiTestCaseLogDTO modified = modifiedMap.get(id);
if (modified == null) {
return;
}
Project project = projectMapper.selectByPrimaryKey(origin.getProjectId());
LogDTO dto = LogDTOBuilder.builder()
.projectId(project.getId())
.organizationId(project.getOrganizationId())

View File

@ -2,9 +2,11 @@ package io.metersphere.api.service.definition;
import io.metersphere.api.domain.ApiTestCase;
import io.metersphere.api.domain.ApiTestCaseExample;
import io.metersphere.api.dto.definition.ApiCaseBatchSyncRequest;
import io.metersphere.api.dto.definition.ApiTestCaseAddRequest;
import io.metersphere.api.dto.definition.ApiTestCaseUpdateRequest;
import io.metersphere.api.mapper.ApiTestCaseMapper;
import io.metersphere.api.mapper.ExtApiTestCaseMapper;
import io.metersphere.sdk.util.BeanUtils;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.SubListUtils;
@ -15,11 +17,11 @@ import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.service.CommonNoticeSendService;
import jakarta.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class ApiTestCaseNoticeService {
@ -30,6 +32,8 @@ public class ApiTestCaseNoticeService {
private ApiTestCaseMapper apiTestCaseMapper;
@Resource
private CommonNoticeSendService commonNoticeSendService;
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
public ApiDefinitionCaseDTO addCaseDto(ApiTestCaseAddRequest request) {
ApiDefinitionCaseDTO caseDTO = new ApiDefinitionCaseDTO();
@ -70,4 +74,56 @@ public class ApiTestCaseNoticeService {
});
}
}
public void batchSyncSendNotice(List<ApiTestCase> apiTestCases, User user, String projectId,
ApiCaseBatchSyncRequest.ApiCaseSyncNotificationRequest notificationConfig, String event) {
if (CollectionUtils.isEmpty(apiTestCases)) {
return;
}
Map<String, Set<String>> caseRefApiScenarioCreatorMap = null;
if (BooleanUtils.isTrue(notificationConfig.getScenarioCreator())) {
List<String> caseIds = apiTestCases.stream().map(ApiTestCase::getId).toList();
// 获取引用该用例的场景的创建人信息
List<ApiTestCase> caseRefApiScenarioCreators = extApiTestCaseMapper.getRefApiScenarioCreator(caseIds);
// 构建用例和创建人的映射关系
caseRefApiScenarioCreatorMap = caseRefApiScenarioCreators.stream().collect(Collectors.groupingBy(ApiTestCase::getId,
Collectors.mapping(ApiTestCase::getCreateUser, Collectors.toSet())));
}
List<ApiDefinitionCaseDTO> noticeLists = apiTestCases.stream()
.map(apiTestCase -> {
ApiDefinitionCaseDTO apiDefinitionCaseDTO = new ApiDefinitionCaseDTO();
BeanUtils.copyBean(apiDefinitionCaseDTO, apiTestCase);
return apiDefinitionCaseDTO;
})
.toList();
List<Map> resources = new ArrayList<>(JSON.parseArray(JSON.toJSONString(noticeLists), Map.class));
if (BooleanUtils.isTrue(notificationConfig.getScenarioCreator()) || BooleanUtils.isTrue(notificationConfig.getApiCaseCreator())) {
for (Map resource : resources) {
String caseId = (String) resource.get("id");
String relatedUsers = null;
if (BooleanUtils.isTrue(notificationConfig.getScenarioCreator())) {
// 添加引用该用例的场景的创建人
Set<String> userIds = Optional.ofNullable(caseRefApiScenarioCreatorMap.get(caseId)).orElse(new HashSet<>(1));
relatedUsers = userIds.stream().collect(Collectors.joining(";"));
}
if (BooleanUtils.isTrue(notificationConfig.getApiCaseCreator())) {
// 添加用例创建人
String createUser = (String) resource.get("createUser");
if (relatedUsers == null) {
relatedUsers = createUser;
} else {
relatedUsers += ";" + createUser;
}
}
// 添加特殊通知人
resource.put("relatedUsers", relatedUsers);
}
}
commonNoticeSendService.sendNotice(NoticeConstants.TaskType.API_DEFINITION_TASK, event, resources, user, projectId);
}
}

View File

@ -32,11 +32,13 @@ import io.metersphere.sdk.dto.api.task.*;
import io.metersphere.sdk.exception.MSException;
import io.metersphere.sdk.mapper.EnvironmentMapper;
import io.metersphere.sdk.util.*;
import io.metersphere.system.domain.User;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.request.NodeMoveRequest;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.log.constants.OperationLogModule;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.service.OperationHistoryService;
import io.metersphere.system.service.UserLoginService;
@ -112,6 +114,8 @@ public class ApiTestCaseService extends MoveNodeService {
private ExtApiReportMapper extApiReportMapper;
@Resource
private FunctionalCaseTestMapper functionalCaseTestMapper;
@Resource
private UserMapper userMapper;
private static final String CASE_TABLE = "api_test_case";
@ -239,6 +243,8 @@ public class ApiTestCaseService extends MoveNodeService {
apiTestCaseDTO.setMethod(apiDefinition.getMethod());
apiTestCaseDTO.setPath(apiDefinition.getPath());
apiTestCaseDTO.setProtocol(apiDefinition.getProtocol());
apiTestCaseDTO.setApiDefinitionNum(apiDefinition.getNum());
apiTestCaseDTO.setApiDefinitionName(apiDefinition.getName());
ApiTestCaseFollowerExample example = new ApiTestCaseFollowerExample();
example.createCriteria().andCaseIdEqualTo(id).andUserIdEqualTo(userId);
List<ApiTestCaseFollower> followers = apiTestCaseFollowerMapper.selectByExample(example);
@ -1001,10 +1007,11 @@ public class ApiTestCaseService extends MoveNodeService {
if (CollectionUtils.isEmpty(ids)) {
return;
}
SubListUtils.dealForSubList(ids, 500, subList -> doBatchSyncApiChange(request, subList, userId));
Project project = projectMapper.selectByPrimaryKey(request.getProjectId());
SubListUtils.dealForSubList(ids, 500, subList -> doBatchSyncApiChange(request, subList, userId, project));
}
public void doBatchSyncApiChange(ApiCaseBatchSyncRequest request, List<String> ids, String userId) {
public void doBatchSyncApiChange(ApiCaseBatchSyncRequest request, List<String> ids, String userId, Project project) {
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andIdIn(ids);
List<ApiTestCase> apiTestCases = apiTestCaseMapper.selectByExample(example);
@ -1026,6 +1033,9 @@ public class ApiTestCaseService extends MoveNodeService {
Map<String, ApiDefinitionBlob> apiDefinitionBlobMap = apiDefinitionBlobMapper.selectByExampleWithBLOBs(apiDefinitionBlobExample)
.stream()
.collect(Collectors.toMap(ApiDefinitionBlob::getId, Function.identity()));
// 清除接口变更标识
extApiTestCaseMapper.clearApiChange(ids);
try {
for (ApiTestCase apiTestCase : apiTestCases) {
ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMap.get(apiTestCase.getApiDefinitionId());
@ -1034,6 +1044,7 @@ public class ApiTestCaseService extends MoveNodeService {
AbstractMsTestElement apiTestCaseMsTestElement = getTestElement(apiTestCaseBlob);
boolean requestParamDifferent = HttpRequestParamDiffUtils.isRequestParamDiff(request.getSyncItems(), apiMsTestElement, apiTestCaseMsTestElement);
if (requestParamDifferent) {
// 如果参数与定义不一致则同步参数并记录日志和发送通知
ApiTestCaseLogDTO originCase = BeanUtils.copyBean(new ApiTestCaseLogDTO(), apiTestCase);
originCase.setRequest(apiTestCaseMsTestElement);
originMap.put(apiTestCase.getId(), originCase);
@ -1051,7 +1062,10 @@ public class ApiTestCaseService extends MoveNodeService {
modifiedMap.put(apiTestCase.getId(), originCase);
}
}
apiTestCaseLogService.batchSyncLog(originMap, modifiedMap);
apiTestCaseLogService.batchSyncLog(originMap, modifiedMap, project);
User user = userMapper.selectByPrimaryKey(userId);
apiTestCaseNoticeService.batchSyncSendNotice(new ArrayList<>(modifiedMap.values()), user, project.getId(), request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE);
} finally {
sqlSession.flushStatements();
SqlSessionUtils.closeSqlSession(sqlSession, sqlSessionFactory);
@ -1064,6 +1078,8 @@ public class ApiTestCaseService extends MoveNodeService {
ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(apiDefinition.getId());
AbstractMsTestElement apiMsTestElement = getApiMsTestElement(apiDefinitionBlob);
AbstractMsTestElement apiTestCaseMsTestElement = ApiDataUtils.parseObject(JSON.toJSONString(request.getApiCaseRequest()), AbstractMsTestElement.class);
// 清除接口变更标识
extApiTestCaseMapper.clearApiChange(List.of(request.getId()));
return HttpRequestParamDiffUtils.syncRequestDiff(request, apiMsTestElement, apiTestCaseMsTestElement);
}
}

View File

@ -17,6 +17,7 @@ import io.metersphere.api.service.ApiCommonService;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.service.BaseFileManagementTestService;
import io.metersphere.api.service.definition.ApiReportService;
import io.metersphere.api.service.definition.ApiTestCaseNoticeService;
import io.metersphere.api.service.definition.ApiTestCaseService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.plan.domain.TestPlanApiCase;
@ -47,10 +48,13 @@ import io.metersphere.sdk.util.JSON;
import io.metersphere.system.base.BaseTest;
import io.metersphere.system.controller.handler.ResultHolder;
import io.metersphere.system.controller.handler.result.MsHttpResultCode;
import io.metersphere.system.domain.User;
import io.metersphere.system.dto.OperationHistoryDTO;
import io.metersphere.system.dto.request.OperationHistoryRequest;
import io.metersphere.system.dto.sdk.request.PosRequest;
import io.metersphere.system.log.constants.OperationLogType;
import io.metersphere.system.mapper.UserMapper;
import io.metersphere.system.notice.constants.NoticeConstants;
import io.metersphere.system.service.OperationHistoryService;
import io.metersphere.system.uid.IDGenerator;
import io.metersphere.system.uid.NumGenerator;
@ -165,6 +169,10 @@ public class ApiTestCaseControllerTests extends BaseTest {
private TestPlanMapper testPlanMapper;
@Resource
private TestPlanApiCaseMapper testPlanApiCaseMapper;
@Resource
private ApiTestCaseNoticeService apiTestCaseNoticeService;
@Resource
private UserMapper userMapper;
@Override
public String getBasePath() {
@ -465,8 +473,9 @@ public class ApiTestCaseControllerTests extends BaseTest {
updateCase.setId(apiTestCase.getId());
apiTestCaseMapper.updateByPrimaryKeySelective(updateCase);
this.requestGetWithOk(API_CHANGE_CLEAR, apiTestCase.getId());
Assertions.assertFalse(apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()).getApiChange());
Assertions.assertTrue(apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()).getIgnoreApiDiff());
ApiTestCase result = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId());
Assertions.assertFalse(result.getApiChange());
Assertions.assertTrue(result.getIgnoreApiDiff());
//校验日志
checkLog(apiTestCase.getId(), OperationLogType.UPDATE, getBasePath() + MessageFormat.format(API_CHANGE_CLEAR, apiTestCase.getId()));
@ -507,6 +516,8 @@ public class ApiTestCaseControllerTests extends BaseTest {
public void batchSyncApiChange() throws Exception {
ApiCaseBatchSyncRequest request = new ApiCaseBatchSyncRequest();
request.setProjectId(DEFAULT_PROJECT_ID);
request.getNotificationConfig().setApiCaseCreator(true);
request.getNotificationConfig().setScenarioCreator(true);
this.requestPostWithOk(BATCH_API_CHANGE_SYNC, request);
request.setSelectIds(List.of(apiTestCase.getId()));
@ -516,6 +527,16 @@ public class ApiTestCaseControllerTests extends BaseTest {
ApiTestCase result = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId());
Assertions.assertFalse(result.getApiChange());
User user = userMapper.selectByPrimaryKey("admin");
request.getNotificationConfig().setScenarioCreator(false);
apiTestCaseNoticeService.batchSyncSendNotice(List.of(apiTestCase), user, DEFAULT_PROJECT_ID, request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE);
request.getNotificationConfig().setApiCaseCreator(false);
apiTestCaseNoticeService.batchSyncSendNotice(List.of(apiTestCase), user, DEFAULT_PROJECT_ID, request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE);
request.getNotificationConfig().setScenarioCreator(true);
apiTestCaseNoticeService.batchSyncSendNotice(List.of(apiTestCase), user, DEFAULT_PROJECT_ID, request.getNotificationConfig(), NoticeConstants.Event.CASE_UPDATE);
//校验日志
checkLog(apiTestCase.getId(), OperationLogType.UPDATE, getBasePath() + BATCH_API_CHANGE_SYNC);
@ -530,6 +551,8 @@ public class ApiTestCaseControllerTests extends BaseTest {
request.setId(apiTestCase.getId());
request.setApiCaseRequest(JSON.parseObject(ApiDataUtils.toJSONString(new MsHTTPElement())));
this.requestPostWithOk(API_CHANGE_SYNC, request);
ApiTestCase result = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId());
Assertions.assertFalse(result.getApiChange());
// @@校验权限
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_UPDATE, API_CHANGE_SYNC, request);
@ -703,6 +726,8 @@ public class ApiTestCaseControllerTests extends BaseTest {
msHTTPElement.setModuleId(apiDefinition.getModuleId());
msHTTPElement.setNum(apiDefinition.getNum());
copyApiTestCaseDTO.setRequest(msTestElement);
copyApiTestCaseDTO.setApiDefinitionName(apiDefinition.getName());
copyApiTestCaseDTO.setApiDefinitionNum(apiDefinition.getNum());
msHTTPElement = (MsHTTPElement) apiTestCaseDTO.getRequest();
Assertions.assertEquals(msHTTPElement.getMethod(), apiDefinition.getMethod());

View File

@ -413,7 +413,7 @@ export interface syncItem {
// 批量同步
export interface batchSyncForm {
notificationConfig: {
apiCreator: boolean;
apiCaseCreator: boolean;
scenarioCreator: boolean;
};
// 同步项目

View File

@ -53,7 +53,7 @@
</a-tooltip>
</div>
<div class="my-[16px] flex items-center">
<a-switch v-model:model-value="form.notificationConfig.apiCreator" size="small" />
<a-switch v-model:model-value="form.notificationConfig.apiCaseCreator" size="small" />
<div class="ml-[8px] text-[var(--color-text-1)]">{{ t('case.NoticeApiCaseCreator') }}</div>
</div>
<div class="my-[16px] flex items-center">
@ -98,7 +98,7 @@
const initForm: batchSyncForm = {
notificationConfig: {
apiCreator: false,
apiCaseCreator: false,
scenarioCreator: false,
},
//