Pular para conteúdo

0022 — A faxina do e2e e quatro specs falam com um container de banco que não existe mais

Status CORRIGIDO
Branch fix/0022-faxina-e2e-container-inexistente
Aberto em 2026-09-27
Severidade MÉDIA
Apps afetados e2e
Revisão do diagnóstico aprovado na rodada 1
Revisão da correção aprovado na rodada 1

Status: RASCUNHO → DIAGNOSTICADO → EM CORREÇÃO → CORRIGIDO

O fix atravessa dois portões, cada um revisado por um subagente de contexto limpo, em até 3 rodadas. Máquina do plugin superpowers, régua em .claude/rubrics/. Qualquer Critical reprova; Important em aberto reprova salvo decisão registrada do usuário.

  1. O diagnóstico (seções 1 a 4) é revisado na seção 5 (reprodução × causa raiz).
  2. A correção (o diff e os testes) é revisada na seção 11 (correção × regressão).

1. Sintoma

A suíte e2e deixou de devolver o banco do dev-remote ao estado em que o encontrou. O globalTeardown que apaga o que a execução criou não apaga nada, e não diz isso no fim da execução: o único rastro é um aviso no começo, no globalSetup:

[limpeza] não foi possível marcar o início: Command failed: docker exec garantora-devremote-db-1 psql …
Error response from daemon: No such container: garantora-devremote-db-1

O efeito aparece no banco compartilhado. Imobiliárias criadas por dia no garantora_db do dev-remote (consulta de só leitura, feita em 2026-09-27):

Período Imobiliárias criadas
2026-08-25 a 2026-09-05 (antes da troca do banco) 6 em quatro dias com registro
2026-09-10 a 2026-09-24 (depois da troca do banco) 113 em oito dias com registro, até 34 num dia só

É o mesmo acúmulo que a faxina foi criada para acabar (fixtures/limpeza.ts:4-13: 3870 imobiliárias, e a do seed caindo para fora da primeira página de uma busca).

2. Reprodução

  1. Na VPS do dev-remote — a máquina onde a suíte roda, o que torna válidos o docker exec e o 127.0.0.1:8025 —, com o .env do e2e apontando DB_CONTAINER para o valor do .env.example (apps/e2e/.env.example:13):
    docker exec garantora-devremote-db-1 psql -U garantora -d garantora_db -tAc 'SELECT now()'
    
  2. O comando termina com exit 1 e Error response from daemon: No such container: garantora-devremote-db-1. É exatamente o comando que marcarInicio() executa (fixtures/limpeza.ts:71-76, chamado em :107).
  3. docker ps não lista container de banco do Garantora: os dele são só garantora-devremote-{api,worker,web,docs}-1 (a VPS também hospeda containers de outros projetos).
  4. Depois de qualquer execução da suíte, apps/e2e/.limpeza-inicio não existe, e as linhas criadas durante a execução continuam no banco.

Reproduz toda vez: é determinístico, e não depende de spec, papel nem viewport.

  • Ambiente: dev-remote (o único alvo do e2e).
  • Superfície: não se aplica — é infraestrutura de teste, sem tela.
  • Dados/pré-condições: o .env do e2e com DB_CONTAINER=garantora-devremote-db-1, como manda o .env.example.

3. Esperado × Atual

  • Esperado: o globalSetup grava o instante de início, e o globalTeardown apaga, numa transação, o que nasceu depois dele, imprimindo a contagem do que removeu. Os specs que manipulam o banco direto (§4, alcance) alcançam o garantora_db do dev-remote.
  • Atual: nenhum comando alcança o banco. A faxina não roda e só avisa no começo; os specs que dependem do banco lançam exceção ou engolem o erro.

4. Causa raiz

Onde. A variável e o comando:

  • apps/e2e/.env.example:13: DB_CONTAINER=garantora-devremote-db-1, o nome do container PostgreSQL que o compose do dev-remote tinha até 2026-09-07.
  • apps/e2e/fixtures/limpeza.ts:73 e :82: docker exec ${DB_CONTAINER} psql …. O acesso ao banco pressupõe que ele é um container.

Regressão, e de qual mudança: o commit c27fe88 (2026-09-07, "infra(dev-remote): db/redis/mailpit para serviços nativos da VPS, Caddy sai") migrou o PostgreSQL do dev-remote para um serviço nativo do host (systemd). O garantora_db passou a viver no PostgreSQL nativo, e o container garantora-devremote-db-1 deixou de existir (infra/dev-remote/docker-compose.yml:17-22). O volume antigo ficou como backup (garantora-devremote_postgres_data, segundo a mensagem do commit). O commit atualizou o compose, o README e a plataforma de documentação, mas não o e2e.

O mecanismo — por que isso produz este sintoma, e em silêncio no fim.

  1. marcarInicio() (limpeza.ts:100) chama psql('SELECT now()') (:107). O execSync do docker exec sai com código 1, e o execSync lança.
  2. O catch de :109-113 transforma a exceção em console.warn e segue, de propósito: a suíte mede a aplicação, não a faxina. Por isso o arquivo de marca .limpeza-inicio nunca é gravado (:108 não chega a rodar).
  3. No fim, limparOQueASuiteCriou() (:117) encontra a marca ausente e retorna em :119 (if (!fs.existsSync(MARCA)) return;) sem imprimir nada. O teardown termina limpo, sem apagar e sem avisar.
  4. O único sinal é o aviso do passo 2, emitido no começo da execução e soterrado pela saída dos testes. O acúmulo da §1 começa logo depois de 2026-09-07.

O alcance — onde mais a mesma premissa morde. Procurei DB_CONTAINER e docker exec em todo o apps/e2e. Além da faxina, quatro specs falam com o banco pelo mesmo caminho, todos com default garantora-db-1 e lendo o mesmo DB_CONTAINER do .env:

Arquivo Linha O que faz com o banco Como reage à falha (lido no código, não executado)
tests/e2e/password-reset.spec.ts :18, :20-31 apaga os códigos de recuperação antes do teste catch vazio em :28 engole o erro; o teste segue e pode esbarrar no rate limit (o próprio comentário diz isso)
tests/e2e/remocao-logica.spec.ts :121, :123-128 empurra deleted_at para o passado (RN5) execSync sem catch: lança, e o teste falha nesse passo
tests/e2e/documento-corrompido.spec.ts :75, :85-92 trunca o conteúdo de um documento para reproduzir o estado do bug execSync sem catch: lança, e o teste falha nesse passo
tests/e2e/onboarding-leva-ao-painel.spec.ts :33, :63-71 lê o código de verificação de e-mail execSync sem catch: lança, e o teste falha nesse passo

Não rodei esses quatro specs para medir a falha: o diagnóstico se apoia no comando reproduzido na §2, que é o mesmo nos cinco arquivos, e na leitura do código.

Fora do alcance, conferido. O MAIL_API (http://127.0.0.1:8025/mail/api/v1) continua certo: o Mailpit também virou nativo, mas na mesma porta do host, e quem fala com ele é HTTP, não docker exec. A API e o worker alcançam o banco nativo por host.docker.internal e não são afetados. Documentação que ainda cita o container antigo: apps/e2e/CLAUDE.md (Environment Setup), os comentários dos cinco arquivos e as specs 0046 e 0051, que são registro histórico e não se reescrevem.

O que já está descartado. Não é permissão do pg_hba, credencial nem o PostgreSQL fora do ar: o erro vem do daemon do Docker, antes de qualquer conexão com o banco. E a API do dev-remote responde normalmente contra o banco nativo (verificado no commit c27fe88).

5. Revisão do diagnóstico

Rodada Critical Important Minor Veredicto
1 0 0 3 aprovado

Resultado: aprovado na rodada 1. Os três Minor (citação uma linha fora em onboarding-leva-ao-painel.spec.ts, o docker ps descrito de forma não literal, a fonte do volume de backup) foram corrigidos no texto.

Pendências aceitas: nenhuma. Recomendação do revisor, levada para o implement: confirmar ao menos um dos quatro specs em execução, como prova do CA2.

6. Correção

O defeito era a premissa "o banco é um container", repetida em cinco arquivos. A correção tira a premissa: um acesso só, que conecta por URL e não supõe como o banco roda.

apps/api

Sem impacto.

apps/web

Sem impacto.

apps/e2e

  • fixtures/banco.ts (novo): consultar(sql) e executarEmTransacao(sql) chamam o psql do host com execFileSync (sem shell no meio). A URL de E2E_DATABASE_URL é decomposta nas variáveis PG* do processo filho: a senha não vai na linha de comando, onde apareceria em ps numa VPS compartilhada. Sem a variável, lança BancoNaoConfigurado com a mensagem E2E_DATABASE_URL não definido.
  • fixtures/limpeza.ts: usa o banco.ts no lugar do docker exec. Mantém a salvaguarda (sem alvo configurado, não limpa e diz por quê). O teardown sem marca de início agora avisa em vez de retornar calado (CA4). marcarInicio e limparOQueASuiteCriou aceitam o caminho da marca, com o de sempre como padrão, para o teste não disputar a marca real. E2E_SEM_LIMPEZA passou a ser lida na chamada, não na importação.
  • Os quatro specs da §4: password-reset, remocao-logica, documento-corrompido e onboarding-leva-ao-painel trocam o docker exec por consultar. Cada um mantém a reação que tinha: o password-reset segue engolindo a falha; os outros três lançam.
  • .env.example e CLAUDE.md: E2E_DATABASE_URL no lugar de DB_CONTAINER, com os dois formatos (dev-remote e local) e de onde vem a senha do dev-remote.
  • Teste de regressão: tests/e2e/limpeza.spec.ts, contra o banco de verdade, um caso por comportamento: a marcação consulta o banco e grava a marca; o teardown sem marca avisa; sem URL, não marca e diz por quê.

7. Risco / impacto colateral

A faxina executa DELETE em doze tabelas. Qualquer correção que mude como ela chega ao banco precisa manter a salvaguarda de limpeza.ts:31-41: sem alvo explícito, não limpa. Apontar para o banco errado apaga trabalho do time. Não toca dinheiro nem regra de negócio.

8. Critérios de aceite

  • [x] CA1. Seguindo a §2, o acesso configurado no .env alcança o garantora_db do dev-remote, e uma execução da suíte grava .limpeza-inicio e, no teardown, imprime [limpeza] removido: …, com as linhas criadas pela execução apagadas.
  • [x] CA2. Os quatro specs da §4 alcançam o banco pelo mesmo caminho da faxina.
  • [x] CA3. Sem alvo configurado, a faxina continua não limpando e dizendo por quê.
  • [x] CA4. Quando a marca de início não existe, o teardown diz isso em vez de retornar calado — o silêncio de limpeza.ts:119 é o que escondeu este defeito por três semanas.
  • [x] CA5. .env.example e apps/e2e/CLAUDE.md descrevem o acesso novo.

9. Plano

  • [x] Escrever o teste que reproduz o bug e vê-lo falhar — antes de qualquer correção
  • [x] Corrigir a causa raiz no apps/e2e
  • [x] Red-green verificado: teste passa → reverter a correção → rodar → falha → restaurar → passa
  • [x] make preflight limpo. O make lint não cobre o apps/e2e (é ruff na API e tsc no web), e o e2e não tem tsc próprio; a checagem de sintaxe foi o npx playwright test --list dos cinco arquivos, que transpila cada um

10. Decisões em aberto

  • [x] Como o e2e alcança o PostgreSQL nativo. Decidido pelo usuário em 2026-09-27: uma URL no .env do e2e, E2E_DATABASE_URL, no lugar do DB_CONTAINER, e o psql do host conecta por ela. O mesmo formato serve ao dev-remote (o PostgreSQL nativo aceita senha em 127.0.0.1, pg_hba.conf) e à stack local (que publica a 5432). Sem a variável, a faxina continua não limpando e diz por quê. A senha do dev-remote o usuário copia para o apps/e2e/.env, que o git ignora; a correção não lê o infra/dev-remote/.env.

11. Revisão da correção

Rodada Critical Important Minor Veredicto
1 0 0 3 aprovado

O teste sobreviveria à reversão? Não. Revertidos fixtures/limpeza.ts (para o do HEAD) e fixtures/banco.ts (removido), com DB_CONTAINER=garantora-devremote-db-1 como o .env antigo mandava, npx playwright test tests/e2e/limpeza.spec.ts --project=chromium --no-deps deu 3 failed: o primeiro caso em a marcação não deveria ter avisado nada, recebendo No such container: garantora-devremote-db-1 — o defeito exato —; o segundo sem o aviso de marca ausente; o terceiro com a mensagem do docker exec no lugar de E2E_DATABASE_URL não definido. Restaurada a correção, 3 passed.

Uma primeira reversão rodada com E2E_SEM_LIMPEZA=1 também deu 3 failed, mas o primeiro caso falhou por outro motivo: o código revertido lê essa variável na importação, e a faxina já estava desligada quando o teste tentou religá-la. Foi descartada como prova e refeita sem a variável; é a que está acima.

Resultado: aprovado na rodada 1. Os três Minor foram corrigidos: a documentação diz que caractere especial da senha vai codificado na URL (.env.example, CLAUDE.md); o teste remove o diretório temporário de cada caso; e o E2E_SEM_LIMPEZA do processo do teste é restaurado depois de cada caso. Relançado, limpeza.spec.ts deu 3 passed, sem resíduo em /tmp.

Defeitos aceitos: nenhum.

12. Registro

  • Decisão de acesso (§10): URL no .env do e2e. O usuário configurou a do dev-remote com a senha do POSTGRES_PASSWORD do infra/dev-remote/.env, trocando host.docker.internal por 127.0.0.1 e postgresql+asyncpg:// por postgresql://.
  • Prova em execução (CA1 e CA2): os cinco arquivos tocados rodados juntos, contra o dev-remote, com a faxina ligada — 22 passed em 1,1 min. O teardown imprimiu [limpeza] removido: credit_analysis_status_history=17 credit_analysis_participants=5 decisions=4 billings=48 guarantees=4 documents=8 credit_analyses=5 properties=5 renters=11 audit_logs=78 e imobiliárias: 119 → 119. Não precisou de /devremote-rebuild: o fix não toca api nem web, e o e2e roda do host.
  • Divergiu do plano: os quatro specs perderam o default garantora-db-1 da stack local. Agora exigem E2E_DATABASE_URL em qualquer ambiente, e a URL local está no .env.example. O host passa a precisar do cliente psql, dito no CLAUDE.md do e2e.
  • Fora do escopo, e fica com o usuário: o que acumulou entre 2026-09-07 e hoje continua no banco (as 113 imobiliárias da §1, entre outros registros). A faxina só apaga o que nasce durante a execução; limpar o acúmulo é decisão à parte.