|
|
@@ -0,0 +1,90 @@
|
|
|
+package com.zksy.WaterSupply;
|
|
|
+
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Gogs #55:管网状态三值域(0-正常 / 1-维护中 / 2-异常)的字典与统计口径。
|
|
|
+ *
|
|
|
+ * <p>字典以后端仓库内的幂等脚本为准,部署时执行生效;脚本不改表结构、不改业务数据。</p>
|
|
|
+ */
|
|
|
+class PipeNetworkStatusDictionaryContractTest {
|
|
|
+
|
|
|
+ private static final String DICT_MIGRATION_NAME = "migration_pipe_network_status_dict.sql";
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void dictionaryScriptDefinesTheThreeAuthoritativeValues() throws Exception {
|
|
|
+ String sql = dictMigration();
|
|
|
+
|
|
|
+ assertTrue(sql.contains("'正常'") && sql.contains("'维护中'") && sql.contains("'异常'"),
|
|
|
+ "字典必须给出 正常 / 维护中 / 异常 三项文案");
|
|
|
+ assertTrue(sql.contains("'0', 'pipe_network_status'")
|
|
|
+ && sql.contains("'1', 'pipe_network_status'")
|
|
|
+ && sql.contains("'2', 'pipe_network_status'"),
|
|
|
+ "字典必须覆盖 0/1/2 三个取值");
|
|
|
+ assertTrue(sql.contains("pipe_network_status"),
|
|
|
+ "字典类型必须保持 pipe_network_status(不改菜单名与字典类型)");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void dictionaryScriptIsRepeatableAndDoesNotTouchSchemaOrBusinessData() throws Exception {
|
|
|
+ String sql = dictMigration();
|
|
|
+
|
|
|
+ assertTrue(sql.contains("DELETE FROM app_user.sys_dict_data WHERE dict_type = 'pipe_network_status'"),
|
|
|
+ "重写字典取值需先按 dict_type 清空该项字典数据,保证可重复执行");
|
|
|
+ assertTrue(sql.contains("WHERE NOT EXISTS (SELECT 1 FROM app_user.sys_dict_type WHERE dict_type = 'pipe_network_status')"),
|
|
|
+ "字典类型登记必须幂等");
|
|
|
+ assertFalse(sql.contains("dict_code"),
|
|
|
+ "字典数据不得写死 dict_code,否则重复执行会撞主键");
|
|
|
+ String upper = sql.toUpperCase();
|
|
|
+ assertFalse(upper.contains("ALTER TABLE"), "本票不改动数据库表结构");
|
|
|
+ assertFalse(upper.contains("CREATE TABLE"), "本票不改动数据库表结构");
|
|
|
+ assertFalse(upper.contains("DROP "), "本票不做破坏性结构变更");
|
|
|
+ assertFalse(upper.contains("UPDATE APP_USER.PIPE_NETWORK_BASE"),
|
|
|
+ "本票不直接改动业务数据");
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 模块目录与仓库根目录两种工作目录下都要能找到 SQL。 */
|
|
|
+ private static String dictMigration() throws Exception {
|
|
|
+ Path cwd = Path.of(System.getProperty("user.dir")).toAbsolutePath();
|
|
|
+ for (Path candidate : new Path[]{
|
|
|
+ cwd.resolve("sql").resolve(DICT_MIGRATION_NAME),
|
|
|
+ cwd.resolve("..").resolve("sql").resolve(DICT_MIGRATION_NAME)}) {
|
|
|
+ Path normalized = candidate.normalize();
|
|
|
+ if (Files.exists(normalized)) {
|
|
|
+ return Files.readString(normalized, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new IllegalStateException("未找到字典迁移脚本:" + DICT_MIGRATION_NAME);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void statisticsTreatPipeStatusAsNormalMaintenanceAndAbnormal() throws Exception {
|
|
|
+ String mapper = Files.readString(Path.of(
|
|
|
+ "src/main/resources/mapper/WaterSupply/PipeNetworkStatistics/PipeNetworkStatisticsMapper.xml"),
|
|
|
+ StandardCharsets.UTF_8);
|
|
|
+
|
|
|
+ int start = mapper.indexOf("SELECT 'pipe'");
|
|
|
+ assertTrue(start > 0, "统计查询必须包含管网设施分组");
|
|
|
+ int end = mapper.indexOf("FROM app_user.pipe_network_base", start);
|
|
|
+ assertTrue(end > start, "管网统计必须读统一管网表");
|
|
|
+ String pipeBlock = mapper.substring(start, end);
|
|
|
+
|
|
|
+ int normal = pipeBlock.indexOf("status = '0'");
|
|
|
+ int abnormal = pipeBlock.indexOf("status = '2'");
|
|
|
+ int maintenance = pipeBlock.indexOf("status = '1'");
|
|
|
+ assertTrue(normal > 0, "0-正常 必须计入正常");
|
|
|
+ assertTrue(abnormal > normal, "2-异常 必须计入异常,且排在正常之后(DTO 列序:正常/异常/预警/未知)");
|
|
|
+ assertTrue(maintenance > abnormal, "1-维护中 必须计入预警侧,不得与异常混淆");
|
|
|
+ assertFalse(pipeBlock.contains("status NOT IN ('0', '1')"),
|
|
|
+ "管网已是三值域,2 不得再被记为未知");
|
|
|
+ assertTrue(pipeBlock.contains("status IS NULL OR status NOT IN ('0', '1', '2')"),
|
|
|
+ "未知只吸收三值域之外的空值与脏值");
|
|
|
+ }
|
|
|
+}
|