Files
NextEdu/scripts/arch-scan/query.test.ts

107 lines
3.7 KiB
TypeScript

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();
});
});