feat: 1、Vo包装识别增强 2、去除 位置&类型 的色块
All checks were successful
序列化结构检查 / serialization-schema-check (push) Has been skipped
All checks were successful
序列化结构检查 / serialization-schema-check (push) Has been skipped
This commit is contained in:
@@ -40,6 +40,41 @@ class SchemaDifferTest {
|
||||
assertEquals(2, moved, "dbName 与 linkList[].id 均应迁移");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsWrapperRemovalAndUpwardMoves() {
|
||||
TypeSchema oldS = new TypeSchema("CacheEnvelope");
|
||||
oldS.add(new FieldSchema("vo", JsonType.OBJECT, "TenantVO"));
|
||||
oldS.add(new FieldSchema("vo.dbName", JsonType.STRING, "String"));
|
||||
oldS.add(new FieldSchema("vo.linkList", JsonType.ARRAY, "List"));
|
||||
oldS.add(new FieldSchema("vo.linkList[]", JsonType.OBJECT, "TenantLinkModel"));
|
||||
oldS.add(new FieldSchema("vo.linkList[].id", JsonType.STRING, "String"));
|
||||
|
||||
TypeSchema newS = new TypeSchema("TenantVO");
|
||||
newS.add(new FieldSchema("dbName", JsonType.STRING, "String"));
|
||||
newS.add(new FieldSchema("linkList", JsonType.ARRAY, "List"));
|
||||
newS.add(new FieldSchema("linkList[]", JsonType.OBJECT, "TenantLinkModel"));
|
||||
newS.add(new FieldSchema("linkList[].id", JsonType.STRING, "String"));
|
||||
|
||||
List<SchemaChange> changes = new SchemaDiffer().diff(oldS, newS);
|
||||
List<ChangeType> types = changes.stream().map(SchemaChange::getChangeType).collect(Collectors.toList());
|
||||
|
||||
assertTrue(types.contains(ChangeType.WRAPPER_REMOVED), "应检测到包装层删除");
|
||||
assertTrue(types.contains(ChangeType.FIELD_PATH_MOVED), "应检测到字段上提迁移");
|
||||
assertTrue(types.stream().noneMatch(t -> t == ChangeType.FIELD_ADDED),
|
||||
"上提字段不应再被当成新增");
|
||||
assertTrue(types.stream().noneMatch(t -> t == ChangeType.FIELD_REMOVED),
|
||||
"上提字段不应再被当成删除");
|
||||
|
||||
SchemaChange wrapper = changes.stream()
|
||||
.filter(c -> c.getChangeType() == ChangeType.WRAPPER_REMOVED)
|
||||
.findFirst()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
assertEquals("vo", wrapper.getFieldPath());
|
||||
|
||||
long moved = changes.stream().filter(c -> c.getChangeType() == ChangeType.FIELD_PATH_MOVED).count();
|
||||
assertEquals(2, moved, "vo.dbName / vo.linkList[].id 均应上提");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detectsTypeChange() {
|
||||
TypeSchema oldS = new TypeSchema("A");
|
||||
|
||||
@@ -65,4 +65,91 @@ class RedisKeyResolverTest {
|
||||
buildCall, clazz, index.get("jnpf.util.TenantDbContentCacheHelper"));
|
||||
assertEquals("tenant:db:content:*", pattern);
|
||||
}
|
||||
|
||||
@Test
|
||||
void resolvesLocalVariableAssignedFromStringFormat() {
|
||||
String source = ""
|
||||
+ "package demo;\n"
|
||||
+ "public class CombinationRepo {\n"
|
||||
+ " private static final String COMBINATION_QUERY_KEY = "
|
||||
+ "\"data_analysis:combination_query_key:%s:%s\";\n"
|
||||
+ " public void write(String tenantId, String hashKey) {\n"
|
||||
+ " String key = String.format(COMBINATION_QUERY_KEY, tenantId, hashKey);\n"
|
||||
+ " redisSet(key);\n"
|
||||
+ " }\n"
|
||||
+ " void redisSet(String k) {}\n"
|
||||
+ "}\n";
|
||||
|
||||
SourceIndex index = new SourceIndex();
|
||||
index.addSource(source);
|
||||
CompilationUnit cu = StaticJavaParser.parse(source);
|
||||
ClassOrInterfaceDeclaration clazz = cu.getType(0).asClassOrInterfaceDeclaration();
|
||||
// redisSet(key) 的实参 key —— 局部变量使用点
|
||||
MethodCallExpr redisSet = cu.findAll(MethodCallExpr.class).stream()
|
||||
.filter(m -> "redisSet".equals(m.getNameAsString()))
|
||||
.findFirst()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
Expression keyArg = redisSet.getArgument(0);
|
||||
|
||||
String pattern = new RedisKeyResolver(index).resolve(
|
||||
keyArg, clazz, index.get("demo.CombinationRepo"));
|
||||
assertEquals("data_analysis:combination_query_key:*:*", pattern);
|
||||
}
|
||||
|
||||
@Test
|
||||
void localReassignmentUsesNearestBindingBeforeUse() {
|
||||
String source = ""
|
||||
+ "package demo;\n"
|
||||
+ "public class ReassignRepo {\n"
|
||||
+ " private static final String A = \"prefix:a:%s\";\n"
|
||||
+ " private static final String B = \"prefix:b:%s\";\n"
|
||||
+ " public void write(String id) {\n"
|
||||
+ " String key = String.format(A, id);\n"
|
||||
+ " key = String.format(B, id);\n"
|
||||
+ " redisSet(key);\n"
|
||||
+ " }\n"
|
||||
+ " void redisSet(String k) {}\n"
|
||||
+ "}\n";
|
||||
|
||||
SourceIndex index = new SourceIndex();
|
||||
index.addSource(source);
|
||||
CompilationUnit cu = StaticJavaParser.parse(source);
|
||||
ClassOrInterfaceDeclaration clazz = cu.getType(0).asClassOrInterfaceDeclaration();
|
||||
MethodCallExpr redisSet = cu.findAll(MethodCallExpr.class).stream()
|
||||
.filter(m -> "redisSet".equals(m.getNameAsString()))
|
||||
.findFirst()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
|
||||
String pattern = new RedisKeyResolver(index).resolve(
|
||||
redisSet.getArgument(0), clazz, index.get("demo.ReassignRepo"));
|
||||
assertEquals("prefix:b:*", pattern);
|
||||
}
|
||||
|
||||
@Test
|
||||
void staticConstantStillPreferredOverLocalNameCollision() {
|
||||
// 局部变量名与静态常量同名时:仍优先静态常量(保持旧语义)
|
||||
String source = ""
|
||||
+ "package demo;\n"
|
||||
+ "public class Collision {\n"
|
||||
+ " private static final String KEY = \"const:prefix:%s\";\n"
|
||||
+ " public void write(String id) {\n"
|
||||
+ " String KEY = \"local:\" + id;\n"
|
||||
+ " redisSet(KEY);\n"
|
||||
+ " }\n"
|
||||
+ " void redisSet(String k) {}\n"
|
||||
+ "}\n";
|
||||
|
||||
SourceIndex index = new SourceIndex();
|
||||
index.addSource(source);
|
||||
CompilationUnit cu = StaticJavaParser.parse(source);
|
||||
ClassOrInterfaceDeclaration clazz = cu.getType(0).asClassOrInterfaceDeclaration();
|
||||
MethodCallExpr redisSet = cu.findAll(MethodCallExpr.class).stream()
|
||||
.filter(m -> "redisSet".equals(m.getNameAsString()))
|
||||
.findFirst()
|
||||
.orElseThrow(IllegalStateException::new);
|
||||
|
||||
String pattern = new RedisKeyResolver(index).resolve(
|
||||
redisSet.getArgument(0), clazz, index.get("demo.Collision"));
|
||||
assertEquals("const:prefix:%s", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.codechecker.cache.diff.SchemaChange;
|
||||
import com.codechecker.cache.diff.Severity;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -60,8 +61,8 @@ class ReportBuilderTest {
|
||||
|
||||
assertTrue(md.contains("- Key --> `saas:period-config:migration:current`"));
|
||||
assertFalse(md.contains("(key 无法解析)"));
|
||||
assertTrue(md.contains("> **位置**: `SaasPeriodConfigMigrationRedisSupport#putCurrent:41`"));
|
||||
assertTrue(md.contains("> **类型**: `MigrationCurrentVo`"));
|
||||
assertTrue(md.contains("> **位置**: SaasPeriodConfigMigrationRedisSupport#putCurrent:41"));
|
||||
assertTrue(md.contains("> **类型**: MigrationCurrentVo"));
|
||||
|
||||
int oldSection = md.indexOf("> **value值由:**");
|
||||
int newSection = md.indexOf("> **变更为:**");
|
||||
@@ -167,6 +168,118 @@ class ReportBuilderTest {
|
||||
"应包含类型变更摘要行,实际 markdown:\n" + md);
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrapperAddedHighlightedBeforeNestedMovedFields() {
|
||||
SchemaChange wrapper = new SchemaChange(ChangeType.WRAPPER_ADDED);
|
||||
wrapper.setFieldPath("vo");
|
||||
wrapper.setMessage("新增包装层 vo");
|
||||
|
||||
SchemaChange movedDb = new SchemaChange(ChangeType.FIELD_PATH_MOVED);
|
||||
movedDb.setFieldPath("vo.dbName");
|
||||
movedDb.setOldValue("dbName");
|
||||
movedDb.setNewValue("vo.dbName");
|
||||
|
||||
SchemaChange movedLink = new SchemaChange(ChangeType.FIELD_PATH_MOVED);
|
||||
movedLink.setFieldPath("vo.linkList[].id");
|
||||
movedLink.setOldValue("linkList[].id");
|
||||
movedLink.setNewValue("vo.linkList[].id");
|
||||
|
||||
List<SchemaChange> details = new ArrayList<>();
|
||||
details.add(wrapper);
|
||||
details.add(movedDb);
|
||||
details.add(movedLink);
|
||||
|
||||
String oldJson = "{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]}";
|
||||
String newJson = "{\"vo\":{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]}}";
|
||||
|
||||
Set<String> wrappers = SkeletonAnnotator.pathsForWrapperAdded(details);
|
||||
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(details);
|
||||
assertEquals(Collections.singleton("vo"), wrappers);
|
||||
assertTrue(newGreen.contains("vo.dbName"));
|
||||
assertFalse(newGreen.contains("vo"), "包装层不应再混入普通新增路径");
|
||||
|
||||
// 先标包装层:整段 vo 对象应绿;子路径随后因已在 font 内可跳过
|
||||
String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, null, wrappers);
|
||||
assertTrue(newMd.contains("<font color=\"info\">\"vo\":{"), newMd);
|
||||
assertTrue(newMd.contains("</font>"), newMd);
|
||||
|
||||
CheckReport report = baseReport();
|
||||
KeyStructureChange key = new KeyStructureChange();
|
||||
key.setKeyPattern("tenant:db:content:*");
|
||||
key.setWriteLocation("TenantDbContentCacheHelper#cacheNotFound:62");
|
||||
key.setValueType("TenantVO");
|
||||
key.setOldSkeletonJson(oldJson);
|
||||
key.setNewSkeletonJson(newJson);
|
||||
key.getFieldDetails().addAll(details);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||
int newSection = md.indexOf("> **变更为:**");
|
||||
assertTrue(newSection >= 0);
|
||||
String newPart = md.substring(newSection);
|
||||
assertTrue(newPart.contains("<font color=\"info\">\"vo\":{"),
|
||||
"包装层 vo 应整段绿色,实际:\n" + newPart);
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrapperRemovedHighlightedBeforeNestedMovedFieldsOnOldSkeleton() {
|
||||
SchemaChange wrapper = new SchemaChange(ChangeType.WRAPPER_REMOVED);
|
||||
wrapper.setFieldPath("vo");
|
||||
wrapper.setOldValue("vo");
|
||||
wrapper.setMessage("删除包装层 vo");
|
||||
|
||||
SchemaChange movedDb = new SchemaChange(ChangeType.FIELD_PATH_MOVED);
|
||||
movedDb.setFieldPath("dbName");
|
||||
movedDb.setOldValue("vo.dbName");
|
||||
movedDb.setNewValue("dbName");
|
||||
|
||||
SchemaChange movedLink = new SchemaChange(ChangeType.FIELD_PATH_MOVED);
|
||||
movedLink.setFieldPath("linkList[].id");
|
||||
movedLink.setOldValue("vo.linkList[].id");
|
||||
movedLink.setNewValue("linkList[].id");
|
||||
|
||||
List<SchemaChange> details = new ArrayList<>();
|
||||
details.add(wrapper);
|
||||
details.add(movedDb);
|
||||
details.add(movedLink);
|
||||
|
||||
String oldJson = "{\"vo\":{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]}}";
|
||||
String newJson = "{\"dbName\":\"\",\"linkList\":[{\"id\":\"\"}]}";
|
||||
|
||||
Set<String> wrappersRemoved = SkeletonAnnotator.pathsForWrapperRemoved(details);
|
||||
Set<String> oldOrange = SkeletonAnnotator.pathsForOldSkeleton(details);
|
||||
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(details);
|
||||
|
||||
assertEquals(Collections.singleton("vo"), wrappersRemoved);
|
||||
assertTrue(oldOrange.contains("vo.dbName"));
|
||||
assertTrue(newGreen.contains("dbName"));
|
||||
assertFalse(newGreen.contains("vo"));
|
||||
|
||||
String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldOrange, null, wrappersRemoved);
|
||||
assertTrue(oldMd.contains("<font color=\"warning\">\"vo\":{"), oldMd);
|
||||
|
||||
String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, null, null);
|
||||
assertTrue(newMd.contains("<font color=\"info\">\"dbName\":\"\"</font>"), newMd);
|
||||
assertFalse(newMd.contains("<font color=\"info\">\"vo\""), newMd);
|
||||
|
||||
CheckReport report = baseReport();
|
||||
KeyStructureChange key = new KeyStructureChange();
|
||||
key.setKeyPattern("tenant:db:content:*");
|
||||
key.setWriteLocation("TenantDbContentCacheHelper#cacheNotFound:62");
|
||||
key.setValueType("TenantVO");
|
||||
key.setOldSkeletonJson(oldJson);
|
||||
key.setNewSkeletonJson(newJson);
|
||||
key.getFieldDetails().addAll(details);
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||
int oldSection = md.indexOf("> **value值由:**");
|
||||
int newSection = md.indexOf("> **变更为:**");
|
||||
assertTrue(oldSection >= 0 && newSection > oldSection);
|
||||
assertTrue(md.substring(oldSection, newSection).contains("<font color=\"warning\">\"vo\":{"),
|
||||
"旧骨架应整段橙标 vo,实际:\n" + md);
|
||||
}
|
||||
|
||||
@Test
|
||||
void consoleContainsFieldDetailsAndWecomMarkdown() {
|
||||
CheckReport report = new CheckReport();
|
||||
@@ -194,8 +307,8 @@ class ReportBuilderTest {
|
||||
assertTrue(console.contains("**删除字段**: x"));
|
||||
assertTrue(console.contains("<font color=\"warning\">\"x\":\"\"</font>"));
|
||||
assertFalse(console.contains("<font color=\"info\">\"x\":\"\"</font>"));
|
||||
assertTrue(console.contains("> **位置**: `"));
|
||||
assertTrue(console.contains("> **类型**: `"));
|
||||
assertTrue(console.contains("> **位置**: "));
|
||||
assertTrue(console.contains("> **类型**: "));
|
||||
assertTrue(console.contains("> **value值由:**"));
|
||||
assertTrue(console.contains("> **变更为:**"));
|
||||
assertFalse(console.contains("`{\"x\":\"\"}`"));
|
||||
@@ -219,8 +332,8 @@ class ReportBuilderTest {
|
||||
|
||||
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||
assertTrue(md.contains("- Key --> `req.getKey()` <font color=\"comment\">(key 无法解析)</font>"));
|
||||
assertTrue(md.contains("> **位置**: `ClockInXxxService#export:128`"));
|
||||
assertTrue(md.contains("> **类型**: `List<ClockInExportVo>`"));
|
||||
assertTrue(md.contains("> **位置**: ClockInXxxService#export:128"));
|
||||
assertTrue(md.contains("> **类型**: List<ClockInExportVo>"));
|
||||
assertFalse(md.contains("`unknown-key`"));
|
||||
}
|
||||
|
||||
@@ -272,6 +385,24 @@ class ReportBuilderTest {
|
||||
assertTrue(console.contains("超长已按 key 拆为 2 条"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void wildcardAsteriskInKeyEscapedForWeCom() {
|
||||
assertEquals("data_analysis:combination_query_key:*:*",
|
||||
ReportBuilder.escapeWeComCode("data_analysis:combination_query_key:*:*"));
|
||||
|
||||
CheckReport report = baseReport();
|
||||
KeyStructureChange key = simpleKey(
|
||||
"data_analysis:combination_query_key:*:*",
|
||||
"{\"a\":0}", "{\"a\":0,\"b\":0}", "b");
|
||||
report.getKeyChanges().add(key);
|
||||
|
||||
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||
assertTrue(md.contains("- Key --> `data_analysis:combination_query_key:*:*`"),
|
||||
"通配符 * 应转成全角*,实际:\n" + md);
|
||||
assertFalse(md.contains("combination_query_key:*:*"),
|
||||
"不应再输出未转义的 *:*, 实际:\n" + md);
|
||||
}
|
||||
|
||||
private static CheckReport baseReport() {
|
||||
CheckReport report = new CheckReport();
|
||||
report.setRepository("jnpf-java-cloud");
|
||||
|
||||
Reference in New Issue
Block a user