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-rate-limit.js"; function makeFixture({ manifestRateLimit, useCaseBody }) { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nurl-")); const featureDir = path.join(repoRoot, "packages", "demo"); fs.mkdirSync(path.join(featureDir, "src", "application", "use-cases"), { recursive: true, }); const rateLimitField = manifestRateLimit.length > 0 ? `, rateLimit: [${manifestRateLimit.map((b) => `"${b}"`).join(", ")}]` : ""; fs.writeFileSync( path.join(featureDir, "src", "feature.manifest.ts"), `export const demoManifest = defineFeature({ name: "demo", requiredCores: [], useCases: { signUp: { mutates: true, audits: [], publishes: [], consumes: []${rateLimitField} }, }, 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-rate-limit", () => { it("passes when rateLimit.consume budget matches manifest rateLimit[]", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestRateLimit: ["signUp.ip"], useCaseBody: `export const signUpUseCase = (rateLimit) => async (input) => { await rateLimit.consume("signUp.ip", input.ip); };`, }); tester.run("no-undeclared-rate-limit", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("fires when rateLimit.consume budget is not declared in manifest", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestRateLimit: [], useCaseBody: `export const signUpUseCase = (rateLimit) => async (input) => { await rateLimit.consume("signUp.ip", input.ip); };`, }); tester.run("no-undeclared-rate-limit", rule, { valid: [], invalid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [ { messageId: "undeclared", data: { budget: "signUp.ip", useCase: "signUp" }, }, ], }, ], }); }); it("fires when a declared budget is never consumed in the use-case body", () => { const { repoRoot, useCaseFile } = makeFixture({ manifestRateLimit: ["signUp.ip"], useCaseBody: `export const signUpUseCase = () => async () => { return { ok: true }; };`, }); tester.run("no-undeclared-rate-limit", rule, { valid: [], invalid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], errors: [ { messageId: "unusedDeclaration", data: { budget: "signUp.ip", useCase: "signUp" }, }, ], }, ], }); }); it("passes when rateLimit.consume budget matches a RateLimitBudget object in manifest rateLimit[]", () => { const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), "nurl-obj-")); const featureDir = path.join(repoRoot, "packages", "demo"); fs.mkdirSync(path.join(featureDir, "src", "application", "use-cases"), { recursive: true, }); fs.writeFileSync( path.join(featureDir, "src", "feature.manifest.ts"), `export const demoManifest = defineFeature({ name: "demo", requiredCores: [], useCases: { signUp: { mutates: true, audits: [], publishes: [], consumes: [], rateLimit: [{ name: "ip", window: "1m", budget: 5 }, { name: "account", window: "1h", budget: 10 }] }, }, realtimeChannels: [], jobs: [], } as const);`, ); const useCaseFile = path.join( featureDir, "src", "application", "use-cases", "sign-up.use-case.ts", ); fs.writeFileSync( useCaseFile, `export const signUpUseCase = (rateLimit) => async (input) => { await rateLimit.consume("ip", input.clientIp); await rateLimit.consume("account", input.username); };`, ); tester.run("no-undeclared-rate-limit", rule, { valid: [ { filename: useCaseFile, code: fs.readFileSync(useCaseFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); it("is a no-op for non-use-case files", () => { const { repoRoot } = makeFixture({ manifestRateLimit: ["signUp.ip"], useCaseBody: `export const signUpUseCase = (rateLimit) => async (input) => { await rateLimit.consume("signUp.ip", input.ip); };`, }); const serviceFile = path.join( repoRoot, "packages", "demo", "src", "application", "services", "sign-up.service.ts", ); fs.mkdirSync(path.dirname(serviceFile), { recursive: true }); fs.writeFileSync( serviceFile, `export function doWork(rateLimit) { rateLimit.consume("undeclared", "x"); }`, ); tester.run("no-undeclared-rate-limit", rule, { valid: [ { filename: serviceFile, code: fs.readFileSync(serviceFile, "utf8"), options: [{ repoRoot }], }, ], invalid: [], }); }); });