Inicio do Projeto Piloto
ci / verify (push) Has been cancelled

This commit is contained in:
camilafagundes committed 2026-10-02 23:59:36 -03:00
1 parent 6e23f1533b
commit 419325db12
52 files changed
+1329 -110

No files matched your search

+82
View File
@@ -0,0 +1,82 @@
import { describe, expect, it } from "vitest";
import { tempTtlSeconds } from "@/config/retention";
import { collectProductionIssues } from "@/config/env";
import { shouldRequeue } from "@/domain/spreadsheet-adapter/failure-policy";
import { devAuthEnabled, verifyDevSession } from "@/server/auth/dev-session";
import { buildCentralLoginUrl, safeReturnPath } from "@/server/auth/return-to";
import { buildSentryPayload } from "@/infra/observability/capture";
describe("política de piloto", () => {
it("recusa returnTo externo e aceita caminho interno", () => {
expect(safeReturnPath("https://evil.example/phish")).toBe("/adaptador");
expect(safeReturnPath("//evil.example")).toBe("/adaptador");
expect(safeReturnPath("/adaptador/operacao")).toBe("/adaptador/operacao");
const url = buildCentralLoginUrl("https://login.elo.example/entrar", "https://evil.example", "https://app.elo.example");
expect(url).not.toContain("evil.example");
expect(url).toContain("returnTo=");
});
it("bloqueia autenticação de desenvolvimento em produção", () => {
const previous = process.env.NODE_ENV;
process.env.NODE_ENV = "production";
process.env.ADAPTER_DEV_AUTH = "1";
process.env.ADAPTER_DEV_AUTH_SECRET = "segredo-local";
try {
expect(devAuthEnabled()).toBe(false);
expect(verifyDevSession("corpo.assinatura")).toBeNull();
} finally {
process.env.NODE_ENV = previous;
}
});
it("recusa produção sem Redis, com inline, com bucket público ou TTL inválido", () => {
const issues = collectProductionIssues({
NODE_ENV: "production",
ADAPTER_DEV_AUTH: "1",
ADAPTER_JOB_MODE: "inline",
SUPABASE_STORAGE_BUCKET: "public",
SPREADSHEET_TEMP_TTL_SECONDS: "0",
});
const names = issues.map((issue) => issue.variable);
expect(names).toContain("REDIS_URL");
expect(names).toContain("ADAPTER_DEV_AUTH");
expect(names).toContain("ADAPTER_JOB_MODE");
expect(names).toContain("SUPABASE_STORAGE_BUCKET");
expect(names).toContain("SPREADSHEET_TEMP_TTL_SECONDS");
});
it("exige TTL explícito em produção", () => {
const node = process.env.NODE_ENV;
const ttl = process.env.SPREADSHEET_TEMP_TTL_SECONDS;
process.env.NODE_ENV = "production";
delete process.env.SPREADSHEET_TEMP_TTL_SECONDS;
try {
expect(() => tempTtlSeconds()).toThrow(/obrigatório/);
process.env.SPREADSHEET_TEMP_TTL_SECONDS = "abc";
expect(() => tempTtlSeconds()).toThrow(/inválido/);
} finally {
process.env.NODE_ENV = node;
if (ttl === undefined) delete process.env.SPREADSHEET_TEMP_TTL_SECONDS;
else process.env.SPREADSHEET_TEMP_TTL_SECONDS = ttl;
}
});
it("não reenfileira timeout e reenfileira falha transitória", () => {
expect(shouldRequeue("TIMEOUT", 504, 1)).toBe(false);
expect(shouldRequeue("PROCESS_FAILED", 500, 1)).toBe(true);
expect(shouldRequeue("PROCESS_FAILED", 500, 3)).toBe(false);
});
it("não envia célula sentinela ao payload do Sentry", () => {
const sentinel = "Ana Silva =CMD()";
const payload = JSON.stringify(
buildSentryPayload(new Error(`valor da célula ${sentinel}`), {
job_id: "11111111-1111-4111-8111-111111111111",
error_code: "PROCESS_FAILED",
}),
);
expect(payload).not.toContain(sentinel);
expect(payload).not.toContain("Ana Silva");
expect(payload).toContain("UNEXPECTED");
});
});
+48
View File
@@ -0,0 +1,48 @@
import { describe, expect, it } from "vitest";
import ExcelJS from "exceljs";
import { readSpreadsheet } from "@/infra/spreadsheet/parse";
import { createModel, addSource, createJob, saveMappings, startProcess } from "@/server/spreadsheet-adapter/service";
import { ctxOf, createReadyDb, depsOf, IDS } from "../helpers/world";
describe("fixtures XLSX", () => {
it("lê abas, fórmula como texto, data, coluna vazia e caracteres especiais sem executar fórmula", async () => {
const book = new ExcelJS.Workbook();
const first = book.addWorksheet("Dados");
first.addRow(["Nome", "Valor", ""]);
first.getCell("A2").value = "José & Ação";
first.getCell("B2").value = new Date("2026-01-15T00:00:00.000Z");
first.mergeCells("A3:B3");
first.getCell("A3").value = "=1+1";
book.addWorksheet("Outra").addRow(["ignorar"]);
const bytes = Buffer.from(await book.xlsx.writeBuffer());
const parsed = await readSpreadsheet("livro.xlsx", bytes, { sheet: "Dados", headerRow: 1 });
expect(parsed.grid.sheetNames).toContain("Outra");
expect(parsed.grid.headers).toContain("Nome");
const flat = parsed.grid.rows.flat().join("|");
expect(flat).toContain("José & Ação");
expect(flat).not.toMatch(/^2$/);
expect(flat).toContain("=1+1");
});
it("não altera os bytes do modelo ao processar", async () => {
const db = await createReadyDb();
const deps = depsOf(db);
const ana = ctxOf("ana", "alpha", "owner", true);
const model = await createModel(deps, ana, {
name: "Livro",
filename: "modelo.csv",
bytes: Buffer.from("Nome\n", "utf8"),
requiredColumns: ["Nome"],
});
const before = await deps.storage.get(`workspaces/${IDS.alpha}/models/${model.id}/original`);
const job = await createJob(deps, ana, model.id, "xlsx-model");
const source = await addSource(deps, ana, job.id, { filename: "fonte.csv", bytes: Buffer.from("Nome\nJosé\n", "utf8") });
if (!("job" in source)) throw new Error("fonte");
const sourceId = source.job.sources[0]?.id ?? "";
await saveMappings(deps, ana, job.id, sourceId, [{ sourceColumn: "Nome", modelColumn: "Nome" }]);
await startProcess(deps, ana, job.id, sourceId, "xlsx-proc");
const after = await deps.storage.get(`workspaces/${IDS.alpha}/models/${model.id}/original`);
expect(after.equals(before)).toBe(true);
await db.close();
});
});