mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
Fold duplicated campanha access, loaders, list/filter/form shells, and scripts into shared modules; reorganize utilities by domain; stabilize e2e; and document prevention-first audit workflow so the same smells cannot creep back in.
38 lines
9.4 KiB
Markdown
38 lines
9.4 KiB
Markdown
# Testing
|
|
|
|
## Test Strategy
|
|
|
|
Three layers, all local-database-only (see `local-database` skill; tests refuse any database not ending in `_test`):
|
|
|
|
- **Unit** (`tests/unit/**/*.unit.spec.ts`, 47 specs as of 2026-07-25 (tooling guards)): pure logic; runs with a deliberately invalid `DATABASE_URL` so nothing can touch a database.
|
|
- **Integration** (`tests/int/**/*.int.spec.ts`, 48 specs): real Payload + Postgres (`teqo_test`); campaign fixtures allocate seeded municipalities via a Postgres sequence so parallel spec files never share one, and purge residue on claim. Shared consent rows with stable keys are leased (`tests/helpers/testDatabaseLease.ts`), never owned.
|
|
- **E2E** (`tests/e2e`, Playwright, 7 specs): smoke-level; most campaign behavior is pinned at the int layer.
|
|
|
|
CI (`.github/workflows/ci.yml`) runs lint (zero warnings), typecheck, knip, unit, migrate, int against a Postgres 17 service.
|
|
|
|
## Safety Net Map
|
|
|
|
Modules the 2026-07 hardening phases touch, and where their behavior is pinned:
|
|
|
|
| Module | Pinned behaviors | Test files | Gaps |
|
|
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
|
| RBAC — `src/utilities/access/*` (split 2026-07-23; `campaignAccess.ts` is the re-export surface) | per-role municipality/leadership/pledge/supporter/demand/plan scoping; leader lockdown; staff field asymmetry | `campaignMunicipalityAccess`, `campaignVotePledge`, `campaignSupporter`, `campaignUserPhone`, `campaignActionPlan`, `campaignDemandWorkflow`, `collectionAccessLockdown` (int) | no dedicated per-export suite; covered through collection behavior |
|
|
| Collection access lockdown | campaign JWT denied on `users`/PII/CMS writes; anonymous read of public content; admin unaffected | `collectionAccessLockdown.int.spec.ts` | REST-transport variant untested (Local API with `overrideAccess: false` is equivalent) |
|
|
| `municipalityMapData.ts` | bundle scoping by URL filters + advisor access; `validVotesByYear` incl. 2026 reuse of 2022; null on empty scope; `hasZoneMunicipalities` + per-zone map keys | `municipalityMapData.int.spec.ts` | |
|
|
| `votePledgeData.ts` | `estimated[scenario] ?? declared` aggregation; role scoping; leader denial | `campaignVotePledge.int.spec.ts` | |
|
|
| Supporter import (token/bulk) | preview→confirm flow, dedup by phone, consent fail-closed | `campaignSupporter.int.spec.ts` | `supporterImportBulk`/`supporterImportToken` internals only via flow |
|
|
| Leader supporter create | linked-municipality scope; out-of-scope rejection; staff rejection | `collectionAccessLockdown.int.spec.ts` | |
|
|
| Posts visibility/caching | `isPostVisible` fail-closed; cached listing | (indirect via routes) | no dedicated `posts` spec — backlog |
|
|
| List URL contracts (Pass 2 W0, pinned 2026-07-25) | `campaignListUrl` canonicalize/clamp/redirect; supporter + actionPlan parse/serialize/href; 5 entity parsers | `campaignListUrl`, `supporterUi`, `actionPlanUi`, `campaignEntityListParsers` (unit) | municipality list URL pinned separately in `municipalityUi.unit.spec.ts` |
|
|
| Form-action ladders (Pass 2 W4d) | `runCampaignFormAction`: success spread, boundary/zod/safe/generic mapping, values+revision echo | `campaignFormActions.unit.spec.ts` | per-route ladders are thin shells; behavior pinned at the wrapper |
|
|
| WhatsApp subscription consent (Pass 2 D3) | fail-closed without `whatsapp-inscricao`; happy path links subscription → keyed consent | `submitWhatsapp.int.spec.ts` | |
|
|
| Vote trend + election number format (Pass 2 W4a survivors) | `computeVoteTrend` pair priority/±10% band; `formatElectionNumber` E18 fraction defect | `voteTrend.unit.spec.ts` | |
|
|
| Entity list loaders (Pass 2 W0) | scope (leader empty/denied), marker-narrowed q, kind/status filters, pagination shape | `campaignEntityListData.int.spec.ts` | advisor loader runs post-gate with `overrideAccess: true` (gate asserted upstream) |
|
|
| Codebase conventions (tooling guards, 2026-07-25) | `server-only` on Payload-coupled utilities; Praça/Núcleo copy ban; eslint legacy-ignore existence; formActions wrapper usage | `codebaseConventions.unit.spec.ts` | boundary rules themselves live in ESLint (`no-restricted-imports`) + madge in CI |
|
|
|
|
## Characterization Backlog
|
|
|
|
- [ ] `src/utilities/posts.ts` — dedicated spec for `isPostVisible` fail-closed matrix and cache wrappers (confirmed 2026-07-28, Pass 3 audit).
|
|
- [ ] `activityViewModels.ts` (ex-`actionPlanViewModels`, renamed by C13) — `activityUi.ts` pinned 2026-07-25 (Pass 2 W0); view models still untested directly (confirmed 2026-07-28, Pass 3 audit).
|
|
- [x] `campaignDemandData.ts` / `leadershipData.ts` / `organizationData.ts` / `stateDeputyData.ts` list loaders — smoked 2026-07-25 (`campaignEntityListData.int.spec.ts`, Pass 2 W0).
|
|
- [x] Election aggregate artifact (Phase 2): snapshot test pinning artifact ↔ `municipalityCatalog` key consistency — delivered as `bahiaElectionAggregates.unit.spec.ts`.
|