cards/docs/SMOKE_TEST.md
Till e3b3a2b478 docs: SMOKE_TEST.md — verifizierter E2E-Lauf gegen lokale Postgres
Phase-3-Verifikation 2026-05-08:
- Drizzle push erfolgreich: 8 Tabellen, 11 Indizes, 6 FKs in
  Schema `cards`
- E2E-Flow durchgespielt: Deck-Create, Card-Create (basic-reverse,
  2 Auto-Reviews), Reviews-Due, FSRS-Grade (state new→learning,
  stability 0→2.3, learning_steps 0→1), Cross-User-Schutz (403),
  Cascade-Delete (Deck→Cards→Reviews alle 0)
- Type-Check + 46 Vitest-Tests grün

Runbook für reproduzierbaren Lauf in docs/SMOKE_TEST.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 16:43:07 +02:00

3.7 KiB

Smoke-Test Runbook

Reproduzierbarer End-to-End-Lauf für cards-api ohne Frontend. Validiert: Drizzle-Schema-Push, Auth-Gate, CRUD, FSRS-Berechnung, Cascade-Delete.

Voraussetzungen

  • Docker läuft
  • pnpm 9.x installiert
  • Bun installiert (brew install bun oder via nvm)
  • pnpm install einmalig im Repo-Root durchgelaufen

Ablauf

# 1. Postgres-Container starten (auf :5435, kollidiert nicht mit Mana-Plattform-:5432)
pnpm docker:up

# 2. Drizzle-Schema pushen (--force, weil non-interactive)
cd apps/api
DATABASE_URL='postgresql://cards:cards@localhost:5435/cards' pnpm exec drizzle-kit push --force
# Erwartet: 8 Tabellen + 11 Indizes + 6 Foreign-Keys in Schema `cards`

# 3. cards-api starten (auf :3081)
DATABASE_URL='postgresql://cards:cards@localhost:5435/cards' CARDS_API_PORT=3081 bun run src/index.ts &
APIPID=$!

# Warten bis ready
until curl -s http://localhost:3081/healthz > /dev/null; do sleep 0.5; done

Verifizierte Endpoints

# Plattform-Endpunkte
curl http://localhost:3081/healthz
# → {"status":"ok"}

curl http://localhost:3081/version
# → {"app":"cards","version":"0.0.0","build":"dev"}

curl http://localhost:3081/.well-known/mana-app.json | jq '.id'
# → "cards"

# Auth-Gate
curl -i http://localhost:3081/api/v1/decks
# → HTTP/1.1 401 Unauthorized
# → {"error":"unauthenticated","detail":"X-User-Id header missing (dev stub)"}

# Deck CRUD
curl -X POST http://localhost:3081/api/v1/decks \
  -H 'X-User-Id: u-test-1' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Deck-Name","color":"#ff8800"}'
# → {"id":"<ULID>","user_id":"u-test-1","name":"Deck-Name",...}

# Card mit basic-reverse → automatisch 2 Reviews initialisiert
curl -X POST http://localhost:3081/api/v1/cards \
  -H 'X-User-Id: u-test-1' \
  -H 'Content-Type: application/json' \
  -d '{"deck_id":"<DECK_ID>","type":"basic-reverse","fields":{"front":"Q","back":"A"}}'
# → {"id":"<CARD_ID>",...}

# Reviews fällig (zwei sub_indices)
curl -H 'X-User-Id: u-test-1' http://localhost:3081/api/v1/reviews/due
# → {"reviews":[{...sub_index:0,state:"new"},{...sub_index:1,state:"new"}],"total":2}

# Grade triggert FSRS-Berechnung
curl -X POST http://localhost:3081/api/v1/reviews/<CARD_ID>/0/grade \
  -H 'X-User-Id: u-test-1' \
  -H 'Content-Type: application/json' \
  -d '{"rating":"good"}'
# → state:"learning", stability:2.3..., learning_steps:1, due:<now+10min>

# Cross-User-Schutz
curl -X POST http://localhost:3081/api/v1/cards \
  -H 'X-User-Id: u-attacker' \
  -H 'Content-Type: application/json' \
  -d '{"deck_id":"<DECK_ID>","type":"basic","fields":{"front":"Q","back":"A"}}'
# → {"error":"deck_not_owned"} (HTTP 403)

# Cascade-Delete (DELETE deck → Cards weg → Reviews weg)
curl -X DELETE http://localhost:3081/api/v1/decks/<DECK_ID> -H 'X-User-Id: u-test-1'
# → {"deleted":"<DECK_ID>"}

# Verifikation per SQL
docker exec cards-postgres psql -U cards -d cards -c "SELECT count(*) FROM cards.decks, cards.cards, cards.reviews;"
# → alle 0 (Cascade hat alles entfernt)

Aufräumen

kill $APIPID
pnpm docker:down
# Lokale DB-Daten in infrastructure/.volumes/cards-postgres bleiben.
# Komplett zurücksetzen: rm -rf infrastructure/.volumes/cards-postgres

Letzter erfolgreicher Lauf

2026-05-08, Phase 3-Verifikation:

  • pnpm install: 136 packages, 8s
  • type-check: 4/4 packages grün (svelte-check 0 errors)
  • vitest: 46 Tests grün
  • drizzle push: 8 Tabellen, 11 Indizes, 6 FKs in cards-Schema
  • E2E-Flow: Deck-Create → Card-Create (basic-reverse, 2 Reviews) → Reviews-Due (2 Einträge) → Grade rating=good (state new→learning, stability 0→2.3, learning_steps 0→1) → 1 Review fällig → Cascade- Delete leert alle Tabellen