task-194: 孤儿 task_file_job 巡检 SQL(01_orphan_job.sql,只读 SELECT 反连无任务/结果;base 提供 8 项静态契约)+ 8 条测试

This commit is contained in:
2026-09-05 00:06:44 +08:00
parent 5688870788
commit bcd5879a08
3 changed files with 123 additions and 0 deletions
@@ -0,0 +1,97 @@
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 static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* 巡检只读 SQL 报表静态契约基座(task-194..199)。
* 校验各 inspection/*.sql:以 SELECT 开头、无任何写语句/锁语句、含 NOT EXISTS/JOIN 关联、
* 带 LIMIT、分号结尾、头注释含目的与只读声明。子类提供文件名。
*/
abstract class InspectionSqlFileTestBase {
abstract String fileName();
private Path path() {
return Path.of(System.getProperty("user.dir"), "src/main/resources/inspection", fileName());
}
private String sql() throws IOException {
return Files.readString(path());
}
private String statements() throws IOException {
// 去掉整行注释与空行后的可执行 SQL 文本(头注释说明可能含"清理/删除"等字样,须排除)
StringBuilder sb = new StringBuilder();
for (String line : sql().split("\n")) {
String t = line.trim();
if (t.isEmpty() || t.startsWith("--")) {
continue;
}
sb.append(line).append('\n');
}
return sb.toString();
}
@Test
void fileExists() throws IOException {
assertTrue(Files.exists(path()), "巡检 SQL 应存在: " + fileName());
}
@Test
void startsWithSelect() throws IOException {
String first = statements().stripLeading();
assertTrue(first.toUpperCase().startsWith("SELECT"), "可执行 SQL 应以 SELECT 开头: " + fileName());
}
private static final String[] FORBIDDEN_WRITE_TOKENS = {
"INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE", "ALTER", "CREATE",
"GRANT", "REPLACE", "CALL", "LOAD", "DO"
};
@Test
void readOnlyNoWriteKeywords() throws IOException {
String upper = statements().toUpperCase();
for (String kw : FORBIDDEN_WRITE_TOKENS) {
java.util.regex.Pattern p = java.util.regex.Pattern.compile("(?<!\\w)" + java.util.regex.Pattern.quote(kw) + "(?!\\w)");
assertFalse(p.matcher(upper).find(), fileName() + " 不应含写语句 " + kw);
}
}
@Test
void hasRelationalJoinCondition() throws IOException {
String upper = statements().toUpperCase();
assertTrue(upper.contains("NOT EXISTS") || upper.contains("JOIN"),
fileName() + " 应含 NOT EXISTS 或 JOIN 关联条件");
}
@Test
void hasRowLimit() throws IOException {
assertTrue(statements().toUpperCase().contains("LIMIT"), fileName() + " 应带 LIMIT");
}
@Test
void noRowLock() throws IOException {
String upper = statements().toUpperCase();
assertFalse(upper.contains("FOR UPDATE"), fileName() + " 不应 FOR UPDATE 加锁");
assertFalse(upper.contains("LOCK"), fileName() + " 不应加锁");
}
@Test
void endsWithSemicolon() throws IOException {
assertTrue(sql().trim().endsWith(";"), fileName() + " 应以分号结尾");
}
@Test
void headerDocumentsPurposeAndReadOnly() throws IOException {
String text = sql();
assertTrue(text.contains("巡检"), fileName() + " 头注释应含巡检说明");
assertTrue(text.contains("只读"), fileName() + " 头注释应声明只读");
}
}
@@ -0,0 +1,9 @@
package com.nanri.aiimage.config;
/** task-194:孤儿 task_file_job 巡检 SQL 契约。 */
class OrphanJobSqlInspectionTest extends InspectionSqlFileTestBase {
@Override
String fileName() {
return "01_orphan_job.sql";
}
}