Files
DeskcommCRM/tests/sonda-overflow-controle.ts
jmpoandClaude Opus 5 f691b9c8f8 merge: 198 commits da main — duas convergências e um desfecho a corrigir
O fork estava 198 commits atrás. A `main` já tinha absorvido o PR #200
(canal Zernio) e renumerado as migrations que vieram daqui, então boa
parte do conflito era o MESMO trabalho com dois números.

─── O que era duplicata ───

`0117_canal_zernio_vocabulario` e `0118_zernio_envio` diferem de `0131` e
`0132` em UMA linha: o número no comentário do cabeçalho. São as minhas,
renumeradas lá. Ficam as de lá — numeração é verdade compartilhada, e
manter as duas deixaria o mesmo schema com dois nomes.

`0119_contato_lookup_telefone` e `0122_telefone_do_lid` PARECIAM a mesma
convergência e não são: uma acrescenta `phone_lookup_at` (fila do cron de
backfill), a outra `wa_lid` (coluna gerada + dedup). Convivem.

─── O defeito que o merge criaria, e que ninguém veria ───

A `0139_kind_check_completo` reconstrói `agent_inbox_items_kind_check`, e
a minha `0120` também. Pelo timestamp a minha roda DEPOIS — e a lista
dela não conhecia `midia_nao_lida` nem `contact_proposal_expired`.
Aplicar as duas em ordem, num clone atualizado, APAGARIA dois kinds em
uso: quem reconstrói por último é quem manda, e uma lista incompleta não
é "aditiva", é uma remoção silenciosa. A `0120` passou a trazer a lista
inteira.

No `baseline.sql` continua UM bloco só para essa constraint, agora com os
kinds dos dois lados — a regra do #159, que o `update.sh` de todo clone
depende.

─── Onde discordei da main, e por quê ───

`zernio.ts` — `isConfigured()` fica `true`, e não
`zernioCredsFromEnv() !== null`.

A preocupação de lá é real: `isConfigured() === true` não pode implicar
"sem credencial", porque o handler grava `sent` quando `send()` não
lança. Só que quem fecha esse par mudou de lugar — `send()` LANÇA
`zernio_not_configured`, e o caso descrito naquele comentário ("devolve
`{externalId: null}` SEM lançar") não existe mais no arquivo.

Exigir env reintroduziria um defeito medido: `resolveZernioCreds` procura
primeiro na SESSÃO e só depois no ambiente. Uma instalação que conectou
pelo botão tem a credencial no banco e nada no `.env` — a checagem por
env diria "não configurado" para um canal conectado e funcionando, e toda
mensagem ficaria em `queued` sem tentar sair.

Mas o teste de lá estava certo no DESFECHO: sem credencial nenhuma, o
certo é `queued` com o motivo, não `failed`. Isso não estava atendido de
nenhum dos dois lados — o `catch` do handler marcava `failed`. Agora
`notConfigured` é traduzido para `queued`, ao lado do
`storage_sign_failed` que já tinha tratamento próprio: falta de
credencial não é falha DESTA mensagem, é canal ainda não conectado, e
marcar `failed` faria o follow-up desistir de algo que sai sozinho assim
que alguém conectar.

Os dez casos de `messages-handler-canal-intermediado` passam.

─── O que fica vermelho, e não é daqui ───

`lib/ai/dispatcher/rate-limit.test.ts` — 5 casos estourando o timeout de
15s. Falha também com os arquivos da main trocados um a um, então não
vem desta resolução; o CI de lá está verde, então é diferença de
ambiente. Fica registrado, não escondido.

3647 casos passam. typecheck, lint:channels e build limpos.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 11:57:33 -03:00

24 lines
1.3 KiB
TypeScript

import { chromium } from "@playwright/test";
import { readFileSync } from "node:fs";
const BASE = "http://127.0.0.1:3100";
const creds = JSON.parse(readFileSync("/Users/rafaelmelgaco/DeskcommCRM/.e2e-creds.json", "utf8"));
async function main() {
const b = await chromium.launch();
const p = await b.newPage({ viewport: { width: 390, height: 844 } });
await p.goto(`${BASE}/login`, { waitUntil: "domcontentloaded" });
await p.click('input[type="email"]');
await p.locator('input[type="email"]').pressSequentially(creds.users.manager.email, { delay: 8 });
await p.click('input[type="password"]');
await p.locator('input[type="password"]').pressSequentially(creds.password, { delay: 8 });
await p.click('button[type="submit"]');
await p.waitForURL(/\/app/, { timeout: 30000 });
for (const rota of ["/app/inbox", "/app/kanban", "/app/metrics"]) {
await p.goto(`${BASE}${rota}`, { waitUntil: "networkidle" });
await p.waitForTimeout(1200);
const m = await p.evaluate(`({ sw: document.documentElement.scrollWidth, cw: document.documentElement.clientWidth })`) as { sw: number; cw: number };
console.log(`${rota.padEnd(14)} scrollWidth=${m.sw} clientWidth=${m.cw} overflow=${m.sw > m.cw}`);
}
await b.close();
}
main().catch((e) => { console.error(e); process.exit(1); });