15 KiB
Testing audit — 2026-08-25
Primeira execução da skill /testing-audit (OPS96, Issue #898 · impl plan ops96-skill-testing-audit-impl.md). Auditoria + implementação das melhorias seguras na mesma noite; PR único ready, sem auto-merge armado.
Sumário executivo
- 16 specs movidos de
tests/intparatests/unitcom conservação exata de totais (prova de movimento puro: soma das camadas constante em 3333 → 3344 testes, delta explicado pelos testes novos do próprio tooling). Nenhum teste foi removido. - 1 assertion reforçada (
api.int.spec.ts) e 1 referência morta corrigida (measure-e2e-family.mjsapontava família e2e removida). - Tooling novo:
scripts/testing-audit-metrics.mjs(+ core puro testado) e cobertura first-party sob flag (@vitest/coverage-v8). - Flake documentado com evidência forte e registrado como #906: família Google Calendar int falhou em 4 de 7 runs full tonight (sempre verde isolada).
- Nenhuma edição em: ci-scope/e2e-manifest/workflows/husky (intocáveis estruturais), specs e2e, arquivos LGPD/RBAC/liderança (intocáveis absolutos), nem remoções autônomas — as propostas ficam na seção própria abaixo.
Metodologia e tetos respeitados
Fases da SKILL.md executadas na ordem: baseline (00:41) → inventário paralelo por 3 leitores read-only (~25min, unit/int/e2e) → rubrica com validação de cada achado contra o fonte (~20min) → loop de melhorias sequencial → fecho. Tetos: budget ~6h ✓ (uso real ≈ 3h30 até este relatório); ≤6 melhorias ✓ (5 aplicadas); ≤45min inventário ✓; ≤30min/passo ✓; debug ≤10min ✓ (sonda do flake revertida após diagnóstico).
Provas: cada passo provou a suíte relevante verde antes/depois com contagens literais. A partir do run D, os runs int usaram VITEST_MAX_WORKERS=4 — mesmo knob documentado no vitest.config.mts para contenção — porque outras sessões saturavam a máquina (load 16/16 núcleos).
Retrato da suíte
Baseline (00:41, SHA inicial da noite)
| camada | arquivos | testes | parede |
|---|---|---|---|
| unit | 259 | 2488 | 19,1s |
| int | 96 | 845 | 53,6s |
| e2e | 44 | 208 | não medido — execução noturna proibida |
Final (SHA deste PR)
| camada | arquivos | testes | parede |
|---|---|---|---|
| unit | 276 (+17)* | 2603 (+115)* | 63,1s† |
| int | 80 (−16) | 741 (−104) | ~55s‡ |
| e2e | 44 (=) | 208 (=) | não medido |
* unit ganhou os 16 specs movidos + o spec novo do tooling (testingAuditMetricsCore, 10 testes). Delta de testes +115 = testes movidos (+104) + spec novo (8→10 após pins de fallback do simplify) + reforço (mesma contagem).
† parede unit subiu com a carga externa da máquina e com specs pesados migrados (eslintConventions roda ESLint programático: 2,8s).
‡ int enxugou: os specs puristas saíram; o que resta é fronteira Payload/DB de verdade (pirâmide OPS90 mais honesta).
Top-8 unit mais lentas no SHA final (tabela completa via node scripts/testing-audit-metrics.mjs unit --top 15):
| arquivo | testes | duração | teste mais lento |
|---|---|---|---|
| tests/unit/activityOverlay.unit.spec.tsx | 14 | 7,6s | troca dia no calendário… (2,6s) |
| tests/unit/campaignCellEditOverlay.unit.spec.ts | 19 | 3,1s | opens 'tendência' in a Drawer… (0,9s) |
| tests/unit/petitionLead.unit.spec.tsx | 4 | 3,0s | fires exactly one Lead… (1,1s) |
| tests/unit/municipalityRelationEditor.unit.spec.tsx | 9 | 2,9s | serializes same-cell mutations… (1,0s) |
| tests/unit/eslintConventions.unit.spec.ts | 12 | 2,8s | enforces PascalCase… filenames (2,4s) |
| tests/unit/campaignQuickActionsDrawer.unit.spec.tsx | 15 | 2,7s | renders when closed… (0,7s) |
| tests/unit/campaignColumnPicker.unit.spec.ts | 8 | 2,4s | batches a whole editing session… (1,2s) |
| tests/unit/codebaseConventions.unit.spec.ts | 25 | 1,9s | keeps conflict markers out… (1,4s) |
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 | relationship, schemaPrimitives, assertTestDatabase, petition-page-layout (specs 100% puros) | mover | alta | imports verificados zero Payload/DB; renames R100 |
| M2 | int→unit | formData, supporterListFilters, siteMetadataFallback, eslintConventions (puros c/ mock/stub) | mover | alta | módulos-alvo sem DB (supporterListFilters.ts: só import type Where); verdes em unit |
| M3 | int→unit | 8 specs de artefatos geográficos (readFileSync+createHash sobre src/lib) |
mover | média | grupo homogêneo, zero Payload; pirâmide: snapshot de dados = small test |
| R1 | int | api.int.spec "fetches users" → toBeDefined() |
reforçar | baixa | shape docs/totalDocs assertado; aposentar total vira proposta A10 |
| D1 | scripts | measure-e2e-family docblock citava campaignPermissionProfile.e2e.spec.ts (removida) |
remover referência | baixa | arquivo confirmado inexistente; manifesto usa campaignPermissionProfileHttp (existe) — intacto |
| K1 | unit | quick-action registry repete catálogos por role já pinados nos specs de domínio | proposta | baixa | seams distintos (registry vs catálogo) — não é duplicata comprovada |
| K2 | int | googleCalendarSync sleep 3,5s + família flaky sob carga | proposta → Issue #906 | alta | evidência de recidiva 4/7 runs nesta noite |
Intocáveis varridos e intactos: consentimento/LGPD, access control/RBAC, lockdown de liderança/estimatedVotes — nenhum toque, nenhuma duplicata envolvendo-os virou remoção.
Evidências verde antes/depois
| passo | antes | depois | commit |
|---|---|---|---|
| M1 | unit 264/2525 ✓ (01:43) · int 91–95 files ✓ exceto flake conhecido (01:47, w4) | unit 264/2525 ✓ (01:44) · int conservação −28/+28 ✓ | 4b54cbfb |
| M2 | = anterior | unit 268/2553 ✓ (01:51) · int 88/789 FULL GREEN (01:52, w4) | c0bdb9fc |
| M3 | = anterior | unit 276/2601 ✓ (01:54) · int 79–80/80 files, única falha = família flake (#906), conservação ✓ | dc1e342b |
| R1+D1 | api.int isolado 1/1 ✓ (02:00) | idem | 36dde76c |
| simplify | spec core 8/8 ✓ | spec core 10/10 ✓ (02:07) · lint/format ✓ | bd977cd2 |
* hash abreviado; ver git log. Conservação global provada em cada passo: unit+int = 3342 testes nos passos M1–M3 (delta final +2 = pins novos do simplify no spec do core).
Passos revertidos / diagnóstico
- Sonda temporária no teste do flake (console.log de
outcome.status+ store) — aplicada para diagnóstico, revertida imediatamente após leitura; nunca commitada. Diagnóstico: isolado o passe 3 cria normalmente (created: 1); a falha só existe sob carga → hipótese do orçamento por-hop/pausa registrada em #906. - Nenhuma melhoria precisou ser revertida (nenhum AFTER falhou por causa da edição).
Flake da família Google Calendar → #906
7 runs full colhidos nesta noite: 4 falhas distintas dentro da mesma família (sempre verde isolada): reopen-test created: 0 ×3 (workers default e 4), lock-test NotFound cleanup ×1, mirror-test created: 3≠2 ×1. Hipótese: sync disparado pelo hook concorre com os passes manuais e/ou budget por-hop estoura sob contenção → paused fail-closed retorna all-zeros que os asserts leem como contagem errada. Fase 1 do fix: assertar outcome.status === 'synced' em toda a família.
Propostas não executadas (com razão)
| proposta | por quê não executou | gatilho/next step |
|---|---|---|
Mover contactPhoneNormalization.int.spec → unit |
hooks exercitados via stub; precisa verificar semântica de req antes (risco médio) |
verificação de 30min num expediente |
Mover payloadTransaction.int.spec → unit |
stub puro, mas testa wrapper de transação — preferível provar contra Payload real ou decidir explicitamente | decidir dono no próximo toque no módulo |
Spec dono direto para campaignSavedFilterStore |
hoje pinado só via 2 wrappers; gap real de cobertura | barato; lote com próximo item de saved filters |
Aposentar api.int.spec.ts inteiro |
boots Payload completo para um smoke fraco; reforçado hoje, mas custo > valor | avaliar junto de mudança no harness de int |
| Migrações e2e (demand-visibility, deliberation, bottom-nav, auth-cookie duplicam pins inferiores; pwa/pixel/admin são HTTP disfarçado; concepts/newsletter/contacts com redundâncias) | edição autônoma de e2e à noite é proibida; exigem trilho OPS87 (HTTP-sem-browser) e medição | cruzar com fila OPS87 e #882; nunca à noite |
Consolidar pins de paginação/q repetidos em 5 *ListUrl specs |
aceitáveis hoje (barato e local) | deferido: mudança no contrato de campaignListUrl ou 6ª lista no padrão |
Extrair parsing de relatório JSON runner para scripts/lib |
2ª ocorrência (metrics CLI + measure-e2e-family) | deferido: 3ª ocorrência extrai (regra dos três) |
Descartados pela triage (sem valor real): dedup registry×catálogo quick-actions (cobertura legítima em seams distintos); describe.each cosmético em worktree.unit; divergência de numeração de fases impl-vs-SKILL (histórico vs operacional); alternativa --outputFile no CLI (sem problema que a justifique).
Estado final do PR
PR #907 — MERGIADO às 05:42:41Z pelo auto-merge (fsolla via AUTOMERGE_PAT), CONTRA o critério "sem auto-merge armado". CI green no SHA final (checks ✓ 13m35s); mergeable ✓; conteúdo íntegro em main.
Errata do processo (falha real, registrada para as próximas noites): a sequência do impl plan D5 — "aguardar o run do workflow de automerge do SHA final terminar e então desarmar" — chega exatamente no momento do merge: o GitHub mergea em segundos após o required check ficar verde, e o comando de desarme (05:43Z) falhou com "Can't disable auto-merge" porque o merge já estava em voo. A janela entre CI green e merge foi de segundos; nenhuma verificação humana aconteceu antes do merge, violando o aceite da intenção. Correção já aplicada na SKILL.md: desarmar imediatamente após cada push, com o CI ainda PENDENTE (o arm só executa merge com checks verdes — desarmar durante os ~13min de CI elimina a corrida). O humano deve julgar pela manhã se aceita o merged ou reverte (git revert -m 1 contra o merge de #907 — nota: main já avançou com OPS97 logo depois, o que torna o revert mais cirúrgico). Falha também classificada como agent-miss em #909.