yeg
2026-08-29 41cfe20aeb9835315c54b3d6873d4e07ff238cfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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.*"));
    }
 
    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);
        }
    }
}