import { afterEach, describe, expect, it } from "vitest"; import { adapterEnv, collectConfigIssues, stagingAuthEnabled } from "@/config/env"; import { devAuthEnabled } from "@/server/auth/dev-session"; /** * O login local sem senha não pode existir em homologação pública, em nenhuma * combinação de variáveis. Há três barreiras independentes e esta suíte cobre * as três, na matriz inteira de ambientes. */ const original = { ADAPTER_ENV: process.env.ADAPTER_ENV, NODE_ENV: process.env.NODE_ENV, ADAPTER_DEV_AUTH: process.env.ADAPTER_DEV_AUTH, ADAPTER_DEV_AUTH_SECRET: process.env.ADAPTER_DEV_AUTH_SECRET, }; function setEnv(values: Record): void { for (const [key, value] of Object.entries(values)) { if (value === undefined) delete process.env[key]; else process.env[key] = value; } } afterEach(() => { setEnv(original); }); describe("barreira 1: devAuthEnabled", () => { const casos: { nome: string; env: Record; esperado: boolean }[] = [ { nome: "development com flag e segredo", env: { ADAPTER_ENV: "development", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: true, }, { nome: "development sem a flag", env: { ADAPTER_ENV: "development", ADAPTER_DEV_AUTH: "0", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, { nome: "development sem segredo", env: { ADAPTER_ENV: "development", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: undefined }, esperado: false, }, { nome: "staging com flag e segredo", env: { ADAPTER_ENV: "staging", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, { nome: "production com flag e segredo", env: { ADAPTER_ENV: "production", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, { nome: "ADAPTER_ENV ausente com NODE_ENV=production", env: { ADAPTER_ENV: undefined, NODE_ENV: "production", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, { nome: "ADAPTER_ENV com valor inválido", env: { ADAPTER_ENV: "homolog", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, { nome: "ADAPTER_ENV com maiúsculas não é aceito como development", env: { ADAPTER_ENV: "Development", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, { nome: "staging mesmo com NODE_ENV=development", env: { ADAPTER_ENV: "staging", NODE_ENV: "development", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }, esperado: false, }, ]; for (const caso of casos) { it(`${caso.nome} → ${caso.esperado ? "ligado" : "desligado"}`, () => { setEnv({ ADAPTER_ENV: undefined, NODE_ENV: "test", ADAPTER_DEV_AUTH: undefined, ADAPTER_DEV_AUTH_SECRET: undefined }); setEnv(caso.env); expect(devAuthEnabled()).toBe(caso.esperado); }); } it("um valor inesperado de ADAPTER_ENV nunca resolve para development", () => { for (const valor of ["prod", "hml", "homolog", "homologacao", "stage", "staging ", "DEV", "Development", "dev"]) { setEnv({ ADAPTER_ENV: valor, NODE_ENV: "production", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }); expect(adapterEnv(), `valor ${JSON.stringify(valor)}`).not.toBe("development"); expect(devAuthEnabled(), `valor ${JSON.stringify(valor)}`).toBe(false); } }); it("espaço em volta de um valor válido é tolerado, de propósito", () => { // Espaço sobrando em arquivo de env é erro de digitação comum, e o valor // ainda é inequívoco. O que nunca acontece é um valor DESCONHECIDO virar // development: o caso acima cobre isso, e `ADAPTER_ENV` vazio cai em // NODE_ENV. Reconhecer " staging " como staging é o comportamento seguro. setEnv({ ADAPTER_ENV: " staging ", NODE_ENV: "production", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }); expect(adapterEnv()).toBe("staging"); expect(devAuthEnabled()).toBe(false); setEnv({ ADAPTER_ENV: " development ", NODE_ENV: "production", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }); expect(adapterEnv()).toBe("development"); expect(devAuthEnabled()).toBe(true); }); }); describe("barreira 2: o start é recusado", () => { const base = { ADAPTER_ENV: "staging", NEXT_PUBLIC_SUPABASE_URL: "https://p.supabase.co", NEXT_PUBLIC_SUPABASE_ANON_KEY: "anon", SUPABASE_SERVICE_ROLE_KEY: "service", SUPABASE_STORAGE_BUCKET: "bucket-hml", DATABASE_URL: "postgres://host/db", REDIS_URL: "redis://host:6379", SPREADSHEET_TEMP_TTL_SECONDS: "86400", ADAPTER_JOB_MODE: "worker", ADAPTER_APPLY_IDENTITY_SHIM: "1", ADAPTER_STAGING_AUTH: "1", }; it("staging com ADAPTER_DEV_AUTH=1 não passa na validação", () => { const issues = collectConfigIssues({ ...base, ADAPTER_DEV_AUTH: "1" }); expect(issues.map((issue) => issue.variable)).toContain("ADAPTER_DEV_AUTH"); }); it("production com ADAPTER_DEV_AUTH=1 não passa na validação", () => { const issues = collectConfigIssues({ ...base, ADAPTER_ENV: "production", ADAPTER_STAGING_AUTH: "0", SENTRY_DSN: "https://k@sentry.io/1", ELO_PRO_LOGIN_URL: "https://login.elo.example", ADAPTER_DEV_AUTH: "1", }); expect(issues.map((issue) => issue.variable)).toContain("ADAPTER_DEV_AUTH"); }); it("staging sem a flag de dev passa", () => { expect(collectConfigIssues({ ...base, ADAPTER_DEV_AUTH: "0" })).toEqual([]); }); }); describe("barreira 3: o provedor não entra no caminho de código", () => { it("fora de development só o Supabase resolve identidade", async () => { setEnv({ ADAPTER_ENV: "staging", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }); const { activeProviders } = await import("@/server/auth/identity-provider"); expect(activeProviders().map((provider) => provider.name)).toEqual(["supabase"]); }); it("em development o provedor local vem primeiro", async () => { setEnv({ ADAPTER_ENV: "development", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "s" }); const { activeProviders } = await import("@/server/auth/identity-provider"); expect(activeProviders().map((provider) => provider.name)).toEqual(["dev", "supabase"]); }); it("o provedor local recusa o token mesmo se for chamado em staging", async () => { setEnv({ ADAPTER_ENV: "development", ADAPTER_DEV_AUTH: "1", ADAPTER_DEV_AUTH_SECRET: "segredo" }); const { signDevSession } = await import("@/server/auth/dev-session"); const token = signDevSession("aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "11111111-1111-4111-8111-111111111111"); setEnv({ ADAPTER_ENV: "staging" }); const { devIdentityProvider } = await import("@/server/auth/identity-provider"); const jar = { get: (name: string) => (name === "adapter_dev_session" ? { name, value: token } : undefined), getAll: () => [{ name: "adapter_dev_session", value: token }], set: () => undefined, }; expect(await devIdentityProvider.resolve(jar as never)).toBeNull(); expect(devIdentityProvider.hadSessionHint(jar as never)).toBe(false); }); }); describe("login de homologação não vaza para produção", () => { it("stagingAuthEnabled é falso em development e production", () => { const base = { ADAPTER_STAGING_AUTH: "1", NEXT_PUBLIC_SUPABASE_URL: "https://p.supabase.co", NEXT_PUBLIC_SUPABASE_ANON_KEY: "anon", }; expect(stagingAuthEnabled({ ...base, ADAPTER_ENV: "staging" })).toBe(true); expect(stagingAuthEnabled({ ...base, ADAPTER_ENV: "production" })).toBe(false); expect(stagingAuthEnabled({ ...base, ADAPTER_ENV: "development" })).toBe(false); }); });