import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { createReadyDb, IDS } from "../helpers/world"; import { FakeJar, jar, makeRuntimeDeps, signIn, signOut } from "./helpers"; import type { Db } from "@/infra/db/types"; // Autenticação local assinada: os testes rodam como development. process.env.ADAPTER_ENV = "development"; process.env.ADAPTER_DEV_AUTH = "1"; process.env.ADAPTER_DEV_AUTH_SECRET = "segredo-de-teste"; let db: Db; let runtime: ReturnType; vi.mock("next/headers", () => ({ cookies: async () => jar as unknown as FakeJar, headers: async () => new Headers(), })); vi.mock("@/infra/db/client", () => ({ getDb: async () => db, probeDb: async () => { await db.withService((query) => query.query("select 1 as ok")); }, })); vi.mock("@/server/spreadsheet-adapter/runtime", () => ({ runtimeDeps: async () => runtime.deps, })); beforeAll(async () => { db = await createReadyDb(); runtime = makeRuntimeDeps(db); }); beforeEach(() => { signOut(); }); describe("rotas de saúde", () => { it("liveness responde sem tocar em dependência", async () => { const { GET } = await import("@/app/api/health/live/route"); const response = GET(); expect(response.status).toBe(200); const body = (await response.json()) as { status: string; environment: string }; expect(body.status).toBe("live"); expect(body.environment).toBe("development"); }); it("readiness reporta cada dependência e não devolve connection string", async () => { const { GET } = await import("@/app/api/health/ready/route"); const response = await GET(); const body = (await response.json()) as { status: string; checks: Record; configIssues: string[]; }; expect(body.checks.database).toBe("ok"); expect(body.checks.migrations).toBe("ok"); expect(body.status).toBe("ready"); expect(JSON.stringify(body)).not.toContain("postgres://"); expect(body.configIssues).toEqual([]); }); }); describe("autenticação das rotas do módulo", () => { it("recusa sem sessão", async () => { const { GET } = await import("@/app/api/spreadsheet-adapter/models/route"); const response = await GET(); expect(response.status).toBe(401); const body = (await response.json()) as { error: { code: string } }; expect(body.error.code).toBe("UNAUTHENTICATED"); }); it("responde com sessão válida no workspace com o módulo ligado", async () => { signIn(IDS.ana, IDS.alpha); const { GET } = await import("@/app/api/spreadsheet-adapter/models/route"); const response = await GET(); expect(response.status).toBe(200); const body = (await response.json()) as { models: unknown[] }; expect(Array.isArray(body.models)).toBe(true); }); it("recusa a configuração para quem não é owner nem admin", async () => { signIn(IDS.bruno, IDS.alpha); const { PATCH } = await import("@/app/api/spreadsheet-adapter/settings/route"); const response = await PATCH( new Request("http://localhost/api/spreadsheet-adapter/settings", { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ allowNonAdminModelUpload: true }), }), ); expect(response.status).toBeGreaterThanOrEqual(400); const body = (await response.json()) as { error: { code: string } }; expect(["FORBIDDEN", "WORKSPACE_FORBIDDEN", "FEATURE_DISABLED"]).toContain(body.error.code); }); it("bloqueia o módulo desligado no workspace", async () => { signIn(IDS.diego, IDS.beta); const { GET } = await import("@/app/api/spreadsheet-adapter/models/route"); const response = await GET(); expect(response.status).toBeGreaterThanOrEqual(400); }); }); describe("seleção de workspace", () => { it("lista só as associações reais do usuário", async () => { signIn(IDS.eva, IDS.beta); const { GET } = await import("@/app/api/auth/workspace/route"); const response = await GET(); expect(response.status).toBe(200); const body = (await response.json()) as { memberships: { workspaceId: string }[] }; expect(body.memberships.map((item) => item.workspaceId)).toEqual([IDS.beta]); }); it("recusa gravar workspace do qual o usuário não é membro", async () => { signIn(IDS.diego, IDS.beta); const { POST } = await import("@/app/api/auth/workspace/route"); const response = await POST( new Request("http://localhost/api/auth/workspace", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId: IDS.alpha }), }), ); expect(response.status).toBe(403); expect(jar.has("elo_pro_workspace_id")).toBe(false); }); it("grava a preferência quando a associação existe", async () => { signIn(IDS.ana, IDS.alpha); const { POST } = await import("@/app/api/auth/workspace/route"); const response = await POST( new Request("http://localhost/api/auth/workspace", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId: IDS.alpha }), }), ); expect(response.status).toBe(200); expect(jar.get("elo_pro_workspace_id")?.value).toBe(IDS.alpha); }); it("recusa sem sessão", async () => { const { POST } = await import("@/app/api/auth/workspace/route"); const response = await POST( new Request("http://localhost/api/auth/workspace", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ workspaceId: IDS.alpha }), }), ); expect(response.status).toBe(401); }); }); describe("login de homologação", () => { it("não existe fora de staging", async () => { const { POST } = await import("@/app/api/auth/staging/session/route"); const response = await POST( new Request("http://localhost/api/auth/staging/session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: "a@b.c", password: "x" }), }), ); expect(response.status).toBe(404); }); }); describe("rate limiting nas rotas", () => { it("devolve 429 com Retry-After ao estourar a janela", async () => { signIn(IDS.ana, IDS.alpha); const { createCoordinator } = await import("@/infra/redis"); const { RATE_LIMITS, windowStart } = await import("@/server/rate-limit"); const { rateLimitKey } = await import("@/infra/redis/redis"); const rule = RATE_LIMITS.read; const key = rateLimitKey("read", `${IDS.alpha}:${IDS.ana}`, windowStart(rule)); const coordinator = createCoordinator(); for (let index = 0; index < rule.limit; index += 1) { await coordinator.hit(key, rule.windowSeconds); } const { GET } = await import("@/app/api/spreadsheet-adapter/models/route"); const response = await GET(); expect(response.status).toBe(429); expect(Number(response.headers.get("Retry-After"))).toBeGreaterThan(0); const body = (await response.json()) as { error: { code: string } }; expect(body.error.code).toBe("RATE_LIMITED"); }); });