const Fastify = require("fastify"); const authPlugin = require("../../../src/plugins/auth"); const { AppError } = require("../../../src/utils/AppError"); const apps = []; afterEach(async () => { await Promise.all(apps.splice(0).map((app) => app.close())); }); async function build() { const app = Fastify(); apps.push(app); const employee = { id: "synthetic-user", status: "active", token_version: 2, roles: [{ role_code: "employee" }] }; const prisma = { employee: { findUnique: vi.fn(async () => employee) } }; app.decorate("config", { jwtSecret: "synthetic-local-auth-contract-key", accessTokenTtl: "5m" }); app.decorate("prisma", prisma); await app.register(authPlugin); app.setErrorHandler((e, _, reply) => reply.status(e instanceof AppError ? e.statusCode : 500).send({ error: { code: e.code, details: e.details } })); app.get("/synthetic-unsupported", { preHandler: app.requireEntityPermission("synthetic-unknown", "submit") }, async () => ({ ok: true })); return { app, prisma, employee }; } describe("auth plugin entity action contract with synthetic JWT only", () => { it("keeps invalid/absent token rejection before entity lookup", async () => { const { app, prisma } = await build(); const r = await app.inject({ url: "/synthetic-unsupported" }); expect(r.statusCode).toBe(401); expect(r.json().error.code).toBe("AUTH_004"); expect(prisma.employee.findUnique).not.toHaveBeenCalled(); }); it("explains unsupported entity/action after authentication", async () => { const { app, employee } = await build(); const token = app.issueAccessToken(employee); const r = await app.inject({ url: "/synthetic-unsupported", headers: { authorization: `Bearer ${token}` } }); expect(r.statusCode).toBe(409); expect(r.json().error).toEqual({ code: "VALID_001", details: { entityType: "synthetic-unknown", action: "submit", permission: "unsupported_entity_action", reasonCode: "auth.entity_action_unsupported" } }); }); it("preserves valid entity mapping and missing permission 403", async () => { const { app } = await build(); expect(app.assertEntityPermission({ permissionCodes: ["procurement.submit"] }, "purchase_request", "submit")).toBe("procurement.submit"); expect(() => app.assertEntityPermission({ permissionCodes: [] }, "purchase_request", "submit")).toThrow(expect.objectContaining({ code: "PERM_001", statusCode: 403 })); }); it.each(["inactive", "stale"])("keeps %s account/token blocked before entity action", async (kind) => { const { app, employee } = await build(); const token = app.issueAccessToken(employee); if (kind === "inactive") employee.status = "archived"; else employee.token_version += 1; const r = await app.inject({ url: "/synthetic-unsupported", headers: { authorization: `Bearer ${token}` } }); expect(r.statusCode).toBe(kind === "inactive" ? 403 : 401); expect(r.json().error.code).toBe(kind === "inactive" ? "AUTH_003" : "AUTH_004"); }); });