package com.ruoyi.config;
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
import java.io.InputStream;
|
import java.nio.charset.StandardCharsets;
|
import java.security.MessageDigest;
|
import java.util.HexFormat;
|
|
import org.junit.jupiter.api.Test;
|
|
class FlywayMigrationContractTest
|
{
|
private static final String V1_SHA256 = "37de76546e5aa0aa0ce7e976b73383a2d80ef97fd2db923d9e23dbc2b34534c9";
|
private static final String V2_SHA256 = "698023958beee6245b099cf4505d35874cd06823e4dbbc3ddcb11b8f76609b89";
|
private static final String V3_SHA256 = "763de1dfaa96e6f07f7ed3b3975e6f281dbc0c2e7f139da3bed6a2f1bfc39727";
|
private static final String V6_SHA256 = "2f20752fcb9a318435d27ba4a0c3e0ba9443ba2f61a884f82bace457270d7db8";
|
|
@Test
|
void keepsImmutableBaselineAndAddsObservationIncrementOnly()
|
{
|
assertEquals(V1_SHA256, sha256("db/migration/V1__ruoyi_platform_baseline.sql"));
|
assertEquals(V2_SHA256, sha256("db/migration/V2__battery_detection_domain.sql"));
|
assertEquals(V3_SHA256, sha256("db/migration/V3__battery_detection_permissions.sql"));
|
|
String v4 = resource("db/migration/V4__observation_persistence_constraints.sql");
|
assertTrue(v4.contains("ADD COLUMN creator_id"));
|
assertTrue(v4.contains("uk_exception_open_dedupe"));
|
assertTrue(v4.contains("adapter_timezone_id"));
|
assertTrue(v4.contains("fk_sample_channel"));
|
assertFalse(v4.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
|
|
String v5 = resource("db/migration/V5__adapter_discovery_runtime.sql");
|
assertTrue(v5.contains("connection_status"));
|
assertTrue(v5.contains("last_observed_at_utc"));
|
assertTrue(v5.contains("chk_adapter_connection_status"));
|
assertTrue(v5.contains("system:deviceAdapter:list"));
|
assertTrue(v5.contains("system:deviceAdapter:query"));
|
assertTrue(v5.contains("system:deviceAdapter:add"));
|
assertTrue(v5.contains("system:deviceAdapter:edit"));
|
assertTrue(v5.contains("system:deviceAdapter:changeStatus"));
|
assertTrue(v5.contains("system:deviceAdapter:discover"));
|
assertTrue(v5.contains("system:detectionCabinet:list"));
|
assertFalse(v5.matches("(?s).*\\bDELETE\\s+FROM\\b.*"));
|
|
String v6 = resource("db/migration/V6__separate_discovery_from_registration.sql");
|
assertEquals(V6_SHA256, sha256("db/migration/V6__separate_discovery_from_registration.sql"));
|
assertTrue(v6.contains("registration_status"));
|
assertTrue(v6.contains("DISCOVERED"));
|
assertTrue(v6.contains("REGISTERED"));
|
assertTrue(v6.contains("chk_cabinet_registration_status"));
|
assertTrue(v6.contains("DEFAULT 'DISCOVERED'"));
|
assertFalse(v6.matches("(?s).*\\bDELETE\\s+FROM\\b.*"));
|
|
String v7 = resource("db/migration/V7__complete_device_registration_boundary.sql");
|
assertTrue(v7.contains("WHEN create_by = 'system' THEN 'DISCOVERED'"));
|
assertTrue(v7.contains("ELSE 'REGISTERED'"));
|
assertTrue(v7.contains("uk_cabinet_mac_del"));
|
assertFalse(v7.matches("(?s).*\\bDELETE\\s+FROM\\b.*"));
|
|
String v8 = resource("db/migration/V8__complete_collector_page_contract.sql");
|
assertTrue(v8.contains("ALTER TABLE t_collector"));
|
assertTrue(v8.contains("test_status"));
|
assertTrue(v8.contains("current_test"));
|
assertTrue(v8.contains("current_sample_name"));
|
assertTrue(v8.contains("chk_collector_test_status"));
|
assertFalse(v8.matches("(?s).*\\bDELETE\\s+FROM\\b.*"));
|
|
String v9 = resource("db/migration/V9__move_discovery_candidates_to_redis.sql");
|
assertTrue(v9.contains("adapter_code VARCHAR(64)"));
|
assertTrue(v9.contains("adapter.adapter_code = 'dev-sim'"));
|
assertTrue(v9.contains("cabinet.registration_status = 'DISCOVERED'"));
|
assertTrue(v9.contains("t_test_channel_binding"));
|
assertTrue(v9.contains("t_test_sample"));
|
assertTrue(v9.contains("t_device_run_event"));
|
assertTrue(v9.contains("SELECT protected_cabinet.cabinet_id"));
|
assertTrue(v9.contains("SELECT DISTINCT sibling.cabinet_id"));
|
assertTrue(v9.contains("uk_channel_stable_identity"));
|
assertTrue(v9.contains("ADD KEY idx_cabinet_adapter (adapter_id)"));
|
assertTrue(v9.contains("SET adapter_id = NULL WHERE adapter_code = 'dev-sim'"));
|
assertFalse(v9.contains("DROP FOREIGN KEY fk_channel_adapter"));
|
assertFalse(v9.contains("DROP FOREIGN KEY fk_cabinet_adapter"));
|
assertFalse(v9.matches("(?s).*t_test_battery\\s+\\w+\\s+WHERE\\s+\\w+\\.cabinet_id.*"));
|
|
String v10 = resource("db/migration/V10__test_configuration_collector.sql");
|
assertTrue(v10.contains("ALTER TABLE t_test_battery"));
|
assertTrue(v10.contains("collector_id"));
|
assertTrue(v10.contains("fk_test_battery_collector"));
|
assertFalse(v10.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
|
|
String v11 = resource("db/migration/V11__persist_battery_group_field_values.sql");
|
assertTrue(v11.contains("ALTER TABLE t_battery_group_relation_field"));
|
assertTrue(v11.contains("field_value VARCHAR(500)"));
|
assertFalse(v11.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
|
|
String v12 = resource("db/migration/V12__custom_field_type_constraints.sql");
|
assertTrue(v12.contains("chk_battery_group_field_type"));
|
assertTrue(v12.contains("'text', 'number', 'time', 'remark'"));
|
assertFalse(v12.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
|
|
String v13 = resource("db/migration/V13__test_required_device_bindings.sql");
|
assertTrue(v13.contains("reception_status"));
|
assertTrue(v13.contains("DEFAULT 'NOT_STARTED'"));
|
assertTrue(v13.contains("CREATE TABLE t_test_collector_channel"));
|
assertTrue(v13.contains("uk_test_collector_channel"));
|
assertTrue(v13.contains("CREATE TABLE t_test_field_snapshot"));
|
assertFalse(v13.matches("(?s).*\\bINSERT\\s+INTO\\b.*"));
|
assertFalse(v13.matches("(?s).*\\bUPDATE\\s+t_test\\b.*"));
|
|
String v14 = resource("db/migration/V14__test_reception_intervals.sql");
|
assertTrue(v14.contains("CREATE TABLE t_test_reception_interval"));
|
assertTrue(v14.contains("start_boundary_at_utc"));
|
assertTrue(v14.contains("CREATE TABLE t_collector_channel_lease"));
|
assertTrue(v14.contains("idx_test_reception_status"));
|
assertTrue(v14.contains("system:test:start"));
|
|
String v16 = resource("db/migration/V16__simplify_test_reception_lifecycle.sql");
|
assertTrue(v16.contains("system:test:stop"));
|
assertTrue(v16.contains("旧区间表保留为历史数据"));
|
assertFalse(v16.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
|
|
String v17 = resource("db/migration/V17__persist_runtime_attach_flag.sql");
|
assertTrue(v17.contains("ALTER TABLE t_test"));
|
assertTrue(v17.contains("ADD COLUMN runtime_attach"));
|
assertTrue(v17.contains("TINYINT(1) NOT NULL DEFAULT 0"));
|
assertTrue(v17.contains("AFTER ended_at_utc"));
|
assertFalse(v17.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
|
|
String v18 = resource("db/migration/V18__collector_disconnect_recovery.sql");
|
assertTrue(v18.contains("CREATE TABLE t_test_collector_exception_interval"));
|
assertTrue(v18.contains("missing_from_utc"));
|
assertTrue(v18.contains("recovered_at_utc"));
|
assertTrue(v18.contains("uk_collector_exception_open"));
|
assertFalse(v18.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
|
|
String v19 = resource("db/migration/V19__collector_disconnect_open_dedupe.sql");
|
assertTrue(v19.contains("DROP INDEX uk_collector_exception_open"));
|
assertTrue(v19.contains("open_dedupe_key VARCHAR(320)"));
|
assertTrue(v19.contains("interval_status = 'OPEN'"));
|
assertTrue(v19.contains("ELSE NULL"));
|
assertTrue(v19.contains("ADD UNIQUE KEY uk_collector_exception_open (open_dedupe_key)"));
|
assertFalse(v19.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
|
|
String v20 = resource("db/migration/V20__manual_end_reception_intervals.sql");
|
assertTrue(v20.contains("chk_reception_interval_boundary_order"));
|
assertTrue(v20.contains("idx_reception_interval_source_order"));
|
assertTrue(v20.contains("system:test:stop"));
|
assertFalse(v20.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
|
|
String testMapper = resource("mapper/system/TTestMapper.xml");
|
assertTrue(testMapper.contains("selectReceivingChannelsForAdapter"));
|
assertTrue(testMapper.contains("RECEPTION_RESUMED"));
|
assertTrue(testMapper.contains("TEST_STOPPED"));
|
}
|
|
private static String sha256(String path)
|
{
|
try
|
{
|
return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
|
.digest(resource(path).getBytes(StandardCharsets.UTF_8)));
|
}
|
catch (Exception exception)
|
{
|
throw new AssertionError("无法读取迁移资源 " + path, exception);
|
}
|
}
|
|
private static String resource(String path)
|
{
|
try (InputStream stream = FlywayMigrationContractTest.class.getClassLoader().getResourceAsStream(path))
|
{
|
if (stream == null) throw new AssertionError("缺少迁移资源 " + path);
|
return new String(stream.readAllBytes(), StandardCharsets.UTF_8);
|
}
|
catch (Exception exception)
|
{
|
throw new AssertionError("无法读取迁移资源 " + path, exception);
|
}
|
}
|
}
|