package com.nanri.aiimage.config; import org.junit.jupiter.api.Test; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; /** * 任务 115:高频查询 EXPLAIN 索引审计(文档产出验证)。 * 审计文档必须覆盖 history/progress/dashboard 三类高频查询的执行计划结论、 * 候选索引清单(表/列/类型/收益/风险)、全表扫描标记、可重复执行步骤, * 且本任务明确不做 DDL(只审计,不加索引)。 */ class ExplainIndexAuditDocTest { private static final Path AUDIT = Paths.get("src", "main", "resources", "..", "..", "..", "docs", "explain-index-audit.md").normalize(); private static final Path FLYWAY_SPEC = Paths.get("src", "main", "resources", "..", "..", "..", "docs", "specs", "12-flyway-and-inspection.md").normalize(); private static String read(Path path) throws IOException { return new String(Files.readAllBytes(path), StandardCharsets.UTF_8); } private static List linesOf(Path path) throws IOException { return List.of(read(path).split("\r?\n")); } private static String lineContaining(String content, String needle) { for (String line : content.split("\r?\n")) { if (line.contains(needle)) { return line; } } return null; } @Test void test_explain_history_plan() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("history"), "审计文档覆盖 history 查询"); assertTrue(doc.contains("EXPLAIN"), "审计文档包含 EXPLAIN 执行计划记录"); } @Test void test_explain_progress_plan() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("progress"), "审计文档覆盖 progress 查询"); } @Test void test_explain_dashboard_plan() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("dashboard"), "审计文档覆盖 dashboard 查询"); assertTrue(doc.contains("GROUP BY"), "dashboard 聚合查询计划已记录"); } @Test void test_full_scan_detected() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("type=ALL") || doc.contains("全表扫描") || doc.contains("ALL") || doc.contains("possible_keys") || doc.contains("扫描"), "审计文档记录执行计划扫描类型(full scan 标记)"); String line = lineContaining(doc, "idx_file_job_task"); assertTrue(line != null && line.contains("task_id"), "审计文档指出 task_id 前缀索引对 (module_type, job_type, task_id IN) 的局限"); } @Test void test_index_candidates_listed() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("idx_file_job_module_type_task") || doc.contains("module_type") && doc.contains("job_type") && doc.contains("task_id"), "候选索引清单含 (module_type, job_type, task_id) 组合"); assertTrue(doc.contains("收益") && doc.contains("风险"), "候选索引清单含收益与风险说明"); } @Test void test_plan_documented() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("biz_task_file_job"), "审计文档引用目标表"); assertTrue(doc.contains("biz_file_task") || doc.contains("biz_file_result"), "审计文档引用业务任务/结果表"); } @Test void test_plan_repeatable() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("EXPLAIN SELECT") || doc.contains("EXPLAIN") && doc.contains("手工执行") || doc.contains("执行"), "审计文档包含可重复执行的 EXPLAIN SQL 步骤"); } @Test void test_no_ddl_this_task() throws Exception { String audit = read(AUDIT); String spec = read(FLYWAY_SPEC); assertTrue(audit.contains("不做 DDL") || audit.contains("本任务无 DDL") || audit.contains("不新增索引") || audit.contains("只审计"), "审计文档明确本任务不做 DDL"); assertFalse(audit.contains("ALTER TABLE"), "审计文档不包含 ALTER TABLE(DDL 属于后续任务 116)"); assertTrue(spec.contains("只读") || spec.contains("巡检"), "12 spec 巡检报表模式与审计文档对应"); } @Test void test_audit_reflects_batch_loading_queries() throws Exception { String doc = read(AUDIT); assertTrue(doc.contains("findAssembleJobsByTaskIds"), "审计覆盖轻量进度 Job 批量查询"); assertTrue(doc.contains("findAssembleJobsByResultIds"), "审计覆盖结果列表 Job 批量查询"); assertTrue(doc.contains("selectMaps") || doc.contains("GROUP BY"), "审计覆盖 dashboard 聚合查询"); } }