test(db): close surviving mutants in merge tests, extend runbook (KAT-05, 2b)
All checks were successful
All checks were successful
Refs #2 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2c2546ab47
commit
127f765d85
4 changed files with 120 additions and 12 deletions
|
|
@ -342,7 +342,7 @@ Drei Ansichten (Views) kommen dazu: `public_profiles` zeigt Mitspielern Name, Av
|
|||
Die Pflege läuft in R1 nur über Supabase Studio bzw. `psql` als Datenbankrolle `postgres` (Klärungen 25/44, Ablauf: `docs/katalog-pflege.md`). Spec: `docs/specs/2-katalog-daten.md`.
|
||||
|
||||
- **Ausblenden:** `games.hidden_at` gesetzt = ausgeblendet. Die View `catalog_games` enthält nur Katalogspiele (`visibility = 'catalog'`) ohne `hidden_at` und ohne `merged_into_id`. **Suche und Entdecken lesen nur diese View.** Ein ausgeblendetes Spiel bleibt per ID lesbar (Sammlungen, Partien und Detailseite verweisen darauf).
|
||||
- **Zusammenführen** (Migration `catalog_merge`, PR 2b): `games.merged_into_id` zeigt auf das Zielspiel; ein Trigger lässt nur Katalogspiele als Ziel zu (Fehler `merged_into_not_catalog`). Gesetzt wird die Spalte nur von der Funktion `public.admin_merge_games(p_source, p_target) returns jsonb` (Ablauf und Ergebnis-JSON: `docs/katalog-pflege.md`, Abschnitt 5). Sie ist `security invoker`, prüft `current_user in ('postgres', 'supabase_admin')` (sonst `admin_only`) und ist für `anon`, `authenticated` und `service_role` nicht ausführbar; einen API-Aufruf gibt es nicht. Beide Spiele müssen Katalogspiele sein, das Ziel sichtbar. Die Funktion hängt über die interne Funktion `public.repoint_game_references(p_from, p_to)` Sammlung, Favoriten, Partien (mit Edition) und Wertungsbögen um, kopiert die Varianten der Quelle ans Ziel (gleichnamige Zielvarianten werden abgebildet: `lower(btrim(name))`, bei mehreren die älteste, dann die kleinste `id`) und löscht danach die alten Varianten. Bei Doppelbesitz bleibt der Zieleintrag, leere Felder füllt die Quelle, die Liste folgt `owned` vor `wishlist` vor `shopping`, der Quell-Eintrag wird gelöscht; ein Favorit der Quelle entfällt, wenn der Nutzer das Ziel schon als Favorit hat. `bgg_id` und `catalog_source_id`/`source_*` gehen aufs Ziel über, wenn es keine hat; sonst werden sie an der Quelle trotzdem geleert und der Konflikt mit den alten Werten im Ergebnis gemeldet. Die Quelle wird nie gelöscht, sondern ausgeblendet und verwiesen (`hidden_at`, `merged_into_id`); Ketten werden geglättet. Zuordnungen (Genre, Mechanik, Kategorie, Verlag) bleiben am Quell-Eintrag. Ein pgTAP-Wächter (`07`) listet alle Fremdschlüssel auf `games` und lässt neue Verweise erst nach einer Entscheidung zu.
|
||||
- **Zusammenführen** (Migration `catalog_merge`, PR 2b): `games.merged_into_id` zeigt auf das Zielspiel; ein Trigger lässt nur Katalogspiele als Ziel zu (Fehler `merged_into_not_catalog`). **Regel: `merged_into_id` wird nur über `public.admin_merge_games(p_source, p_target) returns jsonb` gesetzt, nie von Hand.** (Ablauf und Ergebnis-JSON: `docs/katalog-pflege.md`, Abschnitt 5). Sie ist `security invoker`, prüft `current_user in ('postgres', 'supabase_admin')` (sonst `admin_only`) und ist für `anon`, `authenticated` und `service_role` nicht ausführbar; einen API-Aufruf gibt es nicht. Beide Spiele müssen Katalogspiele sein, das Ziel sichtbar. Die Funktion hängt über die interne Funktion `public.repoint_game_references(p_from, p_to)` Sammlung, Favoriten, Partien (mit Edition) und Wertungsbögen um, kopiert die Varianten der Quelle ans Ziel (gleichnamige Zielvarianten werden abgebildet: `lower(btrim(name))`, bei mehreren die älteste, dann die kleinste `id`) und löscht danach die alten Varianten. Bei Doppelbesitz bleibt der Zieleintrag, leere Felder füllt die Quelle, die Liste folgt `owned` vor `wishlist` vor `shopping`, der Quell-Eintrag wird gelöscht; ein Favorit der Quelle entfällt, wenn der Nutzer das Ziel schon als Favorit hat. `bgg_id` und `catalog_source_id`/`source_*` gehen aufs Ziel über, wenn es keine hat; sonst werden sie an der Quelle trotzdem geleert und der Konflikt mit den alten Werten im Ergebnis gemeldet. Die Quelle wird nie gelöscht, sondern ausgeblendet und verwiesen (`hidden_at`, `merged_into_id`); Ketten werden geglättet. Zuordnungen (Genre, Mechanik, Kategorie, Verlag) bleiben am Quell-Eintrag. Ein pgTAP-Wächter (`07`) listet alle Fremdschlüssel auf `games` und lässt neue Verweise erst nach einer Entscheidung zu.
|
||||
- **Manuell gepflegte Felder:** Der Trigger `games_mark_manual_fields` trägt jedes Feld eines Katalogspiels, das ein Mensch setzt oder ändert, in `manual_fields` ein (beim Anlegen alle gesetzten Felder). Der Trigger `game_links_mark_manual` ergänzt `genres`, `mechanics`, `categories` und `publishers`, wenn Zuordnungen angelegt, geändert oder gelöscht werden. Ausgenommen ist nur ein späterer Import: Rolle `catalog_importer` **und** Einstellung `shuffle.catalog_import = 'on'` (der Import setzt sie per `set local`; das ist Konvention, die Rolle ist die Sperre). Private Spiele werden nicht gekennzeichnet. Eine bewusst entfernte Markierung entfernt der Trigger nicht wieder.
|
||||
- **Anzeige-Erlaubnis:** `catalog_games.description` und `image_path` sind leer, wenn die Quelle (`catalog_sources`) Texte bzw. Bilder nicht erlaubt und das Feld nicht in `manual_fields` steht. Das ist nur die zweite Sicherung: Daten ohne Erlaubnis dürfen gar nicht erst gespeichert werden (Pflicht für den Import).
|
||||
- **Spaltenrechte auf `games`:** Nutzer dürfen nur `owner_id` (nur beim Anlegen), `title`, `description`, `description_locale`, `year`, `min_players`, `max_players`, `min_minutes`, `max_minutes`, `min_age`, `image_path`, `bgg_id` und `visibility` schreiben, nicht `id`, `hidden_at`, `merged_into_id`, `manual_fields`, `catalog_source_id` und `source_*`.
|
||||
|
|
|
|||
|
|
@ -233,12 +233,22 @@ join public.collection_items t on t.owner_id = s.owner_id and t.game_id = '7a1b2
|
|||
where s.game_id = '7a1b2c3d-0000-4000-8000-000000000002';
|
||||
```
|
||||
|
||||
**Schritt 3: Zusammenführen.** In einer Transaktion ausführen (`begin;` davor, nach dem Lesen des Ergebnisses `commit;`, bei Zweifel `rollback;`; die Bausteine stehen wie überall ohne `begin`/`commit`). Bei einem Fehler wird nichts geändert:
|
||||
**Schritt 3: Zusammenführen.** Der Baustein steht wie überall ohne `begin`/`commit`; er wird in einer Transaktion ausgeführt. Bei einem Fehler wird nichts geändert:
|
||||
|
||||
```sql
|
||||
select public.admin_merge_games('7a1b2c3d-0000-4000-8000-000000000002', '7a1b2c3d-0000-4000-8000-000000000003');
|
||||
```
|
||||
|
||||
**Trockenlauf (empfohlen vor dem ersten echten Merge).** Das Ergebnis lesen und alles zurückrollen, **in einem einzigen Lauf**:
|
||||
|
||||
```sql
|
||||
begin;
|
||||
select public.admin_merge_games('7a1b2c3d-0000-4000-8000-000000000002', '7a1b2c3d-0000-4000-8000-000000000003');
|
||||
rollback;
|
||||
```
|
||||
|
||||
Danach ist nichts verändert; mit `commit;` statt `rollback;` wird der Lauf endgültig. Achtung: `begin;` und `commit;`/`rollback;` in getrennten Läufen funktionieren nur in `psql` (eine Sitzung). Der SQL-Editor in Studio führt jeden Lauf womöglich in einer eigenen Verbindung bzw. Transaktion aus; dort immer alles in einem Lauf ausführen. (Dieser Trockenlauf enthält Transaktionssteuerung und kann deshalb nicht wörtlich in pgTAP laufen; der enthaltene Aufruf ist identisch zu Schritt 3 und dort getestet.)
|
||||
|
||||
**Schritt 4: Nachprüfen.** Der Vorschau-Baustein aus Schritt 2 (mit der Quelle) muss jetzt überall `0` liefern. Zustand beider Einträge:
|
||||
|
||||
```sql
|
||||
|
|
@ -246,7 +256,11 @@ select id, title, hidden_at is not null as ausgeblendet, merged_into_id, bgg_id,
|
|||
from public.games where id in ('7a1b2c3d-0000-4000-8000-000000000002', '7a1b2c3d-0000-4000-8000-000000000003');
|
||||
```
|
||||
|
||||
Die Quelle ist ausgeblendet und verweist auf das Ziel, das Ziel ist unverändert sichtbar.
|
||||
Die Quelle ist ausgeblendet und verweist auf das Ziel. Das Ziel bleibt sichtbar; Fakten, Kurztext und Zuordnungen sind unverändert. **Ändern können sich `bgg_id` und die Quelle (`catalog_source_id`, `source_*`)**, wenn das Ziel sie von der Quelle übernommen hat (siehe `bgg_id_transferred` und `source_transferred` im Ergebnis). Außerdem darf kein Spiel mehr auf die Quelle verweisen; diese Abfrage muss leer sein (Ketten sind geglättet):
|
||||
|
||||
```sql
|
||||
select id from public.games where merged_into_id = '7a1b2c3d-0000-4000-8000-000000000002';
|
||||
```
|
||||
|
||||
**Was die Funktion macht**
|
||||
|
||||
|
|
@ -255,7 +269,7 @@ Die Quelle ist ausgeblendet und verweist auf das Ziel, das Ziel ist unverändert
|
|||
| Sammlung (`collection_items`) | Hat der Nutzer nur die Quelle, wechselt der Eintrag auf das Ziel. Hat er beide (Doppelbesitz, Spec-F1): Der Zieleintrag bleibt; leere Felder (Notiz, Erwerbsdatum, Edition) füllt der Quell-Eintrag; bei unterschiedlicher Liste gilt Sammlung vor Wunschliste vor Einkaufsliste; der Quell-Eintrag wird gelöscht. Eine nicht leere Notiz am Ziel wird nie überschrieben. |
|
||||
| Favoriten | Hat der Nutzer das Ziel auch als Favorit, entfällt der Favorit der Quelle (die Lücke in der Reihenfolge ist zulässig); sonst wechselt er auf das Ziel. |
|
||||
| Partien, Wertungsbögen | wechseln auf das Ziel; die Edition einer Partie wird über die Variantenzuordnung übertragen. |
|
||||
| Varianten (Editionen) | Für jede Variante der Quelle: Hat das Ziel eine gleichnamige Variante (Vergleich ohne Groß-/Kleinschreibung und ohne Leerzeichen am Rand), wird darauf abgebildet; gibt es mehrere gleichnamige, gilt die älteste (bei Gleichstand die kleinste ID). Sonst wird die Variante am Ziel neu angelegt (Name, Jahr, Sprache, BGG-Kennung). Danach werden die Varianten der Quelle gelöscht. |
|
||||
| Varianten (Editionen) | Für jede Variante der Quelle: Hat das Ziel eine gleichnamige Variante (Vergleich ohne Groß-/Kleinschreibung und ohne **Leerzeichen** am Rand; Tabulatoren und Zeilenumbrüche am Rand zählen als Teil des Namens), wird darauf abgebildet; gibt es mehrere gleichnamige, gilt die älteste (bei Gleichstand die kleinste ID). Sonst wird die Variante am Ziel neu angelegt (Name, Jahr, Sprache, BGG-Kennung). Danach werden die Varianten der Quelle gelöscht. |
|
||||
| BGG-Kennung und Quelle | Hat das Ziel keine, aber die Quelle, übernimmt das Ziel sie (`bgg_id`; ebenso Quelle, `source_ref`, `source_url`, `source_synced_at` als Gruppe). An der Quelle werden sie in jedem Fall geleert, damit der BGG-Abgleich später den ausgeblendeten Eintrag nicht trifft. |
|
||||
| Ketten | Spiele, die bisher auf die Quelle verwiesen, verweisen jetzt auf das Ziel. |
|
||||
|
||||
|
|
@ -289,9 +303,15 @@ Die Quelle ist ausgeblendet und verweist auf das Ziel, das Ziel ist unverändert
|
|||
| `merge_same_game` | Quelle und Ziel sind dieselbe ID |
|
||||
| `merge_game_not_found` | Quelle oder Ziel gibt es nicht (ID vertippt?) |
|
||||
| `merge_not_catalog` | Quelle oder Ziel ist ein privater Eintrag; Zusammenführen gilt nur für Katalogspiele |
|
||||
| `merge_target_not_listed` | Das Ziel ist ausgeblendet oder selbst schon zusammengeführt; das endgültige Ziel wählen oder zuerst wieder einblenden (siehe 4) |
|
||||
| `merge_target_not_listed` | Das Ziel ist ausgeblendet oder selbst schon zusammengeführt. Ist es zusammengeführt, das endgültige Ziel über `merged_into_id` nachschlagen (Baustein unten, hier mit der Quelle aus dem Beispiel; Ketten sind geglättet, ein Schritt genügt) oder ein ausgeblendetes Ziel zuerst wieder einblenden (siehe 4) |
|
||||
| `merge_source_already_merged` | Die Quelle ist bereits zusammengeführt |
|
||||
|
||||
Nachschlagen, wohin ein zusammengeführtes Spiel verweist (`merged_into_id` ist das endgültige Ziel):
|
||||
|
||||
```sql
|
||||
select id, title, hidden_at, merged_into_id from public.games where id = '7a1b2c3d-0000-4000-8000-000000000002';
|
||||
```
|
||||
|
||||
Ein Aufruf über die App-Schnittstelle (als `anon` oder `authenticated`, auch mit Admin-Claims) scheitert mit `42501`; die Funktion ist nur für die Datenbankrolle ausführbar (E10). Bei Zeitüberschreitung oder anderem Fehler `rollback;` und erneut prüfen. Löscht man ein Zielspiel, auf das etwas verweist, schlägt das mit `23503` fehl (gewollt: ausblenden statt löschen).
|
||||
|
||||
## 6. Meldungen bearbeiten
|
||||
|
|
|
|||
|
|
@ -718,3 +718,5 @@ Umgesetzt: B5 (Migration `20261103193928_catalog_merge.sql`), B6 (pgTAP `07_cata
|
|||
- **Schritt 4 aus 3.3** (`game_reports.target_game_id` umhängen) entfällt bis 2c, wie vorgesehen.
|
||||
- **Hinweis für 2c (B9):** Sobald `game_reports` zwei Fremdschlüssel auf `games` bekommt, wird der Wächter in `07` rot. Die erwartete Liste in `07` muss dort um `game_reports.target_game_id` (behandelt) und `game_reports.provisional_game_id` (bewusst ausgelassen) erweitert werden, nicht nur der Wächter in `08`.
|
||||
- **Runbook:** Alle SQL-Bausteine aus Abschnitt 5 laufen wörtlich in `07` (einmalige Prüfung per Skript; keine CI-Prüfung). Abschnitt 5 enthält die Duplikatsuche, eine Vorschau (Verweise, Doppelbesitz), den Aufruf, die Nachprüfung, die Regeln, die Schlüssel des Ergebnis-JSON und die Fehlertexte.
|
||||
|
||||
- **Nachtrag Review 2b:** Tests ergänzt (Spec-F1-Edition am Ziel, `variants_cleared` im Kollisionszweig, gleichnamige Quellvarianten, zweiter Fremdschlüssel-Wächter auf `game_variants`, unbeteiligte Spiele, Ziel `null`); Runbook um Trockenlauf (`begin; …; rollback;` in einem Lauf, nur dieser Baustein läuft nicht wörtlich in pgTAP), Ketten-Nachprüfung und Nachschlagen von `merged_into_id` ergänzt. Der Null-Check in `admin_merge_games` ist ein äquivalenter Mutant (das spätere `not found` liefert denselben Fehler) und bleibt als ausdrückliche Absicherung. Offen für Chris: Verweise, die nach dem Merge noch auf die Quelle entstehen (W3).
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
-- Hinweis für 2c: Der Fremdschlüssel-Wächter am Ende muss um game_reports.target_game_id (behandelt) und
|
||||
-- game_reports.provisional_game_id (bewusst ausgelassen) erweitert werden, sonst wird er rot.
|
||||
begin;
|
||||
select plan(89);
|
||||
select plan(107);
|
||||
|
||||
insert into auth.users (id, email, raw_user_meta_data) values
|
||||
('11111111-1111-1111-1111-111111111111', 'anna@example.com', '{"age_confirmed":"true","terms_accepted":"true","terms_version":"test","display_name":"Anna"}'),
|
||||
|
|
@ -156,6 +156,16 @@ insert into public.plays (id, owner_id, game_id, variant_id, played_at, client_i
|
|||
insert into public.score_sheets (id, owner_id, game_id, name) values
|
||||
('f7000000-0000-0000-0000-0000000000b5', '11111111-1111-1111-1111-111111111111', '7a1b2c3d-0000-4000-8000-000000000002', 'Bogen');
|
||||
|
||||
-- Unbeteiligtes Spiel mit Verweisen: darf vom Zusammenführen nicht berührt werden
|
||||
insert into public.games (id, title, visibility) values ('f7000000-0000-0000-0000-000000000501', 'Unbeteiligt', 'catalog');
|
||||
insert into public.game_variants (id, game_id, name) values ('f7000000-0000-0000-0000-0000000005e1', 'f7000000-0000-0000-0000-000000000501', 'Deluxe');
|
||||
insert into public.collection_items (id, owner_id, game_id, variant_id, list, note) values
|
||||
('f7000000-0000-0000-0000-0000000005c1', '44444444-4444-4444-4444-444444444444', 'f7000000-0000-0000-0000-000000000501', 'f7000000-0000-0000-0000-0000000005e1', 'wishlist', 'bleibt');
|
||||
insert into public.plays (id, owner_id, game_id, variant_id, played_at, client_id) values
|
||||
('f7000000-0000-0000-0000-0000000005b1', '44444444-4444-4444-4444-444444444444', 'f7000000-0000-0000-0000-000000000501', 'f7000000-0000-0000-0000-0000000005e1', '2026-04-01', 'f7000000-0000-0000-0000-0000000005b1');
|
||||
insert into public.favorite_games (id, profile_id, game_id, position) values
|
||||
('f7000000-0000-0000-0000-0000000005d1', '44444444-4444-4444-4444-444444444444', 'f7000000-0000-0000-0000-000000000501', 1);
|
||||
|
||||
-- Runbook 5, Schritt 1: Duplikate finden (global; geprüft wird nur die Fixture-Zeile)
|
||||
create temp table rb_dup as
|
||||
select lower(btrim(title)) as titel_normalisiert, year,
|
||||
|
|
@ -233,6 +243,17 @@ select set_eq($$select id, ausgeblendet, merged_into_id, bgg_id, catalog_source_
|
|||
('7a1b2c3d-0000-4000-8000-000000000003', false, null, 13, true)$$,
|
||||
'KAT-05 Kriterium 4: Quelle ausgeblendet und verwiesen, Ziel sichtbar und mit BGG-Kennung und Quelle der Quelle');
|
||||
|
||||
-- Runbook 5, Schritt 4: Nachprüfung auf Ketten (muss leer sein)
|
||||
create temp table rb_ketten as
|
||||
select id from public.games where merged_into_id = '7a1b2c3d-0000-4000-8000-000000000002';
|
||||
select is_empty($$select * from rb_ketten$$, 'KAT-05 Kriterium 8: Runbook 5, kein Spiel verweist mehr auf die Quelle');
|
||||
|
||||
-- Runbook 5, Fehlertext merge_target_not_listed: wohin verweist ein zusammengeführtes Spiel?
|
||||
create temp table rb_verweis as
|
||||
select id, title, hidden_at, merged_into_id from public.games where id = '7a1b2c3d-0000-4000-8000-000000000002';
|
||||
select results_eq($$select merged_into_id from rb_verweis$$, $$values ('7a1b2c3d-0000-4000-8000-000000000003'::uuid)$$,
|
||||
'KAT-05 Kriterium 8: Runbook 5, Nachschlagen des endgültigen Ziels über merged_into_id');
|
||||
|
||||
-- Weitere Prüfungen am Ergebnis des Hauptfalls
|
||||
select is((select count(*) from public.catalog_games where id in ('7a1b2c3d-0000-4000-8000-000000000002', '7a1b2c3d-0000-4000-8000-000000000003')), 1::bigint,
|
||||
'KAT-05 Kriterium 4: catalog_games enthält nur noch das Ziel');
|
||||
|
|
@ -268,8 +289,16 @@ select is((select row(list, note, acquired_on)::text from public.collection_item
|
|||
select is((select row(list, note, acquired_on)::text from public.collection_items where id = 'f7000000-0000-0000-0000-0000000000c7'),
|
||||
'(owned,"Doras Quellnotiz",)',
|
||||
'Spec-F1: Dora: owned (Quelle) vor shopping (Ziel); eine leere Notiz am Ziel wird aus der Quelle gefüllt');
|
||||
select is((select count(*) from public.collection_items where owner_id = '44444444-4444-4444-4444-444444444444'), 1::bigint,
|
||||
select is((select count(*) from public.collection_items where owner_id = '44444444-4444-4444-4444-444444444444'
|
||||
and game_id in ('7a1b2c3d-0000-4000-8000-000000000002', '7a1b2c3d-0000-4000-8000-000000000003')), 1::bigint,
|
||||
'Spec-F1: Doras Quell-Eintrag ist gelöscht');
|
||||
select is((select row(c.game_id, c.variant_id, c.list, c.note)::text || '|' || p.game_id::text || p.variant_id::text || '|' || f.game_id::text || f.position::text
|
||||
from public.collection_items c, public.plays p, public.favorite_games f
|
||||
where c.id = 'f7000000-0000-0000-0000-0000000005c1' and p.id = 'f7000000-0000-0000-0000-0000000005b1' and f.id = 'f7000000-0000-0000-0000-0000000005d1'),
|
||||
'(f7000000-0000-0000-0000-000000000501,f7000000-0000-0000-0000-0000000005e1,wishlist,bleibt)|f7000000-0000-0000-0000-000000000501f7000000-0000-0000-0000-0000000005e1|f7000000-0000-0000-0000-0000000005011',
|
||||
'Daten unbeteiligter Spiele bleiben unverändert (Sammlung, Partie, Favorit, Variante)');
|
||||
select is((select count(*) from public.game_variants where game_id = 'f7000000-0000-0000-0000-000000000501'), 1::bigint,
|
||||
'Varianten unbeteiligter Spiele bleiben erhalten');
|
||||
select is((select count(*) from public.collection_items where game_id = '7a1b2c3d-0000-4000-8000-000000000003'), 4::bigint,
|
||||
'Jeder der vier Nutzer hat genau einen Eintrag am Ziel');
|
||||
|
||||
|
|
@ -308,7 +337,7 @@ select is((select count(distinct variant_id) from (
|
|||
-- Löschen eines Ziels mit Verweis (nur der Verweis merged_into_id, ohne andere Verweise)
|
||||
insert into public.games (id, title, visibility) values
|
||||
('f7000000-0000-0000-0000-0000000000b8', 'Löschziel', 'catalog'), ('f7000000-0000-0000-0000-0000000000b9', 'Löschquelle', 'catalog');
|
||||
select public.admin_merge_games('f7000000-0000-0000-0000-0000000000b9', 'f7000000-0000-0000-0000-0000000000b8');
|
||||
select lives_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-0000000000b9', 'f7000000-0000-0000-0000-0000000000b8')$$, 'Zusammenführen läuft ohne Fehler (0000b8)');
|
||||
select throws_ok($$delete from public.games where id = 'f7000000-0000-0000-0000-0000000000b8'$$, '23503',
|
||||
null, 'KAT-05 Kriterium 4: Ein Ziel mit Verweis lässt sich nicht löschen (on delete restrict)');
|
||||
|
||||
|
|
@ -356,7 +385,7 @@ select is((select r ? 'bgg_id_conflict' from rb_tgt), false, 'Kein Konflikt, wen
|
|||
insert into public.games (id, title, visibility, hidden_at) values
|
||||
('f7000000-0000-0000-0000-000000000131', 'Versteckte Quelle', 'catalog', '2020-05-05 00:00:00+00'),
|
||||
('f7000000-0000-0000-0000-000000000132', 'Ziel zu Versteckt', 'catalog', null);
|
||||
select public.admin_merge_games('f7000000-0000-0000-0000-000000000131', 'f7000000-0000-0000-0000-000000000132');
|
||||
select lives_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-000000000131', 'f7000000-0000-0000-0000-000000000132')$$, 'Zusammenführen läuft ohne Fehler (000132)');
|
||||
select is((select hidden_at from public.games where id = 'f7000000-0000-0000-0000-000000000131'), '2020-05-05 00:00:00+00'::timestamptz,
|
||||
'Eine bereits ausgeblendete Quelle behält ihr hidden_at');
|
||||
select is((select merged_into_id from public.games where id = 'f7000000-0000-0000-0000-000000000131'), 'f7000000-0000-0000-0000-000000000132'::uuid,
|
||||
|
|
@ -369,7 +398,7 @@ insert into public.games (id, title, visibility) values
|
|||
('f7000000-0000-0000-0000-000000000141', 'Kette A', 'catalog'),
|
||||
('f7000000-0000-0000-0000-000000000142', 'Kette B', 'catalog'),
|
||||
('f7000000-0000-0000-0000-000000000143', 'Kette C', 'catalog');
|
||||
select public.admin_merge_games('f7000000-0000-0000-0000-000000000143', 'f7000000-0000-0000-0000-000000000141');
|
||||
select lives_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-000000000143', 'f7000000-0000-0000-0000-000000000141')$$, 'Zusammenführen läuft ohne Fehler (000141)');
|
||||
select is((select merged_into_id from public.games where id = 'f7000000-0000-0000-0000-000000000143'), 'f7000000-0000-0000-0000-000000000141'::uuid, 'Kette: C zeigt zuerst auf A');
|
||||
create temp table rb_chain as select public.admin_merge_games('f7000000-0000-0000-0000-000000000141', 'f7000000-0000-0000-0000-000000000142') as r;
|
||||
select is((select merged_into_id from public.games where id = 'f7000000-0000-0000-0000-000000000143'), 'f7000000-0000-0000-0000-000000000142'::uuid,
|
||||
|
|
@ -392,7 +421,7 @@ insert into public.game_variants (id, game_id, name, created_at) values
|
|||
insert into public.plays (id, owner_id, game_id, variant_id, played_at, client_id) values
|
||||
('f7000000-0000-0000-0000-0000000001c1', '11111111-1111-1111-1111-111111111111', 'f7000000-0000-0000-0000-000000000151',
|
||||
'f7000000-0000-0000-0000-0000000001b1', '2026-02-01', 'f7000000-0000-0000-0000-0000000001c1');
|
||||
select public.admin_merge_games('f7000000-0000-0000-0000-000000000151', 'f7000000-0000-0000-0000-000000000152');
|
||||
select lives_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-000000000151', 'f7000000-0000-0000-0000-000000000152')$$, 'Zusammenführen läuft ohne Fehler (000152)');
|
||||
select is((select variant_id from public.plays where id = 'f7000000-0000-0000-0000-0000000001c1'), 'f7000000-0000-0000-0000-0000000001a1'::uuid,
|
||||
'KAT-05 Kriterium 4: bei Gleichstand von created_at gilt die kleinste id');
|
||||
select is((select count(*) from public.game_variants where game_id = 'f7000000-0000-0000-0000-000000000152'), 3::bigint,
|
||||
|
|
@ -456,11 +485,59 @@ insert into public.game_variants (id, game_id, name) values
|
|||
insert into public.collection_items (id, owner_id, game_id, variant_id, list) values
|
||||
('f7000000-0000-0000-0000-0000000003c1', '33333333-3333-3333-3333-333333333333', 'f7000000-0000-0000-0000-0000000003a1', 'f7000000-0000-0000-0000-0000000003e1', 'owned'),
|
||||
('f7000000-0000-0000-0000-0000000003c2', '33333333-3333-3333-3333-333333333333', 'f7000000-0000-0000-0000-0000000003b1', null, 'owned');
|
||||
select public.admin_merge_games('f7000000-0000-0000-0000-0000000003a1', 'f7000000-0000-0000-0000-0000000003b1');
|
||||
select lives_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-0000000003a1', 'f7000000-0000-0000-0000-0000000003b1')$$, 'Zusammenführen läuft ohne Fehler (0003b1)');
|
||||
select is((select v.name || '/' || v.game_id::text from public.collection_items c join public.game_variants v on v.id = c.variant_id
|
||||
where c.id = 'f7000000-0000-0000-0000-0000000003c2'),
|
||||
'Jubiläum/f7000000-0000-0000-0000-0000000003b1', 'Spec-F1: Kollision: die Edition der Quelle kommt über die Zuordnung (Kopie am Ziel) in den Zieleintrag');
|
||||
|
||||
-- Spec-F1 Kernregel (W1): Doppelbesitz, beide Einträge haben eine eigene Edition -> der Zieleintrag behält seine
|
||||
insert into public.games (id, title, visibility) values
|
||||
('f7000000-0000-0000-0000-000000000401', 'Edition Quelle', 'catalog'), ('f7000000-0000-0000-0000-000000000402', 'Edition Ziel', 'catalog');
|
||||
insert into public.game_variants (id, game_id, name) values
|
||||
('f7000000-0000-0000-0000-0000000004e1', 'f7000000-0000-0000-0000-000000000401', 'Y-Edition'),
|
||||
('f7000000-0000-0000-0000-0000000004e2', 'f7000000-0000-0000-0000-000000000402', 'X-Edition');
|
||||
insert into public.collection_items (id, owner_id, game_id, variant_id, list) values
|
||||
('f7000000-0000-0000-0000-0000000004c1', '11111111-1111-1111-1111-111111111111', 'f7000000-0000-0000-0000-000000000401', 'f7000000-0000-0000-0000-0000000004e1', 'owned'),
|
||||
('f7000000-0000-0000-0000-0000000004c2', '11111111-1111-1111-1111-111111111111', 'f7000000-0000-0000-0000-000000000402', 'f7000000-0000-0000-0000-0000000004e2', 'owned');
|
||||
select lives_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-000000000401', 'f7000000-0000-0000-0000-000000000402')$$,
|
||||
'Zusammenführen mit Doppelbesitz und zwei Editionen läuft');
|
||||
select is((select variant_id from public.collection_items where id = 'f7000000-0000-0000-0000-0000000004c2'),
|
||||
'f7000000-0000-0000-0000-0000000004e2'::uuid, 'Spec-F1: Doppelbesitz: der Zieleintrag behält seine eigene Edition (X), die der Quelle (Y) verdrängt sie nicht');
|
||||
|
||||
-- N15 (W2): privater Eintrag -> Katalog, Doppelbesitz, Ziel ohne Edition, Quelle mit Edition ohne Gegenstück -> geleert und gezählt
|
||||
insert into public.games (id, title, visibility, owner_id) values
|
||||
('f7000000-0000-0000-0000-000000000411', 'Vorläufig Cara', 'private', '33333333-3333-3333-3333-333333333333');
|
||||
insert into public.games (id, title, visibility) values ('f7000000-0000-0000-0000-000000000412', 'Ziel Cara', 'catalog');
|
||||
insert into public.game_variants (id, game_id, name) values
|
||||
('f7000000-0000-0000-0000-0000000004e3', 'f7000000-0000-0000-0000-000000000411', 'Nur bei Cara');
|
||||
insert into public.collection_items (id, owner_id, game_id, variant_id, list) values
|
||||
('f7000000-0000-0000-0000-0000000004c3', '33333333-3333-3333-3333-333333333333', 'f7000000-0000-0000-0000-000000000411', 'f7000000-0000-0000-0000-0000000004e3', 'owned'),
|
||||
('f7000000-0000-0000-0000-0000000004c4', '33333333-3333-3333-3333-333333333333', 'f7000000-0000-0000-0000-000000000412', null, 'owned');
|
||||
create temp table rb_n15 as select public.repoint_game_references('f7000000-0000-0000-0000-000000000411', 'f7000000-0000-0000-0000-000000000412') as r;
|
||||
select is((select r from rb_n15),
|
||||
'{"collection_items":0,"collection_conflicts":1,"favorite_games":0,"favorite_conflicts":0,"plays":0,"score_sheets":0,"variants_copied":0,"variants_mapped":0,"variants_cleared":1}'::jsonb,
|
||||
'KAT-05 Kriterium 4 (N15): Doppelbesitz mit Edition ohne Gegenstück: variants_cleared = 1');
|
||||
select is((select variant_id from public.collection_items where id = 'f7000000-0000-0000-0000-0000000004c4'), null::uuid,
|
||||
'KAT-05 Kriterium 4 (N15): die Edition am Zieleintrag bleibt leer (nichts kopiert)');
|
||||
select is((select count(*) from public.game_variants where game_id = 'f7000000-0000-0000-0000-000000000412'), 0::bigint,
|
||||
'KAT-05 Kriterium 4 (N15): das Katalogspiel bekommt keine Variante');
|
||||
|
||||
-- Zwei gleichnamige Quellvarianten werden einzeln kopiert (nur gegen vorher vorhandene Zielvarianten abgeglichen)
|
||||
insert into public.games (id, title, visibility) values
|
||||
('f7000000-0000-0000-0000-000000000421', 'Zwilling Quelle', 'catalog'), ('f7000000-0000-0000-0000-000000000422', 'Zwilling Ziel', 'catalog');
|
||||
insert into public.game_variants (id, game_id, name, created_at) values
|
||||
('f7000000-0000-0000-0000-0000000004f1', 'f7000000-0000-0000-0000-000000000421', 'Twin', '2020-01-01'),
|
||||
('f7000000-0000-0000-0000-0000000004f2', 'f7000000-0000-0000-0000-000000000421', 'twin ', '2021-01-01');
|
||||
create temp table rb_twin as select public.admin_merge_games('f7000000-0000-0000-0000-000000000421', 'f7000000-0000-0000-0000-000000000422') as r;
|
||||
select is((select (r ->> 'variants_copied') || '/' || (r ->> 'variants_mapped') from rb_twin), '2/0',
|
||||
'Zwei gleichnamige Quellvarianten werden beide kopiert, keine auf die eben kopierte abgebildet');
|
||||
select is((select count(*) from public.game_variants where game_id = 'f7000000-0000-0000-0000-000000000422'), 2::bigint,
|
||||
'Das Ziel hat danach beide Kopien');
|
||||
|
||||
-- Ziel null
|
||||
select throws_ok($$select public.admin_merge_games('f7000000-0000-0000-0000-000000000001', null)$$,
|
||||
'P0001', 'merge_game_not_found', 'KAT-05 Kriterium 4: fehlendes Ziel (null) ist merge_game_not_found');
|
||||
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
-- Fremdschlüssel-Wächter: jeder Verweis auf games ist entweder behandelt oder bewusst ausgelassen
|
||||
-- ---------------------------------------------------------------------------------------------
|
||||
|
|
@ -480,5 +557,14 @@ select set_eq($$select fk from fk_found$$,
|
|||
('game_genres.game_id'), ('game_mechanics.game_id'), ('game_categories.game_id'), ('game_publishers.game_id')$$,
|
||||
'KAT-05 Kriterium 4: Fremdschlüssel-Wächter: neue Verweise auf games erzwingen eine Entscheidung im Zusammenführen');
|
||||
|
||||
-- Zweiter Wächter: Verweise auf game_variants. Sie werden über die Variantenzuordnung umgesetzt;
|
||||
-- ein neuer Verweis (z. B. aus SAM-02/SAM-04) erzwingt eine Entscheidung in repoint_game_references.
|
||||
select set_eq($$select (select relname from pg_class where oid = c.conrelid) || '(' ||
|
||||
(select string_agg(a.attname, ', ' order by k.ord) from unnest(c.conkey) with ordinality as k(attnum, ord)
|
||||
join pg_attribute a on a.attrelid = c.conrelid and a.attnum = k.attnum) || ')'
|
||||
from pg_constraint c where c.contype = 'f' and c.confrelid = 'public.game_variants'::regclass$$,
|
||||
$$values ('collection_items(variant_id, game_id)'), ('plays(variant_id, game_id)')$$,
|
||||
'KAT-05 Kriterium 4: Fremdschlüssel-Wächter: Verweise auf game_variants sind genau Sammlung und Partien (beide über die Variantenzuordnung)');
|
||||
|
||||
select * from finish();
|
||||
rollback;
|
||||
|
|
|
|||
Loading…
Reference in a new issue