| | |
| | | public class PushIngestLandingMigrationContractTest |
| | | { |
| | | private static final String PATH = "db/migration-final/V24__push_ingest_landing_table.sql"; |
| | | private static final String VENDOR_CHANNEL_NO_PATH = "db/migration-final/V25__ingest_vendor_channel_no.sql"; |
| | | private static final String ADAPTER = "j22"; |
| | | private static final Instant RECEIVED_AT = Instant.parse("2026-09-27T09:20:00Z"); |
| | | |
| | |
| | | jdbc = new JdbcTemplate(source); |
| | | // H2 无 MySQL 的 HEX();测试中补一个等价别名,生产 SQL 仍在 MySQL 上验证。 |
| | | jdbc.execute("create alias HEX for 'com.battery.business.runtime.PushIngestLandingMigrationContractTest.hexOf'"); |
| | | // 按顺序跑落地表相关的迁移,使内存库结构与生产库一致(V24 建表、V25 加来源通道号列)。 |
| | | ScriptUtils.executeSqlScript(keepAlive, new ClassPathResource(PATH)); |
| | | ScriptUtils.executeSqlScript(keepAlive, new ClassPathResource(VENDOR_CHANNEL_NO_PATH)); |
| | | |
| | | SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); |
| | | factory.setDataSource(source); |
| | |
| | | "select count(*) from t_device_ingest_record where voltage_v is null", Integer.class)); |
| | | } |
| | | |
| | | /** |
| | | * 来源自己的通道叫法那条迁移:只加一个可空列,不写入任何数据; |
| | | * 老行读出来是空(界面回退到「单元-通道」),新行可以带上来源软件里显示的通道号。 |
| | | */ |
| | | @Test |
| | | void vendorChannelNoMigrationOnlyAddsANullableColumn() |
| | | { |
| | | String migration = resource(VENDOR_CHANNEL_NO_PATH); |
| | | assertTrue(migration.contains("ADD COLUMN vendor_channel_no INT NULL")); |
| | | assertFalse(migration.matches("(?s).*(INSERT|UPDATE|DELETE|DROP)\\b.*")); |
| | | |
| | | jdbc.update("insert into t_device_ingest_record(adapter_code, payload_kind, record_key, payload_json," |
| | | + " content_sha256, received_at_utc) values(?,?,?,?,?,?)", ADAPTER, "TOPOLOGY", "topology:26", |
| | | "{\"channel\":26}", new byte[32], Timestamp.from(RECEIVED_AT)); |
| | | // 迁移在 setUp 里已按顺序执行过(V24 建表、V25 加列),这里只核对列的行为。 |
| | | assertNull(jdbc.queryForObject( |
| | | "select vendor_channel_no from t_device_ingest_record where record_key = 'topology:26'", |
| | | Integer.class)); |
| | | jdbc.update("update t_device_ingest_record set vendor_channel_no = ? where record_key = ?", 27, |
| | | "topology:26"); |
| | | assertEquals(27, jdbc.queryForObject( |
| | | "select vendor_channel_no from t_device_ingest_record where record_key = 'topology:26'", |
| | | Integer.class)); |
| | | // 展示列不参与身份:唯一键仍只由来源行身份决定。 |
| | | assertEquals(1, jdbc.queryForObject("select count(*) from t_device_ingest_record", Integer.class)); |
| | | } |
| | | |
| | | @Test |
| | | void repeatedPushIsIgnoredBySourceRowIdentityAndDifferentContentIsKept() |
| | | { |