docs: foundational specs (PRD, architecture, design system, database, security, code style, testing) + AGENTS operational rulebook
This commit is contained in:
+60
@@ -0,0 +1,60 @@
|
||||
# TESTING — anwarpy.ai
|
||||
|
||||
> Owner decision 2026-10-11: **tier (b) is the bar** — Vitest unit tests for the core libs + regression tests for API route behavior. Tier (c) — Playwright E2E for critical journeys — activates when Phase 3 (Stripe) lands; journeys to cover then: signup→verify→login, VIP ghost-read (billing on), checkout happy path, webhook apply.
|
||||
|
||||
## 1. Standing gates (before any commit/merge)
|
||||
|
||||
```bash
|
||||
cd web && npx tsc --noEmit # must exit 0
|
||||
cd web && npm run build # must exit 0 (includes generate:types)
|
||||
```
|
||||
|
||||
Both green = minimum. Gates alone are not proof of behavior — see §3.
|
||||
|
||||
## 2. Unit tests (Vitest, tier b)
|
||||
|
||||
- Add `vitest` as a dev dependency; tests live beside the code or under `web/src/lib/__tests__/` — pick one location and stay consistent (recommend `__tests__/` next to `src/lib`).
|
||||
- **Required suites (the known-risk list):**
|
||||
|
||||
| Suite | Must pin |
|
||||
|---|---|
|
||||
| `launch.test.ts` | policy truth table: `billingEnabled()` false when `LAUNCH_MODE` unset/**free_early_access**/**paid**… — including the fail-safe default |
|
||||
| `lexical.test.ts` | `lexicalToText`/`countLexicalWords` **descend into `content.root`** (regression: the 2026-10-10 launch-blocking bug where both returned 0/'' on real serialized trees); `extractH2s`, `lexicalToPlain` |
|
||||
| `secret-scan.test.ts` | one case per pattern (aws_access_key, pem_private_key, openai_api_key, github_token, generic_credential) returns **pattern name only, never the matched text**; clean payload passes; string content is scanned too |
|
||||
| `topics.test.ts` | `isTopic` accepts exactly agents/models/automation/foundations; `topicsFor` mapping |
|
||||
| `format.test.ts` | `guidesLabel` (1 دليل، 2 دليلان، 3–10 أدلة، 11+), `minutesLabel`, `MEMBERSHIP_PRICE` |
|
||||
|
||||
- Rule: tests assert **behavior**, not implementation text. No snapshot-of-source, no tautologies (`expect(fn).toBeDefined()`), no mock-echo tests, no bare "does not throw".
|
||||
- Runtime env manipulation: use `vi.stubEnv`/`unstubAllEnvs` — never let one test's `LAUNCH_MODE` leak into another.
|
||||
|
||||
## 3. API route regression tests
|
||||
|
||||
Behavior tests for the routes with contracts (per PRD §3.6, SECURITY §4):
|
||||
|
||||
- `POST/PUT /api/agent/posts`: 401 wrong/missing key (note: **`x-agent-key`**, not Bearer); 400 on string `content`; 422 with pattern names on secret-bearing Lexical; `sourceId` dedup returns existing post (no 409, no duplicate row); `pickFields` strips `access`/`readingMinutes`; PUT cannot set `status`.
|
||||
- `POST /api/subscribe`: 503 + exact Arabic body while billing is off.
|
||||
- `POST /api/notify`: records row then honest 503 when mail transport fails; duplicate email → 200 `alreadySubscribed: true`; no double-send on dup.
|
||||
- Comments route: unauthenticated → reject; client-sent `author` ignored (server-forced).
|
||||
- Likes: 401 anonymous; second like by same user → no duplicate row.
|
||||
|
||||
Implementation: pure logic moves into `src/lib` helpers where possible and gets unit-tested; handler-level tests run the route module with a mocked Payload local API (or hit a dev server for the smoke tier — §5). Prefer extracting decision logic over wiring up heavy mocks.
|
||||
|
||||
## 4. What must NOT become a test
|
||||
|
||||
- Wiring/copies/forwarding, mock echoes, source-text greps, "file exists".
|
||||
- Incidental defaults that aren't contracts (an unstyled div's class list is not a spec).
|
||||
- Anything already verified cheaper by the ghost-read/smoke script in §5.
|
||||
- Per the gstack rule: existing tests that pin wording/implementation instead of behavior get deleted, not "fixed".
|
||||
|
||||
## 5. Smoke & verification scripts (throwaway, not committed)
|
||||
|
||||
- Prod verification is HTTP-level and scripted ad hoc (curl batches): ghost-read a VIP post logged out; check `isAccessibleForFree`, og:image 1200×630, JSON-LD types; subscribe 503; notify row + cleanup; sitemap contents; www 308. Use `@example.com` rows and **delete them after** (ground truth: users=6, launch_notifications=0).
|
||||
- Keep the failing-before/passing-after pair when fixing a bug — a permanent regression test in §2/§3 if it's pure logic, the script transcript if it's deploy/config.
|
||||
|
||||
## 6. Before-merge checklist (condensed)
|
||||
|
||||
1. `tsc --noEmit` + `npm run build` exit 0.
|
||||
2. New/changed lib behavior → matching Vitest suite updated or added.
|
||||
3. Changed route contract → §3 regression updated (status + body shape).
|
||||
4. Changed access logic → ghost-read check (logged-out VIP post) recorded in the PR.
|
||||
5. No new `any`, no new `'use client'` without justification, no new dependency without a line of justification (CODE_STYLE §8).
|
||||
Reference in New Issue
Block a user