import { describe, it, expect } from "vitest"; import Database from "better-sqlite3"; import { initSchema } from "./schema"; import { querySymbolRefs, querySymbolForwardCalls, queryModuleDeps, queryModuleReverseDeps, queryViolations, } from "./query"; function setupTestDb(): Database.Database { const db = new Database(":memory:"); initSchema(db); // 插入测试数据 db.prepare( "INSERT INTO modules (name, path, layer, created_at) VALUES (?, ?, ?, ?)" ).run("exams", "src/modules/exams", "modules", "2026-07-07"); db.prepare( "INSERT INTO modules (name, path, layer, created_at) VALUES (?, ?, ?, ?)" ).run("grades", "src/modules/grades", "modules", "2026-07-07"); db.prepare( "INSERT INTO files (module_id, path, kind, lines) VALUES (?, ?, ?, ?)" ).run(1, "src/modules/exams/actions.ts", "actions", 100); db.prepare( "INSERT INTO files (module_id, path, kind, lines) VALUES (?, ?, ?, ?)" ).run(1, "src/modules/exams/data-access.ts", "data-access", 200); db.prepare( "INSERT INTO symbols (file_id, name, kind, is_exported, is_async, is_server_action, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" ).run(1, "createExamAction", "function", 1, 1, 1, 10, 50); db.prepare( "INSERT INTO symbols (file_id, name, kind, is_exported, is_async, is_server_action, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" ).run(2, "createExam", "function", 1, 1, 0, 20, 80); db.prepare( "INSERT INTO calls (caller_id, callee_id, call_line, count) VALUES (?, ?, ?, ?)" ).run(1, 2, 30, 1); // createExamAction 调用 createExam db.prepare( "INSERT INTO module_deps (source_module_id, target_module_id, dep_type) VALUES (?, ?, ?)" ).run(1, 2, "data-access-call"); // exams 依赖 grades return db; } describe("querySymbolRefs", () => { it("should find callers of a symbol (reverse)", () => { const db = setupTestDb(); const refs = querySymbolRefs(db, "createExam"); expect(refs).toHaveLength(1); expect(refs[0].caller_name).toBe("createExamAction"); }); }); describe("querySymbolForwardCalls", () => { it("should find callees of a symbol (forward)", () => { const db = setupTestDb(); const calls = querySymbolForwardCalls(db, "createExamAction"); expect(calls).toHaveLength(1); expect(calls[0].callee_name).toBe("createExam"); }); }); describe("queryModuleDeps", () => { it("should find module dependencies (forward)", () => { const db = setupTestDb(); const deps = queryModuleDeps(db, "exams"); expect(deps).toContainEqual({ target_module: "grades", dep_type: "data-access-call", }); }); }); describe("queryModuleReverseDeps", () => { it("should find module reverse dependencies", () => { const db = setupTestDb(); const deps = queryModuleReverseDeps(db, "grades"); expect(deps).toContainEqual({ source_module: "exams", dep_type: "data-access-call", }); }); }); describe("queryViolations", () => { it("should detect files over 800 lines", () => { const db = setupTestDb(); // 插入一个超长文件 db.prepare( "INSERT INTO files (module_id, path, kind, lines) VALUES (?, ?, ?, ?)" ).run(1, "src/modules/exams/huge.ts", "lib", 900); const violations = queryViolations(db); expect(violations.long_files).toContainEqual({ path: "src/modules/exams/huge.ts", lines: 900, }); }); it("should detect Server Actions without requirePermission", () => { const db = setupTestDb(); const violations = queryViolations(db); // createExamAction 是 Server Action 但没有调用 requirePermission const missing = violations.server_actions_without_permission.find( (v) => v.name === "createExamAction" ); expect(missing).toBeDefined(); }); });