fix(e2e): a suite escrevia no banco de PRODUCAO — tres camadas de conserto
Medido em 2026-08-06. `.env.local` de um checkout de trabalho aponta para a
nuvem, e o playwright sobe o app com `next start`, que o carrega. A suite criava
organizacoes, usuarios e agentes de teste no banco real, passando verde.
A org `e2e-test-org` esta na producao deste projeto desde 2026-04-29. Ela nao
chegou la por acidente de uma sessao: chegou porque esse era o comportamento
normal da suite.
ISOLAR O webServer NAO BASTAVA, e essa foi a descoberta. Os scripts de seed leem
`.env.local` DIRETO DO DISCO, ignorando process.env — entao o env injetado no
servidor nunca os alcancava, e as specs chamam esses seeds sozinhas no meio do
teste. O sintoma que denunciou: o factor TOTP em `.e2e-creds.json` nao existia no
banco local, porque tinha sido criado na nuvem. 93 arquivos tem esse padrao.
As tres camadas:
1. `.env.e2e` (gerado por `pnpm e2e:env`, nao versionado) + injecao explicita
no webServer, com guard que RECUSA rodar se o arquivo faltar — cair no
.env.local em silencio e o modo de falha caro;
2. `pnpm e2e:build`, porque as tres NEXT_PUBLIC_* sao embutidas no BUNDLE:
buildar com o env errado e trocar so no start deixaria a URL de producao
dentro do JavaScript do browser. `node --env-file` nao serve aqui — o next
build cria Workers e o Node recusa propagar a flag
(ERR_WORKER_INVALID_EXEC_ARGV). O script prova as DUAS direcoes: host de
producao ausente do bundle E host local presente (sem o controle positivo,
"nao achei producao" pode ser um grep que nao acha nada);
3. `scripts/lib/env-de-teste.ts` — process.env VENCE o arquivo, e todo seed
ANUNCIA o destino ("escrevendo em LOCAL" / "⚠️ REMOTO"). Um seed que escreve
em producao acha os mesmos dados de teste de sempre e termina com "✅ Seed
completo": sem a linha impressa, nada distingue os dois casos.
`seed-e2e-credentials.ts` migrado (e o que a suite chama sempre). Os outros ~14
seeds e as ~78 sondas ficam declarados como divida no handoff.
GANHO ESTRUTURAL do worktree dedicado: aqui nao existe `.env.local`, entao um
script que o leia do disco falha ALTO (ENOENT) em vez de escrever na nuvem. O
isolamento deixa de depender de disciplina.
Evidencia observada: spec agente-novo-e-uso 5/5 passed contra o banco LOCAL
(era 0/5 antes, por MFA — o factor do banco nao batia com o do arquivo);
suite unit 1467 passed (155 arquivos); typecheck limpo; guard do playwright
recusa sem .env.e2e (verificado).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WKf64hXUbFatzofJHZREr7
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 565 KiB After Width: | Height: | Size: 584 KiB |
|
Before Width: | Height: | Size: 587 KiB After Width: | Height: | Size: 607 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 110 KiB |
@@ -19,6 +19,8 @@
|
||||
"db:migrate": "echo 'TODO: wire supabase db push or pg-migrate' && exit 0",
|
||||
"db:reset": "supabase db reset",
|
||||
"test:e2e": "playwright test",
|
||||
"e2e:build": "bash scripts/e2e-build.sh",
|
||||
"e2e:env": "bash scripts/gerar-env-e2e.sh",
|
||||
"test:journeys": "playwright test -c tests/journeys/playwright.config.ts",
|
||||
"test:unit": "vitest run",
|
||||
"test:db": "bash scripts/test-db.sh",
|
||||
|
||||
@@ -1,5 +1,42 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
import { defineConfig } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Lê o `.env.e2e` — o ambiente LOCAL da suíte.
|
||||
*
|
||||
* Falha ALTO se o arquivo não existir, em vez de deixar o app cair no
|
||||
* `.env.local`: o modo de falha silencioso aqui é a suíte rodar contra o banco
|
||||
* de PRODUÇÃO, que foi exatamente o que acontecia antes deste arquivo existir
|
||||
* (medido em 2026-08-06).
|
||||
*/
|
||||
function envDoE2E(): Record<string, string> {
|
||||
let bruto: string;
|
||||
try {
|
||||
bruto = readFileSync(".env.e2e", "utf8");
|
||||
} catch {
|
||||
throw new Error(
|
||||
"Falta o .env.e2e — rode `pnpm e2e:env` (precisa do Supabase local de pé).\n" +
|
||||
"Sem ele o app sob teste carregaria o .env.local, que aponta para PRODUÇÃO.",
|
||||
);
|
||||
}
|
||||
const env: Record<string, string> = {};
|
||||
for (const linha of bruto.split("\n")) {
|
||||
const limpa = linha.trim();
|
||||
if (limpa === "" || limpa.startsWith("#")) continue;
|
||||
const i = limpa.indexOf("=");
|
||||
if (i <= 0) continue;
|
||||
env[limpa.slice(0, i)] = limpa.slice(i + 1);
|
||||
}
|
||||
const url = env.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
||||
// Um `.env.e2e` apontando para fora do localhost é pior que nenhum, porque
|
||||
// parece seguro.
|
||||
if (!url.startsWith("http://127.0.0.1") && !url.startsWith("http://localhost")) {
|
||||
throw new Error(`.env.e2e aponta para um Supabase que não é local (${url}) — recusado.`);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
// Porta do dev server sob teste. Default 3001; sobrescreva com E2E_PORT quando
|
||||
// a 3001 já estiver ocupada por outro checkout/worktree.
|
||||
const PORT = process.env.E2E_PORT ?? "3001";
|
||||
@@ -35,6 +72,16 @@ export default defineConfig({
|
||||
// Produção (`next build` antes!): dev-server compila por rota (40-80s) e
|
||||
// Turbopack dev quebra cookies() fora do request scope — inviável p/ e2e.
|
||||
command: `pnpm exec next start --port ${PORT}`,
|
||||
// O ambiente do servidor sob teste vem do `.env.e2e`, INJETADO aqui — e não
|
||||
// do `.env.local`, que num checkout de trabalho aponta para PRODUÇÃO.
|
||||
// Variável de ambiente real tem precedência sobre os arquivos `.env*` que o
|
||||
// Next carrega sozinho, e é isto que impede a suíte de escrever no banco
|
||||
// real (medido em 2026-08-06: sem esta injeção, ela escrevia).
|
||||
//
|
||||
// ⚠️ Isto cobre o SERVIDOR. Os scripts de seed que as specs chamam sozinhas
|
||||
// liam `.env.local` direto do disco e escapavam daqui — o conserto do outro
|
||||
// lado é `scripts/lib/env-de-teste.ts`.
|
||||
env: envDoE2E(),
|
||||
url: BASE_URL,
|
||||
// false: reusar um server que já ocupa a porta pode ser OUTRO processo
|
||||
// (ex.: bundle do Remotion na 3000) — o teste precisa do NOSSO next start.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# `next build` com o ambiente do E2E (Supabase LOCAL).
|
||||
#
|
||||
# ═══ POR QUE NÃO `node --env-file=.env.e2e next build` ═══
|
||||
#
|
||||
# Medido: o build morre com
|
||||
# ERR_WORKER_INVALID_EXEC_ARGV: --env-file= is not allowed in NODE_OPTIONS
|
||||
# O `next build` cria Workers, e o Node recusa propagar `--env-file` para eles.
|
||||
# Exportar no shell resolve porque aí são variáveis de ambiente de verdade, que
|
||||
# os Workers herdam — e que têm precedência sobre os arquivos `.env*` que o Next
|
||||
# carrega sozinho (é o que impede o `.env.local`, apontado para produção, de
|
||||
# vencer).
|
||||
#
|
||||
# ═══ POR QUE O BUILD PRECISA DISTO E NÃO SÓ O START ═══
|
||||
#
|
||||
# As três `NEXT_PUBLIC_*` são embutidas no BUNDLE aqui. Buildar com o
|
||||
# `.env.local` e depois trocar o env só no `next start` deixaria a URL de
|
||||
# produção dentro do JavaScript que roda no browser: o servidor falaria com o
|
||||
# banco local e o cliente com a nuvem, no mesmo teste.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if [ ! -f .env.e2e ]; then
|
||||
echo "==> .env.e2e não existe. Rode 'pnpm e2e:env' (precisa do Supabase local de pé)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# `set -a` exporta tudo que for atribuído até o `set +a`.
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
. ./.env.e2e
|
||||
set +a
|
||||
|
||||
echo "==> Buildando contra ${NEXT_PUBLIC_SUPABASE_URL}"
|
||||
pnpm exec next build
|
||||
|
||||
# A PROVA, e não a suposição: se a URL de produção sobreviveu em qualquer
|
||||
# artefato do bundle, o `.env.local` venceu e o teste falaria com a nuvem pela
|
||||
# metade cliente. Falhar aqui é barato; descobrir depois de escrever no banco
|
||||
# real, não.
|
||||
#
|
||||
# O host vem do PRÓPRIO .env.local, então a guarda continua valendo se alguém
|
||||
# apontar aquele arquivo para outro projeto.
|
||||
if [ -f .env.local ]; then
|
||||
HOST_PROD="$(grep -E '^NEXT_PUBLIC_SUPABASE_URL=' .env.local | cut -d= -f2- | sed -E 's#https?://##; s#/.*##')"
|
||||
if [ -n "$HOST_PROD" ] && [ "$HOST_PROD" != "127.0.0.1:54321" ]; then
|
||||
if grep -rqF "$HOST_PROD" .next/static 2>/dev/null; then
|
||||
echo "==> FALHOU: o bundle do browser contém o host de produção ($HOST_PROD)." >&2
|
||||
echo " O build pegou o .env.local. NÃO rode a suíte — ela escreveria em produção." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> OK: o host de produção ($HOST_PROD) não aparece no bundle do browser."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Controle POSITIVO do mesmo grep: se a URL local também não aparecesse, o
|
||||
# "não achei produção" acima não valeria nada — seria um grep que não acha nada.
|
||||
HOST_LOCAL="$(printf '%s' "$NEXT_PUBLIC_SUPABASE_URL" | sed -E 's#https?://##; s#/.*##')"
|
||||
if grep -rqF "$HOST_LOCAL" .next/static 2>/dev/null; then
|
||||
echo "==> OK (controle): o host local ($HOST_LOCAL) ESTÁ no bundle — o grep está vivo."
|
||||
else
|
||||
echo "==> FALHOU (controle): o host local não aparece no bundle." >&2
|
||||
echo " Sem isto, a ausência de produção acima não prova nada." >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Gera o `.env.e2e` — o ambiente da suíte Playwright, apontado para o Supabase
|
||||
# LOCAL.
|
||||
#
|
||||
# ═══ POR QUE ESTE ARQUIVO EXISTE ═══
|
||||
#
|
||||
# O `.env.local` de um checkout de trabalho aponta para o Supabase de PRODUÇÃO
|
||||
# (é com ele que se desenvolve). O `playwright.config.ts` sobe o app com
|
||||
# `next start`, que carrega `.env.local`. Resultado, medido em 2026-08-06:
|
||||
# `pnpm test:e2e` escrevia organizações, usuários e agentes de teste **no banco
|
||||
# real** — o teste passava, e o estrago era invisível.
|
||||
#
|
||||
# A saída não é "lembrar de trocar o .env.local antes de testar": é exatamente o
|
||||
# tipo de disciplina que falha uma vez e ninguém percebe.
|
||||
#
|
||||
# ⚠️ ISTO SOZINHO NÃO BASTA. Os scripts de seed liam `.env.local` DIRETO do
|
||||
# disco, ignorando `process.env` — então nem o env do webServer os alcançava.
|
||||
# O conserto do outro lado é `scripts/lib/env-de-teste.ts`, que faz `process.env`
|
||||
# vencer. Os dois juntos é que fecham o caminho.
|
||||
#
|
||||
# Uso:
|
||||
# pnpm e2e:env # (re)cria o .env.e2e
|
||||
# pnpm e2e:build && pnpm test:e2e # build embute NEXT_PUBLIC_*, ver e2e-build.sh
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
if ! npx supabase status >/dev/null 2>&1; then
|
||||
echo "==> O Supabase local não está de pé. Rode 'npx supabase start' antes." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ENVOUT="$(npx supabase status -o env 2>/dev/null)"
|
||||
ler() { printf '%s\n' "$ENVOUT" | grep "^$1=" | cut -d= -f2- | tr -d '"'; }
|
||||
|
||||
API_URL="$(ler API_URL)"
|
||||
ANON="$(ler ANON_KEY)"
|
||||
SERVICE="$(ler SERVICE_ROLE_KEY)"
|
||||
|
||||
if [ -z "$API_URL" ] || [ -z "$ANON" ] || [ -z "$SERVICE" ]; then
|
||||
echo "==> Não consegui ler as chaves do stack local (API_URL/ANON_KEY/SERVICE_ROLE_KEY)." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Guarda contra o erro que este arquivo existe para impedir. Se o `supabase
|
||||
# status` devolver um host remoto (config apontada para um projeto linkado, por
|
||||
# exemplo), falhar aqui é melhor do que gerar um `.env.e2e` que manda a suíte
|
||||
# para a nuvem — o modo de falha silencioso é o caro.
|
||||
case "$API_URL" in
|
||||
http://127.0.0.1:*|http://localhost:*) ;;
|
||||
*)
|
||||
echo "==> RECUSADO: o stack local respondeu com uma URL que não é local: $API_URL" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
cat > .env.e2e <<EOF
|
||||
# ── Ambiente do E2E — LOCAL, nunca a nuvem ──────────────────────────────────
|
||||
# GERADO por 'pnpm e2e:env'. Não versionado (.gitignore cobre '.env*').
|
||||
# Antes de rodar a suíte: pnpm e2e:build && pnpm test:e2e
|
||||
NEXT_PUBLIC_SUPABASE_URL=$API_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=$ANON
|
||||
SUPABASE_SERVICE_ROLE_KEY=$SERVICE
|
||||
SUPABASE_DB_URL=postgresql://postgres:postgres@127.0.0.1:54322/postgres
|
||||
|
||||
# Placeholders: 'next start' roda em NODE_ENV=production, e lib/env.ts exige
|
||||
# estas vars em produção. As specs não exercitam os serviços por trás delas.
|
||||
# Valores iguais aos do CI (.github/workflows/e2e.yml), para que local e CI
|
||||
# falhem pelos mesmos motivos.
|
||||
INTERNAL_SECRET=e2e-placeholder-nao-e-segredo
|
||||
CPF_ENCRYPTION_KEY=e2e-placeholder-nao-e-segredo
|
||||
WAHA_BYO_ENCRYPTION_KEY=e2e-placeholder-nao-e-segredo
|
||||
AI_CRED_AES_KEY=e2e-placeholder-nao-e-segredo
|
||||
WAHA_API_BASE_URL=http://127.0.0.1:3999
|
||||
WAHA_API_KEY=e2e-placeholder-nao-e-segredo
|
||||
WAHA_WEBHOOK_BASE_URL=http://127.0.0.1:3001
|
||||
UPSTASH_REDIS_REST_URL=http://127.0.0.1:3998
|
||||
UPSTASH_REDIS_REST_TOKEN=e2e-placeholder-nao-e-segredo
|
||||
NEXT_TELEMETRY_DISABLED=1
|
||||
EOF
|
||||
|
||||
echo "==> .env.e2e gerado, apontando para $API_URL"
|
||||
echo "==> Próximo: pnpm e2e:build && pnpm test:e2e"
|
||||
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* De onde um script de seed/sonda lê as credenciais do Supabase.
|
||||
*
|
||||
* ═══ O DEFEITO QUE ISTO CONSERTA (medido em 2026-08-06) ═══
|
||||
*
|
||||
* Os scripts liam `.env.local` **direto do disco**, ignorando `process.env`:
|
||||
*
|
||||
* const envFile = fs.readFileSync(path.join(process.cwd(), ".env.local"), "utf8");
|
||||
*
|
||||
* Num checkout de trabalho, `.env.local` aponta para PRODUÇÃO. Consequência: a
|
||||
* suíte E2E semeava organizações, usuários e agentes de teste no banco real — e
|
||||
* o `.env.e2e` no `webServer` do Playwright **não alcançava** esses scripts,
|
||||
* porque eles nunca olharam para o ambiente. O sintoma que denunciou: o factor
|
||||
* TOTP gravado em `.e2e-creds.json` não existia no banco local, porque tinha
|
||||
* sido criado na nuvem.
|
||||
*
|
||||
* A org `e2e-test-org` está na produção deste projeto desde 2026-04-29. Ela não
|
||||
* chegou lá por acidente de uma sessão: chegou porque **este era o comportamento
|
||||
* normal** da suíte.
|
||||
*
|
||||
* ═══ A REGRA ═══
|
||||
*
|
||||
* `process.env` VENCE o arquivo. É o que permite `set -a; . ./.env.e2e` (ou o
|
||||
* `env` do Playwright) redirecionar qualquer script sem editar nenhum deles.
|
||||
* Sem valor no ambiente, cai em `.env.local` — o comportamento de sempre, para
|
||||
* quem roda uma sonda à mão durante o desenvolvimento.
|
||||
*/
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
|
||||
export interface CredenciaisSupabase {
|
||||
url: string;
|
||||
serviceRole: string;
|
||||
/** anon key — alguns seeds fazem signIn como usuário comum (ex.: enroll TOTP). */
|
||||
anonKey: string;
|
||||
/** base do app, para links gerados pelo seed. */
|
||||
appUrl: string;
|
||||
/** de onde os valores vieram — vai ao log, para o operador não adivinhar. */
|
||||
origem: "ambiente" | "arquivo";
|
||||
}
|
||||
|
||||
function lerArquivo(arquivo: string): Record<string, string> {
|
||||
const caminho = path.join(process.cwd(), arquivo);
|
||||
if (!fs.existsSync(caminho)) return {};
|
||||
const env: Record<string, string> = {};
|
||||
for (const linha of fs.readFileSync(caminho, "utf8").split("\n")) {
|
||||
const m = /^\s*([A-Z0-9_]+)\s*=\s*(.*)$/.exec(linha);
|
||||
if (m) env[m[1]!] = (m[2] ?? "").replace(/^"(.*)"$/, "$1").trim();
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve URL + service role. `process.env` primeiro; `.env.local` depois.
|
||||
*
|
||||
* Lança quando não acha — nunca devolve string vazia, que viraria uma chamada
|
||||
* ao Supabase com credencial vazia e um erro três camadas adiante.
|
||||
*/
|
||||
export function credenciaisSupabaseDeTeste(): CredenciaisSupabase {
|
||||
const doAmbiente = {
|
||||
url: process.env.NEXT_PUBLIC_SUPABASE_URL ?? "",
|
||||
serviceRole: process.env.SUPABASE_SERVICE_ROLE_KEY ?? "",
|
||||
};
|
||||
if (doAmbiente.url !== "" && doAmbiente.serviceRole !== "") {
|
||||
return {
|
||||
...doAmbiente,
|
||||
anonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "",
|
||||
appUrl: process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000",
|
||||
origem: "ambiente",
|
||||
};
|
||||
}
|
||||
|
||||
const arquivo = lerArquivo(".env.local");
|
||||
const url = arquivo.NEXT_PUBLIC_SUPABASE_URL ?? "";
|
||||
const serviceRole = arquivo.SUPABASE_SERVICE_ROLE_KEY ?? "";
|
||||
if (url === "" || serviceRole === "") {
|
||||
throw new Error(
|
||||
"Sem credenciais do Supabase: defina NEXT_PUBLIC_SUPABASE_URL e SUPABASE_SERVICE_ROLE_KEY " +
|
||||
"no ambiente (ex.: `set -a; . ./.env.e2e; set +a`) ou no .env.local.",
|
||||
);
|
||||
}
|
||||
return {
|
||||
url,
|
||||
serviceRole,
|
||||
anonKey: arquivo.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "",
|
||||
appUrl: arquivo.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000",
|
||||
origem: "arquivo",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Diz em voz alta contra QUAL banco o script vai escrever.
|
||||
*
|
||||
* Existe porque o modo de falha caro aqui é silencioso: um seed que escreve na
|
||||
* produção acha exatamente os mesmos dados de teste de sempre e termina com
|
||||
* "✅ Seed completo". Uma linha impressa é o que transforma isso em algo que
|
||||
* alguém pode notar antes de apertar enter na próxima vez.
|
||||
*/
|
||||
export function anunciarDestino(script: string, c: CredenciaisSupabase): void {
|
||||
const local = c.url.startsWith("http://127.0.0.1") || c.url.startsWith("http://localhost");
|
||||
const rotulo = local ? "LOCAL" : "⚠️ REMOTO";
|
||||
console.info(`[${script}] escrevendo em ${rotulo}: ${c.url} (origem: ${c.origem})`);
|
||||
}
|
||||
@@ -14,20 +14,21 @@ import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { generateTotp } from "../tests/e2e/utils/totp";
|
||||
import { anunciarDestino, credenciaisSupabaseDeTeste } from "./lib/env-de-teste";
|
||||
|
||||
// Carrega .env.local manualmente (sem next/env aqui).
|
||||
const envFile = fs.readFileSync(path.join(process.cwd(), ".env.local"), "utf8");
|
||||
const env: Record<string, string> = {};
|
||||
for (const line of envFile.split("\n")) {
|
||||
const m = line.match(/^([A-Z_]+)=(.*)$/);
|
||||
if (m) env[m[1]!] = m[2]!.replace(/^"(.*)"$/, "$1");
|
||||
}
|
||||
|
||||
const SUPABASE_URL = env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||
const SERVICE_ROLE = env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||
if (!SUPABASE_URL || !SERVICE_ROLE) {
|
||||
throw new Error("Missing NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY in .env.local");
|
||||
}
|
||||
// `process.env` VENCE o `.env.local` (ver scripts/lib/env-de-teste.ts).
|
||||
//
|
||||
// A versão anterior lia `.env.local` DIRETO do disco, e por isso a suíte E2E
|
||||
// semeava org, usuários e agentes no banco de PRODUÇÃO: o `.env.e2e` injetado no
|
||||
// webServer do Playwright nunca alcançava este script, porque ele não olhava
|
||||
// para o ambiente. Medido em 2026-08-06 — o factor TOTP em `.e2e-creds.json` não
|
||||
// existia no banco local porque tinha sido criado na nuvem.
|
||||
const credenciais = credenciaisSupabaseDeTeste();
|
||||
anunciarDestino("seed-e2e-credentials", credenciais);
|
||||
const SUPABASE_URL = credenciais.url;
|
||||
const SERVICE_ROLE = credenciais.serviceRole;
|
||||
const ANON_KEY = credenciais.anonKey;
|
||||
const APP_URL = credenciais.appUrl;
|
||||
|
||||
const admin = createClient(SUPABASE_URL, SERVICE_ROLE, {
|
||||
auth: { autoRefreshToken: false, persistSession: false },
|
||||
@@ -204,7 +205,7 @@ async function ensureAdminTotp(adminUserId: string, adminEmail: string): Promise
|
||||
console.log(`[seed] admin TOTP factor removed (rotating): ${f.id}`);
|
||||
}
|
||||
|
||||
const anon = createClient(SUPABASE_URL, env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, {
|
||||
const anon = createClient(SUPABASE_URL, ANON_KEY, {
|
||||
auth: { autoRefreshToken: false, persistSession: false },
|
||||
});
|
||||
const { error: signInErr } = await anon.auth.signInWithPassword({
|
||||
@@ -258,9 +259,9 @@ async function main(): Promise<void> {
|
||||
users,
|
||||
admin_totp: adminTotp,
|
||||
default_agent_id: agentId,
|
||||
app_url: env.NEXT_PUBLIC_APP_URL || "http://localhost:3000",
|
||||
app_url: APP_URL,
|
||||
supabase_url: SUPABASE_URL,
|
||||
supabase_anon_key: env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? "",
|
||||
supabase_anon_key: ANON_KEY,
|
||||
};
|
||||
|
||||
fs.writeFileSync(".e2e-creds.json", JSON.stringify(creds, null, 2));
|
||||
|
||||