yeg
2026-08-28 9fed155adefdb73dd349ff99a897f2d2da5ea03e
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
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.*"));
    }
 
    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);
        }
    }
}