mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
6.7 KiB
6.7 KiB
OPS46 — Infra de teste e2e: e2e local não coleta (next/cache) + allocator de município com wrap
Status: implementado
Atualizado em: 2026-08-23
Issue: #733
Impl plan do plano de intenção: docs/plans/ops46-infra-teste-e2e-local-allocator.md
Fase 1 — e2e local não coleta (Cannot find module 'next/cache')
Causa raiz (medida)
next@15.4.11não declaraexportsno package.json → ESM nativo do Node nunca resolve o subpathnext/cache(o arquivo existe comocache.js, mas ESM não tenta extensões —ERR_MODULE_NOT_FOUNDcom "Did you mean to import next/cache.js?").server-only(0.0.1) resolveindex.js(que lança) sem a condição de exportreact-server(--conditions=react-server).pnpm test:e2ecarrega ambas as correções viaNODE_OPTIONS="--no-deprecation --conditions=react-server --import=tsx/esm"— por isso o CI passa: o CI nunca rodaplaywrightbare, sempre via script. Qualquer invocação bare (extensão VS Code,pnpm exec playwright test --list, runs ad-hoc) quebra na coleta.- CI vs máquina: sem delta — o CI usa o mesmo comando com os mesmos flags; a hipótese do plano ("investigar o que difere no ambiente CI") se resolveu em "nada difere — o CI só roda pelo script".
Abordagem implementada (aprovada no GATE)
Fix no nível do runner, zero mudança em código de produção:
tests/helpers/e2eEsmResolve.mjs— hook de resolução ESM com 2 rewrites:next/cache→next/cache.js(entry oficial, TS mapeia paracache.d.ts);- injeta
react-servernasconditionsde resolução (equivalente exato do flag--conditions=react-server, aplicado em runtime vianextResolve).
tests/helpers/e2eEsmLoader.mjs— entry--importque registra a hook (o padrão do tsx:--importsó funciona se o módulo chamarmodule.register).playwright.config.ts— quandoNODE_OPTIONSnão tem--conditions=react-server(invocação bare):register()a hook no processo main (a coleta do--listacontece aqui, pós-load do config) e propaga--import=<loader>viaNODE_OPTIONSpara os workers (herdam o env no spawn). Com os flags presentes (script), nada muda.
Opções rejeitadas
next/cache→next/cache.jsna fonte (~40 arquivos): toca código de produção para um problema de infra de teste e não resolveserver-only(o comando bare continuaria quebrando); não idiomático.- Patch do
nextvia pnpmpatchedDependenciesadicionandoexports: exports parcial quebra qualquer subpath não listado; frágil por versão. --import/flags no script apenas + documentar: não satisfaz o aceite (pnpm exec playwright test --listcoleta na máquina do humano).
Verificação Fase 1
pnpm exec playwright test --list(bare, sem flags): 198 testes em 41 arquivos (antes:Total: 0 tests in 0 files).- Run real bare
--project=admin: 5 passed (workers com a hook). - Run real bare
--project=campaigncampaignHomeActions: 18 passed. pnpm test:e2e --list(script, flags): inalterado, 198.
Fase 2 — allocator de município com wrap destrutivo
Medição (plano: "medir primeiro")
- Claims por ciclo completo num DB persistente: ~505 (432
getMunicipality()no int + 73claimMunicipality()no e2e) > 435 do catálogo → o wrap acontece em todo ciclo de suíte completa. Bug agudo (não teórico): após o wrap, dois runs concorrentes podem receber o mesmo índice e o purge-on-claim de cada um apaga as linhas vivas do outro.
Abordagem implementada
Registry de claims em módulo compartilhado vitest-free
tests/helpers/campaignMunicipalityAllocator.ts (mesma convenção do
campaignResidue.ts — importável pelo processo Playwright do e2e):
- Tabela
campaign_fixture_municipality_claims(indexPK,run_idtext,claimed_attimestamptz default now()) criada lazy com a sequence (CREATE IF NOT EXISTS, corrida 23505 tolerada). claimMunicipalityIndex(payload, catalogSize, runID):nextval→ índice →INSERT ON CONFLICT ("index") DO NOTHING RETURNING; conflito = run vivo segura o slot → pula; claim stale (TTL 2h, run crashado) → rouba e retenta; tudo ocupado por claims vivos → erro claro ("exhausted") em vez de colisão silenciosa.releaseMunicipalityClaims(payload, runID):DELETE WHERE run_id— chamado nocleanup()de ambos os fixtures (intCampaignFixtures.cleanup, e2eCampaignE2EOwnership.cleanup), só quando o fixture claimou (SetclaimedMunicipalityIndexes— cleanup sem claims não toca a tabela).- O fixture int perdeu o allocator inline (
nextAllocatedMunicipalityIndex- a sequence própria); o e2e idem (CREATE SEQUENCE + nextval inline).
Guards e testes
- Guard estático novo no
testMunicipalityAllocatorConventions.unit.spec.ts: proíbenextval(/campaign_fixture_municipality_allocemtests/int,tests/e2eetests/helpersfora do módulo do allocator — todo claim passa pelo registry. - Spec int comportamental
tests/int/campaignMunicipalityAllocator.int.spec.ts(catalogSize sintético, robusto a concorrência da suíte paralela):- exclusividade viva através de múltiplos wraps (âncora segura 1 slot por 120 claims de runs efêmeros > 3 wraps do espaço de 40);
- exaustão (erro claro) + recuperação após release;
- steal de claim stale (envelhecida 3h);
- release libera slots para reuso.
Verificação Fase 2
- Spec do allocator: 4/4 verdes, estável em re-runs.
- Suíte int completa: 92 arquivos / 819 testes, todos verdes (o fixture refatorado claima ~432 municípios sob 8 workers paralelos).
- Tabela de claims zerada ao final da suíte (release funcionando).
- e2e campaign (
campaignHomeActions, wizard com claims reais): 18/18, claims zeradas após o run.
Riscos e notas
- A hook depende de
module.register(estável desde Node 20.6); só ativa em invocações bare — o path do script (pnpm test:e2e) fica intocado e é o fallback se algo mudar. - TTL 2h > duração de qualquer run vivo; claim crashado bloqueia slots até o TTL (erro de exaustão menciona o prazo).
- Um run vivo não pode claimar mais de
catalogSizeslots (a própria exclusividade exaure) — correto por design; fixtures reais liberam por teste. - Durante a entrega, o
--listbare regenerou oimportMap.jssem envsS3_*(classe OPS69/OPS70 #87, já documentada) — restaurado comgenerate:importmap+ envs dummy.
Aceite
- Fase 1:
pnpm exec playwright test --listcoleta na máquina do humano - Fase 2: sem colisão de claim após N wraps (stress test com wraps reais)
- Guards:
testMunicipalityAllocatorConventionsverde após a mudança