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