Shuffle/docs/specs/13-umsetzung.md
cschlaefke 154e5e4bb5 fix(db): Jahr-Grenzen in find_similar_catalog_games, Tests für Tagesgrenze und Trimmen (Refs #13)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 19:44:02 +01:00

28 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Spec #13 – KAT-04 Fehlendes Spiel melden: Umsetzungs-Nachtrag PR 13a (Backend)
Ergänzt `docs/specs/13-fehlendes-spiel-melden.md` (die Spec selbst wird hier nicht geändert). Stand: 2026-11-04, Branch `feature/13a-fehlendes-spiel-backend` (die Spec nennt `feature/13a-meldungen-backend`), Migration `20261104100000_game_reports_user.sql`, Tests `supabase/tests/database/10_game_reports_user.test.sql`.
## 1. Abweichungen und Präzisierungen zum Vertrag
- **Spec-F6 ist entschieden und umgesetzt (2c):** `game_reports.editions_cleared` und die Zuweisung in `admin_resolve_report` stehen schon in der Migration `game_reports` (PR 2c). 13a legt die Spalte nicht an und ändert `admin_resolve_report` nicht; `adopt_report_target` setzt `editions_cleared` genauso.
- **Tageslimit gilt auch für `postgres`:** Der Trigger `game_reports_daily_limit` unterscheidet nicht nach Rolle (Spec 3.2). Fixtures, die als `postgres` mehr als 10 Nutzer-Meldungen „von heute“ für einen Nutzer anlegen, scheitern deshalb. Folge für die Tests: `08_game_reports.test.sql` schaltet den Trigger am Anfang ab (`alter table … disable trigger`, mit dem Rollback am Ende zurückgenommen); in `10` bekommen Fixtures für gestern/älter feste Zeitstempel. Außerdem ist `created_at` nach dem Anlegen unveränderlich (Trigger `set_updated_at`), alte Zeitstempel müssen also beim Einfügen gesetzt werden.
- **Limit-Zählung über `report_day(created_at) = report_day(now())`** (nicht die optionale Bereichsform aus Spec 3.2); bei höchstens 10 Zeilen je Nutzer und Tag spielt der Index `(owner_id, created_at)` keine Rolle.
- **Trimmen:** `report_missing_game` trimmt Leerzeichen, Tab, CR und LF (nicht nur Leerzeichen). Titel, Verlag und Link werden vor allen Prüfungen getrimmt, Längen gelten für den getrimmten Wert.
- **`find_similar_catalog_games`:** Der Titel wird vor der Normalisierung getrimmt und die Länge (1 bis 200) am getrimmten Wert geprüft. Ist der normalisierte Titel leer (kann nach `normalize_game_title` nur bei leerem Rest vorkommen), gibt es keine Treffer. Je Spiel wird der beste Kandidatentitel gewählt: gleicher Schlüssel vor Ähnlichkeit, dann Haupttitel vor alternativen Titeln, dann Schreibweise. Verlage sind nach `publishers.name_search`, dann `name` sortiert (wie in `search_catalog_games`). Bei gleicher Ähnlichkeit sortiert das Ergebnis nach `title`, dann `game_id`. Die Ähnlichkeit bei `same_title` ist immer `1.0`.
- **`report_provisional_in_use`:** Der Handler um den Insert in `game_reports` wertet den Namen des verletzten Index (`game_reports_provisional_game_id_key`) aus; andere Unique-Verletzungen laufen unverändert weiter. Über die RPC ist der Fall nicht erreichbar (jeder Aufruf legt ein frisches Spiel an); der Test lenkt ihn mit einem temporären Trigger herbei.
- **`adopt_report_target`:** Alle Prüfungen laufen nach dem Sperren von Meldung und Spielen, in der Reihenfolge der Spec-Tabelle. Eine Meldung, die nicht dem Aufrufer gehört, wird schon beim Sperren nicht gefunden (`report_not_found`, `p_report = null` ebenso). `current_user` wird nirgends ausgewertet.
- **Eigentümer von `adopt_report_target`:** Die Migration ändert den Eigentümer nicht ausdrücklich (`alter function … owner to`); sie läuft als `postgres` (lokal und per Supabase CLI). Der Test prüft `proowner = postgres`.
- **Testplan:** `00_schema` unverändert (22 Tabellen), `07`/`08`-Fremdschlüssel-Wächter unberührt (kein neuer Fremdschlüssel), `08` nur um das Abschalten des Limit-Triggers ergänzt (Erwartungen und Beschreibungen unverändert gültig: die Spaltenrechte aus 08 waren schon auf `result_seen_at` und die Insert-Spalten ausgelegt).
- **Review-Nachbesserungen:** `find_similar_catalog_games` behandelt ein Jahr außerhalb 1800-2100 wie `null` (kein Überlauf, kein 22003). Zusätzliche Tests: Tagesgrenze Berlin (10 Meldungen kurz nach Mitternacht Berlin, Trigger und Vorprüfung), Trimmen von Tab/Zeilenumbruch.
- **08-Fixtures (N3) bleiben bei `disable trigger`:** Ben und weitere Nutzer haben dort über 20 Fixture-Inserts mit Standard-`created_at`; sie alle auf feste alte Zeitstempel umzustellen wäre ein großer, fehleranfälliger Eingriff in 2c-Tests. Das Limit prüft `10` ausführlich.
- **Zielsperre (N1) unverändert `for update`:** nicht umgestellt, weil `admin_resolve_report` und `admin_merge_games` ebenfalls `for update` sperren und eine gemischte Sperrstufe die Verklemmungsanalyse (Spec R2) ändern würde.
## 2. Mutationsprüfung
Die Mutanten sind veränderte Kopien der Migration, jeweils in einer Transaktion zusammen mit dem Testlauf angewendet und mit dem Rollback des Tests zurückgenommen (am echten Schema bleibt nichts). Die Skripte liegen nicht im Repo. Ergebnis siehe Bericht an Chris; die überlebenden Mutanten sind entweder Doppelabsicherungen (zum Beispiel `origin = 'user'` in der Insert-Policy, die schon durch die Spaltenrechte gesichert ist) oder nur mit Parallelität testbar (Advisory-Lock).
## 3. Offene Punkte
- Zeitpunkt des Hinweises: Klickt der Nutzer den Hinweis „abgelehnt mit Verweis“ weg (`result_seen_at` gesetzt) und wechselt danach auf den Zieleintrag, bleibt `result_seen_at` gesetzt (Spec 4.4: unverändert). Der Hinweis „Dein Eintrag zeigt jetzt auf …“ erscheint dann am Zieleintrag nicht mehr. Siehe Bericht, Frage an Chris.
- Paralleltest des Limits (zwei `psql`-Sitzungen unter READ COMMITTED) ist nicht automatisiert (Spec 7.1) und gehört zu T1.