yeg
2026-08-26 de98fe81cca90dd855e0519f065e77fce56333ef
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
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";
 
    @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.*"));
    }
 
    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);
        }
    }
}