feat(接口测试): 接口定义参数变更时,添加用例标识

--task=1015861 --user=陈建星 【接口测试】接口用例支持同步更新接口变更-是否与定义不一致查询接口 https://www.tapd.cn/55049933/s/1557706
This commit is contained in:
AgAngle 2024-08-01 16:55:02 +08:00 committed by Craftsman
parent 17090ffa3a
commit 92ee8c2bff
16 changed files with 809 additions and 48 deletions

View File

@ -87,6 +87,14 @@ public class ApiTestCase implements Serializable {
@NotNull(message = "{api_test_case.deleted.not_blank}", groups = {Created.class})
private Boolean deleted;
@Schema(description = "接口定义参数变更标识", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{api_test_case.api_change.not_blank}", groups = {Created.class})
private Boolean apiChange;
@Schema(description = "忽略接口定义参数变更", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "{api_test_case.ignore_api_change.not_blank}", groups = {Created.class})
private Boolean ignoreApiChange;
private static final long serialVersionUID = 1L;
public enum Column {
@ -109,7 +117,9 @@ public class ApiTestCase implements Serializable {
updateUser("update_user", "updateUser", "VARCHAR", false),
deleteTime("delete_time", "deleteTime", "BIGINT", false),
deleteUser("delete_user", "deleteUser", "VARCHAR", false),
deleted("deleted", "deleted", "BIT", false);
deleted("deleted", "deleted", "BIT", false),
apiChange("api_change", "apiChange", "BIT", false),
ignoreApiChange("ignore_api_change", "ignoreApiChange", "BIT", false);
private static final String BEGINNING_DELIMITER = "`";

View File

@ -1477,6 +1477,126 @@ public class ApiTestCaseExample {
addCriterion("deleted not between", value1, value2, "deleted");
return (Criteria) this;
}
public Criteria andApiChangeIsNull() {
addCriterion("api_change is null");
return (Criteria) this;
}
public Criteria andApiChangeIsNotNull() {
addCriterion("api_change is not null");
return (Criteria) this;
}
public Criteria andApiChangeEqualTo(Boolean value) {
addCriterion("api_change =", value, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeNotEqualTo(Boolean value) {
addCriterion("api_change <>", value, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeGreaterThan(Boolean value) {
addCriterion("api_change >", value, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeGreaterThanOrEqualTo(Boolean value) {
addCriterion("api_change >=", value, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeLessThan(Boolean value) {
addCriterion("api_change <", value, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeLessThanOrEqualTo(Boolean value) {
addCriterion("api_change <=", value, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeIn(List<Boolean> values) {
addCriterion("api_change in", values, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeNotIn(List<Boolean> values) {
addCriterion("api_change not in", values, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeBetween(Boolean value1, Boolean value2) {
addCriterion("api_change between", value1, value2, "apiChange");
return (Criteria) this;
}
public Criteria andApiChangeNotBetween(Boolean value1, Boolean value2) {
addCriterion("api_change not between", value1, value2, "apiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeIsNull() {
addCriterion("ignore_api_change is null");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeIsNotNull() {
addCriterion("ignore_api_change is not null");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeEqualTo(Boolean value) {
addCriterion("ignore_api_change =", value, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeNotEqualTo(Boolean value) {
addCriterion("ignore_api_change <>", value, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeGreaterThan(Boolean value) {
addCriterion("ignore_api_change >", value, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeGreaterThanOrEqualTo(Boolean value) {
addCriterion("ignore_api_change >=", value, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeLessThan(Boolean value) {
addCriterion("ignore_api_change <", value, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeLessThanOrEqualTo(Boolean value) {
addCriterion("ignore_api_change <=", value, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeIn(List<Boolean> values) {
addCriterion("ignore_api_change in", values, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeNotIn(List<Boolean> values) {
addCriterion("ignore_api_change not in", values, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeBetween(Boolean value1, Boolean value2) {
addCriterion("ignore_api_change between", value1, value2, "ignoreApiChange");
return (Criteria) this;
}
public Criteria andIgnoreApiChangeNotBetween(Boolean value1, Boolean value2) {
addCriterion("ignore_api_change not between", value1, value2, "ignoreApiChange");
return (Criteria) this;
}
}
public static class Criteria extends GeneratedCriteria {

View File

@ -22,6 +22,8 @@
<result column="delete_time" jdbcType="BIGINT" property="deleteTime" />
<result column="delete_user" jdbcType="VARCHAR" property="deleteUser" />
<result column="deleted" jdbcType="BIT" property="deleted" />
<result column="api_change" jdbcType="BIT" property="apiChange" />
<result column="ignore_api_change" jdbcType="BIT" property="ignoreApiChange" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -120,9 +122,9 @@
</where>
</sql>
<sql id="Base_Column_List">
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
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, api_change, ignore_api_change
</sql>
<select id="selectByExample" parameterType="io.metersphere.api.domain.ApiTestCaseExample" resultMap="BaseResultMap">
select
@ -139,7 +141,7 @@
</if>
</select>
<select id="selectByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
select
select
<include refid="Base_Column_List" />
from api_test_case
where id = #{id,jdbcType=VARCHAR}
@ -155,22 +157,22 @@
</if>
</delete>
<insert id="insert" parameterType="io.metersphere.api.domain.ApiTestCase">
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 (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{priority,jdbcType=VARCHAR},
#{num,jdbcType=BIGINT}, #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{status,jdbcType=VARCHAR}, #{lastReportStatus,jdbcType=VARCHAR}, #{lastReportId,jdbcType=VARCHAR},
#{pos,jdbcType=BIGINT}, #{projectId,jdbcType=VARCHAR}, #{apiDefinitionId,jdbcType=VARCHAR},
#{versionId,jdbcType=VARCHAR}, #{environmentId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{createUser,jdbcType=VARCHAR}, #{updateTime,jdbcType=BIGINT}, #{updateUser,jdbcType=VARCHAR},
#{deleteTime,jdbcType=BIGINT}, #{deleteUser,jdbcType=VARCHAR}, #{deleted,jdbcType=BIT}
)
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,
api_change, ignore_api_change)
values (#{id,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR}, #{priority,jdbcType=VARCHAR},
#{num,jdbcType=BIGINT}, #{tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{status,jdbcType=VARCHAR}, #{lastReportStatus,jdbcType=VARCHAR}, #{lastReportId,jdbcType=VARCHAR},
#{pos,jdbcType=BIGINT}, #{projectId,jdbcType=VARCHAR}, #{apiDefinitionId,jdbcType=VARCHAR},
#{versionId,jdbcType=VARCHAR}, #{environmentId,jdbcType=VARCHAR}, #{createTime,jdbcType=BIGINT},
#{createUser,jdbcType=VARCHAR}, #{updateTime,jdbcType=BIGINT}, #{updateUser,jdbcType=VARCHAR},
#{deleteTime,jdbcType=BIGINT}, #{deleteUser,jdbcType=VARCHAR}, #{deleted,jdbcType=BIT},
#{apiChange,jdbcType=BIT}, #{ignoreApiChange,jdbcType=BIT})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.api.domain.ApiTestCase">
insert into api_test_case
@ -235,6 +237,12 @@
<if test="deleted != null">
deleted,
</if>
<if test="apiChange != null">
api_change,
</if>
<if test="ignoreApiChange != null">
ignore_api_change,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -297,6 +305,12 @@
<if test="deleted != null">
#{deleted,jdbcType=BIT},
</if>
<if test="apiChange != null">
#{apiChange,jdbcType=BIT},
</if>
<if test="ignoreApiChange != null">
#{ignoreApiChange,jdbcType=BIT},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.api.domain.ApiTestCaseExample" resultType="java.lang.Long">
@ -368,6 +382,12 @@
<if test="record.deleted != null">
deleted = #{record.deleted,jdbcType=BIT},
</if>
<if test="record.apiChange != null">
api_change = #{record.apiChange,jdbcType=BIT},
</if>
<if test="record.ignoreApiChange != null">
ignore_api_change = #{record.ignoreApiChange,jdbcType=BIT},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -394,7 +414,9 @@
update_user = #{record.updateUser,jdbcType=VARCHAR},
delete_time = #{record.deleteTime,jdbcType=BIGINT},
delete_user = #{record.deleteUser,jdbcType=VARCHAR},
deleted = #{record.deleted,jdbcType=BIT}
deleted = #{record.deleted,jdbcType=BIT},
api_change = #{record.apiChange,jdbcType=BIT},
ignore_api_change = #{record.ignoreApiChange,jdbcType=BIT}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -459,6 +481,12 @@
<if test="deleted != null">
deleted = #{deleted,jdbcType=BIT},
</if>
<if test="apiChange != null">
api_change = #{apiChange,jdbcType=BIT},
</if>
<if test="ignoreApiChange != null">
ignore_api_change = #{ignoreApiChange,jdbcType=BIT},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -482,24 +510,27 @@
update_user = #{updateUser,jdbcType=VARCHAR},
delete_time = #{deleteTime,jdbcType=BIGINT},
delete_user = #{deleteUser,jdbcType=VARCHAR},
deleted = #{deleted,jdbcType=BIT}
deleted = #{deleted,jdbcType=BIT},
api_change = #{apiChange,jdbcType=BIT},
ignore_api_change = #{ignoreApiChange,jdbcType=BIT}
where id = #{id,jdbcType=VARCHAR}
</update>
<insert id="batchInsert" parameterType="map">
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)
(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, api_change, ignore_api_change
)
values
<foreach collection="list" item="item" separator=",">
(#{item.id,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR}, #{item.priority,jdbcType=VARCHAR},
#{item.num,jdbcType=BIGINT}, #{item.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{item.status,jdbcType=VARCHAR}, #{item.lastReportStatus,jdbcType=VARCHAR}, #{item.lastReportId,jdbcType=VARCHAR},
#{item.pos,jdbcType=BIGINT}, #{item.projectId,jdbcType=VARCHAR}, #{item.apiDefinitionId,jdbcType=VARCHAR},
#{item.versionId,jdbcType=VARCHAR}, #{item.environmentId,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT},
#{item.createUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR},
#{item.deleteTime,jdbcType=BIGINT}, #{item.deleteUser,jdbcType=VARCHAR}, #{item.deleted,jdbcType=BIT}
)
(#{item.id,jdbcType=VARCHAR}, #{item.name,jdbcType=VARCHAR}, #{item.priority,jdbcType=VARCHAR},
#{item.num,jdbcType=BIGINT}, #{item.tags,jdbcType=VARCHAR,typeHandler=io.metersphere.handler.ListTypeHandler},
#{item.status,jdbcType=VARCHAR}, #{item.lastReportStatus,jdbcType=VARCHAR}, #{item.lastReportId,jdbcType=VARCHAR},
#{item.pos,jdbcType=BIGINT}, #{item.projectId,jdbcType=VARCHAR}, #{item.apiDefinitionId,jdbcType=VARCHAR},
#{item.versionId,jdbcType=VARCHAR}, #{item.environmentId,jdbcType=VARCHAR}, #{item.createTime,jdbcType=BIGINT},
#{item.createUser,jdbcType=VARCHAR}, #{item.updateTime,jdbcType=BIGINT}, #{item.updateUser,jdbcType=VARCHAR},
#{item.deleteTime,jdbcType=BIGINT}, #{item.deleteUser,jdbcType=VARCHAR}, #{item.deleted,jdbcType=BIT},
#{item.apiChange,jdbcType=BIT}, #{item.ignoreApiChange,jdbcType=BIT})
</foreach>
</insert>
<insert id="batchInsertSelective" parameterType="map">
@ -572,6 +603,12 @@
<if test="'deleted'.toString() == column.value">
#{item.deleted,jdbcType=BIT}
</if>
<if test="'api_change'.toString() == column.value">
#{item.apiChange,jdbcType=BIT}
</if>
<if test="'ignore_api_change'.toString() == column.value">
#{item.ignoreApiChange,jdbcType=BIT}
</if>
</foreach>
)
</foreach>

View File

@ -0,0 +1 @@
select database();

View File

@ -0,0 +1,12 @@
-- set innodb lock wait timeout
SET SESSION innodb_lock_wait_timeout = 7200;
ALTER TABLE api_test_case ADD api_change BIT(1) DEFAULT 0 NOT NULL COMMENT '接口定义参数变更标识';
ALTER TABLE api_test_case ADD ignore_api_change BIT(1) DEFAULT 0 NOT NULL COMMENT '忽略接口定义参数变更';
-- set innodb lock wait timeout to default
SET SESSION innodb_lock_wait_timeout = DEFAULT;

View File

@ -120,7 +120,7 @@ public class XMLUtils {
return new LinkedHashMap<>();
}
private static Map<String, Object> elementToMap(Element node) {
public static Map<String, Object> elementToMap(Element node) {
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
List<Element> listElement = node.elements();// 所有一级子节点的list

View File

@ -95,4 +95,12 @@ public class ApiTestCaseDTO {
@Schema(description = "协议")
private String protocol;
@Schema(description = "接口定义参数变更标识")
private Boolean apiChange;
@Schema(description = "与接口定义不一致")
private Boolean inconsistentWithApi;
@Schema(description = "忽略接口定义参数变更")
private Boolean ignoreApiChange;
}

View File

@ -109,4 +109,6 @@ public interface ExtApiTestCaseMapper {
List<ApiTestCase> getListBySelectIds(@Param("projectId") String projectId, @Param("ids") List<String> ids, @Param("testPlanId") String testPlanId, @Param("protocols") List<String> protocols);
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);
}

View File

@ -28,6 +28,13 @@
set pos =#{pos}
where id = #{id}
</update>
<update id="setApiChangeByApiDefinitionId">
update api_test_case
set api_change = true
where api_definition_id = #{apiDefinitionId}
and api_change is false
and ignore_api_change is false
</update>
<select id="getPos" resultType="java.lang.Long">
SELECT pos
@ -60,7 +67,9 @@
a.path,
a.method,
a.protocol,
atc.pos
atc.pos,
atc.api_change,
atc.ignore_api_change
FROM
api_test_case atc
INNER JOIN api_definition a ON atc.api_definition_id = a.id

View File

@ -259,6 +259,7 @@ public class ApiDefinitionService extends MoveNodeService {
public ApiDefinition update(ApiDefinitionUpdateRequest request, String userId) {
ApiDefinition originApiDefinition = checkApiDefinition(request.getId());
ApiDefinitionBlob originApiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(request.getId());
ApiDefinition apiDefinition = new ApiDefinition();
BeanUtils.copyBean(apiDefinition, request);
apiDefinition.setTags(ServiceUtils.parseTags(apiDefinition.getTags()));
@ -295,6 +296,9 @@ public class ApiDefinitionService extends MoveNodeService {
resourceUpdateRequest.setDeleteFileIds(request.getDeleteFileIds());
apiFileResourceService.updateFileResource(resourceUpdateRequest);
AbstractMsTestElement originRequest = ApiDataUtils.parseObject(new String(originApiDefinitionBlob.getRequest()), AbstractMsTestElement.class);
// 处理接口定义参数变更
apiTestCaseService.handleApiParamChange(apiDefinition.getId(), request.getRequest(), originRequest);
return apiDefinition;
}

View File

@ -12,6 +12,7 @@ import io.metersphere.api.service.ApiCommonService;
import io.metersphere.api.service.ApiExecuteService;
import io.metersphere.api.service.ApiFileResourceService;
import io.metersphere.api.utils.ApiDataUtils;
import io.metersphere.api.utils.HttpRequestParamDiffUtils;
import io.metersphere.functional.domain.FunctionalCaseTestExample;
import io.metersphere.functional.mapper.FunctionalCaseTestMapper;
import io.metersphere.plugin.api.spi.AbstractMsTestElement;
@ -72,6 +73,8 @@ public class ApiTestCaseService extends MoveNodeService {
@Resource
private ApiDefinitionMapper apiDefinitionMapper;
@Resource
private ApiDefinitionBlobMapper apiDefinitionBlobMapper;
@Resource
private ApiTestCaseBlobMapper apiTestCaseBlobMapper;
@Resource
private ProjectMapper projectMapper;
@ -243,6 +246,10 @@ public class ApiTestCaseService extends MoveNodeService {
apiCommonService.setEnableCommonScriptProcessorInfo(msTestElement);
apiCommonService.setApiDefinitionExecuteInfo(msTestElement, apiDefinition);
apiTestCaseDTO.setRequest(msTestElement);
ApiDefinitionBlob apiDefinitionBlob = apiDefinitionBlobMapper.selectByPrimaryKey(apiDefinition.getId());
AbstractMsTestElement apiMsTestElement = ApiDataUtils.parseObject(new String(apiDefinitionBlob.getRequest()), AbstractMsTestElement.class);
apiTestCaseDTO.setInconsistentWithApi(HttpRequestParamDiffUtils.isRequestParamDiff(apiMsTestElement, msTestElement));
return apiTestCaseDTO;
}
@ -922,4 +929,19 @@ public class ApiTestCaseService extends MoveNodeService {
public List<ReferenceDTO> getReference(ReferenceRequest request) {
return extApiDefinitionMapper.getReference(request);
}
/**
* 处理接口定义参数变更
*
* @param changeRequest
* @param originRequest
*/
public void handleApiParamChange(String apiDefinitionId, Object changeRequest, Object originRequest) {
boolean requestParamDifferent = HttpRequestParamDiffUtils.isRequestParamDiff(changeRequest, originRequest);
if (requestParamDifferent) {
// 添加接口变更标识
extApiTestCaseMapper.setApiChangeByApiDefinitionId(apiDefinitionId);
}
}
}

View File

@ -0,0 +1,166 @@
package io.metersphere.api.utils;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.body.Body;
import io.metersphere.api.dto.request.http.body.JsonBody;
import io.metersphere.api.dto.request.http.body.XmlBody;
import io.metersphere.project.api.KeyValueParam;
import io.metersphere.sdk.util.EnumValidator;
import io.metersphere.sdk.util.JSON;
import io.metersphere.sdk.util.XMLUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* @Author: jianxing
* @CreateTime: 2024-08-01 14:01
*/
public class HttpRequestParamDiffUtils {
public static boolean isRequestParamDiff(Object request1, Object request2) {
if (!(request1 instanceof MsHTTPElement)) {
// 其他协议不比较
return false;
}
MsHTTPElement httpElement1 = (MsHTTPElement) request1;
MsHTTPElement httpElement2 = (MsHTTPElement) request2;
boolean isQueryDiff = isParamKeyDiff(httpElement1.getQuery(), httpElement2.getQuery());
boolean isRestDiff = isParamKeyDiff(httpElement1.getRest(), httpElement2.getRest());
boolean isHeaderDiff = isParamKeyDiff(httpElement1.getHeaders(), httpElement2.getHeaders());
boolean isBodyDiff = isBodyDiff(httpElement1.getBody(), httpElement2.getBody());
if (isQueryDiff || isRestDiff || isHeaderDiff || isBodyDiff) {
return true;
}
return false;
}
public static boolean isBodyDiff(Body body1, Body body2) {
if (body1 == null && body2 == null) {
return false;
}
if (body1 == null || body2 == null) {
return true;
}
if (body1.getBodyType() != body2.getBodyType()) {
// 类型不一样则发生变更
return true;
}
Body.BodyType bodyType = EnumValidator.validateEnum(Body.BodyType.class, body1.getBodyType());
switch (bodyType) {
case FORM_DATA:
return isParamKeyDiff(body1.getFormDataBody().getFormValues(), body2.getFormDataBody().getFormValues());
case WWW_FORM:
return isParamKeyDiff(body1.getWwwFormBody().getFormValues(), body2.getWwwFormBody().getFormValues());
case JSON:
return isJsonBodyDiff(body1.getJsonBody(), body2.getJsonBody());
case XML:
return isXmlBodyDiff(body1.getXmlBody(), body2.getXmlBody());
default:
// RAW,BINARY 不比较
return false;
}
}
public static boolean isJsonBodyDiff(JsonBody jsonBody1, JsonBody jsonBody2) {
String jsonValue1 = jsonBody1.getJsonValue();
String jsonValue2 = jsonBody2.getJsonValue();
if (StringUtils.isBlank(jsonValue1) && StringUtils.isBlank(jsonValue2)) {
return false;
}
if (StringUtils.isBlank(jsonValue1) || StringUtils.isBlank(jsonValue2)) {
return true;
}
try {
Object json1 = JSON.parseObject(jsonValue1);
Object json2 = JSON.parseObject(jsonValue2);
return !getBlankJon(json1).equals(getBlankJon(json2));
} catch (Exception e) {
return !getJsonKeys(jsonValue1).equals(getJsonKeys(jsonValue2));
}
}
/**
* 将json对象的属性值都置空
* 便于比较参数名是否一致
* @param obj
* @return
*/
public static Object getBlankJon(Object obj) {
if (obj == null) {
return StringUtils.EMPTY;
}
if (obj instanceof Map map) {
map.keySet().forEach(key -> {
Object value = map.get(key);
map.put(key, getBlankJon(value));
});
return map;
} else if (obj instanceof List list) {
if (CollectionUtils.isEmpty(list)) {
return list;
}
Object first = list.getFirst();
// 数组只获取第一个元素进行比较
return new ArrayList<>(List.of(getBlankJon(first)));
} else {
return StringUtils.EMPTY;
}
}
/**
* json 串中获取 key 列表
* 因为数值类型使用 mock 函数会导致 json 串为非法 json
* 这里使用正则表达式获取key
* 使用 LinkedHashSet 按序获取近似比较两个 json 串的 key
* @param jsonValue
* @return
*/
public static Set<String> getJsonKeys(String jsonValue) {
String pattern = "\"([^\"]*)\"\\s*:";
Pattern regex = Pattern.compile(pattern);
Matcher matcher = regex.matcher(jsonValue);
Set<String> keys = new LinkedHashSet<>();
while (matcher.find()) {
keys.add(matcher.group(1));
}
return keys;
}
public static boolean isXmlBodyDiff(XmlBody xmlBody1, XmlBody xmlBody2) {
String value1 = xmlBody1.getValue();
String value2 = xmlBody2.getValue();
if (StringUtils.isBlank(value1) && StringUtils.isBlank(value2)) {
return false;
}
if (StringUtils.isBlank(value1) || StringUtils.isBlank(value2)) {
return true;
}
try {
Set<String> keySet1 = XMLUtils.elementToMap(XMLUtils.stringToDocument(value1).getRootElement()).keySet();
Set<String> keySet2 = XMLUtils.elementToMap(XMLUtils.stringToDocument(value2).getRootElement()).keySet();
return !keySet1.equals(keySet2);
} catch (Exception e) {
return !StringUtils.equals(value1, value2);
}
}
public static boolean isParamKeyDiff(List<? extends KeyValueParam> params1, List<? extends KeyValueParam> params2) {
params1 = params1 == null ? List.of() : params1;
params2 = params2 == null ? List.of() : params2;
Set<String> keSet1 = params1.stream()
.filter(KeyValueParam::isValid)
.map(KeyValueParam::getKey)
.collect(Collectors.toSet());
Set<String> keSet2 = params2.stream()
.filter(KeyValueParam::isValid)
.map(KeyValueParam::getKey)
.collect(Collectors.toSet());
return !keSet1.equals(keSet2);
}
}

View File

@ -14,7 +14,6 @@ import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class JsonSchemaBuilder {

View File

@ -2851,6 +2851,8 @@ public class ApiScenarioControllerTests extends BaseTest {
apiTestCase.setApiDefinitionId("system-api-id");
apiTestCase.setVersionId("1.0");
apiTestCase.setLastReportStatus("未执行");
apiTestCase.setApiChange(false);
apiTestCase.setIgnoreApiChange(false);
apiTestCases.add(apiTestCase);
}
apiTestCaseMapper.batchInsert(apiTestCases);

View File

@ -11,6 +11,7 @@ import io.metersphere.api.dto.ReferenceRequest;
import io.metersphere.api.dto.definition.*;
import io.metersphere.api.dto.request.ApiTransferRequest;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.body.Body;
import io.metersphere.api.mapper.*;
import io.metersphere.api.service.ApiCommonService;
import io.metersphere.api.service.ApiFileResourceService;
@ -230,6 +231,8 @@ public class ApiTestCaseControllerTests extends BaseTest {
apiTestCase.setVersionId("1.0");
apiTestCase.setDeleted(false);
apiTestCase.setLastReportStatus("SUCCESS");
apiTestCase.setApiChange(false);
apiTestCase.setIgnoreApiChange(false);
caseMapper.insert(apiTestCase);
ApiTestCaseBlob apiTestCaseBlob = new ApiTestCaseBlob();
apiTestCaseBlob.setId(apiTestCase.getId());
@ -396,7 +399,37 @@ public class ApiTestCaseControllerTests extends BaseTest {
request.setProjectId(DEFAULT_PROJECT_ID);
request.setName("permission");
requestPostPermissionTest(PermissionConstants.PROJECT_API_DEFINITION_CASE_ADD, ADD, request);
}
@Test
@Order(3)
public void handleApiParamChange() {
apiTestCaseService.handleApiParamChange(apiTestCase.getApiDefinitionId(), new MsHTTPElement(), new MsHTTPElement());
ApiTestCaseExample example = new ApiTestCaseExample();
example.createCriteria().andApiChangeEqualTo(true);
Assertions.assertEquals(apiTestCaseMapper.selectByExample(example), List.of());
// 设置忽略变更通知
ApiTestCase updateCase = new ApiTestCase();
updateCase.setId(apiTestCase.getId());
updateCase.setIgnoreApiChange(true);
apiTestCaseMapper.updateByPrimaryKeySelective(updateCase);
MsHTTPElement changeRequest = new MsHTTPElement();
changeRequest.setBody(new Body());
changeRequest.getBody().setBodyType(Body.BodyType.FORM_DATA.name());
MsHTTPElement originRequest = new MsHTTPElement();
originRequest.setBody(new Body());
originRequest.getBody().setBodyType(Body.BodyType.XML.name());
apiTestCaseService.handleApiParamChange(apiTestCase.getApiDefinitionId(), changeRequest, originRequest);
// 校验忽略变更通知
Assertions.assertEquals(apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()).getApiChange(), false);
updateCase.setIgnoreApiChange(false);
apiTestCaseMapper.updateByPrimaryKeySelective(updateCase);
apiTestCaseService.handleApiParamChange(apiTestCase.getApiDefinitionId(), changeRequest, originRequest);
// 校验变更通知
Assertions.assertEquals(apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId()).getApiChange(), true);
}
/**
@ -540,24 +573,25 @@ public class ApiTestCaseControllerTests extends BaseTest {
// @@请求成功
MvcResult mvcResult = this.requestGetWithOk(GET + apiTestCase.getId())
.andReturn();
ApiTestCaseDTO apiDebugDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResult).get("data")), ApiTestCaseDTO.class);
ApiTestCaseDTO apiTestCaseDTO = ApiDataUtils.parseObject(JSON.toJSONString(parseResponse(mvcResult).get("data")), ApiTestCaseDTO.class);
// 校验数据是否正确
ApiTestCase testCase = apiTestCaseMapper.selectByPrimaryKey(apiTestCase.getId());
ApiTestCaseDTO copyApiDebugDTO = BeanUtils.copyBean(new ApiTestCaseDTO(), testCase);
ApiTestCaseDTO copyApiTestCaseDTO = BeanUtils.copyBean(new ApiTestCaseDTO(), testCase);
copyApiTestCaseDTO.setInconsistentWithApi(true);
if (CollectionUtils.isNotEmpty(testCase.getTags())) {
copyApiDebugDTO.setTags(testCase.getTags());
copyApiTestCaseDTO.setTags(testCase.getTags());
} else {
copyApiDebugDTO.setTags(new ArrayList<>());
copyApiTestCaseDTO.setTags(new ArrayList<>());
}
ApiDefinition apiDefinition = apiDefinitionMapper.selectByPrimaryKey(apiTestCase.getApiDefinitionId());
copyApiDebugDTO.setMethod(apiDefinition.getMethod());
copyApiDebugDTO.setPath(apiDefinition.getPath());
copyApiDebugDTO.setProtocol(apiDefinition.getProtocol());
copyApiTestCaseDTO.setMethod(apiDefinition.getMethod());
copyApiTestCaseDTO.setPath(apiDefinition.getPath());
copyApiTestCaseDTO.setProtocol(apiDefinition.getProtocol());
ApiTestCaseBlob apiTestCaseBlob = apiTestCaseBlobMapper.selectByPrimaryKey(apiTestCase.getId());
ApiTestCaseFollowerExample example = new ApiTestCaseFollowerExample();
example.createCriteria().andCaseIdEqualTo(apiTestCase.getId()).andUserIdEqualTo("admin");
List<ApiTestCaseFollower> followers = apiTestCaseFollowerMapper.selectByExample(example);
copyApiDebugDTO.setFollow(CollectionUtils.isNotEmpty(followers));
copyApiTestCaseDTO.setFollow(CollectionUtils.isNotEmpty(followers));
AbstractMsTestElement msTestElement = ApiDataUtils.parseObject(new String(apiTestCaseBlob.getRequest()), AbstractMsTestElement.class);
apiCommonService.setLinkFileInfo(apiTestCase.getId(), msTestElement);
MsHTTPElement msHTTPElement = (MsHTTPElement) msTestElement;
@ -565,13 +599,13 @@ public class ApiTestCaseControllerTests extends BaseTest {
msHTTPElement.setPath(apiDefinition.getPath());
msHTTPElement.setModuleId(apiDefinition.getModuleId());
msHTTPElement.setNum(apiDefinition.getNum());
copyApiDebugDTO.setRequest(msTestElement);
copyApiTestCaseDTO.setRequest(msTestElement);
msHTTPElement = (MsHTTPElement) apiDebugDTO.getRequest();
msHTTPElement = (MsHTTPElement) apiTestCaseDTO.getRequest();
Assertions.assertEquals(msHTTPElement.getMethod(), apiDefinition.getMethod());
Assertions.assertEquals(msHTTPElement.getPath(), apiDefinition.getPath());
Assertions.assertEquals(msHTTPElement.getModuleId(), apiDefinition.getModuleId());
Assertions.assertEquals(apiDebugDTO, copyApiDebugDTO);
Assertions.assertEquals(apiTestCaseDTO, copyApiTestCaseDTO);
this.requestGetWithOk(GET + anotherApiTestCase.getId())
.andReturn();

View File

@ -0,0 +1,335 @@
package io.metersphere.api.utils;
import io.metersphere.api.dto.request.http.MsHTTPElement;
import io.metersphere.api.dto.request.http.MsHeader;
import io.metersphere.api.dto.request.http.QueryParam;
import io.metersphere.api.dto.request.http.RestParam;
import io.metersphere.api.dto.request.http.body.*;
import io.metersphere.project.api.KeyValueParam;
import io.metersphere.sdk.util.JSON;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* @Author: jianxing
* @CreateTime: 2024-08-01 14:04
*/
public class HttpRequestParamDiffUtilsTests {
@Test
public void getBlankJon() {
Object blankJon = HttpRequestParamDiffUtils.getBlankJon(JSON.parseObject("""
{
"id": 10,
"name": "doggie",
"category": {
"id": null,
"name": "Dogs"
},
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": "available"
}
"""));
Object result = JSON.parseObject("""
{
"id": "",
"name": "",
"category": {
"id": "",
"name": ""
},
"photoUrls": [
""
],
"tags": [
{
"id": "",
"name": ""
}
],
"status": ""
}""");
Assertions.assertEquals(blankJon, result);
}
@Test
public void getJsonKeys() {
Set<String> jsonKeys = HttpRequestParamDiffUtils.getJsonKeys("""
sdfds"key1" :dsdfdsfd,sdfds"key2"
:ddd,
""");
Assertions.assertEquals(jsonKeys, new HashSet<>(List.of("key1", "key2")));
jsonKeys = HttpRequestParamDiffUtils.getJsonKeys("""
sdfds"key1" :dsdfdsfd,sdfds"key2"
:ddd,
""");
Assertions.assertEquals(jsonKeys, new HashSet<>(List.of("key1", "key2")));
jsonKeys = HttpRequestParamDiffUtils.getJsonKeys("""
sdfds"key1 1111" :dsdfdsfd,sdfds"key2-111"
:ddd,
""");
Assertions.assertEquals(jsonKeys, new HashSet<>(List.of("key1 1111", "key2-111")));
}
@Test
public void isJsonBodyDiff() {
JsonBody jsonBody1 = new JsonBody();
JsonBody jsonBody2 = new JsonBody();
Assertions.assertFalse(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
jsonBody1.setJsonValue("1111");
jsonBody2.setJsonValue(null);
Assertions.assertTrue(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
jsonBody1.setJsonValue(null);
jsonBody2.setJsonValue("1111");
Assertions.assertTrue(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
jsonBody1.setJsonValue("""
{"id1":"444"}
""");
jsonBody2.setJsonValue("""
{"id1":"33"}
""");
Assertions.assertFalse(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
jsonBody1.setJsonValue("""
{"id1":"444"}ddd
""");
jsonBody2.setJsonValue("""
{"id1":"33"}fff
""");
Assertions.assertFalse(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
jsonBody1.setJsonValue("""
{"id1":"444"}
""");
jsonBody2.setJsonValue("""
{"id2":"33"}
""");
Assertions.assertTrue(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
jsonBody1.setJsonValue("""
{"id1":"444","id2":"33"}
""");
jsonBody2.setJsonValue("""
{"id2":"33"}
""");
Assertions.assertTrue(HttpRequestParamDiffUtils.isJsonBodyDiff(jsonBody1, jsonBody2));
}
@Test
public void isXmlBodyDiff() {
XmlBody xmlBody1 = new XmlBody();
XmlBody xmlBody2 = new XmlBody();
Assertions.assertFalse(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
xmlBody1.setValue("1111");
xmlBody2.setValue(null);
Assertions.assertTrue(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
xmlBody2.setValue("1111");
xmlBody1.setValue(null);
Assertions.assertTrue(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
xmlBody1.setValue("""
<tag1>11</tag1>
""");
xmlBody2.setValue("""
<tag1>222</tag1>
""");
Assertions.assertFalse(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
xmlBody1.setValue("""
!@#$%^&*(
""");
xmlBody2.setValue("""
!@#$%^&*(
""");
Assertions.assertFalse(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
xmlBody1.setValue("""
<tag1>11</tag1>
""");
xmlBody2.setValue("""
<tag1>111</tag1>
<tag2>2222</tag2>
""");
Assertions.assertTrue(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
xmlBody1.setValue("""
11111!@#$%^&*(
""");
xmlBody2.setValue("""
!@#$%^&*(
""");
Assertions.assertTrue(HttpRequestParamDiffUtils.isXmlBodyDiff(xmlBody1, xmlBody2));
}
@Test
public void isParamKeyDiff() {
Assertions.assertFalse(HttpRequestParamDiffUtils.isParamKeyDiff(null, null));
Assertions.assertFalse(HttpRequestParamDiffUtils.isParamKeyDiff(null, new ArrayList<>()));
Assertions.assertFalse(HttpRequestParamDiffUtils.isParamKeyDiff(new ArrayList<>(), null));
List<KeyValueParam> params1 = new ArrayList<>();
KeyValueParam kv1 = new KeyValueParam();
kv1.setKey("key1");
kv1.setValue("value1");
params1.add(kv1);
List<KeyValueParam> params2 = new ArrayList<>();
KeyValueParam kv2 = new KeyValueParam();
kv2.setKey("key1");
kv2.setValue("value2");
params2.add(kv2);
Assertions.assertFalse(HttpRequestParamDiffUtils.isParamKeyDiff(params1, params2));
params1 = new ArrayList<>();
kv1 = new KeyValueParam();
kv1.setKey("key1");
kv1.setValue("value1");
params1.add(kv1);
params2 = new ArrayList<>();
kv2 = new KeyValueParam();
kv2.setKey("kv2");
kv2.setValue("value2");
params2.add(kv2);
Assertions.assertTrue(HttpRequestParamDiffUtils.isParamKeyDiff(params1, params2));
}
@Test
public void isBodyDiff() {
Assertions.assertFalse(HttpRequestParamDiffUtils.isBodyDiff(null, null));
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(null, new Body()));
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(new Body(), null));
Body body1 = new Body();
Body body2 = new Body();
body1.setBodyType(Body.BodyType.FORM_DATA.name());
body2.setBodyType(Body.BodyType.RAW.name());
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
body1.setBodyType(Body.BodyType.FORM_DATA.name());
body1.setFormDataBody(new FormDataBody());
FormDataKV formDataKV1 = new FormDataKV();
formDataKV1.setKey("key1");
formDataKV1.setValue("value");
body1.getFormDataBody().getFormValues().add(formDataKV1);
body2.setBodyType(Body.BodyType.FORM_DATA.name());
body2.setFormDataBody(new FormDataBody());
FormDataKV formDataKV2 = new FormDataKV();
formDataKV2.setKey("key2");
formDataKV2.setValue("value");
body2.getFormDataBody().getFormValues().add(formDataKV2);
// 校验 FORM_DATA
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
body1.setBodyType(Body.BodyType.WWW_FORM.name());
body1.setWwwFormBody(new WWWFormBody());
WWWFormKV wwwFormKV1 = new WWWFormKV();
wwwFormKV1.setKey("key1");
wwwFormKV1.setValue("value");
body1.getWwwFormBody().getFormValues().add(wwwFormKV1);
body2.setBodyType(Body.BodyType.WWW_FORM.name());
body2.setWwwFormBody(new WWWFormBody());
WWWFormKV wwwFormKV2 = new WWWFormKV();
wwwFormKV2.setKey("key2");
wwwFormKV2.setValue("value");
body2.getWwwFormBody().getFormValues().add(wwwFormKV2);
// 校验 WWW_FORM
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
body1.setBodyType(Body.BodyType.JSON.name());
body1.setJsonBody(new JsonBody());
body1.getJsonBody().setJsonValue("""
{"id1":""}
""");
body2.setBodyType(Body.BodyType.JSON.name());
body2.setJsonBody(new JsonBody());
body2.getJsonBody().setJsonValue("""
{"id2":""}
""");
// 校验 JSON
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
body1.setBodyType(Body.BodyType.XML.name());
body1.setXmlBody(new XmlBody());
body1.getXmlBody().setValue("""
<tag1><tag1/>
""");
body2.setBodyType(Body.BodyType.XML.name());
body2.setXmlBody(new XmlBody());
body2.getXmlBody().setValue("""
<tag2><tag2/>
""");
// 校验 XML
Assertions.assertTrue(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
body1.setBodyType(Body.BodyType.RAW.name());
body1.setRawBody(new RawBody());
body1.getRawBody().setValue("value1");
body2.setBodyType(Body.BodyType.RAW.name());
body2.setRawBody(new RawBody());
body2.getRawBody().setValue("value2");
// 校验 RAW
Assertions.assertFalse(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
body1.setBodyType(Body.BodyType.BINARY.name());
body2.setBodyType(Body.BodyType.BINARY.name());
// 校验 BINARY
Assertions.assertFalse(HttpRequestParamDiffUtils.isBodyDiff(body1, body2));
}
@Test
public void isRequestParamDiff() {
Assertions.assertFalse(HttpRequestParamDiffUtils.isRequestParamDiff("", ""));
MsHTTPElement msHTTPElement2 = new MsHTTPElement();
MsHTTPElement msHTTPElement1 = new MsHTTPElement();
QueryParam queryParam = new QueryParam();
queryParam.setKey("111");
msHTTPElement1.setQuery(List.of(queryParam));
Assertions.assertTrue(HttpRequestParamDiffUtils.isRequestParamDiff(msHTTPElement1, msHTTPElement2));
msHTTPElement1 = new MsHTTPElement();
RestParam restParam = new RestParam();
restParam.setKey("111");
msHTTPElement1.setRest(List.of(restParam));
Assertions.assertTrue(HttpRequestParamDiffUtils.isRequestParamDiff(msHTTPElement1, msHTTPElement2));
msHTTPElement1 = new MsHTTPElement();
MsHeader msHeader = new MsHeader();
msHeader.setKey("111");
msHTTPElement1.setHeaders(List.of(msHeader));
Assertions.assertTrue(HttpRequestParamDiffUtils.isRequestParamDiff(msHTTPElement1, msHTTPElement2));
msHTTPElement1 = new MsHTTPElement();
msHTTPElement1.setBody(new Body());
Assertions.assertTrue(HttpRequestParamDiffUtils.isRequestParamDiff(msHTTPElement1, msHTTPElement2));
}
}