mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
ops(OPS73-FOLLOWUP): fecha #760 — resíduos da race do usuário compartilhado no e2e (lock único de login) + docs drift
This commit is contained in:
parent
038758eb81
commit
5b7448832b
8 changed files with 126 additions and 64 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Deploy & CI
|
||||
|
||||
Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the Cloudflare tunnel; container/database `teqo-1313`/`teqo_1313`). **There is no Vercel deploy anymore** — the Vercel deploy job, `vercel-promote.yml`, the Vercel scripts/`vercel.json` and the `VERCEL_*` repo secrets were removed in OPS50 (2026-08-17). **CI/PR/deploy live on GitHub Actions (OPS71, 2026-08-19); the ISSUE TRACKER stays on the Forgejo** (`git.solla.dev/fsolla/teqo` — labels, claims, `pnpm issue`/`agent:*` and the post-merge flips all keep talking to the Forgejo API; GitHub is only the Actions host). The PR CI (`ci-pr.yml`, job único `checks` — OPS62) runs the same cascade as always (guards → lint → format → typecheck → knip → cycles → unit → int → build → e2e selected/afetado, skips via `ci-scope.mjs`); its check-run `CI (PR) / checks` is the required check of `main`. **There is NO main verifier (`ci.yml`) anymore**: publishing is a **manual `workflow_dispatch`** (`.github/workflows/deploy.yml`) with two jobs — `verify` (hosted `ubuntu-latest`, suíte **full** incl. e2e full) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`): the GitHub self-hosted runner ON the homeserver executes `scripts/deploy-homeserver.sh` **locally** (no SSH; outbound-only runner, works behind the tunnel; the hosted job never touches the homeserver). The remote Docker deployment is gone; the production container runs the monolith directly. The Docker Compose file is used ONLY for local dev databases. The `run-deploy.yml` (OPS38/OPS46) was deleted in OPS71. There is no `run-deploy.yml` or equivalent in GitHub Actions — deploy is `deploy.yml` only.
|
||||
Production is self-hosted on the homeserver (`jorgesolla1313.com.br`, behind the Cloudflare tunnel; container/database `teqo-1313`/`teqo_1313`). **There is no Vercel deploy anymore** — the Vercel deploy job, `vercel-promote.yml`, the Vercel scripts/`vercel.json` and the `VERCEL_*` repo secrets were removed in OPS50 (2026-08-17). **CI/PR/deploy AND the ISSUE TRACKER live on GitHub (OPS71 + OPS76, 2026-08-21)** (`github.com/fsolla/teqo/issues` — labels, claims, `pnpm issue`/`agent:*` and the post-merge flips talk to the GitHub API; GitHub is the single host). The PR CI (`ci-pr.yml`, job único `checks` — OPS62) runs the same cascade as always (guards → lint → format → typecheck → knip → cycles → unit → int → build → e2e selected/afetado, skips via `ci-scope.mjs`); its check-run `CI (PR) / checks` is the required check of `main`. **There is NO main verifier (`ci.yml`) anymore**: publishing is a **manual `workflow_dispatch`** (`.github/workflows/deploy.yml`) with two jobs — `verify` (hosted `ubuntu-latest`, suíte **full** incl. e2e full) → `deploy` (`needs: [verify]`, `runs-on: [self-hosted, homeserver]`): the GitHub self-hosted runner ON the homeserver executes `scripts/deploy-homeserver.sh` **locally** (no SSH; outbound-only runner, works behind the tunnel; the hosted job never touches the homeserver). The remote Docker deployment is gone; the production container runs the monolith directly. The Docker Compose file is used ONLY for local dev databases. The `run-deploy.yml` (OPS38/OPS46) was deleted in OPS71. There is no `run-deploy.yml` or equivalent in GitHub Actions — deploy is `deploy.yml` only.
|
||||
|
||||
**Guarding `pnpm push` (OPS24+):** every PR push goes through `scripts/git-push.mjs`, which runs `ensure-repo-deps` (install + Husky in worktrees — OPS51), then `gate:push` (the pre-push hook), which chains `gate:ci` — a serialized mirror of the CI `checks` job (**without e2e**: OPS72 moved e2e to an explicit skill step) — plus the `docs-guards` (changelog append-only, aggregation sync, conflict markers — OPS63; the rewrite escape stays in CI only). The hook blocks `git push` on failure; escape: `git push --no-verify` (WIP only). There is no YAML `push` gate — the hook is the gate. **CI red on your PR is yours**, even pre-existing or infra — "out of feature scope" is not a stop condition (policy "Dono do PR, dono do CI" in `docs/AGENT-OPS.md`).
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**OPS46-S1 — hook ESM mapeia next/cache incondicionalmente (2026-08-23):** débito da revisão estrutural do OPS46 (issue #789): `tests/helpers/e2eEsmResolve.mjs` remapeava `next/cache` → `next/cache.js` sem tentar a resolução bare — quando o next publicar um `exports` map, o remap podia mascarar erro de resolução real (fail-loud vira fail-silent). Fix no branch `next/cache`: `await nextResolve(specifier, context)` primeiro; só em `ERR_MODULE_NOT_FOUND` remapeia para `next/cache.js`; qualquer outro erro propaga. Branch `react-server` intocado (injeção de conditions não mascara erro). Spec unit novo `tests/unit/e2eEsmResolve.unit.spec.ts` (3 casos com `nextResolve` mockado: bare-ok sem remap, `ERR_MODULE_NOT_FOUND` remapeia com `context` intacto, erro de outro código propaga). Verificação: `gate:fast` verde (unit 2432), sanity OPS72: `playwright test --list` coleta 202 testes com o hook carregado. Gatilho de reavaliação: bump major do next (fallback vira código morto e pode ser removido). Impl plan: `docs/plans/ops46-s1-hook-esm-bare-first-impl.md`.
|
||||
|
||||
**OPS47 — agent-pool/SKILL.md aponta para plano com política pré-main obsoleta (--base stage + auto-merge) (2026-08-23):** docs-only, zero mudança de comportamento. O `.agents/skills/agent-pool/SKILL.md:18` cita `docs/plans/agent-pool-orchestrator.md` como referência viva de arquitetura ("Arquitetura e elegibilidade"), mas o plano (escrito 2026-07-30, era pré-main) ainda descrevia a política obsoleta: PR `--base stage`, `issue-done-on-stage-merge.yml`, promote `stage→main`, `refresh-stage` — realidade removida em 2026-08-01 (promote/refresh) e 2026-08-12 (migration-lock). **Edição linha-a-linha do plano (dono do concern), frame histórico do pool dormente (OPS65) preservado:** overview e resumo executivo passam a "workers PR direto para main" com nota do promote removido; diagrama mermaid `pull_request closed em stage`→`em main` e `merge em main → issue-done-on-main-merge.yml`; contrato de prompt do worker `gh pr create --base stage`→`--base main` (reflete o vivo: `POOL_BASE_REF='main'` em `scripts/agent-pool.mjs:70` e prompt `--base main` em `scripts/lib/agent-pool-prompt.mjs:40-41`); gargalo de migrations reescrito de "job `migration-lock`" para `countOpenSchemaPrs` (`scripts/lib/agent-pool-forgejo.mjs:83`, com nota da remoção do job em 2026-08-12); referências à skill deletada `model-selection` → `scripts/lib/agent-pool-models.mjs`; `scripts/lib/agent-github.mjs` (inexistente) → `agent-forgejo.mjs`; `.github/workflows/agent-pool.yml` e `gh workflow run agent-pool.yml` marcados como removidos no OPS65 (dispatch falha 404); `startingRef: 'stage'`→`POOL_BASE_REF` (=`'main'`); D7/D8/D1 e rabbit holes alinhados (sem `refresh-stage`, sem "slot ocupado até merge em stage"). **SKILL.md:** resíduo L79 "Antes do promote, use dispatch na branch" → nota histórica (promote removido 2026-08-01, workflow removido OPS65). **Touch-ups de comentário/label (sem mudança de lógica):** `tests/unit/agentPoolState.unit.spec.ts:103` "CI flipped on stage merge"→"on main merge"; `scripts/lib/agent-pool-state.mjs:131` "merge in stage"→"merge in main". Fora de escopo (órfão/histórico): `docs/plans/paradigma-agentes-paralelos.md`; código do pool e workflows (dormentes, não tocados). Impl plan: `docs/plans/agent-pool-skill-md-aponta-para-plano-com-politica-pre-impl.md`.
|
||||
**OPS73-FOLLOWUP — re-home do S10 (pixel Meta) no GitHub main, origin do workspace homeserver + race do seedUser no e2e full (2026-08-23):** fechamento dos débitos colhidos no OPS73 (#760). Das três fases planejadas, **duas já estavam resolvidas** por eventos posteriores ao plano de intenção (escrito 2026-08-20): (1) o **S10 não precisou de cherry-pick** — o merge de convergência OPS75 (`c723d6ac`, GitHub como fonte única, 2026-08-21) já trouxe o pixel Meta completo para o GitHub main (os 11 arquivos da feature são byte-idênticos à ponta Forgejo `504f6c37`; migração `20260819_213947_add_site_settings_facebook_pixel_id`, e2e `campaignHomePixel`, unit `campaignNewsletterLead`, changelog/plano todos em main); (2) o **origin do workspace do homeserver** (`~/teqo-deploy`) já era `https://github.com/fsolla/teqo.git` — verificado via SSH, e o `deploy-homeserver.sh` re-aponta idempotentemente a cada deploy. O **código real da entrega** foi o resíduo da F3: a race do usuário compartilhado `dev@payloadcms.com` no e2e full estava **parcialmente fixa** no main (o simplify do S10 trouxe `seedTestUser` create-if-missing + helper `adminHeaders` com `pg_advisory_lock(727001)`), mas restavam 3 pontos de risco: o `afterAll` de `admin.e2e.spec.ts` ainda **deletava** o usuário compartilhado no meio da suíte paralela (`cleanupTestUser`), e dois specs roteavam logins REST diretos **sem o advisory lock** (`admin.e2e.spec.ts` ×2 e a função inline `adminHeaders` sem lock em `frontend.e2e.spec.ts`). A entrega removeu o `cleanupTestUser` (chamada + helper morto, zero call sites) e roteou todos os logins restantes pelo helper com lock — um único ponto de serialização para a suite inteira. Deploy do main (que inclui o S10) via `deploy.yml` disparado manualmente: o `verify` full **flakou** com 3 reds + 5 flaky pré-existentes **não relacionados** ao seedUser (timeouts de `locator.check` em `campaignMunicipalities:115`, `campaignPermissionProfile` C142, etc. — classe consolidada na casa de flakes #553), então o deploy precisa de re-dispatch pós-merge. Dívida capturada: **#805** (`E2E-ADMIN-LOGIN-LOCK`, depends #760) — o `beforeEach` UI login do admin (page-based, form) continua sem o lock (resíduo da própria classe que o #760 ataca); correção inline de docs drift em `AGENTS-infra.md` (tracker já migrado para GitHub no OPS76).
|
||||
|
||||
**OPS46 — infra de teste e2e: coleta local consertada (next/cache) + allocator de município sem colisão no wrap (2026-08-23):** entrega de dois débitos do OPS45 (capture-review-debts). **Fase 1 — e2e local não coleta:** `pnpm exec playwright test --list` falhava em todo spec com `Cannot find module 'node_modules/next/cache'` (e o diagnóstico correto do CI-vs-máquina: não há delta — o CI só roda pelo script `pnpm test:e2e`, que carrega `--conditions=react-server --import=tsx/esm` no `NODE_OPTIONS`; comando bare não tinha nada). Causa: `next@15` não declara `exports` → ESM nativo não resolve `next/cache` (o arquivo é `cache.js`), e `server-only` precisa da condição `react-server`. Fix no runner, zero mudança em produção: hook de resolução ESM vitest-free `tests/helpers/e2eEsmResolve.mjs` (mapeia `next/cache`→`next/cache.js` e injeta `react-server` nas conditions) registrada no processo main via `module.register` e propagada aos workers via `NODE_OPTIONS=--import=e2eEsmLoader.mjs` no `playwright.config.ts` — só quando os flags do script já não estão presentes. Aceite: bare `playwright test --list` coleta 198 testes em 41 arquivos (antes: 0); runs reais bare admin 5/5 e campaign 18/18; `pnpm test:e2e` intocado. **Fase 2 — allocator de município com wrap destrutivo:** a sequência persistente `campaign_fixture_municipality_alloc` nunca é resetada e um ciclo completo de suíte claima ~505 do catálogo de 435 (medido: 432 no int + 73 no e2e) → o wrap acontece em todo ciclo em DB persistente de worktree, e dois runs concorrentes podiam receber o mesmo índice e purgar as linhas vivas um do outro (purge-on-claim). Fix: registry de claims em módulo compartilhado vitest-free `tests/helpers/campaignMunicipalityAllocator.ts` — tabela `campaign_fixture_municipality_claims` (`index` PK, `run_id`, `claimed_at`), claim via `INSERT ON CONFLICT DO NOTHING RETURNING` (conflito = slot vivo → pula; claim stale >2h de run crashado → rouba; tudo ocupado → erro claro de exaustão), release no `cleanup()` dos dois fixtures (int + e2e, só quando claimaram). Guard estático novo no `testMunicipalityAllocatorConventions` (proíbe `nextval`/sequence fora do módulo) + spec int de stress `campaignMunicipalityAllocator.int.spec.ts` (exclusividade viva por 3 wraps, exaustão+recuperação, steal, release). Verificação: tsc/lint/format/cycles verdes (knip: limitação P3 pré-existente de não carregar payload.config, igual no HEAD), unit 2420 + int 819 (92 arquivos) verdes com tabela de claims zerada ao final, e2e local afetado (admin 5/5 + campaign 18/18 bare) verdes; importMap.js restaurado com envs `S3_*` dummy após o `--list` bare regenerá-lo sem elas (classe OPS69 — o guard do #87 cobre no CI). Impl plan: `docs/plans/ops46-infra-teste-e2e-local-allocator-impl.md`.
|
||||
|
||||
|
|
|
|||
1
docs/changelog/2026-08-23-ops73-followup.md
Normal file
1
docs/changelog/2026-08-23-ops73-followup.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**OPS73-FOLLOWUP — re-home do S10 (pixel Meta) no GitHub main, origin do workspace homeserver + race do seedUser no e2e full (2026-08-23):** fechamento dos débitos colhidos no OPS73 (#760). Das três fases planejadas, **duas já estavam resolvidas** por eventos posteriores ao plano de intenção (escrito 2026-08-20): (1) o **S10 não precisou de cherry-pick** — o merge de convergência OPS75 (`c723d6ac`, GitHub como fonte única, 2026-08-21) já trouxe o pixel Meta completo para o GitHub main (os 11 arquivos da feature são byte-idênticos à ponta Forgejo `504f6c37`; migração `20260819_213947_add_site_settings_facebook_pixel_id`, e2e `campaignHomePixel`, unit `campaignNewsletterLead`, changelog/plano todos em main); (2) o **origin do workspace do homeserver** (`~/teqo-deploy`) já era `https://github.com/fsolla/teqo.git` — verificado via SSH, e o `deploy-homeserver.sh` re-aponta idempotentemente a cada deploy. O **código real da entrega** foi o resíduo da F3: a race do usuário compartilhado `dev@payloadcms.com` no e2e full estava **parcialmente fixa** no main (o simplify do S10 trouxe `seedTestUser` create-if-missing + helper `adminHeaders` com `pg_advisory_lock(727001)`), mas restavam 3 pontos de risco: o `afterAll` de `admin.e2e.spec.ts` ainda **deletava** o usuário compartilhado no meio da suíte paralela (`cleanupTestUser`), e dois specs roteavam logins REST diretos **sem o advisory lock** (`admin.e2e.spec.ts` ×2 e a função inline `adminHeaders` sem lock em `frontend.e2e.spec.ts`). A entrega removeu o `cleanupTestUser` (chamada + helper morto, zero call sites) e roteou todos os logins restantes pelo helper com lock — um único ponto de serialização para a suite inteira. Deploy do main (que inclui o S10) via `deploy.yml` disparado manualmente: o `verify` full **flakou** com 3 reds + 5 flaky pré-existentes **não relacionados** ao seedUser (timeouts de `locator.check` em `campaignMunicipalities:115`, `campaignPermissionProfile` C142, etc. — classe consolidada na casa de flakes #553), então o deploy precisa de re-dispatch pós-merge. Dívida capturada: **#805** (`E2E-ADMIN-LOGIN-LOCK`, depends #760) — o `beforeEach` UI login do admin (page-based, form) continua sem o lock (resíduo da própria classe que o #760 ataca); correção inline de docs drift em `AGENTS-infra.md` (tracker já migrado para GitHub no OPS76).
|
||||
102
docs/plans/ops73-followup-rehome-s10-infra-impl.md
Normal file
102
docs/plans/ops73-followup-rehome-s10-infra-impl.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Impl: Follow-up pós-OPS73 — re-home S10 no GitHub, origin do workspace homeserver + fix da race de seedUser do e2e
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-08-23
|
||||
Issue: #760
|
||||
Intenção: docs/plans/ops73-followup-rehome-s10-infra.md
|
||||
Appetite restante: ~0,4 dia (herdado)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** (1) S10 (pixel Meta na home de campanha) no GitHub main **e rodando em prod**; (2) `~/teqo-deploy` no homeserver com origin = GitHub; (3) e2e full do GitHub CI estável no `seedTestUser` (sem flake de duplicate email).
|
||||
- **O que NÃO negociar:** DB de prod intocável (testes/dev nunca apontam pra ele); migration `20260819_213947_add_site_settings_facebook_pixel_id` já registrada — imutável; o usuário compartilhado `dev@payloadcms.com` e a home `/` de campanha com `#meta-pixel-*` são contratos existentes; e2e full do CI continua rodando com 4 workers paralelos.
|
||||
- **O que reavaliar:** a hipótese central da intenção — "S10 não está no GitHub main" — **está errada**. O merge de convergência OPS75 `c723d6ac` (2026-08-21) trouxe o S10 completo para o main (os 11 arquivos S10 são byte-idênticos entre `504f6c37` e `origin/main`; diff vazio). **Não há cherry-pick a fazer.** F2 também já está feito (`origin = https://github.com/fsolla/teqo.git` verificado no homeserver). O único **código real** restante é o resíduo de F3: logins sem advisory lock e o `cleanupTestUser()` deletando o usuário compartilhado no meio da suíte paralela.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph F1["F1 — deploy + verificação (sem cherry-pick)"]
|
||||
A1[Deploy manual em andamento no GH Actions<br/>verify full → deploy] --> A2[Pós-deploy: revision nova no teqo-1313<br/>+ #meta-pixel-* na home / com facebookPixelId setado]
|
||||
end
|
||||
subgraph F2["F2 — verificação (já feito)"]
|
||||
B1[git -C ~/teqo-deploy remote get-url origin = GitHub]
|
||||
end
|
||||
subgraph F3["F3 — resíduos da race do usuário compartilhado"]
|
||||
C1[admin.e2e.spec.ts: remover afterAll cleanupTestUser<br/>+ remover helper cleanupTestUser morto]
|
||||
C2[admin.e2e.spec.ts:50,152 + frontend.e2e.spec.ts:613<br/>roteados pelo helper adminHeaders com advisory lock]
|
||||
end
|
||||
A2 --> G[Gate:fast + changelog + push]
|
||||
B1 --> G
|
||||
C1 --> C2 --> G
|
||||
```
|
||||
|
||||
**Opções consideradas:** A | B | C (duas decisões independentes em F3)
|
||||
|
||||
**Decisão 1 — destino do `cleanupTestUser()` (admin.e2e.spec.ts:18-20):**
|
||||
**Recomendação:** **B** — remover a chamada no `afterAll` **e** o helper `cleanupTestUser` de `tests/helpers/seedUser.ts`. O `seedTestUser` já é create-if-missing com credenciais constantes: o delete no fim da suíte não serve a nada e abre a janela "user vanished" + invalidação de tokens para workers paralelos ainda rodando. Remover só a chamada (opção A) deixaria um export morto — knip roda com `"exports": "error"` (knip.jsonc) e o projeto inclui `tests/**/*.ts`, então o gate de CI quebraria; código morto também viola o princípio de simplify.
|
||||
**Rejeitadas:** A — remover só a chamada e manter o helper (export morto → knip `exports: error`); C — manter o `afterAll` mas neutralizar o delete (código morto idêntico, pior: ritual enganoso).
|
||||
|
||||
**Decisão 2 — rotear os logins diretos pelo lock:**
|
||||
**Recomendação:** **A** — substituir os logins diretos (admin.e2e.spec.ts:50 e :152) por `adminHeaders(request, baseURL)` do helper `tests/helpers/adminApi.ts`, e no frontend.e2e.spec.ts **apagar a função local `adminHeaders` (:613-620)** e importar o helper. O advisory lock `pg_advisory_lock(727001)` já é o ponto único de serialização da sessão read-modify-write do Payload — um lock por login, um por suite inteira; rotear os call sites existentes (608, 810, 968, 1001, 1082, 1135, 1199, 1289, 1347 no frontend) custa zero mudança de comportamento além da serialização. O `campaignHomePixel.e2e.spec.ts` e o `campaignNewsletter.e2e.spec.ts` já usam o helper.
|
||||
**Rejeitadas:** B — adicionar lock ad hoc em cada login inline (duplica a mecânica do lock em N pontos; o helper já existe — DRY, viola depth check de pass-through); C — serializar os arquivos com `test.describe.configure({ mode: 'serial' })` (mata o paralelismo de 4 workers, infla o wall-clock do full, e trata o sintoma de perda de sessão com perda de throughput).
|
||||
|
||||
**F1/F2:** nenhuma decisão de engenharia — monitorar o deploy em andamento e verificar. Se o deploy atual não cobrir o main com S10, re-disparar `deploy.yml` manual (`workflow_dispatch`).
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`tests/e2e/admin.e2e.spec.ts`**: remove `cleanupTestUser` do import (:2) e do `afterAll` (:18-20); troca os dois logins diretos (:50-55 e :152-156) por `adminHeaders(request, baseURL)` do helper.
|
||||
- **`tests/e2e/frontend.e2e.spec.ts`**: apaga a definição local `adminHeaders` (:613-620, que sobrescreve o helper no arquivo) e importa `adminHeaders` de `../helpers/adminApi`; todos os ~9 call sites passam a usar o helper — a assinatura exige `baseURL` (já definido no arquivo), então cada chamada passa `request, baseURL`.
|
||||
- **`tests/helpers/seedUser.ts`**: remove o export `cleanupTestUser` (zero call sites após a mudança acima).
|
||||
- **`tests/helpers/adminApi.ts`**: sem mudança — dono do lock (1 lock `727001` serializa a suíte inteira).
|
||||
- **Migration:** sem migration (só testes/e2e; schema intocado).
|
||||
- **Access / Consent:** N/A — nada de prod, PII ou Consent.
|
||||
- **UI:** N/A.
|
||||
- **Changelog:** `docs/changelog/2026-08-23-ops73-followup.md` + `pnpm changelog:build`.
|
||||
|
||||
### Dados → forma (se aplicável)
|
||||
|
||||
N/A — sem mudança de dados; as mudanças são exclusivamente em specs e helper de teste.
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **F1 — verificar deploy do S10** (dominante no appetite): monitorar o `workflow_dispatch` em andamento (verify full → deploy). Pós-deploy no homeserver: conferir revision nova no container `teqo-1313` e `#meta-pixel-*` presente na home `/` com `SiteSettings.facebookPixelId` setado. Se o deploy atual não cobrir, disparar `deploy.yml` manual. Verificação via SSH/`docker` no homeserver (leitura apenas).
|
||||
2. **F2 — verificação barata**: `git -C ~/teqo-deploy remote get-url origin` → deve devolver `https://github.com/fsolla/teqo.git`. Sem code change.
|
||||
3. **F3 — código + gates**: editar os 3 arquivos de teste conforme Componentes/mudanças; `pnpm gate:fast` (lint + typecheck + unit; knip entra no cascade do CI); e2e afetado: `admin`, `frontend`, `campaignHomePixel`, `campaignNewsletter` (o `deploy.yml` `verify` roda a full e é o juiz final).
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- **Não** reabrir o diagnóstico do admin (OPS73 fechado).
|
||||
- **Não** re-homar S8/S9 (já em main via PR #751).
|
||||
- **Não** reescrever `seedTestUser`/`assertTestDatabase`/chave do lock nem mudar `dev@payloadcms.com`.
|
||||
- **Não** editar a migration `20260819_213947_...` nem schema.
|
||||
- **Não** reduzir workers (4→1) nem serializar specs para "consertar" a race.
|
||||
- **Não** mexer no retry 403→relogin do `campaignHomePixel.e2e.spec.ts:41`: com todos os logins no lock ele fica inerte — removê-lo é polish barato, deferível, sem gatilho de risco.
|
||||
- Guard #87 (importMap sem envs S3) é Issue própria.
|
||||
- F1/F2 não exigem cherry-pick nem arquivos do repo.
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Deploy em andamento falha no verify (flake)**: o próprio resíduo de F3 pode flakear o full em prod mode → mitigação: F3 reduz essa flakiness; após merge, re-disparar `deploy.yml` se necessário.
|
||||
- **Pixel ausente em prod mesmo com deploy ok**: `SiteSettings.facebookPixelId` vazio no admin de prod faz a home renderizar sem `#meta-pixel-*` → mitigação: passo explícito de verificação em F1; se vazio, o ID real precisa ser configurado no admin (é dado de produto — perguntar ao humano, nunca inventar ID).
|
||||
- **Shadowing no frontend.e2e.spec.ts**: `adminHeaders(request)` sem `baseURL` em :608 quebra na assinatura do helper → mitigação: passar `request, baseURL` em todos os call sites; o typecheck pega antes do push.
|
||||
- **Knip pega resíduo inesperado ao remover o helper**: gates locais (`gate:fast` + knip) antes do push.
|
||||
- **Usuário persistente entre runs após remover o cleanup**: aceito — credenciais constantes, create-if-missing, é o estado pós-seed atual.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [ ] Aceite de produto da intenção ainda coberto (S10 em main + prod verificado; origin homeserver = GitHub; e2e full sem flake de duplicate email)
|
||||
- [ ] Invariantes AGENTS/engineering-standards (testes/dev nunca tocam prod; migration imutável; sem collection/Consent/URL nova)
|
||||
- [ ] Testes de domínio previstos onde write paths mudam: sem write path de app — e2e afetados (`admin`, `frontend`, `campaignHomePixel`, `campaignNewsletter`) verdes no full
|
||||
- [ ] Changelog `docs/changelog/2026-08-23-ops73-followup.md` + `pnpm changelog:build`
|
||||
- [ ] `pnpm gate:fast` verde (lint + typecheck + unit) e knip sem export morto
|
||||
|
||||
---
|
||||
|
||||
**Self-score decision-quality: 5/5** (gate ≥4)
|
||||
|
||||
1. Decisões caras têm rejeitadas? **Sim** — as duas decisões reais (destino do `cleanupTestUser`; roteamento dos logins) têm A/B/C documentado.
|
||||
2. Abordagem cabe no appetite? **Sim** — F1/F2 são verificação/monitoramento; F3 é edição pontual em 3 arquivos de teste; ~0,4 dia cabe.
|
||||
3. Rabbit holes nomeados? **Sim** — seção própria com 7 itens (incl. workers, retry 403, migration, S8/S9).
|
||||
4. Depth check: reusa shells/helpers? **Sim** — roteia tudo pelo `adminHeaders`/advisory lock existente; não cria helper novo nem duplica lock.
|
||||
5. Intenção permanece satisfeita? **Sim** — aceite de produto intacto; a divergência é só a hipótese de direção ("S10 fora do main"), corrigida e documentada em Leitura da intenção.
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { adminHeaders } from '../helpers/adminApi'
|
||||
import { login } from '../helpers/login'
|
||||
import { cleanupTestUser, seedTestUser, testUser } from '../helpers/seedUser'
|
||||
import { seedTestUser, testUser } from '../helpers/seedUser'
|
||||
import { instagramStubUrlFor } from '../helpers/socialStub'
|
||||
import { expect, test } from './fixtures/e2eTest'
|
||||
|
||||
|
|
@ -15,10 +16,6 @@ test.describe('Admin Panel', () => {
|
|||
await login({ page, serverURL: baseURL, user: testUser })
|
||||
})
|
||||
|
||||
test.afterAll(async () => {
|
||||
await cleanupTestUser()
|
||||
})
|
||||
|
||||
test('can navigate to dashboard', async ({ page }) => {
|
||||
await page.goto(`${baseURL}/admin`)
|
||||
await expect(page).toHaveURL(`${baseURL}/admin`)
|
||||
|
|
@ -47,12 +44,7 @@ test.describe('Admin Panel', () => {
|
|||
// Seed the social-feed global with an Instagram snapshot so the exclusion
|
||||
// picker (S3) has posts to show. The snapshot is plain data — the picker
|
||||
// lists it and writes `excludedItems` (the same mechanism YouTube uses).
|
||||
const login = await request.post(`${baseURL}/api/users/login`, {
|
||||
data: { email: testUser.email, password: testUser.password },
|
||||
})
|
||||
expect(login.ok()).toBeTruthy()
|
||||
const { token } = await login.json()
|
||||
const headers = { cookie: `payload-token=${token}` }
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
|
||||
const snapshot = {
|
||||
username: 'depjorgesolla',
|
||||
|
|
@ -149,12 +141,7 @@ test.describe('Admin Panel', () => {
|
|||
}
|
||||
await setStub('ok')
|
||||
|
||||
const login = await request.post(`${baseURL}/api/users/login`, {
|
||||
data: { email: testUser.email, password: testUser.password },
|
||||
})
|
||||
expect(login.ok()).toBeTruthy()
|
||||
const { token } = await login.json()
|
||||
const headers = { cookie: `payload-token=${token}` }
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
|
||||
const seed = await request.post(`${baseURL}/api/globals/social-feed-settings`, {
|
||||
headers,
|
||||
|
|
|
|||
|
|
@ -33,11 +33,10 @@ test.describe('Campaign home Meta pixel', () => {
|
|||
headers: Record<string, string>,
|
||||
pixelId: string | null,
|
||||
) => {
|
||||
// Insurance against the same-user concurrent-login session race with
|
||||
// specs that do NOT route their login through the advisory-locked
|
||||
// `adminHeaders` helper (e.g. the frontend spec's inline logins): a 403
|
||||
// means the session row was lost to another worker — re-login mints a
|
||||
// fresh one. The locked helper already serializes the specs that use it.
|
||||
// Insurance against the same-user concurrent-login session race during
|
||||
// retries of this very spec's own non-serialized write: a 403 means the
|
||||
// session row was lost to another worker — re-login mints a fresh one.
|
||||
// The locked helper already serializes the specs that use it.
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
const response = await request.post(`${baseURL}/api/globals/site-settings`, {
|
||||
headers,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { APIRequestContext, Locator, Page } from '@playwright/test'
|
||||
|
||||
import { seedTestUser, testUser } from '../helpers/seedUser'
|
||||
import { adminHeaders } from '../helpers/adminApi'
|
||||
import { seedTestUser } from '../helpers/seedUser'
|
||||
import { instagramStubUrlFor, youtubeStubUrlFor } from '../helpers/socialStub'
|
||||
import { expect, test } from './fixtures/e2eTest'
|
||||
|
||||
|
|
@ -605,20 +606,11 @@ test.describe('Campaign home content section', () => {
|
|||
// Start every run with the social feed unconfigured so the empty-state
|
||||
// test stays deterministic even when a previous S2 run left settings or a
|
||||
// persisted snapshot behind (same reasoning as the `posts` bust below).
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await resetSocialFeedSettings(request, headers)
|
||||
await bustSocialFeed(request)
|
||||
})
|
||||
|
||||
const adminHeaders = async (request: APIRequestContext): Promise<Record<string, string>> => {
|
||||
const login = await request.post(`${baseURL}/api/users/login`, {
|
||||
data: { email: testUser.email, password: testUser.password },
|
||||
})
|
||||
expect(login.ok()).toBeTruthy()
|
||||
const { token } = await login.json()
|
||||
return { cookie: `payload-token=${token}` }
|
||||
}
|
||||
|
||||
const createTag = async (
|
||||
request: APIRequestContext,
|
||||
headers: Record<string, string>,
|
||||
|
|
@ -807,7 +799,7 @@ test.describe('Campaign home content section', () => {
|
|||
request,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
|
||||
const visibleTag = await createTag(request, headers, categoryName, categorySlug)
|
||||
const hiddenTag = await createTag(request, headers, hiddenTagName, hiddenTagSlug, true)
|
||||
|
|
@ -965,7 +957,7 @@ test.describe('Campaign home content section', () => {
|
|||
await page.goto('about:blank')
|
||||
await expect(page).toHaveURL(/about:blank/)
|
||||
} finally {
|
||||
const headers2 = await adminHeaders(request).catch(() => undefined)
|
||||
const headers2 = await adminHeaders(request, baseURL).catch(() => undefined)
|
||||
if (headers2) {
|
||||
for (const id of createdPosts) {
|
||||
await request
|
||||
|
|
@ -998,7 +990,7 @@ test.describe('Campaign home content section', () => {
|
|||
request,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setYouTubeStubState(request, 'ok')
|
||||
|
||||
const mixTag = await createTag(request, headers, `E2e Mix ${runSuffix}`, `e2e-mix-${runSuffix}`)
|
||||
|
|
@ -1079,7 +1071,7 @@ test.describe('Campaign home content section', () => {
|
|||
request,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setYouTubeStubState(request, 'fail')
|
||||
|
||||
const fallbackTag = await createTag(
|
||||
|
|
@ -1132,7 +1124,7 @@ test.describe('Campaign home content section', () => {
|
|||
|
||||
test('keeps the last snapshot while the API is down', async ({ page, request }) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setYouTubeStubState(request, 'ok')
|
||||
const baseSettings = {
|
||||
enabled: true,
|
||||
|
|
@ -1196,7 +1188,7 @@ test.describe('Campaign home content section', () => {
|
|||
request,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setYouTubeStubState(request, 'ok')
|
||||
await setInstagramStubState(request, 'ok')
|
||||
|
||||
|
|
@ -1286,7 +1278,7 @@ test.describe('Campaign home content section', () => {
|
|||
request,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setInstagramStubState(request, 'fail')
|
||||
|
||||
const fallbackTag = await createTag(
|
||||
|
|
@ -1344,7 +1336,7 @@ test.describe('Campaign home content section', () => {
|
|||
|
||||
test('keeps the last Instagram snapshot while the API is down', async ({ page, request }) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setInstagramStubState(request, 'ok')
|
||||
const baseSettings = {
|
||||
enabled: true,
|
||||
|
|
@ -1415,7 +1407,7 @@ test.describe('Campaign home content section', () => {
|
|||
request,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setYouTubeStubState(request, 'ok')
|
||||
await setInstagramStubState(request, 'ok')
|
||||
|
||||
|
|
@ -1480,7 +1472,7 @@ test.describe('Campaign home content section', () => {
|
|||
context,
|
||||
}) => {
|
||||
await seedTestUser()
|
||||
const headers = await adminHeaders(request)
|
||||
const headers = await adminHeaders(request, baseURL)
|
||||
await setYouTubeStubState(request, 'ok')
|
||||
await setInstagramStubState(request, 'ok')
|
||||
|
||||
|
|
|
|||
|
|
@ -35,20 +35,3 @@ export async function seedTestUser(): Promise<void> {
|
|||
// A parallel worker already seeded the shared user; nothing to reset.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up test user after tests
|
||||
*/
|
||||
export async function cleanupTestUser(): Promise<void> {
|
||||
assertTestDatabase(process.env.DATABASE_URL)
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
await payload.delete({
|
||||
collection: 'users',
|
||||
where: {
|
||||
email: {
|
||||
equals: testUser.email,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue