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-event-publish.js"; function makeFixture({ manifestUseCases, useCaseBody, eventFile }) { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nuep-")); 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: [${uc.publishes.map((p) => `"${p}"`).join(", ")}], 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);`, ); if (eventFile) { const eventsDir = path.join(featureDir, "src", "events"); fs.mkdirSync(eventsDir, { recursive: true }); fs.writeFileSync( path.join(eventsDir, eventFile.filename), eventFile.contents, ); } 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-event-publish", () => { it("passes when bus.publish event name matches manifest publishes[]", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [], publishes: ["demo.signed-up"] }, }, useCaseBody: `export const signUpUseCase = (bus) => async () => { bus.publish("demo.signed-up", {}); };`, }); tester.run("no-undeclared-event-publish", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("fires when bus.publish event name is not in manifest", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [], publishes: [] }, }, useCaseBody: `export const signUpUseCase = (bus) => async () => { bus.publish("demo.signed-up", {}); };`, }); tester.run("no-undeclared-event-publish", rule, { valid: [], invalid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [ { messageId: "undeclared", data: { event: "demo.signed-up", useCase: "signUp" }, }, ], }, ], }); }); it("is a no-op when bus.publish is called with an unresolvable identifier", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [], publishes: [] }, }, useCaseBody: `export const signUpUseCase = (bus, name) => async () => { bus.publish(name, {}); };`, }); tester.run("no-undeclared-event-publish", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("passes when an imported inline event descriptor is declared", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [], publishes: ["demo.signed-up"] }, }, eventFile: { filename: "signed-up.event.ts", contents: `export const signedUpEvent = { name: "demo.signed-up" as const, schema: {} };`, }, useCaseBody: `import { signedUpEvent } from "../../events/signed-up.event"; export const signUpUseCase = (bus) => async () => { bus.publish(signedUpEvent, {}); };`, }); tester.run("no-undeclared-event-publish", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("fires when an imported inline event descriptor is not declared", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [], publishes: [] }, }, eventFile: { filename: "signed-up.event.ts", contents: `export const signedUpEvent = { name: "demo.signed-up" as const, schema: {} };`, }, useCaseBody: `import { signedUpEvent } from "../../events/signed-up.event"; export const signUpUseCase = (bus) => async () => { bus.publish(signedUpEvent, {}); };`, }); tester.run("no-undeclared-event-publish", rule, { valid: [], invalid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [ { messageId: "undeclared", data: { event: "demo.signed-up", useCase: "signUp" }, }, ], }, ], }); }); it("fires when an imported defineEvent() descriptor is not declared", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestUseCases: { signUp: { mutates: true, audits: [], publishes: [] }, }, eventFile: { filename: "signed-up.event.ts", contents: `export const signedUpEvent = defineEvent("demo.signed-up", {});`, }, useCaseBody: `import { signedUpEvent } from "../../events/signed-up.event"; export const signUpUseCase = (bus) => async () => { bus.publish(signedUpEvent, {}); };`, }); tester.run("no-undeclared-event-publish", rule, { valid: [], invalid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [ { messageId: "undeclared", data: { event: "demo.signed-up", useCase: "signUp" }, }, ], }, ], }); }); });