feat: 修改字段类型修改不生效的问题
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:
@@ -22,29 +22,41 @@ public class SchemaDiffer {
|
|||||||
public List<SchemaChange> diff(TypeSchema oldSchema, TypeSchema newSchema) {
|
public List<SchemaChange> diff(TypeSchema oldSchema, TypeSchema newSchema) {
|
||||||
List<SchemaChange> changes = new ArrayList<>();
|
List<SchemaChange> changes = new ArrayList<>();
|
||||||
|
|
||||||
Map<String, JsonType> oldLeaves = leaves(oldSchema);
|
Map<String, FieldSchema> oldAll = fieldsByPath(oldSchema);
|
||||||
Map<String, JsonType> newLeaves = leaves(newSchema);
|
Map<String, FieldSchema> newAll = fieldsByPath(newSchema);
|
||||||
|
|
||||||
|
Map<String, FieldSchema> oldLeaves = leaves(oldSchema);
|
||||||
|
Map<String, FieldSchema> newLeaves = leaves(newSchema);
|
||||||
|
|
||||||
|
Set<String> typeChangedPaths = new LinkedHashSet<>();
|
||||||
|
|
||||||
|
// 类型变更:同路径下 JsonType 或 Java 类型任一变化(覆盖 BigDecimal→String 等同桶变更)
|
||||||
|
for (String path : oldAll.keySet()) {
|
||||||
|
FieldSchema oldField = oldAll.get(path);
|
||||||
|
FieldSchema newField = newAll.get(path);
|
||||||
|
if (newField == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isTypeChange(oldField, newField)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
typeChangedPaths.add(path);
|
||||||
|
String oldLabel = typeLabel(oldField);
|
||||||
|
String newLabel = typeLabel(newField);
|
||||||
|
SchemaChange c = new SchemaChange(ChangeType.TYPE_CHANGED);
|
||||||
|
c.setFieldPath(path);
|
||||||
|
c.setOldValue(oldLabel);
|
||||||
|
c.setNewValue(newLabel);
|
||||||
|
c.setMessage("字段 " + path + " 类型由 " + oldLabel + " 变为 " + newLabel);
|
||||||
|
changes.add(c);
|
||||||
|
}
|
||||||
|
|
||||||
Set<String> removed = new LinkedHashSet<>(oldLeaves.keySet());
|
Set<String> removed = new LinkedHashSet<>(oldLeaves.keySet());
|
||||||
removed.removeAll(newLeaves.keySet());
|
removed.removeAll(newLeaves.keySet());
|
||||||
|
removed.removeAll(typeChangedPaths);
|
||||||
Set<String> added = new LinkedHashSet<>(newLeaves.keySet());
|
Set<String> added = new LinkedHashSet<>(newLeaves.keySet());
|
||||||
added.removeAll(oldLeaves.keySet());
|
added.removeAll(oldLeaves.keySet());
|
||||||
|
added.removeAll(typeChangedPaths);
|
||||||
// 类型变更(同路径)
|
|
||||||
for (String path : oldLeaves.keySet()) {
|
|
||||||
if (newLeaves.containsKey(path)) {
|
|
||||||
JsonType oldType = oldLeaves.get(path);
|
|
||||||
JsonType newType = newLeaves.get(path);
|
|
||||||
if (oldType != newType) {
|
|
||||||
SchemaChange c = new SchemaChange(ChangeType.TYPE_CHANGED);
|
|
||||||
c.setFieldPath(path);
|
|
||||||
c.setOldValue(oldType.name());
|
|
||||||
c.setNewValue(newType.name());
|
|
||||||
c.setMessage("字段 " + path + " 类型由 " + oldType + " 变为 " + newType);
|
|
||||||
changes.add(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 路径迁移检测(removed 的路径是某 added 路径的后缀)
|
// 路径迁移检测(removed 的路径是某 added 路径的后缀)
|
||||||
List<String[]> moves = new ArrayList<>();
|
List<String[]> moves = new ArrayList<>();
|
||||||
@@ -99,7 +111,7 @@ public class SchemaDiffer {
|
|||||||
}
|
}
|
||||||
SchemaChange c = new SchemaChange(ChangeType.FIELD_REMOVED);
|
SchemaChange c = new SchemaChange(ChangeType.FIELD_REMOVED);
|
||||||
c.setFieldPath(r);
|
c.setFieldPath(r);
|
||||||
c.setOldValue(oldLeaves.get(r).name());
|
c.setOldValue(typeLabel(oldLeaves.get(r)));
|
||||||
c.setMessage("删除字段 " + r);
|
c.setMessage("删除字段 " + r);
|
||||||
changes.add(c);
|
changes.add(c);
|
||||||
}
|
}
|
||||||
@@ -111,7 +123,7 @@ public class SchemaDiffer {
|
|||||||
}
|
}
|
||||||
SchemaChange c = new SchemaChange(ChangeType.FIELD_ADDED);
|
SchemaChange c = new SchemaChange(ChangeType.FIELD_ADDED);
|
||||||
c.setFieldPath(a);
|
c.setFieldPath(a);
|
||||||
c.setNewValue(newLeaves.get(a).name());
|
c.setNewValue(typeLabel(newLeaves.get(a)));
|
||||||
c.setMessage("新增字段 " + a);
|
c.setMessage("新增字段 " + a);
|
||||||
changes.add(c);
|
changes.add(c);
|
||||||
}
|
}
|
||||||
@@ -119,11 +131,104 @@ public class SchemaDiffer {
|
|||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, JsonType> leaves(TypeSchema schema) {
|
private boolean isTypeChange(FieldSchema oldField, FieldSchema newField) {
|
||||||
Map<String, JsonType> result = new LinkedHashMap<>();
|
if (oldField.getJsonType() != newField.getJsonType()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String oldJava = normalizeJavaType(oldField.getJavaType());
|
||||||
|
String newJava = normalizeJavaType(newField.getJavaType());
|
||||||
|
if (oldJava.isEmpty() || newJava.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !oldJava.equals(newJava);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 展示用类型标签:优先 Java 简单名,并附带 JsonType(便于识别同桶变更)。
|
||||||
|
*/
|
||||||
|
private String typeLabel(FieldSchema field) {
|
||||||
|
if (field == null) {
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
String java = simpleJavaType(field.getJavaType());
|
||||||
|
JsonType json = field.getJsonType();
|
||||||
|
if (java.isEmpty() || "?".equals(java)) {
|
||||||
|
return json == null ? "?" : json.name();
|
||||||
|
}
|
||||||
|
if (json == null) {
|
||||||
|
return java;
|
||||||
|
}
|
||||||
|
return java + "/" + json.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化后用于相等比较:去掉泛型与包名,基本类型与包装类型视为同一序列化类型。
|
||||||
|
*/
|
||||||
|
static String normalizeJavaType(String javaType) {
|
||||||
|
String simple = simpleJavaType(javaType);
|
||||||
|
if (simple.isEmpty() || "?".equals(simple)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
switch (simple) {
|
||||||
|
case "int":
|
||||||
|
return "Integer";
|
||||||
|
case "long":
|
||||||
|
return "Long";
|
||||||
|
case "short":
|
||||||
|
return "Short";
|
||||||
|
case "byte":
|
||||||
|
return "Byte";
|
||||||
|
case "double":
|
||||||
|
return "Double";
|
||||||
|
case "float":
|
||||||
|
return "Float";
|
||||||
|
case "boolean":
|
||||||
|
return "Boolean";
|
||||||
|
case "char":
|
||||||
|
return "Character";
|
||||||
|
default:
|
||||||
|
return simple;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String simpleJavaType(String javaType) {
|
||||||
|
if (javaType == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String s = javaType.trim();
|
||||||
|
if (s.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int lt = s.indexOf('<');
|
||||||
|
if (lt > 0) {
|
||||||
|
s = s.substring(0, lt).trim();
|
||||||
|
}
|
||||||
|
int dot = s.lastIndexOf('.');
|
||||||
|
if (dot >= 0 && dot < s.length() - 1) {
|
||||||
|
s = s.substring(dot + 1);
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, FieldSchema> fieldsByPath(TypeSchema schema) {
|
||||||
|
Map<String, FieldSchema> result = new LinkedHashMap<>();
|
||||||
|
if (schema == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (FieldSchema f : schema.getFields().values()) {
|
||||||
|
result.put(f.getPath(), f);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, FieldSchema> leaves(TypeSchema schema) {
|
||||||
|
Map<String, FieldSchema> result = new LinkedHashMap<>();
|
||||||
|
if (schema == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
for (FieldSchema f : schema.getFields().values()) {
|
for (FieldSchema f : schema.getFields().values()) {
|
||||||
if (f.getJsonType() != JsonType.OBJECT && f.getJsonType() != JsonType.ARRAY) {
|
if (f.getJsonType() != JsonType.OBJECT && f.getJsonType() != JsonType.ARRAY) {
|
||||||
result.put(f.getPath(), f.getJsonType());
|
result.put(f.getPath(), f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -128,11 +128,12 @@ public class ReportBuilder {
|
|||||||
String oldJson = nvl(kc.getOldSkeletonJson());
|
String oldJson = nvl(kc.getOldSkeletonJson());
|
||||||
String newJson = nvl(kc.getNewSkeletonJson());
|
String newJson = nvl(kc.getNewSkeletonJson());
|
||||||
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
Set<String> oldHighlight = SkeletonAnnotator.pathsForOldSkeleton(kc.getFieldDetails());
|
||||||
Set<String> newHighlight = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
|
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(kc.getFieldDetails());
|
||||||
|
Set<String> newOrange = SkeletonAnnotator.pathsForTypeChanged(kc.getFieldDetails());
|
||||||
String oldRendered = oldJson.isEmpty()
|
String oldRendered = oldJson.isEmpty()
|
||||||
? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight) + "”";
|
? "" : "“" + SkeletonAnnotator.annotateOldForWecom(oldJson, oldHighlight) + "”";
|
||||||
String newRendered = newJson.isEmpty()
|
String newRendered = newJson.isEmpty()
|
||||||
? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newHighlight) + "”";
|
? "" : "“" + SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange) + "”";
|
||||||
if (oldJson.isEmpty() && !newJson.isEmpty()) {
|
if (oldJson.isEmpty() && !newJson.isEmpty()) {
|
||||||
sb.append(" > **value 新增为:** ").append(newRendered).append("\n\n");
|
sb.append(" > **value 新增为:** ").append(newRendered).append("\n\n");
|
||||||
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
|
} else if (!oldJson.isEmpty() && newJson.isEmpty()) {
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ import java.util.Set;
|
|||||||
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
|
* <li>删除字段 → 旧骨架,橙色 {@code warning}</li>
|
||||||
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
|
* <li>新增字段 / 新增包装层 → 新骨架,绿色 {@code info}</li>
|
||||||
* <li>路径迁移 → 旧路径橙、新路径绿</li>
|
* <li>路径迁移 → 旧路径橙、新路径绿</li>
|
||||||
|
* <li>类型变更 → 新旧骨架均为橙色 {@code warning}(提高识别度)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
final class SkeletonAnnotator {
|
final class SkeletonAnnotator {
|
||||||
|
|
||||||
/** 企微橙:删除 / 旧侧变更 */
|
/** 企微橙:删除 / 类型变更 / 旧侧变更 */
|
||||||
static final String COLOR_REMOVE = "warning";
|
static final String COLOR_REMOVE = "warning";
|
||||||
/** 企微绿:新增 / 新侧变更 */
|
/** 企微绿:新增 / 新侧路径迁移 */
|
||||||
static final String COLOR_ADD = "info";
|
static final String COLOR_ADD = "info";
|
||||||
|
|
||||||
private static final String FONT_CLOSE = "</font>";
|
private static final String FONT_CLOSE = "</font>";
|
||||||
@@ -29,6 +30,7 @@ final class SkeletonAnnotator {
|
|||||||
private SkeletonAnnotator() {
|
private SkeletonAnnotator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 旧骨架应橙色标注的路径:删除、路径迁移旧侧、类型变更。 */
|
||||||
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
|
static Set<String> pathsForOldSkeleton(List<SchemaChange> details) {
|
||||||
Set<String> paths = new LinkedHashSet<>();
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
if (details == null) {
|
if (details == null) {
|
||||||
@@ -38,7 +40,8 @@ final class SkeletonAnnotator {
|
|||||||
if (c == null || c.getChangeType() == null) {
|
if (c == null || c.getChangeType() == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (c.getChangeType() == ChangeType.FIELD_REMOVED) {
|
if (c.getChangeType() == ChangeType.FIELD_REMOVED
|
||||||
|
|| c.getChangeType() == ChangeType.TYPE_CHANGED) {
|
||||||
addIfPresent(paths, c.getFieldPath());
|
addIfPresent(paths, c.getFieldPath());
|
||||||
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
|
} else if (c.getChangeType() == ChangeType.FIELD_PATH_MOVED) {
|
||||||
addIfPresent(paths, c.getOldValue());
|
addIfPresent(paths, c.getOldValue());
|
||||||
@@ -47,6 +50,7 @@ final class SkeletonAnnotator {
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 新骨架应绿色标注的路径:新增、包装层、路径迁移新侧(不含类型变更)。 */
|
||||||
static Set<String> pathsForNewSkeleton(List<SchemaChange> details) {
|
static Set<String> pathsForNewSkeleton(List<SchemaChange> details) {
|
||||||
Set<String> paths = new LinkedHashSet<>();
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
if (details == null) {
|
if (details == null) {
|
||||||
@@ -69,18 +73,40 @@ final class SkeletonAnnotator {
|
|||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 新骨架应橙色标注的路径:类型变更(与旧侧同色,便于识别)。 */
|
||||||
|
static Set<String> pathsForTypeChanged(List<SchemaChange> details) {
|
||||||
|
Set<String> paths = new LinkedHashSet<>();
|
||||||
|
if (details == null) {
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
for (SchemaChange c : details) {
|
||||||
|
if (c != null && c.getChangeType() == ChangeType.TYPE_CHANGED) {
|
||||||
|
addIfPresent(paths, c.getFieldPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标注旧骨架改动字段(删除 → 橙色)。
|
* 标注旧骨架改动字段(删除 / 类型变更 → 橙色)。
|
||||||
*/
|
*/
|
||||||
static String annotateOldForWecom(String json, Set<String> highlightPaths) {
|
static String annotateOldForWecom(String json, Set<String> highlightPaths) {
|
||||||
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
return annotateForWecom(json, highlightPaths, COLOR_REMOVE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 标注新骨架改动字段(新增 → 绿色)。
|
* 标注新骨架:类型变更先着橙色,其余新增着绿色。
|
||||||
*/
|
*/
|
||||||
static String annotateNewForWecom(String json, Set<String> highlightPaths) {
|
static String annotateNewForWecom(String json, Set<String> greenPaths) {
|
||||||
return annotateForWecom(json, highlightPaths, COLOR_ADD);
|
return annotateNewForWecom(json, greenPaths, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标注新骨架改动字段(类型变更 → 橙色;新增 / 迁移新侧 → 绿色)。
|
||||||
|
*/
|
||||||
|
static String annotateNewForWecom(String json, Set<String> greenPaths, Set<String> orangePaths) {
|
||||||
|
String result = annotateForWecom(json, orangePaths, COLOR_REMOVE);
|
||||||
|
return annotateForWecom(result, greenPaths, COLOR_ADD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,12 +21,11 @@ public class JavaSchemaExtractor {
|
|||||||
|
|
||||||
private static final Set<String> STRING_TYPES = new HashSet<>(Arrays.asList(
|
private static final Set<String> STRING_TYPES = new HashSet<>(Arrays.asList(
|
||||||
"String", "CharSequence", "char", "Character", "UUID",
|
"String", "CharSequence", "char", "Character", "UUID",
|
||||||
"Date", "LocalDate", "LocalDateTime", "LocalTime", "Instant", "Timestamp",
|
"Date", "LocalDate", "LocalDateTime", "LocalTime", "Instant", "Timestamp"));
|
||||||
"BigDecimal"));
|
|
||||||
private static final Set<String> NUMBER_TYPES = new HashSet<>(Arrays.asList(
|
private static final Set<String> NUMBER_TYPES = new HashSet<>(Arrays.asList(
|
||||||
"int", "long", "short", "byte", "double", "float",
|
"int", "long", "short", "byte", "double", "float",
|
||||||
"Integer", "Long", "Short", "Byte", "Double", "Float",
|
"Integer", "Long", "Short", "Byte", "Double", "Float",
|
||||||
"Number", "BigInteger", "AtomicInteger", "AtomicLong"));
|
"Number", "BigInteger", "BigDecimal", "AtomicInteger", "AtomicLong"));
|
||||||
private static final Set<String> BOOLEAN_TYPES = new HashSet<>(Arrays.asList(
|
private static final Set<String> BOOLEAN_TYPES = new HashSet<>(Arrays.asList(
|
||||||
"boolean", "Boolean"));
|
"boolean", "Boolean"));
|
||||||
private static final Set<String> COLLECTION_TYPES = new HashSet<>(Arrays.asList(
|
private static final Set<String> COLLECTION_TYPES = new HashSet<>(Arrays.asList(
|
||||||
|
|||||||
@@ -51,6 +51,57 @@ class SchemaDifferTest {
|
|||||||
assertEquals(1, changes.size());
|
assertEquals(1, changes.size());
|
||||||
assertEquals(ChangeType.TYPE_CHANGED, changes.get(0).getChangeType());
|
assertEquals(ChangeType.TYPE_CHANGED, changes.get(0).getChangeType());
|
||||||
assertEquals(Severity.P0, changes.get(0).getSeverity());
|
assertEquals(Severity.P0, changes.get(0).getSeverity());
|
||||||
|
assertTrue(changes.get(0).getMessage().contains("String"));
|
||||||
|
assertTrue(changes.get(0).getMessage().contains("Integer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsBigDecimalToStringEvenWhenBothWereStringBucketHistorically() {
|
||||||
|
// 即便 JsonType 同为 STRING,javaType 变化也必须检出(用户真实漏报场景)
|
||||||
|
TypeSchema oldS = new TypeSchema("A");
|
||||||
|
oldS.add(new FieldSchema("amount", JsonType.STRING, "BigDecimal"));
|
||||||
|
TypeSchema newS = new TypeSchema("A");
|
||||||
|
newS.add(new FieldSchema("amount", JsonType.STRING, "String"));
|
||||||
|
|
||||||
|
List<SchemaChange> changes = new SchemaDiffer().diff(oldS, newS);
|
||||||
|
assertEquals(1, changes.size());
|
||||||
|
assertEquals(ChangeType.TYPE_CHANGED, changes.get(0).getChangeType());
|
||||||
|
assertEquals("amount", changes.get(0).getFieldPath());
|
||||||
|
assertTrue(changes.get(0).getOldValue().contains("BigDecimal"));
|
||||||
|
assertTrue(changes.get(0).getNewValue().contains("String"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsBigDecimalToStringViaJsonTypeAndJavaType() {
|
||||||
|
TypeSchema oldS = new TypeSchema("A");
|
||||||
|
oldS.add(new FieldSchema("amount", JsonType.NUMBER, "BigDecimal"));
|
||||||
|
TypeSchema newS = new TypeSchema("A");
|
||||||
|
newS.add(new FieldSchema("amount", JsonType.STRING, "String"));
|
||||||
|
|
||||||
|
List<SchemaChange> changes = new SchemaDiffer().diff(oldS, newS);
|
||||||
|
assertEquals(1, changes.size());
|
||||||
|
assertEquals(ChangeType.TYPE_CHANGED, changes.get(0).getChangeType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detectsIntegerToLongWithinNumberBucket() {
|
||||||
|
TypeSchema oldS = new TypeSchema("A");
|
||||||
|
oldS.add(new FieldSchema("id", JsonType.NUMBER, "Integer"));
|
||||||
|
TypeSchema newS = new TypeSchema("A");
|
||||||
|
newS.add(new FieldSchema("id", JsonType.NUMBER, "Long"));
|
||||||
|
|
||||||
|
List<SchemaChange> changes = new SchemaDiffer().diff(oldS, newS);
|
||||||
|
assertEquals(1, changes.size());
|
||||||
|
assertEquals(ChangeType.TYPE_CHANGED, changes.get(0).getChangeType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noChangeWhenPrimitiveAndWrapperEquivalent() {
|
||||||
|
TypeSchema a = new TypeSchema("A");
|
||||||
|
a.add(new FieldSchema("n", JsonType.NUMBER, "int"));
|
||||||
|
TypeSchema b = new TypeSchema("A");
|
||||||
|
b.add(new FieldSchema("n", JsonType.NUMBER, "Integer"));
|
||||||
|
assertTrue(new SchemaDiffer().diff(a, b).isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -111,6 +111,57 @@ class ReportBuilderTest {
|
|||||||
assertFalse(newMd.startsWith("`"));
|
assertFalse(newMd.startsWith("`"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void typeChangedHighlightedOrangeInBothSkeletons() {
|
||||||
|
SchemaChange typeChanged = new SchemaChange(ChangeType.TYPE_CHANGED);
|
||||||
|
typeChanged.setFieldPath("amount");
|
||||||
|
typeChanged.setOldValue("BigDecimal/NUMBER");
|
||||||
|
typeChanged.setNewValue("String/STRING");
|
||||||
|
typeChanged.setMessage("字段 amount 类型由 BigDecimal/NUMBER 变为 String/STRING");
|
||||||
|
|
||||||
|
String oldJson = "{\"amount\":0,\"name\":\"\"}";
|
||||||
|
String newJson = "{\"amount\":\"\",\"name\":\"\"}";
|
||||||
|
List<SchemaChange> details = Collections.singletonList(typeChanged);
|
||||||
|
|
||||||
|
Set<String> oldPaths = SkeletonAnnotator.pathsForOldSkeleton(details);
|
||||||
|
Set<String> newGreen = SkeletonAnnotator.pathsForNewSkeleton(details);
|
||||||
|
Set<String> newOrange = SkeletonAnnotator.pathsForTypeChanged(details);
|
||||||
|
|
||||||
|
assertEquals(Collections.singleton("amount"), oldPaths);
|
||||||
|
assertTrue(newGreen.isEmpty());
|
||||||
|
assertEquals(Collections.singleton("amount"), newOrange);
|
||||||
|
|
||||||
|
String oldMd = SkeletonAnnotator.annotateOldForWecom(oldJson, oldPaths);
|
||||||
|
String newMd = SkeletonAnnotator.annotateNewForWecom(newJson, newGreen, newOrange);
|
||||||
|
|
||||||
|
assertTrue(oldMd.contains("<font color=\"warning\">\"amount\":0</font>"));
|
||||||
|
assertFalse(oldMd.contains("<font color=\"warning\">\"name\":\"\"</font>"));
|
||||||
|
assertTrue(newMd.contains("<font color=\"warning\">\"amount\":\"\"</font>"));
|
||||||
|
assertFalse(newMd.contains("<font color=\"info\">\"amount\""));
|
||||||
|
assertFalse(newMd.contains("<font color=\"warning\">\"name\":\"\"</font>"));
|
||||||
|
|
||||||
|
CheckReport report = baseReport();
|
||||||
|
KeyStructureChange key = new KeyStructureChange();
|
||||||
|
key.setKeyPattern("cache:amount");
|
||||||
|
key.setWriteLocation("Demo#put:1");
|
||||||
|
key.setValueType("AmountVo");
|
||||||
|
key.setOldSkeletonJson(oldJson);
|
||||||
|
key.setNewSkeletonJson(newJson);
|
||||||
|
key.getFieldDetails().add(typeChanged);
|
||||||
|
report.getKeyChanges().add(key);
|
||||||
|
|
||||||
|
String md = new ReportBuilder("[序列化结构变更]").toMarkdown(report);
|
||||||
|
int oldSection = md.indexOf("> **value值由:**");
|
||||||
|
int newSection = md.indexOf("> **变更为:**");
|
||||||
|
assertTrue(oldSection >= 0);
|
||||||
|
assertTrue(newSection > oldSection);
|
||||||
|
String oldPart = md.substring(oldSection, newSection);
|
||||||
|
String newPart = md.substring(newSection);
|
||||||
|
assertTrue(oldPart.contains("<font color=\"warning\">\"amount\":0</font>"));
|
||||||
|
assertTrue(newPart.contains("<font color=\"warning\">\"amount\":\"\"</font>"));
|
||||||
|
assertFalse(newPart.contains("<font color=\"info\">\"amount\""));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void consoleContainsFieldDetailsAndWecomMarkdown() {
|
void consoleContainsFieldDetailsAndWecomMarkdown() {
|
||||||
CheckReport report = new CheckReport();
|
CheckReport report = new CheckReport();
|
||||||
|
|||||||
@@ -81,4 +81,21 @@ class JavaSchemaExtractorTest {
|
|||||||
assertTrue(AnnotationSupport.ignoredProperties(type).contains("a"));
|
assertTrue(AnnotationSupport.ignoredProperties(type).contains("a"));
|
||||||
assertTrue(AnnotationSupport.ignoredProperties(type).contains("b"));
|
assertTrue(AnnotationSupport.ignoredProperties(type).contains("b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void bigDecimalMappedAsNumberNotString() {
|
||||||
|
String source = ""
|
||||||
|
+ "package demo;\n"
|
||||||
|
+ "import java.math.BigDecimal;\n"
|
||||||
|
+ "public class MoneyVo {\n"
|
||||||
|
+ " private BigDecimal amount;\n"
|
||||||
|
+ " private String currency;\n"
|
||||||
|
+ "}\n";
|
||||||
|
SourceIndex index = new SourceIndex();
|
||||||
|
index.addSource(source);
|
||||||
|
TypeSchema schema = new JavaSchemaExtractor(index, 8).extract("demo.MoneyVo");
|
||||||
|
assertEquals(JsonType.NUMBER, schema.getFields().get("amount").getJsonType());
|
||||||
|
assertEquals("BigDecimal", schema.getFields().get("amount").getJavaType());
|
||||||
|
assertEquals(JsonType.STRING, schema.getFields().get("currency").getJsonType());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user