Adds the unchanged runbook queries (rolconfig, pg_available_extensions, pg_settings) and asserts the settings that keep search terms out of logs. katalog-pflege 8.2 now states that the three log settings depend on each other. Refs #3 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
834 lines
78 KiB
PL/PgSQL
834 lines
78 KiB
PL/PgSQL
-- KAT-01 (PR 3a): Katalogsuche. search_normalize, generierte Spalten, game_alt_titles, RPC
|
||
-- search_catalog_games, Rechte, Idempotenz der alternativen Titel im Startbestand-Werkzeug und die
|
||
-- SQL-Bausteine aus docs/katalog-pflege.md (Abschnitte 3.1, 5 und 8; Kommentare mit Abschnittsverweis).
|
||
-- Läuft auch mit eingespieltem Startbestand: eigene Fixtures mit festen IDs (Präfix f9) und dem
|
||
-- Kunstwort "qxz" in allen Titeln und Verlagen, damit Suchen nie Startbestandsspiele treffen.
|
||
-- Ohne Suchbegriff wird nur auf Fixture-IDs geprüft (Hilfsfunktion pg_temp.hits blättert durch).
|
||
-- Die Include-Dateien in ../support/ (\ir) erzeugt supabase/catalog/build_startbestand.ts.
|
||
begin;
|
||
select plan(217);
|
||
|
||
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"}'),
|
||
('22222222-2222-2222-2222-222222222222', 'ben@example.com', '{"age_confirmed":"true","terms_accepted":"true","terms_version":"test","display_name":"Ben"}');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Erweiterungen und Rechte (Sicherheit Kriterium 2, Spec 3.2)
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select is((select string_agg(extname || ':' || extnamespace::regnamespace::text, ',' order by extname)
|
||
from pg_extension where extname in ('pg_trgm', 'unaccent')),
|
||
'pg_trgm:extensions,unaccent:extensions', 'KAT-01: pg_trgm und unaccent liegen im Schema extensions');
|
||
select ok(has_function_privilege('anon', 'extensions.unaccent(regdictionary, text)', 'execute')
|
||
and has_function_privilege('authenticated', 'extensions.unaccent(regdictionary, text)', 'execute')
|
||
and has_function_privilege('anon', 'extensions.similarity(text, text)', 'execute')
|
||
and has_function_privilege('authenticated', 'extensions.similarity(text, text)', 'execute')
|
||
and has_schema_privilege('anon', 'extensions', 'usage') and has_schema_privilege('authenticated', 'extensions', 'usage'),
|
||
'KAT-01: anon und authenticated dürfen unaccent und similarity im Schema extensions ausführen (B5)');
|
||
|
||
select ok(has_function_privilege('anon', 'public.search_normalize(text)', 'execute')
|
||
and has_function_privilege('authenticated', 'public.search_normalize(text)', 'execute')
|
||
and has_function_privilege('service_role', 'public.search_normalize(text)', 'execute'),
|
||
'KAT-01: search_normalize ist für anon, authenticated und service_role ausführbar');
|
||
select is((select p.proacl is not null and not exists (select 1 from aclexplode(p.proacl) a where a.grantee = 0)
|
||
from pg_proc p where p.oid = 'public.search_normalize(text)'::regprocedure), true,
|
||
'KAT-01: search_normalize ist für public (alle) nicht ausführbar (Rechteliste ausdrücklich gesetzt)');
|
||
select ok(has_function_privilege('anon', 'public.search_catalog_games(text, smallint, smallint, smallint, uuid[], uuid[], integer, integer)', 'execute')
|
||
and has_function_privilege('authenticated', 'public.search_catalog_games(text, smallint, smallint, smallint, uuid[], uuid[], integer, integer)', 'execute'),
|
||
'KAT-01 Sicherheit Kriterium 2: anon und authenticated dürfen search_catalog_games ausführen');
|
||
select ok(not has_function_privilege('service_role', 'public.search_catalog_games(text, smallint, smallint, smallint, uuid[], uuid[], integer, integer)', 'execute'),
|
||
'KAT-01: service_role darf search_catalog_games nicht ausführen');
|
||
select is((select p.proacl is not null and not exists (select 1 from aclexplode(p.proacl) a where a.grantee = 0)
|
||
from pg_proc p where p.oid = 'public.search_catalog_games(text, smallint, smallint, smallint, uuid[], uuid[], integer, integer)'::regprocedure), true,
|
||
'KAT-01: search_catalog_games ist für public (alle) nicht ausführbar (Rechteliste ausdrücklich gesetzt)');
|
||
select is((select count(*) from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
||
where n.nspname = 'public' and p.proname = 'search_normalize'
|
||
and p.provolatile = 'i' and p.proisstrict and p.proparallel = 's' and not p.prosecdef
|
||
and p.proconfig = array['search_path=""']), 1::bigint,
|
||
'KAT-01: search_normalize ist immutable, strict, parallel safe, security invoker mit leerem search_path');
|
||
select is((select count(*) from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
||
where n.nspname = 'public' and p.proname = 'search_catalog_games'
|
||
and p.provolatile = 's' and not p.prosecdef and p.proconfig = array['search_path=""']), 1::bigint,
|
||
'KAT-01: search_catalog_games ist stable, security invoker mit leerem search_path');
|
||
select is((select count(*) from pg_proc p join pg_namespace n on n.oid = p.pronamespace
|
||
where n.nspname = 'public' and p.proname = 'search_catalog_games'), 1::bigint,
|
||
'KAT-01: es gibt genau eine Überladung von search_catalog_games');
|
||
select ok(exists (select 1 from pg_roles where rolname = 'anon' and exists (select 1 from unnest(rolconfig) c where c like 'statement_timeout=%'))
|
||
and exists (select 1 from pg_roles where rolname = 'authenticated' and exists (select 1 from unnest(rolconfig) c where c like 'statement_timeout=%')),
|
||
'KAT-01 Sicherheit (R4): anon und authenticated haben ein statement_timeout (schützt die Suche ohne Anmeldung)');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- search_normalize: Pflichtfälle (Spec 3.2 Nr. 2) und weitere Fälle
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select is(public.search_normalize('Die Siedler von Catan'), 'die siedler von catan', 'KAT-01 Kriterium 1: Pflichtfall Die Siedler von Catan');
|
||
select is(public.search_normalize('CATAN®'), 'catan', 'KAT-01 Kriterium 1: Pflichtfall CATAN® (Schutzzeichen entfernt, kein "catan r")');
|
||
select is(public.search_normalize('Spiel™ ½'), 'spiel 1 2', 'KAT-01 Kriterium 1: Pflichtfall Spiel™ ½ (Bruch wie von unaccent umgeschrieben)');
|
||
select is(public.search_normalize('Æon’s End'), 'aeon s end', 'KAT-01 Kriterium 1: Pflichtfall Æon’s End (Apostroph wird Leerzeichen)');
|
||
select is(public.search_normalize('7 Wonders: Duel'), '7 wonders duel', 'KAT-01 Kriterium 1: Pflichtfall 7 Wonders: Duel');
|
||
select is(public.search_normalize('Café Größe'), 'cafe grosse', 'KAT-01 Kriterium 1: Pflichtfall Café Größe (é, ö, ß)');
|
||
select is(public.search_normalize('Ticket to Ride – Europe'), 'ticket to ride europe', 'KAT-01 Kriterium 1: Pflichtfall Gedankenstrich');
|
||
select is(public.search_normalize('!!!'), null, 'KAT-01 Kriterium 1: nur Sonderzeichen ergibt null');
|
||
select is(public.search_normalize(''), null, 'KAT-01 Kriterium 1: leerer Text ergibt null');
|
||
select is(public.search_normalize(null), null, 'KAT-01: null ergibt null (strict)');
|
||
select is(public.search_normalize(' ©Ärger℠ ℗ Ü '), 'arger u', 'KAT-01 Kriterium 1: alle Schutzzeichen, Umlaute und Ränder');
|
||
select is(public.search_normalize('Die Siedler, von Catan'), 'die siedler von catan', 'KAT-01: mehrere Trenner werden zu einem Leerzeichen');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Generierte Spalten, Indizes, Tabelle game_alt_titles (Kriterium 2)
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select is((select string_agg(attrelid::regclass::text || '.' || attname, ',' order by attrelid::regclass::text)
|
||
from pg_attribute where attgenerated = 's' and attname in ('title_search', 'name_search')
|
||
and attrelid in ('public.games'::regclass, 'public.publishers'::regclass, 'public.game_alt_titles'::regclass)),
|
||
'game_alt_titles.title_search,games.title_search,publishers.name_search',
|
||
'KAT-01: title_search und name_search sind gespeicherte generierte Spalten');
|
||
select is((select count(*) from pg_indexes where schemaname = 'public'
|
||
and indexdef like '%USING gin (title_search %gin_trgm_ops)%'
|
||
and indexname in ('games_title_search_trgm_idx', 'game_alt_titles_title_search_trgm_idx')), 2::bigint,
|
||
'KAT-01: Trigramm-Indizes auf games.title_search und game_alt_titles.title_search');
|
||
select is((select count(*) from pg_indexes where schemaname = 'public' and indexname = 'games_title_search_trgm_idx'
|
||
and indexdef like '%WHERE%visibility%catalog%hidden_at IS NULL%merged_into_id IS NULL%'), 1::bigint,
|
||
'KAT-01: der Index auf games.title_search ist ein Partial-Index mit den Bedingungen der View catalog_games');
|
||
select is((select count(*) from pg_indexes where schemaname = 'public' and indexname = 'publishers_name_search_trgm_idx'
|
||
and indexdef like '%USING gin (name_search %gin_trgm_ops)%'), 1::bigint,
|
||
'KAT-01: Trigramm-Index auf publishers.name_search');
|
||
select is((select confdeltype::text from pg_constraint where conrelid = 'public.game_alt_titles'::regclass and contype = 'f'
|
||
and confrelid = 'public.games'::regclass), 'c', 'KAT-01 Kriterium 2: game_alt_titles.game_id löscht mit dem Spiel (on delete cascade)');
|
||
select ok(exists (select 1 from pg_trigger where tgrelid = 'public.game_alt_titles'::regclass and tgname = 'game_links_mark_manual'),
|
||
'KAT-01: game_alt_titles hat den Trigger game_links_mark_manual');
|
||
|
||
-- Rechte der Tabelle: nur Lesen für API-Rollen
|
||
select ok(has_table_privilege('anon', 'public.game_alt_titles', 'select') and has_table_privilege('authenticated', 'public.game_alt_titles', 'select'),
|
||
'KAT-01 Kriterium 2: anon und authenticated dürfen game_alt_titles lesen');
|
||
select is_empty($$select r, p from unnest(array['anon', 'authenticated']) r,
|
||
unnest(array['insert', 'update', 'delete', 'truncate', 'references', 'trigger']) p
|
||
where has_table_privilege(r, 'public.game_alt_titles', p)$$,
|
||
'KAT-01 Kriterium 2: anon und authenticated haben kein Schreibrecht und keine weiteren Rechte auf game_alt_titles');
|
||
select is_empty($$select r, c from unnest(array['anon', 'authenticated']) r, unnest(array['game_id', 'title', 'locale', 'id']) c
|
||
where has_column_privilege(r, 'public.game_alt_titles', c, 'insert') or has_column_privilege(r, 'public.game_alt_titles', c, 'update')$$,
|
||
'KAT-01 Kriterium 2: auch auf Spaltenebene kein Schreibrecht auf game_alt_titles');
|
||
select results_eq($$select polcmd::text, array(select rolname from pg_roles where oid = any(polroles) order by 1)
|
||
from pg_policy where polrelid = 'public.game_alt_titles'::regclass$$,
|
||
$$values ('r'::text, array['anon', 'authenticated']::name[])$$,
|
||
'KAT-01 Kriterium 2: game_alt_titles hat genau eine Policy, nur select für anon und authenticated');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Fixtures (alle Titel und Verlage mit "qxz")
|
||
-- ---------------------------------------------------------------------------------------------
|
||
insert into public.genres (id, name_de, name_en) values
|
||
('f9000000-0000-0000-0000-0000000000e1', 'Qxz Genre A', 'Qxz genre A'),
|
||
('f9000000-0000-0000-0000-0000000000e2', 'Qxz Genre B', 'Qxz genre B'),
|
||
('f9000000-0000-0000-0000-0000000000e3', 'Qxz Genre Q', 'Qxz genre Q'),
|
||
('f9000000-0000-0000-0000-0000000000e4', 'Qxz Genre R', 'Qxz genre R');
|
||
insert into public.mechanics (id, name_de, name_en) values
|
||
('f9000000-0000-0000-0000-0000000000d1', 'Qxz Mechanik A', 'Qxz mechanic A'),
|
||
('f9000000-0000-0000-0000-0000000000d2', 'Qxz Mechanik B', 'Qxz mechanic B');
|
||
insert into public.publishers (id, name) values
|
||
('f9000000-0000-0000-0000-0000000000b2', 'Qxz Hersteller Eins'),
|
||
('f9000000-0000-0000-0000-0000000000b1', 'Qxz Hersteller Zwei'),
|
||
('f9000000-0000-0000-0000-0000000000b3', 'Qxz Zorb Verlag');
|
||
|
||
insert into public.games (id, title, visibility) values
|
||
('f9000000-0000-0000-0000-000000000001', 'QXZ Zorb', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000002', 'Qxz Zorb Deluxe', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000003', 'Das Qxz Zorb', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000004', 'Zorb of Qxz', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000005', 'Qxz Great Zorb', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000006', 'Qxz Spiel', 'catalog'),
|
||
-- Gleichstand: erst die größere ID eingefügt, damit die Heap-Reihenfolge nicht zufällig die ID-Reihenfolge ist
|
||
('f9000000-0000-0000-0000-000000000008', 'Qxz Twin', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000007', 'QXZ TWIN', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000010', 'Qxz Hauptspiel Alpha', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000011', 'Alpha Qxz Bird', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000012', 'Qxz Einzigartig', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000013', 'Qxz Käse Größe', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000014', 'Qxz Æon’s End', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000015', 'Qxz 7 Wonders', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000016', 'QXZ CATAN®', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000040', 'Qxz Doppelspiel', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000041', 'Qxz Einzelspiel', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000060', 'Qxz Cabin', 'catalog'),
|
||
-- Sortierung ist binär (collate "C"): "qxz b" kommt vor "qxzab". Hinweis: Mit ICU-Sortierung (wie lokal) gilt das auch
|
||
-- ohne collate "C"; der Test ist dort nicht mutationsempfindlich und schützt vor libc-Sortierungen, die Leerzeichen ignorieren.
|
||
('f9000000-0000-0000-0000-000000000081', 'Qxzab', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000095', 'Qxz Zwilling', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000093', 'Qxz Zwilling', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000091', 'Qxz Zwilling', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000094', 'Qxz Zwilling', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000092', 'Qxz Zwilling', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000090', 'Qxz Zwilling', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000017', 'Qxz Wanderer', 'catalog'),
|
||
('f9000000-0000-0000-0000-000000000080', 'Qxz B', 'catalog');
|
||
|
||
-- Filter-Fixtures (20-25); 22 ohne jede Angabe, 23 nur max_minutes, 24 nur min_minutes
|
||
insert into public.games (id, title, visibility, min_players, max_players, min_minutes, max_minutes, min_age) values
|
||
('f9000000-0000-0000-0000-000000000020', 'Qxz Filter 20', 'catalog', 2, 4, 30, 60, 8),
|
||
('f9000000-0000-0000-0000-000000000021', 'Qxz Filter 21', 'catalog', 3, 6, 60, 90, 12),
|
||
('f9000000-0000-0000-0000-000000000022', 'Qxz Filter 22', 'catalog', null, null, null, null, null),
|
||
('f9000000-0000-0000-0000-000000000023', 'Qxz Filter 23', 'catalog', 2, 2, null, 45, 6),
|
||
('f9000000-0000-0000-0000-000000000024', 'Qxz Filter 24', 'catalog', 1, 8, 20, null, 0),
|
||
('f9000000-0000-0000-0000-000000000025', 'Qxz Filter 25', 'catalog', 2, 5, 30, 30, 10);
|
||
insert into public.game_genres (game_id, genre_id) values
|
||
('f9000000-0000-0000-0000-000000000020', 'f9000000-0000-0000-0000-0000000000e1'),
|
||
('f9000000-0000-0000-0000-000000000020', 'f9000000-0000-0000-0000-0000000000e2'),
|
||
('f9000000-0000-0000-0000-000000000021', 'f9000000-0000-0000-0000-0000000000e1'),
|
||
('f9000000-0000-0000-0000-000000000022', 'f9000000-0000-0000-0000-0000000000e1'),
|
||
('f9000000-0000-0000-0000-000000000025', 'f9000000-0000-0000-0000-0000000000e2'),
|
||
('f9000000-0000-0000-0000-000000000005', 'f9000000-0000-0000-0000-0000000000e3'),
|
||
('f9000000-0000-0000-0000-000000000060', 'f9000000-0000-0000-0000-0000000000e3'),
|
||
('f9000000-0000-0000-0000-000000000080', 'f9000000-0000-0000-0000-0000000000e4'),
|
||
('f9000000-0000-0000-0000-000000000081', 'f9000000-0000-0000-0000-0000000000e4');
|
||
insert into public.game_mechanics (game_id, mechanic_id) values
|
||
('f9000000-0000-0000-0000-000000000020', 'f9000000-0000-0000-0000-0000000000d1'),
|
||
('f9000000-0000-0000-0000-000000000021', 'f9000000-0000-0000-0000-0000000000d1'),
|
||
('f9000000-0000-0000-0000-000000000021', 'f9000000-0000-0000-0000-0000000000d2'),
|
||
('f9000000-0000-0000-0000-000000000025', 'f9000000-0000-0000-0000-0000000000d2');
|
||
|
||
-- Verlage: 01 und 30 bei Eins; 40 bei Eins und Zwei; 41 bei Zwei; 06 bei "Qxz Zorb Verlag"
|
||
insert into public.game_publishers (game_id, publisher_id) values
|
||
('f9000000-0000-0000-0000-000000000001', 'f9000000-0000-0000-0000-0000000000b2'),
|
||
('f9000000-0000-0000-0000-000000000040', 'f9000000-0000-0000-0000-0000000000b1'),
|
||
('f9000000-0000-0000-0000-000000000040', 'f9000000-0000-0000-0000-0000000000b2'),
|
||
('f9000000-0000-0000-0000-000000000041', 'f9000000-0000-0000-0000-0000000000b1'),
|
||
('f9000000-0000-0000-0000-000000000006', 'f9000000-0000-0000-0000-0000000000b3');
|
||
|
||
-- Ausgeblendet, zusammengeführt, privat
|
||
insert into public.games (id, title, visibility, hidden_at) values
|
||
('f9000000-0000-0000-0000-000000000030', 'Qxz Verborgen', 'catalog', now());
|
||
insert into public.games (id, title, visibility, hidden_at, merged_into_id) values
|
||
('f9000000-0000-0000-0000-000000000031', 'Qxz Verschmolzen', 'catalog', now(), 'f9000000-0000-0000-0000-000000000001');
|
||
insert into public.games (id, title, visibility, owner_id) values
|
||
('f9000000-0000-0000-0000-000000000032', 'Qxz Privat', 'private', '11111111-1111-1111-1111-111111111111'),
|
||
('f9000000-0000-0000-0000-000000000033', 'QXZ ZORB', 'private', '22222222-2222-2222-2222-222222222222');
|
||
insert into public.game_publishers (game_id, publisher_id) values
|
||
('f9000000-0000-0000-0000-000000000030', 'f9000000-0000-0000-0000-0000000000b2'),
|
||
('f9000000-0000-0000-0000-000000000033', 'f9000000-0000-0000-0000-0000000000b2');
|
||
|
||
-- Bilder: 50 mit Quelle ohne Erlaubnis (maskiert), 51 manuell gepflegt, 52 ohne Quelle
|
||
insert into public.games (id, title, visibility, image_path, catalog_source_id, source_ref) values
|
||
('f9000000-0000-0000-0000-000000000050', 'Qxz Cover Quelle', 'catalog', 'covers/quelle.png', (select id from public.catalog_sources where key = 'bgg'), '501'),
|
||
('f9000000-0000-0000-0000-000000000051', 'Qxz Cover Manuell', 'catalog', 'covers/manuell.png', (select id from public.catalog_sources where key = 'bgg'), '502'),
|
||
('f9000000-0000-0000-0000-000000000052', 'Qxz Cover Eigen', 'catalog', 'covers/eigen.png', null, null);
|
||
update public.games set manual_fields = '{}' where id = 'f9000000-0000-0000-0000-000000000050';
|
||
update public.games set manual_fields = array['image_path'] where id = 'f9000000-0000-0000-0000-000000000051';
|
||
|
||
-- 40 Spiele für das Nachladen (Kriterium 5)
|
||
insert into public.games (id, title, visibility)
|
||
select ('f9000000-0000-0000-0000-0000000001' || lpad(n::text, 2, '0'))::uuid, 'Qxz Seite ' || lpad(n::text, 2, '0'), 'catalog'
|
||
from generate_series(1, 40) n;
|
||
|
||
-- Alternative Titel (als postgres, wie im Studio)
|
||
insert into public.game_alt_titles (id, game_id, locale, title) values
|
||
('f9000000-0000-0000-0000-0000000000a1', 'f9000000-0000-0000-0000-000000000010', 'de', 'Qxz Zugvogel'),
|
||
('f9000000-0000-0000-0000-0000000000a2', 'f9000000-0000-0000-0000-000000000010', 'en', 'Qxz Migrant Bird'),
|
||
('f9000000-0000-0000-0000-0000000000a3', 'f9000000-0000-0000-0000-000000000011', 'en', 'Qxz Bird'),
|
||
('f9000000-0000-0000-0000-0000000000a4', 'f9000000-0000-0000-0000-000000000012', 'de', 'Qxz Einzigartig Alt'),
|
||
('f9000000-0000-0000-0000-0000000000a5', 'f9000000-0000-0000-0000-000000000012', 'en', 'QXZ EINZIGARTIG ALT'),
|
||
('f9000000-0000-0000-0000-0000000000a6', 'f9000000-0000-0000-0000-000000000030', 'de', 'Qxz Verborgen Alt'),
|
||
('f9000000-0000-0000-0000-0000000000a7', 'f9000000-0000-0000-0000-000000000031', 'de', 'Qxz Verschmolzen Alt'),
|
||
('f9000000-0000-0000-0000-0000000000a8', 'f9000000-0000-0000-0000-000000000032', 'de', 'Qxz Privat Alt'),
|
||
('f9000000-0000-0000-0000-0000000000a9', 'f9000000-0000-0000-0000-000000000033', 'en', 'Qxz Bens Alt'),
|
||
-- drei Treffer-Titel für "qxz nomad": Stufe 2, Stufe 1 (länger, weniger ähnlich), Stufe 1 (kürzer, ähnlicher)
|
||
('f9000000-0000-0000-0000-0000000000c1', 'f9000000-0000-0000-0000-000000000017', 'de', 'Stern Qxz Nomad'),
|
||
('f9000000-0000-0000-0000-0000000000c2', 'f9000000-0000-0000-0000-000000000017', 'en', 'Qxz Nomad Sternenwanderer'),
|
||
('f9000000-0000-0000-0000-0000000000c3', 'f9000000-0000-0000-0000-000000000017', 'de', 'Qxz Nomadisch');
|
||
|
||
select is((select title_search from public.games where id = 'f9000000-0000-0000-0000-000000000014'), 'qxz aeon s end',
|
||
'KAT-01 Kriterium 1: games.title_search wird beim Anlegen gefüllt');
|
||
select is((select title_search from public.game_alt_titles where id = 'f9000000-0000-0000-0000-0000000000a5'), 'qxz einzigartig alt',
|
||
'KAT-01 Kriterium 2: game_alt_titles.title_search wird gefüllt');
|
||
select is((select name_search from public.publishers where id = 'f9000000-0000-0000-0000-0000000000b1'), 'qxz hersteller zwei',
|
||
'KAT-01 Kriterium 3: publishers.name_search wird gefüllt');
|
||
update public.publishers set name = 'Qxz Hersteller Zwei & Söhne' where id = 'f9000000-0000-0000-0000-0000000000b1';
|
||
select is((select name_search from public.publishers where id = 'f9000000-0000-0000-0000-0000000000b1'), 'qxz hersteller zwei sohne',
|
||
'KAT-01: publishers.name_search folgt einer Namensänderung');
|
||
update public.publishers set name = 'Qxz Hersteller Zwei' where id = 'f9000000-0000-0000-0000-0000000000b1';
|
||
|
||
-- Hilfsfunktion: alle Fixture-Treffer durchblättern (nur als postgres, für Abfragen ohne Suchbegriff)
|
||
create function pg_temp.hits(p_query text default null, p_players smallint default null, p_max_minutes smallint default null,
|
||
p_age smallint default null, p_genre_ids uuid[] default null, p_mechanic_ids uuid[] default null)
|
||
returns setof uuid language sql as $$
|
||
select r.id
|
||
from generate_series(0, (select coalesce(max(t.total_count), 0)
|
||
from public.search_catalog_games(p_query, p_players, p_max_minutes, p_age, p_genre_ids, p_mechanic_ids, 1, 0) t)::int, 50) o,
|
||
lateral public.search_catalog_games(p_query, p_players, p_max_minutes, p_age, p_genre_ids, p_mechanic_ids, 50, o) r
|
||
where r.id::text like 'f9000000%'
|
||
$$;
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Kriterium 1: Teilzeichenkette, Groß/Klein, Sonderzeichen, Relevanzstufen
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select results_eq($$select id, match_rank::int, total_count::int from public.search_catalog_games('qxz zorb')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid, 0, 6),
|
||
('f9000000-0000-0000-0000-000000000002', 1, 6),
|
||
('f9000000-0000-0000-0000-000000000003', 2, 6),
|
||
('f9000000-0000-0000-0000-000000000004', 3, 6),
|
||
('f9000000-0000-0000-0000-000000000005', 3, 6),
|
||
('f9000000-0000-0000-0000-000000000006', 4, 6)$$,
|
||
'KAT-01 Kriterium 1: Relevanzstufen 0 bis 4 in der erwarteten Reihenfolge; gleiche Stufe nach Ähnlichkeit (nicht nach Titel); Verlagstreffer zuletzt');
|
||
select results_eq($$select id from public.search_catalog_games('QXZ: ZORB!')$$,
|
||
$$select id from public.search_catalog_games('qxz zorb')$$,
|
||
'KAT-01 Kriterium 1: Groß-/Kleinschreibung und Sonderzeichen ändern das Ergebnis nicht');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('qxz zo')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid, 1), ('f9000000-0000-0000-0000-000000000002', 1),
|
||
('f9000000-0000-0000-0000-000000000003', 2), ('f9000000-0000-0000-0000-000000000004', 3),
|
||
('f9000000-0000-0000-0000-000000000005', 3), ('f9000000-0000-0000-0000-000000000006', 4)$$,
|
||
'KAT-01 Kriterium 1: ein Teil des Titels genügt (Wort ab 2 Zeichen als Teilzeichenkette)');
|
||
select results_eq($$select id from public.search_catalog_games('qxz twin')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000007'::uuid), ('f9000000-0000-0000-0000-000000000008')$$,
|
||
'KAT-01 Kriterium 5: bei völligem Gleichstand entscheidet die ID (stabile Reihenfolge)');
|
||
select results_eq($$select id from public.search_catalog_games('qxz zorb', null, null, null, null, null, 4, 0)$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid), ('f9000000-0000-0000-0000-000000000002'), ('f9000000-0000-0000-0000-000000000003'), ('f9000000-0000-0000-0000-000000000004')$$,
|
||
'KAT-01 Kriterium 5: die erste Seite enthält die relevantesten Treffer (Stufe, dann Ähnlichkeit), nicht die ersten nach Titel');
|
||
select results_eq($$select id from public.search_catalog_games('qxz zorb', null, null, null, null, null, 2, 0)
|
||
union all select id from public.search_catalog_games('qxz zorb', null, null, null, null, null, 2, 2)
|
||
union all select id from public.search_catalog_games('qxz zorb', null, null, null, null, null, 2, 4)$$,
|
||
$$select id from public.search_catalog_games('qxz zorb')$$,
|
||
'KAT-01 Kriterium 5: Seiten zu je zwei ergeben dieselbe Reihenfolge wie die ganze Liste');
|
||
select results_eq($$select id from public.search_catalog_games('qxz twin', null, null, null, null, null, 1, 0)
|
||
union all select id from public.search_catalog_games('qxz twin', null, null, null, null, null, 1, 1)$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000007'::uuid), ('f9000000-0000-0000-0000-000000000008')$$,
|
||
'KAT-01 Kriterium 5: bei völligem Gleichstand entscheidet die ID auch über die Seitengrenze hinweg');
|
||
select results_eq($$select id from public.search_catalog_games('qxz zwilling', null, null, null, null, null, 3, 0)
|
||
union all select id from public.search_catalog_games('qxz zwilling', null, null, null, null, null, 3, 3)$$,
|
||
$$select id from public.games where title = 'Qxz Zwilling' order by id$$,
|
||
'KAT-01 Kriterium 5: sechs gleich gute Treffer erscheinen seitenübergreifend in ID-Reihenfolge');
|
||
select results_eq($$select id, match_rank::int, matched_alt_title from public.search_catalog_games('qxz nomad')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000017'::uuid, 1, 'Qxz Nomadisch'::text)$$,
|
||
'KAT-01 Kriterium 2: von mehreren treffenden alternativen Titeln gewinnt die beste Stufe, dann die größte Ähnlichkeit');
|
||
select results_eq($$select id, match_rank::int, matched_alt_title from public.search_catalog_games('qxz einzig')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000012'::uuid, 1, null::text)$$,
|
||
'KAT-01 Kriterium 2: bei gleicher Stufe von Haupttitel und alternativem Titel gilt der Haupttitel (matched_alt_title null)');
|
||
select is((select count(*) from public.search_catalog_games('qxz käse')), 1::bigint, 'KAT-01 Kriterium 1: Umlaut im Suchbegriff findet den Umlaut-Titel');
|
||
select is((select count(*) from public.search_catalog_games('QXZ KASE GROSSE')), 1::bigint, 'KAT-01 Kriterium 1: ohne Umlaut und ß wird derselbe Titel gefunden');
|
||
select is((select count(*) from public.search_catalog_games('qxz grösse')), 1::bigint, 'KAT-01 Kriterium 1: ö und ß in der Eingabe');
|
||
select is((select count(*) from public.search_catalog_games('qxz kaese')), 0::bigint, 'KAT-01 Spec-F5: "ae" findet "ä" in R1 nicht');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('qxz aeons end')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000014'::uuid, 0)$$,
|
||
'KAT-01 Kriterium 1: "aeons end" findet "Æon’s End" über die Schreibweise ohne Leerzeichen');
|
||
select is((select count(*) from public.search_catalog_games('qxzaeonsend')), 1::bigint, 'KAT-01 Kriterium 1: ohne jedes Leerzeichen eingegeben');
|
||
select is((select count(*) from public.search_catalog_games('qxz æon’s end')), 1::bigint, 'KAT-01 Kriterium 1: Æ und Apostroph in der Eingabe');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('qxz 7wonders')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000015'::uuid, 0)$$,
|
||
'KAT-01 Kriterium 1: "7wonders" findet "7 Wonders"');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('Qxz-Catan!')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000016'::uuid, 0)$$,
|
||
'KAT-01 Kriterium 1: "CATAN®" wird über "catan" gefunden');
|
||
select is((select count(*) from public.search_catalog_games('!!!')), 0::bigint, 'KAT-01 Kriterium 1: nur Sonderzeichen ergibt keine Treffer');
|
||
select is((select count(*) from public.search_catalog_games('zzznichtvorhanden')), 0::bigint, 'KAT-01 Kriterium 6: Suche ohne Treffer liefert keine Zeilen');
|
||
select results_eq($$select id from public.search_catalog_games('qxz 9 zorb') order by id$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid), ('f9000000-0000-0000-0000-000000000002'), ('f9000000-0000-0000-0000-000000000003'),
|
||
('f9000000-0000-0000-0000-000000000004'), ('f9000000-0000-0000-0000-000000000005'), ('f9000000-0000-0000-0000-000000000006')$$,
|
||
'KAT-01 Kriterium 1: Wörter mit einem Zeichen werden verworfen ("qxz 9 zorb" = "qxz zorb")');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('a b', null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[])$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid, 3)$$,
|
||
'KAT-01 Kriterium 1: "a b" (nur Wörter mit einem Zeichen) trifft nur über die Schreibweise ohne Leerzeichen');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('É', null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[])$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid, null::int), ('f9000000-0000-0000-0000-000000000005', null)$$,
|
||
'KAT-01 Kriterium 1: ein normalisierter Begriff unter 2 Zeichen gilt als kein Suchbegriff (alles, match_rank null)');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games(' ', null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[])$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid, null::int), ('f9000000-0000-0000-0000-000000000005', null)$$,
|
||
'KAT-01 Kriterium 8: nur Leerzeichen gelten als kein Suchbegriff');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games(null, null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[])$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid, null::int), ('f9000000-0000-0000-0000-000000000005', null)$$,
|
||
'KAT-01 Kriterium 8: null ist kein Suchbegriff, Sortierung nach Titel');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Kriterium 2: alternative Titel
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select results_eq($$select id, matched_alt_title, match_rank::int from public.search_catalog_games('qxz zugvogel')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000010'::uuid, 'Qxz Zugvogel'::text, 0)$$,
|
||
'KAT-01 Kriterium 2: der deutsche alternative Titel findet das Spiel, matched_alt_title nennt ihn');
|
||
select results_eq($$select id, matched_alt_title, match_rank::int from public.search_catalog_games('qxz migrant bird')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000010'::uuid, 'Qxz Migrant Bird'::text, 0)$$,
|
||
'KAT-01 Kriterium 2: der englische alternative Titel findet dasselbe Spiel');
|
||
select results_eq($$select id, matched_alt_title, match_rank::int from public.search_catalog_games('qxz hauptspiel')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000010'::uuid, null::text, 1)$$,
|
||
'KAT-01 Kriterium 2: Treffer über den Haupttitel hat matched_alt_title null');
|
||
select results_eq($$select id, matched_alt_title, match_rank::int, total_count::int from public.search_catalog_games('qxz bird')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000011'::uuid, 'Qxz Bird'::text, 0, 2),
|
||
('f9000000-0000-0000-0000-000000000010', 'Qxz Migrant Bird', 3, 2)$$,
|
||
'KAT-01 Kriterium 2: der alternative Titel zählt, wenn er besser trifft als der Haupttitel (Stufe 0 statt 2)');
|
||
select results_eq($$select id, matched_alt_title, match_rank::int, total_count::int from public.search_catalog_games('qxz einzigartig')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000012'::uuid, null::text, 0, 1)$$,
|
||
'KAT-01 Kriterium 2: ein Spiel erscheint nur einmal, auch wenn Haupt- und alternative Titel treffen; bei besserem Haupttitel null');
|
||
select results_eq($$select id, lower(matched_alt_title), total_count::int from public.search_catalog_games('qxz einzigartig alt')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000012'::uuid, 'qxz einzigartig alt'::text, 1)$$,
|
||
'KAT-01 Kriterium 2: zwei alternative Titel (de, en) mit gleicher Stufe ergeben eine Zeile');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Kriterium 3: Verlage
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select results_eq($$select id, match_rank::int, publisher_names from public.search_catalog_games('qxz hersteller zwei')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000040'::uuid, 4, array['Qxz Hersteller Eins', 'Qxz Hersteller Zwei']::text[]),
|
||
('f9000000-0000-0000-0000-000000000041', 4, array['Qxz Hersteller Zwei'])$$,
|
||
'KAT-01 Kriterium 3: der Verlag findet alle seine Spiele (Stufe 4); ein Spiel mit zwei Verlagen über beide; publisher_names sortiert und vollständig');
|
||
select results_eq($$select id from public.search_catalog_games('qxz hersteller eins') order by id$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid), ('f9000000-0000-0000-0000-000000000040')$$,
|
||
'KAT-01 Kriterium 3: der andere Verlag des Spiels findet es ebenfalls; ausgeblendete Spiele des Verlags fehlen');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('qxz zorb zorb eins')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid, 4)$$,
|
||
'KAT-01 Kriterium 3: ein doppelt eingegebenes Wort zählt einfach (Verlagstreffer bleibt auffindbar)');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('qxz zorb eins')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid, 4)$$,
|
||
'KAT-01 Kriterium 3: ein Wort aus dem Titel plus ein Wort aus dem Verlag (UND)');
|
||
select results_eq($$select id, publisher_names from public.search_catalog_games('qxz cabin')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid, '{}'::text[])$$,
|
||
'KAT-01 Kriterium 3: publisher_names ist leer statt null');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Kriterium 4 und 8: Filter, mit und ohne Suchbegriff
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select results_eq($$select h from pg_temp.hits(p_players := 3::smallint) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000021'), ('f9000000-0000-0000-0000-000000000024'), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Kriterium 8: Spielerzahl 3 ohne Suchbegriff (Spiele ohne Angabe fehlen, 2-2 passt nicht)');
|
||
select results_eq($$select h from pg_temp.hits('qxz filter', p_players := 3::smallint) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000021'), ('f9000000-0000-0000-0000-000000000024'), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Kriterium 4: Spielerzahl 3 mit Suchbegriff');
|
||
select results_eq($$select h from pg_temp.hits(p_players := 2::smallint) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000023'), ('f9000000-0000-0000-0000-000000000024'), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Kriterium 8: Spielerzahl 2 (untere und obere Grenze zählen mit)');
|
||
select results_eq($$select h from pg_temp.hits(p_max_minutes := 45::smallint) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000023'::uuid), ('f9000000-0000-0000-0000-000000000024'), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Spec-F2: höchstens 45 Minuten; nur max_minutes oder nur min_minutes bekannt zählt, ohne Angabe fehlt');
|
||
select results_eq($$select h from pg_temp.hits('qxz filter', p_max_minutes := 60::smallint) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000023'), ('f9000000-0000-0000-0000-000000000024'), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Kriterium 4: höchstens 60 Minuten (Grenze zählt mit) mit Suchbegriff');
|
||
select results_eq($$select h from pg_temp.hits(p_age := 8::smallint) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000023'), ('f9000000-0000-0000-0000-000000000024')$$,
|
||
'KAT-01 Kriterium 8: Alter 8 (Mindestalter bis einschließlich 8)');
|
||
select results_eq($$select h from pg_temp.hits(p_age := 0::smallint) h$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000024'::uuid)$$,
|
||
'KAT-01 Kriterium 4: Alter 0 findet nur Mindestalter 0');
|
||
select results_eq($$select h from pg_temp.hits(p_genre_ids := array['f9000000-0000-0000-0000-0000000000e1']::uuid[]) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000021'), ('f9000000-0000-0000-0000-000000000022')$$,
|
||
'KAT-01 Kriterium 4: Genre allein');
|
||
select results_eq($$select h from pg_temp.hits(p_genre_ids := array['f9000000-0000-0000-0000-0000000000e1', 'f9000000-0000-0000-0000-0000000000e2']::uuid[]) h$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid)$$,
|
||
'KAT-01 Spec-F1: zwei Genres: nur Spiele mit beiden');
|
||
select results_eq($$select h from pg_temp.hits(p_genre_ids := array['f9000000-0000-0000-0000-0000000000e1', 'f9000000-0000-0000-0000-0000000000e1']::uuid[]) h order by 1$$,
|
||
$$select h from pg_temp.hits(p_genre_ids := array['f9000000-0000-0000-0000-0000000000e1']::uuid[]) h order by 1$$,
|
||
'KAT-01 Kriterium 4: Duplikate im Array zählen einfach');
|
||
select results_eq($$select h from pg_temp.hits(p_mechanic_ids := array['f9000000-0000-0000-0000-0000000000d2']::uuid[]) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000021'::uuid), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Kriterium 4: Mechanik allein');
|
||
select results_eq($$select h from pg_temp.hits(p_mechanic_ids := array['f9000000-0000-0000-0000-0000000000d2', 'f9000000-0000-0000-0000-0000000000d2']::uuid[]) h order by 1$$,
|
||
$$select h from pg_temp.hits(p_mechanic_ids := array['f9000000-0000-0000-0000-0000000000d2']::uuid[]) h order by 1$$,
|
||
'KAT-01 Kriterium 4: Duplikate im Mechanik-Array zählen einfach');
|
||
select results_eq($$select h from pg_temp.hits(p_mechanic_ids := array['f9000000-0000-0000-0000-0000000000d1', 'f9000000-0000-0000-0000-0000000000d2']::uuid[]) h$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000021'::uuid)$$,
|
||
'KAT-01 Spec-F1: zwei Mechaniken: nur Spiele mit beiden');
|
||
select results_eq($$select h from pg_temp.hits(p_players := 3::smallint, p_age := 12::smallint,
|
||
p_genre_ids := array['f9000000-0000-0000-0000-0000000000e1']::uuid[], p_mechanic_ids := array['f9000000-0000-0000-0000-0000000000d2']::uuid[]) h$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000021'::uuid)$$,
|
||
'KAT-01 Kriterium 4: alle Filter zusammen sind UND-verknüpft');
|
||
select results_eq($$select h from pg_temp.hits('qxz filter', p_players := 2::smallint, p_max_minutes := 60::smallint,
|
||
p_genre_ids := array['f9000000-0000-0000-0000-0000000000e2']::uuid[]) h order by 1$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000020'::uuid), ('f9000000-0000-0000-0000-000000000025')$$,
|
||
'KAT-01 Kriterium 4: Suchbegriff plus Spielerzahl, Dauer und Genre');
|
||
select is((select count(*) from public.search_catalog_games(null, null, null, null, array[gen_random_uuid()])), 0::bigint,
|
||
'KAT-01 Kriterium 4: unbekannte Genre-ID ergibt 0 Treffer');
|
||
select is((select count(*) from public.search_catalog_games(null, null, null, null, null, array[gen_random_uuid()])), 0::bigint,
|
||
'KAT-01 Kriterium 4: unbekannte Mechanik-ID ergibt 0 Treffer');
|
||
select results_eq($$select id, match_rank::int, matched_alt_title, total_count::int from public.search_catalog_games(null, null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[])$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid, null::int, null::text, 2),
|
||
('f9000000-0000-0000-0000-000000000005', null, null, 2)$$,
|
||
'KAT-01 Kriterium 8: ohne Suchbegriff nach Titel sortiert, match_rank und matched_alt_title null');
|
||
select results_eq($$select id from public.search_catalog_games(null, null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[], null, 1, 0)
|
||
union all select id from public.search_catalog_games(null, null, null, null, array['f9000000-0000-0000-0000-0000000000e3']::uuid[], null, 1, 1)$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000060'::uuid), ('f9000000-0000-0000-0000-000000000005')$$,
|
||
'KAT-01 Kriterium 5: ohne Suchbegriff bestimmt der Titel (nicht die ID) auch die Seitengrenze');
|
||
select results_eq($$select id from public.search_catalog_games(null, null, null, null, array['f9000000-0000-0000-0000-0000000000e4']::uuid[])$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000080'::uuid), ('f9000000-0000-0000-0000-000000000081')$$,
|
||
'KAT-01 Kriterium 8: ohne Suchbegriff wird binär nach dem normalisierten Titel sortiert (Leerzeichen vor Buchstaben)');
|
||
select results_eq($$select h from pg_temp.hits() h order by 1$$,
|
||
$$select id from public.games where id::text like 'f9000000%' and visibility = 'catalog' and hidden_at is null and merged_into_id is null order by id$$,
|
||
'KAT-01 Kriterium 8: ohne Suchbegriff und ohne Filter erscheinen alle sichtbaren Fixture-Spiele (ohne ausgeblendete, zusammengeführte, private)');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Kriterium 5: Nachladen
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select results_eq($$select id from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 0)
|
||
union all select id from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 15)
|
||
union all select id from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 30)$$,
|
||
$$select id from public.games where title like 'Qxz Seite %' order by title$$,
|
||
'KAT-01 Kriterium 5: drei Seiten ergeben ohne Überschneidung genau die 40 Treffer in stabiler Reihenfolge');
|
||
select results_eq($$select count(*), count(distinct id), count(distinct total_count), min(total_count)
|
||
from (select * from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 0)
|
||
union all select * from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 15)
|
||
union all select * from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 30)) s$$,
|
||
$$values (40::bigint, 40::bigint, 1::bigint, 40::bigint)$$,
|
||
'KAT-01 Kriterium 5: total_count ist in jeder Zeile jeder Seite gleich (40)');
|
||
select is((select count(*) from public.search_catalog_games('qxz seite', null, null, null, null, null, 15, 45)), 0::bigint,
|
||
'KAT-01 Kriterium 5: hinter dem Ende keine Zeilen (hasMore false)');
|
||
select is((select count(*) from public.search_catalog_games('qxz seite')), 30::bigint, 'KAT-01 Kriterium 5: Standard-Seitengröße 30');
|
||
select is((select count(*) from public.search_catalog_games('qxz seite', p_limit := 50)), 40::bigint, 'KAT-01 Kriterium 5: Seitengröße bis 50 erlaubt');
|
||
select results_eq($$select id from public.search_catalog_games('qxz seite', null, null, null, null, null, 10, 0)$$,
|
||
$$select id from public.search_catalog_games('qxz seite', null, null, null, null, null, 10, 0)$$,
|
||
'KAT-01 Kriterium 5: wiederholter Aufruf liefert dieselbe Reihenfolge');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- KAT-05 Kriterium 4 und 10: ausgeblendet, zusammengeführt, privat, Bild
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select is((select count(*) from public.search_catalog_games('qxz verborgen')), 0::bigint, 'KAT-05 Kriterium 4: ein ausgeblendetes Spiel erscheint nicht über den Titel');
|
||
select is((select count(*) from public.search_catalog_games('qxz verborgen alt')), 0::bigint, 'KAT-05 Kriterium 4: ... nicht über den alternativen Titel');
|
||
select is((select count(*) from public.search_catalog_games('qxz verschmolzen alt')), 0::bigint, 'KAT-05 Kriterium 4: ein zusammengeführtes Spiel erscheint nicht über seinen alternativen Titel');
|
||
select is((select count(*) from public.search_catalog_games('qxz verschmolzen')), 0::bigint, 'KAT-05 Kriterium 4: ... und nicht über den Titel');
|
||
select is((select count(*) from public.search_catalog_games('qxz privat')), 0::bigint, 'KAT-01 Sicherheit: ein privates Spiel erscheint nie (Titel)');
|
||
select is((select count(*) from public.search_catalog_games('qxz bens alt')), 0::bigint, 'KAT-01 Sicherheit: ... auch nicht über einen alternativen Titel');
|
||
select is((select count(*) from public.search_catalog_games(null, null, null, null, null, null, 50, 0) where id::text in ('f9000000-0000-0000-0000-000000000030', 'f9000000-0000-0000-0000-000000000031', 'f9000000-0000-0000-0000-000000000032', 'f9000000-0000-0000-0000-000000000033')), 0::bigint,
|
||
'KAT-05 Kriterium 4: ohne Suchbegriff fehlen ausgeblendete, zusammengeführte und private Spiele ebenso (erste Seite)');
|
||
select is((select count(*) from pg_temp.hits() h where h in ('f9000000-0000-0000-0000-000000000030', 'f9000000-0000-0000-0000-000000000031', 'f9000000-0000-0000-0000-000000000032', 'f9000000-0000-0000-0000-000000000033')), 0::bigint,
|
||
'KAT-05 Kriterium 4: ohne Suchbegriff fehlen ausgeblendete, zusammengeführte und private Spiele auch beim Durchblättern');
|
||
select results_eq($$select id, image_path from public.search_catalog_games('qxz cover') order by id$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000050'::uuid, null::text), ('f9000000-0000-0000-0000-000000000051', 'covers/manuell.png'),
|
||
('f9000000-0000-0000-0000-000000000052', 'covers/eigen.png')$$,
|
||
'KAT-05 Kriterium 10: image_path kommt aus catalog_games (maskiert bei Quelle ohne Erlaubnis, sichtbar bei manueller Pflege und ohne Quelle)');
|
||
select results_eq($$select title, year, min_players, max_players, min_minutes, max_minutes, min_age from public.search_catalog_games('qxz filter 20')$$,
|
||
$$values ('Qxz Filter 20'::text, null::smallint, 2::smallint, 4::smallint, 30::smallint, 60::smallint, 8::smallint)$$,
|
||
'KAT-01: die Ausgabe enthält Titel und Kurzangaben');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Eingabegrenzen
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select throws_ok($$select * from public.search_catalog_games(repeat('a', 101))$$, 'P0001', 'search_query_too_long', 'KAT-01: 101 Zeichen sind zu lang');
|
||
select lives_ok($$select * from public.search_catalog_games(repeat('a', 100))$$, 'KAT-01: 100 Zeichen sind erlaubt');
|
||
select throws_ok($$select * from public.search_catalog_games('aa bb cc dd ee ff gg hh ii jj kk')$$, 'P0001', 'search_query_too_long', 'KAT-01: 11 Wörter sind zu viel');
|
||
select throws_ok($$select * from public.search_catalog_games('a b c d e f g h i j k')$$, 'P0001', 'search_query_too_long', 'KAT-01: 11 Wörter zählen auch, wenn sie nur ein Zeichen haben');
|
||
select lives_ok($$select * from public.search_catalog_games('aa bb cc dd ee ff gg hh ii jj')$$, 'KAT-01: 10 Wörter sind erlaubt');
|
||
select throws_ok($$select * from public.search_catalog_games(null, 0::smallint)$$, 'P0001', 'search_filter_invalid', 'KAT-01: Spielerzahl 0');
|
||
select throws_ok($$select * from public.search_catalog_games(null, 100::smallint)$$, 'P0001', 'search_filter_invalid', 'KAT-01: Spielerzahl 100');
|
||
select lives_ok($$select * from public.search_catalog_games(null, 1::smallint); select * from public.search_catalog_games(null, 99::smallint)$$, 'KAT-01: Spielerzahl 1 und 99 sind erlaubt');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, 0::smallint)$$, 'P0001', 'search_filter_invalid', 'KAT-01: Dauer 0');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, 1441::smallint)$$, 'P0001', 'search_filter_invalid', 'KAT-01: Dauer 1441');
|
||
select lives_ok($$select * from public.search_catalog_games(null, null, 1::smallint); select * from public.search_catalog_games(null, null, 1440::smallint)$$, 'KAT-01: Dauer 1 und 1440 sind erlaubt');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, (-1)::smallint)$$, 'P0001', 'search_filter_invalid', 'KAT-01: Alter -1');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, 100::smallint)$$, 'P0001', 'search_filter_invalid', 'KAT-01: Alter 100');
|
||
select lives_ok($$select * from public.search_catalog_games(null, null, null, 0::smallint); select * from public.search_catalog_games(null, null, null, 99::smallint)$$, 'KAT-01: Alter 0 und 99 sind erlaubt');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, null, (select array_agg(gen_random_uuid()) from generate_series(1, 21)))$$, 'P0001', 'search_filter_invalid', 'KAT-01: 21 Genre-IDs');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, null, null, (select array_agg(gen_random_uuid()) from generate_series(1, 21)))$$, 'P0001', 'search_filter_invalid', 'KAT-01: 21 Mechanik-IDs');
|
||
select lives_ok($$select * from public.search_catalog_games(null, null, null, null, (select array_agg(gen_random_uuid()) from generate_series(1, 20)), (select array_agg(gen_random_uuid()) from generate_series(1, 20)))$$, 'KAT-01: je 20 IDs sind erlaubt');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, null, null, null, 0, 0)$$, 'P0001', 'search_paging_invalid', 'KAT-01: Seitengröße 0');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, null, null, null, 51, 0)$$, 'P0001', 'search_paging_invalid', 'KAT-01: Seitengröße 51');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, null, null, null, 30, -1)$$, 'P0001', 'search_paging_invalid', 'KAT-01: Versatz -1');
|
||
select throws_ok($$select * from public.search_catalog_games(null, null, null, null, null, null, 30, 10001)$$, 'P0001', 'search_paging_invalid', 'KAT-01: Versatz 10001');
|
||
select lives_ok($$select * from public.search_catalog_games(null, null, null, null, null, null, 1, 0); select * from public.search_catalog_games(null, null, null, null, null, null, 50, 10000)$$, 'KAT-01: Seitengröße 1 und 50, Versatz 0 und 10000 sind erlaubt');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Tabelle game_alt_titles: Checks, Unique, manual_fields, Kaskade
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'fr', 'Titre')$$,
|
||
'23514', null, 'KAT-01 Kriterium 2: Sprache nur de oder en (Check)');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', repeat('x', 201))$$,
|
||
'23514', null, 'KAT-01 Kriterium 2: Titel höchstens 200 Zeichen (Check)');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', '')$$,
|
||
'23514', null, 'KAT-01 Kriterium 2: Titel nicht leer (Check)');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', '!!! ®')$$,
|
||
'23514', null, 'KAT-01 Kriterium 2: ein Titel nur aus Sonderzeichen ist nicht zulässig (title_search nicht null)');
|
||
select lives_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', repeat('x', 200))$$,
|
||
'KAT-01 Kriterium 2: 200 Zeichen sind erlaubt');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000012', 'de', 'QXZ einzigartig alt!')$$,
|
||
'23505', null, 'KAT-01 Kriterium 2: derselbe Titel in derselben Sprache nur einmal, unabhängig von der Schreibweise (Unique)');
|
||
select lives_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', 'Qxz Zweitname')$$,
|
||
'KAT-01 Kriterium 2: weiterer Titel in einer Sprache');
|
||
select lives_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'en', 'QXZ zweitname!')$$,
|
||
'KAT-01 Kriterium 2: derselbe Titel in der anderen Sprache ist zulässig');
|
||
select lives_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', 'Qxz Dritter Name')$$,
|
||
'KAT-01 Kriterium 2: mehrere Titel je Sprache sind erlaubt');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values (gen_random_uuid(), 'de', 'Qxz Waise')$$,
|
||
'23503', null, 'KAT-01 Kriterium 2: alternative Titel brauchen ein vorhandenes Spiel (Fremdschlüssel)');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title, title_search) values ('f9000000-0000-0000-0000-000000000013', 'de', 'Qxz Eigen', 'x')$$,
|
||
'428C9', null, 'KAT-01: title_search ist generiert und nicht beschreibbar (auch nicht als postgres)');
|
||
select throws_ok($$update public.games set title_search = 'x' where id = 'f9000000-0000-0000-0000-000000000013'$$,
|
||
'428C9', null, 'KAT-01: games.title_search ist nicht beschreibbar');
|
||
|
||
-- manual_fields: Einfügen, Ändern und Löschen markieren alt_titles (nur Katalogspiele)
|
||
select ok((select 'alt_titles' = any(manual_fields) from public.games where id = 'f9000000-0000-0000-0000-000000000010'),
|
||
'KAT-01 Kriterium 2: Einfügen alternativer Titel markiert alt_titles in manual_fields (Katalogspiel)');
|
||
select ok(not ((select 'alt_titles' = any(manual_fields) from public.games where id = 'f9000000-0000-0000-0000-000000000040')),
|
||
'KAT-01: ein Spiel ohne alternative Titel ist nicht als alt_titles markiert');
|
||
select is((select manual_fields from public.games where id = 'f9000000-0000-0000-0000-000000000032'), '{}'::text[],
|
||
'KAT-01: alternative Titel eines privaten Spiels markieren nichts');
|
||
insert into public.games (id, title, visibility) values ('f9000000-0000-0000-0000-000000000071', 'Qxz Markierung', 'catalog');
|
||
select ok(not ((select 'alt_titles' = any(manual_fields) from public.games where id = 'f9000000-0000-0000-0000-000000000071')), 'Ausgangslage: Markierungsspiel ohne alt_titles');
|
||
insert into public.game_alt_titles (id, game_id, locale, title) values ('f9000000-0000-0000-0000-0000000000b9', 'f9000000-0000-0000-0000-000000000071', 'de', 'Qxz Mark Alt');
|
||
select ok((select 'alt_titles' = any(manual_fields) from public.games where id = 'f9000000-0000-0000-0000-000000000071'), 'KAT-01: insert markiert alt_titles');
|
||
update public.games set manual_fields = array_remove(manual_fields, 'alt_titles') where id = 'f9000000-0000-0000-0000-000000000071';
|
||
update public.game_alt_titles set title = 'Qxz Mark Alt Zwei' where id = 'f9000000-0000-0000-0000-0000000000b9';
|
||
select ok((select 'alt_titles' = any(manual_fields) from public.games where id = 'f9000000-0000-0000-0000-000000000071'), 'KAT-01: update markiert alt_titles');
|
||
update public.games set manual_fields = array_remove(manual_fields, 'alt_titles') where id = 'f9000000-0000-0000-0000-000000000071';
|
||
delete from public.game_alt_titles where id = 'f9000000-0000-0000-0000-0000000000b9';
|
||
select ok((select 'alt_titles' = any(manual_fields) from public.games where id = 'f9000000-0000-0000-0000-000000000071'), 'KAT-01: delete markiert alt_titles');
|
||
|
||
-- manual_fields-Check: vollständige Liste von main plus alt_titles
|
||
select lives_ok($$update public.games set manual_fields = array['title', 'description', 'description_locale', 'year',
|
||
'min_players', 'max_players', 'min_minutes', 'max_minutes', 'min_age', 'image_path',
|
||
'genres', 'mechanics', 'categories', 'publishers', 'alt_titles'] where id = 'f9000000-0000-0000-0000-000000000071'$$,
|
||
'KAT-01: der Check akzeptiert alle 14 bisherigen Schlüssel und alt_titles');
|
||
select throws_ok($$update public.games set manual_fields = array['alt_titles', 'unbekannt'] where id = 'f9000000-0000-0000-0000-000000000071'$$,
|
||
'23514', null, 'KAT-01: der Check lehnt unbekannte Schlüssel weiterhin ab');
|
||
|
||
-- Kaskade: Spiel löschen löscht seine alternativen Titel
|
||
insert into public.games (id, title, visibility) values ('f9000000-0000-0000-0000-000000000070', 'Qxz Löschtest', 'catalog');
|
||
insert into public.game_alt_titles (game_id, locale, title) values
|
||
('f9000000-0000-0000-0000-000000000070', 'de', 'Qxz Löschtest Alt'), ('f9000000-0000-0000-0000-000000000070', 'en', 'Qxz Delete test');
|
||
delete from public.games where id = 'f9000000-0000-0000-0000-000000000070';
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000070'), 0::bigint,
|
||
'KAT-01 Kriterium 2: Spiel löschen löscht seine alternativen Titel');
|
||
|
||
-- updated_at: Trigger setzt ihn bei Änderung (ctid statt now(), die Transaktion hat eine feste Zeit)
|
||
select ok(exists (select 1 from pg_trigger where tgrelid = 'public.game_alt_titles'::regclass and tgname = 'set_updated_at'),
|
||
'KAT-01: game_alt_titles setzt updated_at per Trigger');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Rollen: anon, authenticated (Sicherheit Kriterium 2)
|
||
-- ---------------------------------------------------------------------------------------------
|
||
set local role anon;
|
||
select lives_ok($$select * from public.search_catalog_games('qxz zorb')$$, 'KAT-01 Sicherheit Kriterium 2: die Suche als anon läuft ohne Rechtefehler');
|
||
select results_eq($$select id, match_rank::int from public.search_catalog_games('qxz zorb')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid, 0), ('f9000000-0000-0000-0000-000000000002', 1),
|
||
('f9000000-0000-0000-0000-000000000003', 2), ('f9000000-0000-0000-0000-000000000004', 3),
|
||
('f9000000-0000-0000-0000-000000000005', 3), ('f9000000-0000-0000-0000-000000000006', 4)$$,
|
||
'KAT-01 Sicherheit Kriterium 2: anon sieht dieselben Treffer; das private Spiel "QXZ ZORB" eines Nutzers fehlt');
|
||
select results_eq($$select id, matched_alt_title from public.search_catalog_games('qxz zugvogel')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000010'::uuid, 'Qxz Zugvogel'::text)$$, 'KAT-01 Kriterium 2: alternative Titel funktionieren für anon');
|
||
select results_eq($$select id, publisher_names from public.search_catalog_games('qxz doppelspiel')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000040'::uuid, array['Qxz Hersteller Eins', 'Qxz Hersteller Zwei']::text[])$$,
|
||
'KAT-01 Kriterium 3: Verlage funktionieren für anon');
|
||
select results_eq($$select id, image_path from public.search_catalog_games('qxz cover') order by id$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000050'::uuid, null::text), ('f9000000-0000-0000-0000-000000000051', 'covers/manuell.png'),
|
||
('f9000000-0000-0000-0000-000000000052', 'covers/eigen.png')$$, 'KAT-05 Kriterium 10: Maskierung des Bildes gilt für anon');
|
||
select is((select count(*) from public.search_catalog_games(null, null, null, null, array['f9000000-0000-0000-0000-0000000000e1', 'f9000000-0000-0000-0000-0000000000e2']::uuid[])), 1::bigint,
|
||
'KAT-01 Kriterium 4: Filter wirken auch für anon');
|
||
select is((select count(*) from public.search_catalog_games('qxz seite', p_limit := 50)), 40::bigint, 'KAT-01 Kriterium 5: Nachladen für anon');
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000010'), 2::bigint,
|
||
'KAT-01 Kriterium 2: anon liest alternative Titel von Katalogspielen');
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000032'), 0::bigint,
|
||
'KAT-01 Kriterium 2: anon liest keine alternativen Titel eines privaten Spiels');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000013', 'de', 'Qxz Anon')$$,
|
||
'42501', null, 'KAT-01 Kriterium 2: anon kann keine alternativen Titel anlegen');
|
||
select throws_ok($$update public.game_alt_titles set title = 'Qxz Anon' where id = 'f9000000-0000-0000-0000-0000000000a1'$$,
|
||
'42501', null, 'KAT-01 Kriterium 2: anon kann keine alternativen Titel ändern');
|
||
select throws_ok($$delete from public.game_alt_titles where id = 'f9000000-0000-0000-0000-0000000000a1'$$,
|
||
'42501', null, 'KAT-01 Kriterium 2: anon kann keine alternativen Titel löschen');
|
||
reset role;
|
||
|
||
-- Anna (authentifiziert): legt ein privates Spiel an und ändert es (title_search wird mit den Rechten des Nutzers berechnet)
|
||
select set_config('request.jwt.claims', '{"sub":"11111111-1111-1111-1111-111111111111","role":"authenticated"}', true);
|
||
set local role authenticated;
|
||
select lives_ok($$insert into public.games (owner_id, title, visibility) values ('11111111-1111-1111-1111-111111111111', 'Qxz Mölkky ®', 'private')$$,
|
||
'KAT-01 Sicherheit: authenticated legt ein privates Spiel an (search_normalize ausführbar, kein 42501)');
|
||
select is((select title_search from public.games where title = 'Qxz Mölkky ®'), 'qxz molkky',
|
||
'KAT-01 Sicherheit: title_search ist nach dem Anlegen gefüllt');
|
||
select is((select count(*) from public.search_catalog_games('qxz molkky')), 0::bigint, 'KAT-01 Sicherheit: Anna findet das gerade angelegte private Spiel nicht über die Katalogsuche');
|
||
select lives_ok($$update public.games set title = 'Spiel™ ½' where title = 'Qxz Mölkky ®'$$,
|
||
'KAT-01 Sicherheit: authenticated ändert den Titel seines privaten Spiels (kein 42501)');
|
||
select is((select title_search from public.games where title = 'Spiel™ ½'), 'spiel 1 2',
|
||
'KAT-01 Sicherheit: title_search ist nach dem Ändern aktualisiert');
|
||
select throws_ok($$insert into public.games (owner_id, title, visibility, title_search) values ('11111111-1111-1111-1111-111111111111', 'Qxz X', 'private', 'x')$$,
|
||
'428C9', null, 'KAT-01: authenticated kann title_search nicht setzen (generierte Spalte)');
|
||
select ok(not has_column_privilege('authenticated', 'public.games', 'title_search', 'insert')
|
||
and not has_column_privilege('authenticated', 'public.games', 'title_search', 'update')
|
||
and not has_column_privilege('anon', 'public.games', 'title_search', 'update')
|
||
and has_column_privilege('anon', 'public.games', 'title_search', 'select'),
|
||
'KAT-01: title_search ist für API-Rollen nur lesbar (Spaltenrechte)');
|
||
select lives_ok($$select * from public.search_catalog_games('qxz zorb')$$, 'KAT-01: die Suche als authenticated läuft');
|
||
select results_eq($$select id from public.search_catalog_games('qxz zorb')$$,
|
||
$$values ('f9000000-0000-0000-0000-000000000001'::uuid), ('f9000000-0000-0000-0000-000000000002'), ('f9000000-0000-0000-0000-000000000003'),
|
||
('f9000000-0000-0000-0000-000000000004'), ('f9000000-0000-0000-0000-000000000005'), ('f9000000-0000-0000-0000-000000000006')$$,
|
||
'KAT-01 Sicherheit: Annas Suche zeigt nur Katalogspiele (Bens privates "QXZ ZORB" fehlt)');
|
||
select is((select count(*) from public.search_catalog_games('qxz privat')), 0::bigint, 'KAT-01 Sicherheit: Anna findet ihr eigenes privates Spiel nicht über die Katalogsuche');
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000032'), 1::bigint, 'KAT-01 Kriterium 2: Anna liest die alternativen Titel ihres privaten Spiels');
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000033'), 0::bigint, 'KAT-01 Kriterium 2: Anna liest nicht die alternativen Titel von Bens privatem Spiel');
|
||
select throws_ok($$insert into public.game_alt_titles (game_id, locale, title) values ('f9000000-0000-0000-0000-000000000032', 'de', 'Qxz Anna Neu')$$,
|
||
'42501', null, 'KAT-01 Kriterium 2: auch am eigenen privaten Spiel kann Anna keine alternativen Titel anlegen');
|
||
select throws_ok($$update public.game_alt_titles set title = 'Qxz Anna' where id = 'f9000000-0000-0000-0000-0000000000a8'$$,
|
||
'42501', null, 'KAT-01 Kriterium 2: ... nicht ändern');
|
||
select throws_ok($$delete from public.game_alt_titles where id = 'f9000000-0000-0000-0000-0000000000a8'$$,
|
||
'42501', null, 'KAT-01 Kriterium 2: ... nicht löschen');
|
||
reset role;
|
||
|
||
-- Ben: die eigene Suche zeigt Annas privates Spiel nicht und sieht keine fremden alternativen Titel
|
||
select set_config('request.jwt.claims', '{"sub":"22222222-2222-2222-2222-222222222222","role":"authenticated"}', true);
|
||
set local role authenticated;
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000032'), 0::bigint, 'KAT-01 Kriterium 2: Ben liest keine alternativen Titel von Annas privatem Spiel');
|
||
select is((select count(*) from public.search_catalog_games('qxz privat')), 0::bigint, 'KAT-01 Sicherheit: Ben findet Annas privates Spiel nicht');
|
||
select is((select count(*) from public.search_catalog_games('qxz zorb')), 6::bigint, 'KAT-01 Sicherheit: Ben findet sein eigenes privates "QXZ ZORB" nicht, nur die sechs Katalogspiele');
|
||
reset role;
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Startbestand-Werkzeug: alternative Titel, Idempotenz (Spec 4.3, Krit. 2)
|
||
-- Testspiel B hat laut testdata/startbestand.csv zwei alternative Titel, Testspiel A keinen.
|
||
-- ---------------------------------------------------------------------------------------------
|
||
\ir ../support/vocabulary_fixture.inc
|
||
\ir ../support/startbestand_fixture.inc
|
||
|
||
select results_eq($$select a.locale, a.title from public.game_alt_titles a join public.games g on g.id = a.game_id
|
||
where g.title = 'Testspiel B' order by a.locale$$,
|
||
$$values ('de'::text, 'Testspiel Bravo'::text), ('en', 'Test Game Bravo')$$,
|
||
'KAT-01 Kriterium 2: Testspiel B bekommt aus der CSV einen deutschen und einen englischen alternativen Titel');
|
||
select is((select count(*) from public.game_alt_titles a join public.games g on g.id = a.game_id where g.title = 'Testspiel A'), 0::bigint,
|
||
'KAT-01 Kriterium 2: Testspiel A hat keine alternativen Titel');
|
||
select ok((select 'alt_titles' = any(manual_fields) from public.games where title = 'Testspiel B'),
|
||
'KAT-01 Kriterium 2: das Einspielen markiert alt_titles in manual_fields (SB8)');
|
||
select results_eq($$select id, match_rank::int, matched_alt_title from public.search_catalog_games('test game bravo')
|
||
where id = extensions.uuid_generate_v5('6f1d9a52-3c4e-4b8a-9f27-5d0e8c71a3b4', 'game:testspiel-b')$$,
|
||
$$values (extensions.uuid_generate_v5('6f1d9a52-3c4e-4b8a-9f27-5d0e8c71a3b4', 'game:testspiel-b'), 0, 'Test Game Bravo'::text)$$,
|
||
'KAT-01 Kriterium 2: ein eingespielter alternativer Titel ist über die Suche auffindbar');
|
||
|
||
create temp table snap_alt on commit drop as
|
||
select a.id, a.game_id, a.locale, a.title, a.ctid::text as c, a.updated_at as u from public.game_alt_titles a
|
||
where a.game_id in (select id from public.games where title like 'Testspiel %');
|
||
\ir ../support/startbestand_fixture.inc
|
||
select is((select count(*) from snap_alt), 2::bigint, 'Schnappschuss der alternativen Titel enthält die zwei Zeilen von Testspiel B');
|
||
select is_empty($$select 1 from snap_alt s where not exists (
|
||
select 1 from public.game_alt_titles n where n.id = s.id and n.game_id = s.game_id and n.locale = s.locale
|
||
and n.title = s.title and n.ctid::text = s.c and n.updated_at = s.u)$$,
|
||
'KAT-01 Kriterium 2: erneutes Einspielen ändert keine Zeile von game_alt_titles (ctid und updated_at gleich)');
|
||
select is((select count(*) from public.game_alt_titles a join public.games g on g.id = a.game_id where g.title like 'Testspiel %'), 2::bigint,
|
||
'KAT-01 Kriterium 2: erneutes Einspielen legt keine Duplikate an');
|
||
|
||
-- Ein in der CSV nicht (mehr) stehender Titel wird gelöscht, auch bei Testspiel A ohne alt_titles; eine geänderte
|
||
-- Schreibweise stellt nur den Titel wieder her (gleiche ID), unveränderte Zeilen bleiben unberührt.
|
||
insert into public.game_alt_titles (game_id, locale, title)
|
||
select id, 'en', 'Entfernter Titel' from public.games where title = 'Testspiel B';
|
||
insert into public.game_alt_titles (game_id, locale, title)
|
||
select id, 'de', 'Fremder Titel' from public.games where title = 'Testspiel A';
|
||
update public.game_alt_titles set title = 'TESTSPIEL BRAVO' where locale = 'de' and game_id = (select id from public.games where title = 'Testspiel B');
|
||
create temp table snap_alt2 on commit drop as
|
||
select a.id, a.locale, a.title, a.ctid::text as c from public.game_alt_titles a where a.game_id in (select id from public.games where title like 'Testspiel %');
|
||
\ir ../support/startbestand_fixture.inc
|
||
select results_eq($$select a.locale, a.title from public.game_alt_titles a where a.game_id in (select id from public.games where title like 'Testspiel %') order by a.locale$$,
|
||
$$values ('de'::text, 'Testspiel Bravo'::text), ('en', 'Test Game Bravo')$$,
|
||
'KAT-01 Kriterium 2: ein in der CSV entfernter alternativer Titel wird gelöscht (auch bei einem Spiel ohne alt_titles); geänderte Schreibweise wird wiederhergestellt');
|
||
select is((select count(*) from public.game_alt_titles a join snap_alt2 s on s.id = a.id and s.title = 'TESTSPIEL BRAVO'), 1::bigint,
|
||
'KAT-01 Kriterium 2: die geänderte Schreibweise behält ihre ID (nur title aktualisiert)');
|
||
select is((select count(*) from public.game_alt_titles a join snap_alt2 s on s.id = a.id and s.locale = 'en' and s.title = 'Test Game Bravo' and s.c = a.ctid::text), 1::bigint,
|
||
'KAT-01 Kriterium 2: die unveränderte Zeile bleibt unberührt (gleiche ctid)');
|
||
select is((select count(*) from public.game_alt_titles a join snap_alt2 s on s.id = a.id and s.locale = 'de' and s.c <> a.ctid::text), 1::bigint,
|
||
'KAT-01 Kriterium 2: nur die geänderte Zeile wurde geschrieben');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- SQL-Bausteine aus docs/katalog-pflege.md (unverändert, ohne begin/commit)
|
||
-- Abschnitt 3.1: Alternative Titel pflegen (Spiel 7a1b...0001 "Beispielspiel Runbook")
|
||
-- ---------------------------------------------------------------------------------------------
|
||
insert into public.games (id, title, year, min_players, max_players, min_minutes, max_minutes, min_age, description, description_locale, visibility)
|
||
values ('7a1b2c3d-0000-4000-8000-000000000001', 'Beispielspiel Runbook', 2020, 2, 4, 30, 60, 8, 'Eigener Kurztext.', 'de', 'catalog');
|
||
|
||
insert into public.game_alt_titles (game_id, locale, title)
|
||
values ('7a1b2c3d-0000-4000-8000-000000000001', 'de', 'Beispielspiel Deutsch'),
|
||
('7a1b2c3d-0000-4000-8000-000000000001', 'en', 'Example game')
|
||
on conflict (game_id, locale, title_search) do update set title = excluded.title
|
||
where public.game_alt_titles.title is distinct from excluded.title;
|
||
|
||
select results_eq($$select locale, title from public.game_alt_titles
|
||
where game_id = '7a1b2c3d-0000-4000-8000-000000000001' order by locale, title$$,
|
||
$$values ('de'::text, 'Beispielspiel Deutsch'::text), ('en', 'Example game')$$,
|
||
'Runbook 3.1: Alternative Titel anlegen');
|
||
insert into public.game_alt_titles (game_id, locale, title)
|
||
values ('7a1b2c3d-0000-4000-8000-000000000001', 'de', 'BEISPIELSPIEL DEUTSCH'),
|
||
('7a1b2c3d-0000-4000-8000-000000000001', 'en', 'Example game')
|
||
on conflict (game_id, locale, title_search) do update set title = excluded.title
|
||
where public.game_alt_titles.title is distinct from excluded.title;
|
||
select results_eq($$select locale, title from public.game_alt_titles
|
||
where game_id = '7a1b2c3d-0000-4000-8000-000000000001' order by locale, title$$,
|
||
$$values ('de'::text, 'BEISPIELSPIEL DEUTSCH'::text), ('en', 'Example game')$$,
|
||
'Runbook 3.1: derselbe Baustein mit geänderter Schreibweise aktualisiert den Titel statt einen zweiten anzulegen');
|
||
select results_eq($$select id, match_rank::int, matched_alt_title from public.search_catalog_games('example game')$$,
|
||
$$values ('7a1b2c3d-0000-4000-8000-000000000001'::uuid, 0, 'Example game'::text)$$,
|
||
'Runbook 3.1: der angelegte Titel ist sofort über die Suche auffindbar');
|
||
|
||
delete from public.game_alt_titles
|
||
where game_id = '7a1b2c3d-0000-4000-8000-000000000001' and locale = 'en' and title = 'Example game';
|
||
select results_eq($$select locale from public.game_alt_titles where game_id = '7a1b2c3d-0000-4000-8000-000000000001'$$,
|
||
$$values ('de'::text)$$, 'Runbook 3.1: Alternativen Titel löschen');
|
||
|
||
-- Abschnitt 5, Schritt 2b: alternative Titel der Quelle am Ziel ergänzen, danach zusammenführen
|
||
insert into public.games (id, title, year, min_players, max_players, min_minutes, max_minutes, min_age, description, description_locale, visibility) values
|
||
('7a1b2c3d-0000-4000-8000-000000000002', 'Duplikat Runbook', 2020, 2, 4, 30, 60, 8, 'Eigener Kurztext.', 'de', 'catalog'),
|
||
('7a1b2c3d-0000-4000-8000-000000000003', 'Ziel Runbook', 2020, 2, 4, 30, 60, 8, 'Eigener Kurztext.', 'de', 'catalog');
|
||
insert into public.game_alt_titles (game_id, locale, title) values
|
||
('7a1b2c3d-0000-4000-8000-000000000002', 'de', 'Runbook Quellname'),
|
||
('7a1b2c3d-0000-4000-8000-000000000002', 'en', 'Runbook source name'),
|
||
('7a1b2c3d-0000-4000-8000-000000000003', 'en', 'RUNBOOK SOURCE NAME');
|
||
|
||
select locale, title from public.game_alt_titles
|
||
where game_id = '7a1b2c3d-0000-4000-8000-000000000002' order by locale, title;
|
||
|
||
insert into public.game_alt_titles (game_id, locale, title)
|
||
select '7a1b2c3d-0000-4000-8000-000000000003', a.locale, a.title
|
||
from public.game_alt_titles a
|
||
where a.game_id = '7a1b2c3d-0000-4000-8000-000000000002'
|
||
on conflict (game_id, locale, title_search) do nothing;
|
||
|
||
select results_eq($$select locale, title from public.game_alt_titles where game_id = '7a1b2c3d-0000-4000-8000-000000000003' order by locale, title$$,
|
||
$$values ('de'::text, 'Runbook Quellname'::text), ('en', 'RUNBOOK SOURCE NAME')$$,
|
||
'Runbook 5 Schritt 2b: Titel der Quelle werden am Ziel ergänzt, vorhandene (auch in anderer Schreibweise) bleiben unverändert');
|
||
select results_eq($$select id from public.search_catalog_games('runbook quellname')$$,
|
||
$$values ('7a1b2c3d-0000-4000-8000-000000000002'::uuid), ('7a1b2c3d-0000-4000-8000-000000000003')$$,
|
||
'Runbook 5 Schritt 2b: das Ziel ist jetzt über den Titel der Quelle auffindbar (vor dem Zusammenführen zusammen mit der Quelle)');
|
||
|
||
select public.admin_merge_games('7a1b2c3d-0000-4000-8000-000000000002', '7a1b2c3d-0000-4000-8000-000000000003');
|
||
|
||
select is((select count(*) from public.game_alt_titles where game_id = '7a1b2c3d-0000-4000-8000-000000000002'), 2::bigint,
|
||
'Spec-F6 A: nach dem Zusammenführen bleiben die alternativen Titel am ausgeblendeten Quell-Eintrag');
|
||
select is((select count(*) from public.game_alt_titles where game_id = '7a1b2c3d-0000-4000-8000-000000000003'), 2::bigint,
|
||
'Spec-F6 A: das Ziel behält seine alternativen Titel');
|
||
select results_eq($$select id from public.search_catalog_games('runbook quellname')$$,
|
||
$$values ('7a1b2c3d-0000-4000-8000-000000000003'::uuid)$$, 'Spec-F6 A: nach dem Zusammenführen findet die Suche nur noch das Ziel, nie die Quelle');
|
||
select is((select count(*) from public.search_catalog_games('duplikat runbook')), 0::bigint, 'KAT-05 Kriterium 4: die zusammengeführte Quelle erscheint nicht mehr');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Kaskade beim Konto-Löschen (QUE-04): alternative Titel privater Spiele verschwinden mit dem Konto
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000033'), 1::bigint, 'Ausgangslage: Bens privates Spiel hat einen alternativen Titel');
|
||
delete from auth.users where id = '22222222-2222-2222-2222-222222222222';
|
||
select is((select count(*) from public.game_alt_titles where game_id = 'f9000000-0000-0000-0000-000000000033'), 0::bigint,
|
||
'KAT-01: Konto löschen löscht auch die alternativen Titel seiner privaten Spiele');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Abschnitt 8 des Runbooks: Normalisierung nach einem Image-Upgrade prüfen (R6)
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select results_eq($$select 'games' as tabelle, count(*) as abweichend from public.games
|
||
where title_search is distinct from public.search_normalize(title)
|
||
union all select 'game_alt_titles', count(*) from public.game_alt_titles
|
||
where title_search is distinct from public.search_normalize(title)
|
||
union all select 'publishers', count(*) from public.publishers
|
||
where name_search is distinct from public.search_normalize(name)$$,
|
||
$$values ('games'::text, 0::bigint), ('game_alt_titles', 0), ('publishers', 0)$$,
|
||
'Runbook 8: die Prüfabfrage meldet bei unverändertem Stand überall 0 Abweichungen');
|
||
|
||
-- ---------------------------------------------------------------------------------------------
|
||
-- Abschnitt 8.2 des Runbooks: Betrieb (Prüfabfragen unverändert, dazu die Annahmen für QUE-04)
|
||
-- ---------------------------------------------------------------------------------------------
|
||
select set_eq($$select rolname, rolconfig from pg_roles where rolname in ('anon', 'authenticated')$$,
|
||
$$select rolname, rolconfig from pg_roles where rolname in ('anon', 'authenticated') and exists (select 1 from unnest(rolconfig) c where c like 'statement_timeout=%')$$,
|
||
'Runbook 8.2: anon und authenticated haben ein statement_timeout in rolconfig');
|
||
select is((select count(*) from pg_available_extensions where name in ('pg_trgm', 'unaccent')), 2::bigint,
|
||
'Runbook 8.2: pg_trgm und unaccent sind verfügbar (select * from pg_available_extensions …)');
|
||
select is((select count(*) from pg_settings where name in ('log_min_duration_statement', 'log_statement', 'log_parameter_max_length', 'log_parameter_max_length_on_error', 'auto_explain.log_min_duration', 'pgaudit.log', 'pgaudit.log_parameter')), 7::bigint,
|
||
'Runbook 8.2: die sieben Protokoll-Einstellungen der Prüfabfrage sind abfragbar');
|
||
select is((select setting from pg_settings where name = 'log_parameter_max_length_on_error'), '0',
|
||
'QUE-04: bei Fehlern werden keine Parameterwerte protokolliert (Suchbegriffe im JSON-Parameter von PostgREST)');
|
||
select ok((select setting from pg_settings where name = 'log_statement') in ('none', 'ddl')
|
||
and (select setting from pg_settings where name = 'log_min_duration_statement') = '-1'
|
||
and (select setting from pg_settings where name = 'pgaudit.log') = 'none'
|
||
and (select setting from pg_settings where name = 'pgaudit.log_parameter') = 'off',
|
||
'QUE-04: weder Anweisungen mit Parametern (log_statement, log_min_duration_statement, pgaudit) noch langsame Anweisungen werden protokolliert');
|
||
select ok((select max((regexp_match(c, '^statement_timeout=(\d+)s$'))[1]::int) * 1000
|
||
from pg_roles r, unnest(r.rolconfig) c where r.rolname in ('anon', 'authenticated') and c like 'statement_timeout=%')
|
||
< (select setting::int from pg_settings where name = 'auto_explain.log_min_duration'),
|
||
'QUE-04: auto_explain (mit Parametern) greift erst nach dem längsten Zeitlimit der API-Rollen, eine abgebrochene Suche erscheint also nie im Protokoll');
|
||
|
||
-- Drift simulieren: Die Funktion wird geändert, die gespeicherten Spalten bleiben (wie nach einem Image-Upgrade)
|
||
create or replace function public.search_normalize(p text) returns text language sql immutable strict parallel safe set search_path = ''
|
||
as $$ select upper(p) $$;
|
||
select results_eq($$select 'games' as tabelle, count(*) > 0 as abweichend from public.games
|
||
where title_search is distinct from public.search_normalize(title)
|
||
union all select 'game_alt_titles', count(*) > 0 from public.game_alt_titles
|
||
where title_search is distinct from public.search_normalize(title)
|
||
union all select 'publishers', count(*) > 0 from public.publishers
|
||
where name_search is distinct from public.search_normalize(name)$$,
|
||
$$values ('games'::text, true), ('game_alt_titles', true), ('publishers', true)$$,
|
||
'Runbook 8: die Prüfabfrage erkennt eine geänderte Normalisierung in allen drei Tabellen');
|
||
|
||
select * from finish();
|
||
rollback;
|