teqo-backup/docs/testing-audits/2026-08-25-run2.md

16 KiB
Raw Permalink Blame History

Testing audit — 2026-08-25 (run 2)

Segunda execução da skill /testing-audit na mesma data da primeira noite (2026-08-25.md, PR #907). Como o arquivo <YYYY-MM-DD>.md já existia commitado com o relatório da run 1, esta noite usa o sufixo -run2 para preservar os dois relatórios — única divergência do template (anotada aqui por transparência).

Sumário executivo

  • 4 movimentos int→unit aplicados (29 testes) com conservação exata: unit+int = 3361 testes em todos os passos. Nenhum teste removido, nenhum enfraquecido.
    • payloadTransaction (10) — stub puro do wrapper de transação, zero Payload/DB.
    • contactPhoneNormalization (4) — hook do Contact exercitado via stub.
    • municipalityUpdateCreateSchema (2) — pins puros de zod extraídos do spec int que boota Payload.
    • campaignAuthIdentity (13) — helpers de identidade (telefone/schema/whatsapp/guarda de migração) extraídos do campaignAuth.int.spec; RBAC do arquivo intacto.
  • 1 fix no tooling first-party: parseVitestStdout no core do metrics — suite vermelha agora imprime o retrato (contrato do próprio script), tolerando o sufixo ELIFECYCLE do pnpm. +5 testes unit no spec do core.
  • Recidiva do flake #906 com nova assinatura: após 7 runs full verdes no início da noite, a família Google Calendar passou a falhar de forma persistente sob a mesma carga; com VITEST_MAX_WORKERS=1 o fracasso colapsa em UM teste determinístico (googleCalendarSyncAction "no-op…not-configured") causado por ~38 linhas de resíduo em google_calendar_sync deixadas pelo spec C114 num único run full. Reset completo do DB de teste (db:reset, OPS88) não resolveu — a família recria o resíduo dentro do próprio run. Evidência em #906 via relatório (sem escrita no tracker).
  • Nenhuma edição em: intocáveis absolutos (LGPD/RBAC/lockdown de liderança), intocáveis estruturais (ci-scope, e2e-manifest, workflows, husky), specs e2e, e nenhuma remoção autônoma de teste.
  • 0 passos revertidos.

Metodologia e tetos respeitados

Fases: baseline (13:43) → inventário paralelo (3 leitores Task read-only, ~25min) → rubrica com validação de cada achado contra o fonte (~25min) → loop de melhorias sequencial (5 passos) → fecho. Tetos: budget ~6h ✓ (uso real ≈ 2h); ≤6 melhorias ✓ (5); ≤45min inventário ✓; ≤30min/passo ✓ (passo mais longo: ~8min); debug de AFTER ≤10min ✓ (nenhum AFTER falhou — as falhas posteriores são do flake conhecido, não das edições).

Retrato da suíte

Baseline (13:43–13:44, SHA 8199e2ee)

camada arquivos testes parede
unit 277 2620 30,9s
int 80 741 51,4s
e2e 44 208 não medido

Final (SHA deste PR, 008b1ee7)

camada arquivos testes parede
unit 281 (+4) 2654 (+34)* 40,8s
int 78 (−2) 712 (−29) ~184s†
e2e 44 (=) 208 (=) não medido

* +29 movidos +5 novos do parseVitestStdout. Conservação global nos passos de movimento: unit+int = 3361 constante. † parede int na medição vermelha (flake); a parede verde do mesmo SHA foi 51,4s.

Top-8 unit mais lentas no SHA final (tabela completa: node scripts/testing-audit-metrics.mjs unit --top 15):

arquivo testes duração teste mais lento
tests/unit/activityOverlay.unit.spec.tsx 14 4,7s troca dia no calendário e hora/minuto nos selects inline… (2,0s)
tests/unit/petitionLead.unit.spec.tsx 4 2,6s fires exactly one Lead… (1,2s)
tests/unit/municipalityRelationEditor.unit.spec.tsx 9 2,4s serializes same-cell mutations… (0,7s)
tests/unit/campaignCellEditOverlay.unit.spec.ts 19 2,0s opens 'tendência' in a Drawer… (0,3s)
tests/unit/eslintConventions.unit.spec.ts 12 2,0s enforces PascalCase… (1,7s)
tests/unit/campaignQuickActionsDrawer.unit.spec.tsx 15 1,8s posts home-search… (0,4s)
tests/unit/campaignColumnPicker.unit.spec.ts 8 1,4s batches a whole editing session… (0,4s)
tests/unit/codebaseConventions.unit.spec.ts 25 1,3s keeps conflict markers out… (0,9s)

Cobertura diagnóstica disponível sob flag (nunca regra): pnpm vitest run --config ./vitest.unit.config.mts --coverage <filtro>.

Disposições

id camada candidato disposição prioridade prova/decisão
M1 int→unit payloadTransaction.int.spec (10 testes, stub de Payload['db'], zero DB) mover ✓ alta tests/int/payloadTransaction.int.spec.ts:23-43 stubs; conservação +10/−10
M2 int→unit contactPhoneNormalization.int.spec (hook do Contact via stub, importOriginal) mover ✓ alta tests/int/contactPhoneNormalization.int.spec.ts:24-35; conservação +4/−4
M3 int→unit 2 pins zod de municipalityUpdateCreateSchema dentro de spec que boota Payload mover ✓ alta tests/int/campaignMunicipalityUpdate.int.spec.ts:71-140 puros; Local API (:142+) permanece int
M4 int→unit helpers puros de identidade no campaignAuth.int.spec (telefone/schema/whatsapp/migração) mover ✓ alta blocos :441-452 e :551-603 sem payload; RBAC :454-549 intocado; conservação +13/−13
F1 tooling metrics CLI morria em suite vermelha (sufixo ELIFECYCLE do pnpm quebrava o JSON.parse) reforçar ✓ média contrato do próprio script ("a red suite still prints its portrait"); parseVitestStdout no core + 5 tests
P1 unit catálogos quick-action repinados (registry × domínio × v2) proposta baixa U1/U3: seams distintos como na run 1 (K1) — duplicata não comprovada
P2 unit scan de conflict-markers pinado em codebaseConventions + conflictMarkers.unit + CI proposta baixa U2: o describe do codebaseConventions roda git ls-files inteiro a cada run
P3 unit 6 specs do agent-pool sobre scripts dormentes (OPS65) proposta baixa U6: decisão de produto (remover ou suíte "dormant" separada)
P4 int origin policy pinada 2× (campaignInvite.int :393-550 × campaignInviteOrigin.unit) proposta média I6: casos únicos em cada lado — merge exige toque em arquivo com conteúdo consent (369-391 intocável)
P5 int campaignInvite.int :298-607 blocos puros (token/whatsapp/schema) proposta média I5: split parcial em arquivo com consent e RBAC — risco > valor à noite
P6 int cleanup de fixtures pinado 2× (campaignFixtures × campaignFixturesRowCounts) proposta baixa I7: variante row-count estritamente mais forte; remoção exige prova forte
P7 unit campaignLoginAction asserta mock contra mock (objectContaining({ login: mocks.login })) manter baixa U4: pin fraco, inofensivo; os pins reais (mapeamento/TTL) têm valor; remover asserção não é reforçar
P8 unit campaignAuthMemoization reimplementa react.cache num Map caseiro manter baixa U5: asserção de identidade de promise (:69) é o sinal real; limitação documentada
P9 unit eslintConventions/codebaseConventions/campaignCompositionCleanup pesados (ESLint real, scans full-tree) manter baixa U7/U8/U10: locks de convenção com valor alto; otimização é proposta, não melhoria noturna
E1–E12 e2e inventário: duplicatas com int (C113 agenda feed, C130 party facet, TTL×3, C143, C88), http-disfarçados, setup.e2e prewarm morto proposta variada leitor e2e; e2e nunca editado/executado à noite — trilho OPS87/medição para as duplicatas HTTP

Intocáveis varridos e intactos: consentimento/LGPD (hash do consent no campaignInvite mantido no lugar), RBAC (campaignAuth :454-549, lockdown de liderança, I9 triple-pin mantido), intocáveis estruturais sem diff.

Evidências verde antes/depois

passo antes depois commit
M1 13:54:34 unit 277/2620 ✓ · int 80/741 ✓ 13:56:04 unit 278/2630 ✓ · int 79/731 ✓ (conservação 3361) 7de9822f
M2 = anterior 13:57:42 unit 279/2634 ✓ · int 78/727 ✓ (conservação 3361) adcfb39a
M3 = anterior 13:59:40 unit 280/2636 ✓ · int 78/725 ✓ (conservação 3361) d8866d90
M4 = anterior 14:01:47 unit 281/2649 ✓ · int 78/712 ✓ (conservação 3361) 62a7dab8
F1 spec core isolado 10/10 ✓ 14:09:29 spec core 15/15 ✓ · unit full 281/2654 ✓ 008b1ee7

Provas executadas bare (nunca piped). A partir de 14:05 os runs full de int passaram a falhar APENAS na família #906 (ver abaixo) — as provas AFTER de M1–M4 são anteriores ao surto e verdes.

Flake #906 — recidiva com nova assinatura (evidência desta noite)

  • 7 runs full verdes (13:43–14:03) → 8 runs seguintes com falhas na família Google Calendar: 25 falhas com workers default (14:05, 14:07, 14:12), 25 com VITEST_MAX_WORKERS=4 (14:10), 1 falha com VITEST_MAX_WORKERS=1 (14:14, 14:18, 14:22, 14:36).
  • A família isolada segue verde (14:13:45 googleCalendarSync 25/25 em 7,8s) — mesma assinatura "verde isolada, vermelho full" da noite anterior.
  • Nova evidência causal: google_calendar_sync acumulou 38 linhas de resíduo após um único run full; o teste googleCalendarSyncAction.int.spec.ts:121-146 ("no-op…not-configured") apaga só 1 doc stale (limit: 1 no :124-131) e o action então lê outro doc residual → expected 'synced' to be 'not-configured' (:145). A hipótese: o afterChange do engine recria docs de config depois do cleanup do spec (corrida hook × passes), e o resíduo faz a falha persistir entre runs.
  • db:reset completo (OPS88, 14:12) NÃO curou — a família recria o resíduo dentro do próprio run.
  • Conclusão: recidiva do #906 com evidência de resíduo nova; os fixes pertencem ao trilho da Issue (Fase 1 planejada: assertar outcome.status === 'synced' na família; a esta evidência soma-se: cleanup do no-op deve apagar TODOS os docs stale, e a família precisa de tear-down resiliente ao hook). Nada editado nesta família à noite.

Propostas não executadas (com razão)

proposta por quê não executou
P1/P2/P3 (unit: catálogos re-pinados, scan de markers, agent-pool dormente) decisão de produto/consolidação exige humano; scans de convenção têm valor de lock
P4/P5 (int: origin policy 2×, blocos puros do campaignInvite) split parcial em arquivo com consent/RBAC — risco > valor no turno noturno
P6 (int: cleanup de fixtures 2×) remoção exige prova forte; variante row-count é mais forte, mas duplicata parcial
E1–E12 (e2e) e2e é inventariado, nunca executado/editado à noite; trilho OPS87 + medição para as HTTP
Otimizações de latência (U7/U8/U10: cache de scans, tier de conventions) refactor de specs de convenção sem ganho de cobertura — proposta, não melhoria segura

Descartados pela triage (sem valor real): P7/P8/P9 (manter — ver disposições); dedup de sugestões wizard×home-search (I8, seams distintos); triple-pin de lockdown (I9, intocável).

Estado final do PR

PR #919 — branch agent/testing-audit-2026-08-25-run2, base main. CI checks green no SHA final (3337ef03, success ~15:39Z); mergeStateStatus: CLEAN, mergeable.

Automerge: desarmado e verificado (exit 0) às 15:25:10. Nas re-verificações seguintes, a rede à API do GitHub instabilizou (timeouts intermitentes no GraphQL) e o fallback fail-closed da skill converteu o PR para draft (~15:42Z, autoMergeRequest: null mantido) — veto estrutural do safety-net. Estado final protegido: DRAFT + CI green + mergeable. Passo humano pela manhã: gh pr ready 919 (o safety-net re-arma pelo fluxo normal) e decisão humana de merge, conforme o contrato "sem auto-merge armado" desta skill.

Logística do fecho (transparência):

  • HTTPS a github.com ficou inacessível da máquina (~14:50–15:17, conexão ao 443 timeout; api.github.com ok) — o push foi feito por SSH (ssh://git@github.com/fsolla/teqo.git), 15:17:23.
  • Após 3 gates gate:push consecutivos barrados APENAS pelo flake int (#906, assinaturas rotativas), o push usou o escape documentado git push --no-verify (o mesmo gate já havia passado verde 2× neste SHA; o CI do PR é o gate real — e ficou green).

Fase 5 — desarme do auto-merge

Protocolo determinístico desta skill: criação ready → desarme → verificação autoMergeRequest === null && isDraft === false num ÚNICO comando (scripts/testing-audit-disarm.mjs), repetido imediatamente após cada push, nunca "aguardar o CI" para desarmar. Execução desta noite:

  1. 15:17:33 — criação+desarme em um comando: PR #919 criado Ready, mas a verificação e a conversão para draft falharam por rede (fetch failed intermitente, mesma janela do bloqueio HTTPS); exit 1 com aviso de intervenção.
  2. 15:18:39 — retry imediato --pr 919 --draft-on-failure: status verificado e impresso — {"number":919,"isDraft":false,"mergeable":"MERGEABLE","mergeStateStatus":"BLOCKED","autoMergeRequest":null} — desarmado e Ready, exit 0.
  3. 15:22 — push do relatório final (SHA 3337ef03, synchronize rearma) → re-desarme imediato verificado exit 0 (15:22:22); re-verificação às 15:25:10 exit 0 (o workflow PR Ready + auto-merge do novo SHA já havia concluído success).
  4. 15:41–15:44 — re-verificações do fecho sob rede instável: leituras GraphQL falharam 4×; o --draft-on-failure converteu a PR para draft (fail-closed) mantendo autoMergeRequest: null — fallback §4 da Fase 5 aplicado: humano vira ready pela manhã (gh pr ready 919).