import { describe, it } from "vitest"; import { RuleTester } from "eslint"; import path from "node:path"; import os from "node:os"; import fs from "node:fs"; import rule from "./no-undeclared-audit.js"; function makeFixture({ manifestUseCases, useCaseBody }) { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nua-")); const featureDir = path.join(repoRoot, "packages", "demo"); fs.mkdirSync(path.join(featureDir, "src", "application", "use-cases"), { recursive: true }); const useCasesObj = Object.entries(manifestUseCases) .map(([name, uc]) => ` ${name}: { mutates: ${uc.mutates}, audits: [${uc.audits.map((a) => `"${a}"`).join(", ")}], publishes: [], consumes: [] },`, ) .join("\n"); fs.writeFileSync( path.join(featureDir, "src", "feature.manifest.ts"), `export const demoManifest = defineFeature({ name: "demo", requiredCores: [], useCases: { ${useCasesObj} }, realtimeChannels: [], jobs: [], } as const);`, ); const useCaseFile = path.join(featureDir, "src", "application", "use-cases", "sign-up.use-case.ts"); fs.writeFileSync(useCaseFile, useCaseBody); return { repoRoot, useCaseFile }; } const tester = new RuleTester({ languageOptions: { parser: await import("@typescript-eslint/parser"), ecmaVersion: "latest", sourceType: "module", }, }); describe("no-undeclared-audit", () => { it("passes when auditLog.record type matches manifest audits[]", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: ["user.created"] } }, useCaseBody: `export const signUpUseCase = (auditLog) => async () => { auditLog.record({ type: "user.created", subject: "x", actor: "y" }); };`, }); tester.run("no-undeclared-audit", rule, { valid: [{ filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }] }], invalid: [], }); }); it("fires when auditLog.record type is not in manifest", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [] } }, useCaseBody: `export const signUpUseCase = (auditLog) => async () => { auditLog.record({ type: "user.created", subject: "x" }); };`, }); tester.run("no-undeclared-audit", rule, { valid: [], invalid: [{ filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [{ messageId: "undeclared", data: { event: "user.created", useCase: "signUp" } }], }], }); }); it("is a no-op when auditLog.record is called with a non-literal type", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [] } }, useCaseBody: `export const signUpUseCase = (auditLog, type) => async () => { auditLog.record({ type, subject: "x" }); };`, }); tester.run("no-undeclared-audit", rule, { valid: [{ filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }] }], invalid: [], }); }); });