Cobre rotas HTTP, proxy, worker assíncrono e barreiras do dev auth

Os 34 testes existentes batiam só na camada de serviço: as rotas, o proxy e o
loop do worker não tinham cobertura alguma. A suíte vai de 34 para 102.

- `tests/http/`: rotas reais com o caminho real de autenticação, sem subir
  servidor. Cobre 401 sem sessão, módulo desligado, papel sem permissão, 403
  ao tentar gravar workspace de terceiro, e 429 com Retry-After.
- `tests/http/proxy.test.ts`: o proxy não propaga query da requisição para o
  login, o que impediria um returnTo forjado.
- `tests/integration/worker.test.ts`: jornada assíncrona pelo loop real. A web
  enfileira e devolve `queued`, o download falha antes do tick, o tick conclui
  e os três formatos saem. Também: job sobrevive à perda da fila do Redis,
  worker relê o contexto e não processa com a flag desligada, TTL expira
  temporários sem tocar o modelo, e desligamento gracioso.
- `tests/unit/dev-auth-guard.test.ts`: matriz de ambientes das três barreiras
  do login local. Um token de dev válido é recusado quando o ambiente é
  staging. Espaço em volta de um valor de ADAPTER_ENV é tolerado de propósito,
  e isso está declarado no teste; valor desconhecido nunca vira development.
- `tests/services/`: Postgres e Redis reais. Confere `app_user` sem
  BYPASSRLS, FORCE RLS em todas as tabelas, isolamento entre workspaces,
  auditoria não alterável pelo papel da aplicação, e lock entre conexões
  diferentes. Sem as variáveis de serviço os casos são IGNORADOS, nunca
  aprovados, e ficam fora de `npm test` para a suíte padrão passar em
  qualquer máquina.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
camilafagundesandClaude Opus 5 committed 2026-10-15 20:39:43 -03:00
1 parent dea510c3b4
commit ce11a50091
10 files changed
+1182

No files matched your search

+187
View File
@@ -0,0 +1,187 @@
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<string, string | undefined>): 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<string, string | undefined>; 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);
});
});