task-194: 孤儿 task_file_job 巡检 SQL(01_orphan_job.sql,只读 SELECT 反连无任务/结果;base 提供 8 项静态契约)+ 8 条测试
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
-- =============================================================
|
||||
-- 巡检 01:孤儿 task_file_job(job 无对应任务/结果)— 只读报表(task-194)
|
||||
-- 对应 Java 巡检:OrphanJobInspector(module-09);仅副本库只读执行,无副作用。
|
||||
-- 结果按 updated_at 升序;每批建议 limit <= 500。
|
||||
-- =============================================================
|
||||
SELECT j.id AS job_id,
|
||||
j.task_id,
|
||||
j.result_id,
|
||||
j.module_type,
|
||||
j.status,
|
||||
j.updated_at
|
||||
FROM biz_task_file_job j
|
||||
WHERE NOT EXISTS (SELECT 1 FROM biz_file_task t WHERE t.id = j.task_id)
|
||||
AND (j.result_id IS NULL
|
||||
OR NOT EXISTS (SELECT 1 FROM biz_file_result r WHERE r.id = j.result_id))
|
||||
ORDER BY j.updated_at ASC
|
||||
LIMIT 200;
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user