diff --git a/TODO.md b/TODO.md index 6c56e8e..8c5a7d8 100644 --- a/TODO.md +++ b/TODO.md @@ -321,20 +321,22 @@ banco. Todos os fixtures de teste foram removidos/desativados ao final. aceite) não exige essa tela. Criar uma tela sem a API por trás seria construir um mockup, o que a diretriz do projeto proíbe. Fica registrado como pendência explícita, não descartado silenciosamente. -- [ ] **Verificação visual em navegador não foi possível nesta sessão** — - ambiente é um servidor Debian headless sem display/browser. A - verificação feita foi: `tsc --noEmit` limpo, `eslint` limpo, build de - produção (`next build`) gerando as 27 rotas sem erro, e testes - funcionais via `curl` reproduzindo exatamente as chamadas que o - navegador faria — login via `/api/auth/login`, cookie de sessão - validado pelo middleware (`/` redireciona para `/login` sem cookie, - libera com cookie), todas as 24 páginas protegidas retornando HTTP - 200 através do Nginx, e os endpoints de dados (`/api/dashboard`, - `/api/reports/*`, `/api/compliance/*` etc.) retornando dados reais - com o mesmo cookie de sessão. O que **não** foi verificado: renderização - visual real, interações de clique/formulário no DOM, responsividade, - tema escuro na prática. Recomenda-se ao usuário abrir - `http://10.10.32.142/` em um navegador para essa validação final. +- [x] **Verificação visual em navegador** — não foi possível durante a + construção (ambiente headless, sem display/browser); feita depois + pelo usuário real em `http://10.10.32.142/`, e **encontrou dois bugs + reais que o teste via curl nunca teria pego**: (1) o cliente HTTP do + frontend mandava `Content-Type: application/json` em requisições sem + corpo, e o Fastify rejeitava com 400 "body is empty" — quebrava + **todo** botão de ação sem payload (disponibilizar agente, pausar, + publicar dialplan, iniciar/pausar campanha, redefinir senha de + ramal); (2) botão "Desbloquear" da lista de bloqueio sempre falhava + porque a API exige motivo no corpo do DELETE e o cliente não tinha + como enviá-lo. Ambos corrigidos na raiz (função `request()` central) + e confirmados pelo usuário: "testei, funcionou". Detalhes em + `docs/TROUBLESHOOTING.md`. **Lição registrada**: testes via curl + provam que o backend funciona, não que o frontend consegue falar com + ele — teste real em navegador continua sendo necessário antes de dar + uma tela por concluída. ## Fase 9 — Segurança e produção - [x] Criptografia de segredos de trunk (AES-256-GCM) — já implementado na diff --git a/docs/RELATORIO_FINAL.md b/docs/RELATORIO_FINAL.md index f8f7796..93eca8d 100644 --- a/docs/RELATORIO_FINAL.md +++ b/docs/RELATORIO_FINAL.md @@ -130,8 +130,12 @@ Itens genuinamente pendentes, nunca escondidos: 3. **`scripts/install.sh`/`update.sh`** não foram testados de ponta a ponta num servidor limpo (o servidor atual já estava provisionado) — validados por inspeção linha a linha contra os comandos manuais já executados. -4. **Verificação visual em navegador real** não foi feita (ambiente - headless) — validado via `curl` reproduzindo as chamadas do navegador. +4. ~~Verificação visual em navegador real~~ — feita posteriormente pelo + usuário em produção. Confirmou que o `curl` sozinho não é suficiente: + achou dois bugs reais de integração frontend↔API invisíveis a testes + via curl (Content-Type em requisições sem corpo quebrando todo botão de + ação; DELETE de supressão sem forma de enviar o motivo exigido). Ambos + corrigidos, usuário confirmou funcionamento. Ver `docs/TROUBLESHOOTING.md`. 5. **Tela de Callbacks** no frontend não existe — o agendamento e a reativação automática funcionam via API/worker (`POST /agent-console/ dispose`, `GET /api/callbacks`, `callback-sweep.ts`), mas não há uma diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index eac6ef4..519e430 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -3,6 +3,39 @@ Problemas reais encontrados durante o desenvolvimento deste projeto e como foram diagnosticados/resolvidos — mantido como referência para o futuro. +## "Body cannot be empty when content-type is set to 'application/json'" em quase todo botão de ação + +**Sintoma**: usuário real testando no navegador (não `curl`) relatou erro +"body is empty" ao clicar em redefinir senha de um ramal. Investigação +mostrou que o mesmo bug afetava **todo** botão de ação sem payload: +disponibilizar agente, pausar/despausar, publicar dialplan, iniciar/ +pausar/parar campanha, etc. + +**Causa**: o cliente HTTP central do frontend +(`apps/frontend/src/lib/api-client.ts`) sempre enviava +`Content-Type: application/json` mesmo em requisições sem corpo. O +Fastify rejeita isso com 400 antes de a requisição chegar ao controller +— o `@Body()` do NestJS nem entra em cena, o parser de body do Fastify já +barra antes. + +**Por que passou despercebido por tanto tempo**: todo teste de API deste +projeto, em toda fase anterior, foi feito via `curl` — e `curl -X POST` +sem `-H 'Content-Type: ...'` e sem `-d` não define esse header. O +`fetch()` do navegador (usado pelo cliente HTTP real do frontend) sempre +define o header por padrão. Ou seja: **testar só via curl não teria +pegado nunca esse bug** — só apareceu quando um usuário de verdade clicou +em botões reais no navegador. Lição: `curl` prova que o *backend* +funciona: não prova que o *frontend* consegue falar com ele. + +**Fix**: só incluir o header `Content-Type` quando `body !== undefined`, +na única função `request()` usada por `get`/`post`/`patch`/`delete`. + +**Bug relacionado, achado na mesma revisão**: `DELETE /api/suppression/:id` +exige `removalReason` no corpo, mas `api.delete()` nem aceitava um +argumento de corpo — o botão "Desbloquear" da lista de bloqueio sempre +falhava com 403. Mesma causa raiz (função de ação sem forma de levar +payload), mesmo tipo de bug que só um clique real revela. + ## "Argument calledNumber is missing" ao originar chamada **Sintoma**: `PrismaClientValidationError` ao reservar um lead, mesmo a