package jnpf.elnMapper; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.Version; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnEntity.template.ElnTemplateCurrentVersionEntity; import jnpf.elnEntity.template.ElnTemplateCheckboxGroupEntity; import jnpf.elnEntity.template.ElnTemplateEnums; import jnpf.elnEntity.template.ElnTemplateRepeatGroupEntity; import jnpf.elnEntity.template.ElnTemplateStageEntity; import jnpf.elnEntity.template.ElnTemplateStepEntity; import jnpf.elnEntity.template.ElnTemplateVersionEntity; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Select; import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Arrays; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class ElnTemplateMapperContractTest { @Test public void mapsTemplateVersionColumnsAndMapper() throws Exception { assertEquals("eln_template", tableName(ElnTemplateVersionEntity.class)); assertEntityContract(ElnTemplateVersionEntity.class, templateVersionColumns()); assertMapper(ElnTemplateMapper.class, ElnTemplateVersionEntity.class); } @Test public void mapsCurrentVersionColumnsAndMapper() throws Exception { assertEquals("eln_template_current_version", tableName(ElnTemplateCurrentVersionEntity.class)); assertEntityContract(ElnTemplateCurrentVersionEntity.class, currentVersionColumns()); assertMapper(ElnTemplateCurrentVersionMapper.class, ElnTemplateCurrentVersionEntity.class); } @Test public void mapsBindingColumnsAndMapper() throws Exception { assertEquals("eln_template_binding", tableName(ElnTemplateBindingEntity.class)); assertEntityContract(ElnTemplateBindingEntity.class, bindingColumns()); assertMapper(ElnTemplateBindingMapper.class, ElnTemplateBindingEntity.class); } @Test public void mapsRepeatGroupColumnsAndMapper() throws Exception { assertEquals("eln_template_repeat_group", tableName(ElnTemplateRepeatGroupEntity.class)); assertEntityContract(ElnTemplateRepeatGroupEntity.class, repeatGroupColumns()); assertMapper(ElnTemplateRepeatGroupMapper.class, ElnTemplateRepeatGroupEntity.class); } @Test public void mapsCheckboxGroupRuleColumnsAndMapper() throws Exception { assertEquals("eln_template_checkbox_group", tableName(ElnTemplateCheckboxGroupEntity.class)); assertEntityContract(ElnTemplateCheckboxGroupEntity.class, checkboxGroupColumns()); assertMapper(ElnTemplateCheckboxGroupMapper.class, ElnTemplateCheckboxGroupEntity.class); } @Test public void mapsStageAndStepColumnsAndMappers() throws Exception { assertEquals("eln_template_stage", tableName(ElnTemplateStageEntity.class)); assertEntityContract(ElnTemplateStageEntity.class, stageColumns()); assertMapper(ElnTemplateStageMapper.class, ElnTemplateStageEntity.class); assertEquals("eln_template_step", tableName(ElnTemplateStepEntity.class)); assertEntityContract(ElnTemplateStepEntity.class, stepColumns()); assertMapper(ElnTemplateStepMapper.class, ElnTemplateStepEntity.class); } @Test public void exposesTemplateLifecycleEnums() { assertArrayEquals(new String[]{"DRAFT", "PUBLISHED"}, enumNames(ElnTemplateEnums.TemplateStatus.class)); assertArrayEquals(new String[]{"PENDING", "ACTIVE", "CANCELLED", "REMOVED"}, enumNames(ElnTemplateEnums.BindingStatus.class)); assertArrayEquals(new String[]{"PRIMARY", "REFERENCE", "REPEAT"}, enumNames(ElnTemplateEnums.ControlRole.class)); assertArrayEquals(new String[]{"TEXT", "MULTILINE", "DROPDOWN", "CHECKBOX", "DATE"}, enumNames(ElnTemplateEnums.ControlType.class)); } @Test public void templateMapperDoesNotQueryLimsTestDefinitionTable() { for (java.lang.reflect.Method method : ElnTemplateMapper.class.getDeclaredMethods()) { Select select = method.getAnnotation(Select.class); if (select != null) { String sql = String.join(" ", select.value()).toLowerCase(java.util.Locale.ROOT); assertTrue("ELN Mapper 不得直接查询 LIMS 检验项目表: " + method.getName(), !sql.contains("jianyan_xiangmu_guanli")); } } } @Test public void templateDirectoryOrdersOnlyByTemplateCreationTimeDescending() throws Exception { Select select = ElnTemplateMapper.class.getDeclaredMethod( "selectTemplateDirectory", com.baomidou.mybatisplus.core.metadata.IPage.class, String.class, String.class, String.class, String.class, String.class, String.class).getAnnotation(Select.class); String sql = String.join(" ", select.value()) .toLowerCase(java.util.Locale.ROOT).replaceAll("\\s+", " "); String orderBy = sql.substring(sql.indexOf("order by")); assertTrue(orderBy.startsWith( "order by min(coalesce(m.chuangjian_shijian, m.f_creator_time)) desc")); assertTrue(!orderBy.contains("f_last_modify_time")); } private static String tableName(Class entityType) { return entityType.getAnnotation(TableName.class).value(); } private static void assertEntityContract(Class entityType, Map expected) throws Exception { Set actual = new HashSet<>(); for (Field field : entityType.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers())) { actual.add(field.getName()); } } assertEquals("实体字段集合不一致: " + entityType.getSimpleName(), expected.keySet(), actual); for (Map.Entry entry : expected.entrySet()) { Field field = entityType.getDeclaredField(entry.getKey()); FieldSpec spec = entry.getValue(); assertEquals(spec.javaType, field.getType()); if (spec.id) { TableId tableId = field.getAnnotation(TableId.class); assertTrue("主键缺少 @TableId: " + entry.getKey(), tableId != null); assertEquals(spec.column, tableId.value()); assertEquals(IdType.INPUT, tableId.type()); assertNull("主键不得同时标注 @TableField: " + entry.getKey(), field.getAnnotation(TableField.class)); } else { assertNull("非主键字段不得标注 @TableId: " + entry.getKey(), field.getAnnotation(TableId.class)); TableField tableField = field.getAnnotation(TableField.class); assertTrue("普通字段缺少 @TableField: " + entry.getKey(), tableField != null); assertEquals(spec.column, tableField.value()); assertEquals(spec.fill, tableField.fill()); } } assertPlatformAnnotations(entityType); } private static void assertPlatformAnnotations(Class entityType) throws Exception { TableId tableId = entityType.getDeclaredField("id").getAnnotation(TableId.class); assertEquals("f_id", tableId.value()); assertEquals(IdType.INPUT, tableId.type()); Field version = entityType.getDeclaredField("fVersion"); assertTrue(version.isAnnotationPresent(Version.class)); assertEquals("f_version", version.getAnnotation(TableField.class).value()); for (Field field : entityType.getDeclaredFields()) { if (!Modifier.isStatic(field.getModifiers()) && !"fVersion".equals(field.getName())) { assertTrue("非 fVersion 字段不得标注 @Version: " + field.getName(), !field.isAnnotationPresent(Version.class)); } } assertFill(entityType, "fCreatorUserId", "f_creator_user_id", FieldFill.INSERT); assertFill(entityType, "fCreatorTime", "f_creator_time", FieldFill.INSERT); assertFill(entityType, "fLastModifyUserId", "f_last_modify_user_id", FieldFill.INSERT_UPDATE); assertFill(entityType, "fLastModifyTime", "f_last_modify_time", FieldFill.INSERT_UPDATE); } private static void assertFill(Class entityType, String fieldName, String column, FieldFill expectedFill) throws Exception { TableField tableField = entityType.getDeclaredField(fieldName) .getAnnotation(TableField.class); assertEquals(column, tableField.value()); assertEquals(expectedFill, tableField.fill()); } private static void assertMapper(Class mapperType, Class entityType) { assertTrue(BaseMapper.class.isAssignableFrom(mapperType)); assertTrue(mapperType.isAnnotationPresent(Mapper.class)); Type mapperInterface = Arrays.stream(mapperType.getGenericInterfaces()) .filter(type -> type instanceof ParameterizedType) .findFirst() .orElseThrow(() -> new AssertionError("Mapper 必须声明 BaseMapper 泛型")); ParameterizedType parameterized = (ParameterizedType) mapperInterface; assertEquals(BaseMapper.class, parameterized.getRawType()); assertEquals(entityType, parameterized.getActualTypeArguments()[0]); } private static String[] enumNames(Class> enumType) { return Arrays.stream(enumType.getEnumConstants()).map(Enum::name).toArray(String[]::new); } private static Map templateVersionColumns() { Map columns = platformColumns(); columns.put("mubanBianhao", field("muban_bianhao", String.class)); columns.put("mubanBanben", field("muban_banben", String.class)); columns.put("mubanMingcheng", field("muban_mincheng", String.class)); columns.put("shiyanBuzhou", field("shiyan_buzhou", String.class)); columns.put("jianyanXiangmu", field("jianyan_xiangmu", String.class)); columns.put("jianyanXiangmuBianma", field("jianyan_xiangmu_bianma", String.class)); columns.put("jianyanXiangmuMingcheng", field("jianyan_xiangmu_mingcheng", String.class)); columns.put("mubanFenlei", field("muban_fenlei", String.class)); columns.put("mubanFenleiMingcheng", field("muban_fenlei_mingcheng", String.class)); columns.put("chuangjianRenyuan", field("chuangjian_renyuan", String.class)); columns.put("chuangjianShijian", field("chuangjian_shijian", Date.class)); columns.put("mubanFile", field("muban_file", String.class)); columns.put("shengxiaoRiqi", field("shengxiao_riqi", Date.class)); columns.put("bizStatus", field("biz_status", String.class)); columns.put("bizEnabled", field("biz_enabled", String.class)); columns.put("laiyuanMubanId", field("laiyuan_muban_id", String.class)); columns.put("shengbanCaozuoBianma", field("shengban_caozuo_bianma", String.class)); return columns; } private static Map currentVersionColumns() { Map columns = platformColumns(); columns.put("mubanBianhao", field("muban_bianhao", String.class)); columns.put("dangqianMubanId", field("dangqian_muban_id", String.class)); columns.put("jianyanXiangmuBianma", field("jianyan_xiangmu_bianma", String.class)); return columns; } private static Map bindingColumns() { Map columns = platformColumns(); columns.put("mubanId", field("muban_id", String.class)); columns.put("mubanBianhao", field("muban_bianhao", String.class)); columns.put("ziduanDingyiId", field("ziduan_dingyi_id", String.class)); columns.put("ziduanBianma", field("ziduan_bianma", String.class)); columns.put("ziduanBanbenId", field("ziduan_banben_id", String.class)); columns.put("kongjianBiaoqian", field("kongjian_biaoqian", String.class)); columns.put("kongjianBieming", field("kongjian_bieming", String.class)); columns.put("kongjianLeixing", field("kongjian_leixing", String.class)); columns.put("kongjianJuese", field("kongjian_juese", String.class)); columns.put("yinyongShiliId", field("yinyong_shili_id", String.class)); columns.put("repeatGroupCode", field("repeat_group_code", String.class)); columns.put("repeatPrototypeRowId", field("repeat_prototype_row_id", String.class)); columns.put("checkboxGroupCode", field("checkbox_group_code", String.class)); columns.put("stepId", field("step_id", String.class)); columns.put("bangdingZhuangtai", field("bangding_zhuangtai", String.class)); columns.put("caozuoBianma", field("caozuo_bianma", String.class)); columns.put("laiyuanBangdingId", field("laiyuan_bangding_id", String.class)); columns.put("daoqiShijian", field("daoqi_shijian", Date.class)); columns.put("zhuangtaiYuanyin", field("zhuangtai_yuanyin", String.class)); return columns; } private static Map repeatGroupColumns() { Map columns = platformColumns(); columns.put("mubanId", field("muban_id", String.class)); columns.put("repeatGroupCode", field("repeat_group_code", String.class)); columns.put("minRows", field("min_rows", Integer.class)); columns.put("maxRows", field("max_rows", Integer.class)); columns.put("stepId", field("step_id", String.class)); return columns; } private static Map checkboxGroupColumns() { Map columns = platformColumns(); columns.put("mubanId", field("muban_id", String.class)); columns.put("groupCode", field("group_code", String.class)); columns.put("minSelected", field("min_selected", Integer.class)); columns.put("maxSelected", field("max_selected", Integer.class)); return columns; } private static Map stageColumns() { Map columns = platformColumns(); columns.put("mubanId", field("muban_id", String.class)); columns.put("jieduanMingcheng", field("jieduan_mingcheng", String.class)); columns.put("paixu", field("paixu", Integer.class)); columns.put("laiyuanJieduanId", field("laiyuan_jieduan_id", String.class)); return columns; } private static Map stepColumns() { Map columns = platformColumns(); columns.put("mubanId", field("muban_id", String.class)); columns.put("jieduanId", field("jieduan_id", String.class)); columns.put("buzhouMingcheng", field("buzhou_mingcheng", String.class)); columns.put("paixu", field("paixu", Integer.class)); columns.put("laiyuanBuzhouId", field("laiyuan_buzhou_id", String.class)); return columns; } private static Map platformColumns() { Map columns = new LinkedHashMap<>(); columns.put("id", id("f_id", String.class)); columns.put("fTenantId", field("f_tenant_id", String.class)); columns.put("fVersion", field("f_version", Integer.class)); columns.put("fCreatorUserId", field("f_creator_user_id", String.class, FieldFill.INSERT)); columns.put("fCreatorTime", field("f_creator_time", Date.class, FieldFill.INSERT)); columns.put("fLastModifyUserId", field("f_last_modify_user_id", String.class, FieldFill.INSERT_UPDATE)); columns.put("fLastModifyTime", field("f_last_modify_time", Date.class, FieldFill.INSERT_UPDATE)); columns.put("fDeleteUserId", field("f_delete_user_id", String.class)); columns.put("fDeleteTime", field("f_delete_time", Date.class)); columns.put("fDeleteMark", field("f_delete_mark", Integer.class)); return columns; } private static FieldSpec id(String column, Class javaType) { return new FieldSpec(column, javaType, true, FieldFill.DEFAULT); } private static FieldSpec field(String column, Class javaType) { return field(column, javaType, FieldFill.DEFAULT); } private static FieldSpec field(String column, Class javaType, FieldFill fill) { return new FieldSpec(column, javaType, false, fill); } private static final class FieldSpec { private final String column; private final Class javaType; private final boolean id; private final FieldFill fill; private FieldSpec(String column, Class javaType, boolean id, FieldFill fill) { this.column = column; this.javaType = javaType; this.id = id; this.fill = fill; } } }