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

109 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Testing audit — 2026-08-25
Primeira execução da skill `/testing-audit` (OPS96, Issue #898 · impl plan [`ops96-skill-testing-audit-impl.md`](../plans/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/int` para `tests/unit`** com 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.mjs` apontava 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](https://github.com/fsolla/teqo/issues/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](https://github.com/fsolla/teqo/issues/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.