yeg
2026-09-05 7259e265b9237286996b09da03038ea5a133e1e0
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
package com.battery.business.runtime;
 
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.util.Set;
 
import org.flywaydb.core.api.Location;
import org.flywaydb.core.internal.resolver.ChecksumCalculator;
import org.flywaydb.core.internal.resource.classpath.ClassPathResource;
import org.junit.jupiter.api.Test;
 
/** Ticket03 最终数据库基线的结构契约。 */
class FinalDatabaseBaselineContractTest
{
    private static final Set<String> REQUIRED_TABLES = Set.of(
            "t_battery_group", "t_battery_group_field", "t_battery_group_relation_field",
            "t_device_adapter", "t_detection_cabinet", "t_detection_channel", "t_collector",
            "t_test", "t_test_battery", "t_test_channel_binding", "t_test_collector_channel",
            "t_test_warning_threshold", "t_test_realtime_record", "t_test_step_record",
            "t_test_cycle_record", "t_test_data_revision_action", "t_test_warning_exception");
 
    private static final Set<String> RETIRED_TABLES = Set.of(
            "t_test_field_snapshot", "t_test_battery_field", "t_test_plan_step", "t_test_audit",
            "t_protocol_message", "t_test_reception_interval", "t_history_sync_cursor", "t_data_gap",
            "t_channel_lease", "t_collector_channel_lease", "t_test_data_override",
            "t_test_console_import_record");
 
    @Test
    void finalBaselineKeepsTheChecksumAlreadyAppliedToDevelopmentDatabase()
    {
        // 已执行的基线必须保持 Flyway 校验值,新增字段只能追加迁移。
        ClassPathResource baseline = new ClassPathResource(new Location("classpath:db/migration-final"),
                "db/migration-final/V2__final_business_baseline.sql", getClass().getClassLoader(),
                StandardCharsets.UTF_8);
        assertEquals(-1864711325, ChecksumCalculator.calculate(baseline));
    }
 
    @Test
    void finalBaselineContainsOnlyTheAcceptedBusinessModel()
    {
        String sql = resource("db/migration-final/V2__final_business_baseline.sql");
        String platformSql = resource("db/migration-final/V1__ruoyi_platform_baseline.sql");
        for (String table : REQUIRED_TABLES)
        {
            assertTrue(sql.matches("(?s).*CREATE TABLE\\s+" + table + "\\s*\\(.*"),
                    "缺少最终业务表 " + table);
        }
        for (String table : RETIRED_TABLES)
        {
            assertFalse(sql.matches("(?s).*CREATE TABLE\\s+" + table + "\\s*\\(.*"),
                    "不应创建废止表 " + table);
        }
        assertFalse(sql.matches("(?is).*\\bFOREIGN\\s+KEY\\b.*"), "最终基线不得建立物理外键");
        assertFalse(platformSql.matches("(?is).*\\bFOREIGN\\s+KEY\\b.*"), "平台基线不得建立物理外键");
        assertFalse(platformSql.matches("(?is).*\\bINT\\s+NOT\\s+NULL\\s+AUTO_INCREMENT\\b.*"),
                "最终基线自增主键统一使用 BIGINT");
        assertTrue(sql.contains("test_battery_id BIGINT NOT NULL AUTO_INCREMENT"));
        assertTrue(sql.contains("DATETIME(3)"));
        assertTrue(sql.contains("before_content_json JSON NULL"));
        assertTrue(sql.contains("source_identity VARCHAR(256) NOT NULL"));
        assertTrue(sql.contains("adapter_timezone_id VARCHAR(64) NOT NULL"));
        assertTrue(sql.contains("connection_status IN ('ONLINE', 'OFFLINE')"));
        assertTrue(sql.contains("reception_status IN ('NOT_STARTED', 'RECEIVING', 'PAUSED', 'ENDED')"));
        assertTrue(sql.contains("threshold_id BIGINT NOT NULL"));
        assertTrue(sql.contains("open_dedupe_key VARCHAR(512) GENERATED ALWAYS AS"));
    }
 
    @Test
    void flywayPointsAtTheFinalBaselineLocation()
    {
        String config = resource("application.yml");
        assertTrue(config.contains("locations: classpath:db/migration-final"));
    }
 
    private static String resource(String path)
    {
        try (InputStream stream = FinalDatabaseBaselineContractTest.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);
        }
    }
}