diff --git a/backend-java/src/test/java/com/nanri/aiimage/config/ModuleTypeAlignmentTest.java b/backend-java/src/test/java/com/nanri/aiimage/config/ModuleTypeAlignmentTest.java new file mode 100644 index 00000000..3806428f --- /dev/null +++ b/backend-java/src/test/java/com/nanri/aiimage/config/ModuleTypeAlignmentTest.java @@ -0,0 +1,83 @@ +package com.nanri.aiimage.config; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * module_type 对齐守卫(task 外防回归)。 + * + * 服务端 module_type 规范为大写下划线(SIMILAR_ASIN / COLLECT_DATA / …),写入 DB 的 + * 值必须与各 Service 的 MODULE_TYPE 常量一致;小写写法(如 similarasin)会导致按模块 + * 查找/心跳/handler 不命中。本测试扫描 src/main:① MODULE_TYPE 等常量取值必须大写; + * ② setModuleType("小写") 不允许(仅允许显式 legacy 兼容值 "collectdata")。 + */ +class ModuleTypeAlignmentTest { + + private static final Set ALLOWED_LEGACY = Set.of("collectdata"); + + private static final Pattern CONST_DECL = Pattern.compile( + "(?:MODULE_TYPE|LEGACY_MODULE_TYPE|_MODULE)\\s*=\\s*\"([A-Za-z0-9_]+)\""); + private static final Pattern WRITE_LITERAL = Pattern.compile( + "setModuleType\\s*\\(\\s*\"([A-Za-z0-9_]+)\""); + private static final Pattern UPPER = Pattern.compile("^[A-Z][A-Z0-9_]*$"); + + private static List javaFiles() throws IOException { + Path root = Path.of(System.getProperty("user.dir"), "src/main/java/com/nanri/aiimage"); + try (Stream s = Files.walk(root)) { + return s.filter(p -> p.toString().endsWith(".java")).toList(); + } + } + + @Test + void moduleTypeConstantsAreUppercase() throws IOException { + List violations = new ArrayList<>(); + for (Path f : javaFiles()) { + List lines = Files.readAllLines(f); + for (int i = 0; i < lines.size(); i++) { + Matcher m = CONST_DECL.matcher(lines.get(i)); + while (m.find()) { + String value = m.group(1); + if (!ALLOWED_LEGACY.contains(value) && !UPPER.matcher(value).matches()) { + violations.add(f + ":" + (i + 1) + " 小写/不规范模块常量: " + value); + } + } + } + } + assertTrue(violations.isEmpty(), "module_type 常量必须规范大写:\n " + String.join("\n ", violations)); + } + + @Test + void noLowercaseModuleTypeWrites() throws IOException { + List violations = new ArrayList<>(); + for (Path f : javaFiles()) { + List lines = Files.readAllLines(f); + for (int i = 0; i < lines.size(); i++) { + Matcher m = WRITE_LITERAL.matcher(lines.get(i)); + while (m.find()) { + String value = m.group(1); + if (!ALLOWED_LEGACY.contains(value) && !UPPER.matcher(value).matches()) { + violations.add(f + ":" + (i + 1) + " setModuleType(\"" + value + "\") 应用常量而非小写字面量"); + } + } + } + } + assertTrue(violations.isEmpty(), "写入 module_type 应用规范大写常量:\n " + String.join("\n ", violations)); + } + + @Test + void similarasinCanonicalValueLocksUpperSnake() { + // 锚定本次发现的隐患点:similarasin 的规范值为大写 SIMILAR_ASIN + assertTrue(UPPER.matcher("SIMILAR_ASIN").matches()); + } +} diff --git a/backend-java/src/test/java/com/nanri/aiimage/config/PythonContractFixtureTest.java b/backend-java/src/test/java/com/nanri/aiimage/config/PythonContractFixtureTest.java index 53d724cc..18e025b8 100644 --- a/backend-java/src/test/java/com/nanri/aiimage/config/PythonContractFixtureTest.java +++ b/backend-java/src/test/java/com/nanri/aiimage/config/PythonContractFixtureTest.java @@ -60,6 +60,8 @@ class PythonContractFixtureTest { JsonNode resp = json("heartbeat.response.json"); assertTrue(req.has("moduleType") && req.has("phase")); assertTrue(resp.has("alive") && resp.has("status")); + assertEquals("SIMILAR_ASIN", req.path("moduleType").asText(), + "夹具 moduleType 须用规范大写(与服务 MODULE_TYPE 常量一致,防小写漂移 bug)"); } @Test diff --git a/backend-java/src/test/resources/python-contract/heartbeat.request.json b/backend-java/src/test/resources/python-contract/heartbeat.request.json index 0ca768a4..3b1535ba 100644 --- a/backend-java/src/test/resources/python-contract/heartbeat.request.json +++ b/backend-java/src/test/resources/python-contract/heartbeat.request.json @@ -1,5 +1,5 @@ { - "moduleType": "similarasin", + "moduleType": "SIMILAR_ASIN", "phase": "search", "current": 3, "total": 10,