package cn.iocoder.yudao.module.qcreport.dal.dataobject.version; import com.baomidou.mybatisplus.extension.handlers.Jackson3TypeHandler; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.lang.reflect.Field; import java.util.List; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; /** * Schema 读写兼容性测试。 *

* 走的是生产同款读取路径——MyBatis-Plus 的 {@link Jackson3TypeHandler} 配 * {@code QcReportTemplateVersionDO.schema} 字段,而不是测试自己 new 一个 ObjectMapper, * 因为要防的就是「反序列化读取」这条链路:{@code schema_json} 列里存的都是历史 JSON。 *

* 契约 1.1 删掉了 dataSources / bindings / styles 三个从未使用的字段,库里已经存着带这三个键的 * 旧记录。这里守的是「旧 JSON 仍然读得出、且该有的字段一个不少」——出问题的表现是 * 「老模板点进去报系统异常」,只有存量数据会中招,事后很难查。 *

* 它负责证明 {@code ReportTemplateSchema} 上 {@code @JsonIgnoreProperties} 的必要性: * 实测把那行注解去掉本测试照样通过(Jackson 3 默认就忽略未知属性)。 * 注解的意义写在那个类上,别把这条测试当成它的守卫。 */ class ReportTemplateSchemaCompatibilityTest { /** 1.0 时期存进去的 Schema:含已删除的三个字段,components 还是无类型的 Map */ private static final String LEGACY_JSON = """ { "schemaVersion": "1.0", "page": { "size": "A4", "orientation": "portrait", "margin": { "top": 12, "right": 8, "bottom": 12, "left": 8 } }, "grapes": { "pages": [ { "frames": [] } ] }, "components": [ { "id": "abc", "index": 0, "qualityType": "quality-table", "attributes": { "data-qc-repeat": "inspectionItems" } } ], "dataSources": [], "bindings": [], "rules": [ { "id": "r1", "name": "实测值在规格内", "scope": "item", "expression": "item.actualValue >= item.lowerLimit", "enabled": true } ], "styles": [] }"""; @Test @DisplayName("含已删除字段的旧 Schema 仍能读出来(否则老模板打不开)") void readsLegacySchemaWithRemovedFields() throws Exception { ReportTemplateSchema schema = parse(LEGACY_JSON); assertNotNull(schema); assertEquals("1.0", schema.getSchemaVersion(), "旧记录的版本号要原样读出来,不能因为版本升级就丢掉"); assertEquals("A4", schema.getPage().getSize()); assertEquals(12.0, schema.getPage().getMargin().getTop().doubleValue()); assertFalse(schema.getGrapes().isEmpty(), "画布数据不能丢,丢了模板就只剩空壳"); List components = schema.getComponents(); assertEquals(1, components.size()); assertEquals("quality-table", components.get(0).getQualityType()); assertEquals(0, components.get(0).getIndex().intValue()); assertEquals("inspectionItems", components.get(0).getAttributes().get("data-qc-repeat")); assertEquals(1, schema.getRules().size(), "规则是设计器管不到的字段,读的时候绝不能少"); Map rule = schema.getRules().get(0); assertEquals("item", rule.get("scope")); assertEquals("item.actualValue >= item.lowerLimit", rule.get("expression")); } @Test @DisplayName("写入再读出:当前格式往返不丢内容") void roundTripsCurrentSchema() throws Exception { ReportTemplateSchema schema = parse(LEGACY_JSON); Jackson3TypeHandler handler = handler(); ReportTemplateSchema reloaded = (ReportTemplateSchema) handler.parse(handler.toJson(schema)); assertEquals(schema.getSchemaVersion(), reloaded.getSchemaVersion()); assertEquals("quality-table", reloaded.getComponents().get(0).getQualityType()); assertEquals("item.actualValue >= item.lowerLimit", reloaded.getRules().get(0).get("expression")); assertEquals(1, reloaded.getRules().size()); } @Test @DisplayName("缺字段的旧 Schema 也能读:只有 schemaVersion 不报错") void readsSchemaMissingOptionalFields() throws Exception { ReportTemplateSchema schema = parse("{\"schemaVersion\":\"1.0\"}"); assertEquals("1.0", schema.getSchemaVersion()); assertNull(schema.getPage()); assertNull(schema.getGrapes()); } /** 走 MyBatis 实例化类型处理器的方式:带字段,genericType 一并带上 */ private static ReportTemplateSchema parse(String json) throws Exception { return (ReportTemplateSchema) handler().parse(json); } private static Jackson3TypeHandler handler() throws NoSuchFieldException { Field field = QcReportTemplateVersionDO.class.getDeclaredField("schema"); return new Jackson3TypeHandler(QcReportTemplateVersionDO.class, field); } }