/** * Testdaten fuer die Katalogsuche (KAT-01, Issue #3, PR 3c). NUR lokal/CI (`withDb`, als `postgres`). * * Jede Spec-Datei bekommt ein EIGENES Datenset (eigener UUID-Praefix, eigenes Kunstwort), weil die * Dateien unter `fullyParallel` gleichzeitig laufen: Raeumt eine Datei auf, darf sie keine Zeilen der * anderen loeschen. Das Kunstwort steht in allen Titeln, Verlagen und Begriffen; Suchen mit dem * Kunstwort treffen deshalb nie Startbestandsspiele, nie die Fixtures von pgTAP (`qxz`) und nie das * Datenset der anderen Datei. Ohne Suchbegriff wird nur ueber die eigenen Genres/Mechaniken gefiltert. * * Aufraeumen ueber den UUID-Praefix (Spiele, Verlage, Genres, Mechaniken); Zuordnungen und * alternative Titel haengen per `on delete cascade` an den Spielen. `seed` loescht zuerst, ist also * nach einem abgebrochenen Lauf idempotent. */ import type { Client } from 'pg'; import { withDb } from './db'; export interface CatalogDatasetOptions { /** 8 Hex-Zeichen, UUID-Praefix aller Zeilen des Sets. */ prefix: string; /** Kunstwort (nur Kleinbuchstaben a-z), steht in jedem Titel. */ word: string; } const uuid = (prefix: string, n: number) => `${prefix}-0000-4000-8000-${n.toString(16).padStart(12, '0')}`; const cap = (w: string) => w.charAt(0).toUpperCase() + w.slice(1); /** Anzahl der Massenspiele (Seitengroesse 30 + 6, KAT-01 Kriterium 5). */ export const MASS_COUNT = 36; export function catalogDataset({ prefix, word }: CatalogDatasetOptions) { const W = cap(word); const id = (n: number) => uuid(prefix, n); const mass = Array.from({ length: MASS_COUNT }, (_, i) => { const nn = String(i + 1).padStart(2, '0'); return { id: id(1000 + i + 1), title: `${W} Massenspiel ${nn}` }; }); const ids = { cafe: id(1), wald: id(2), rot: id(3), blau: id(4), familie: id(5), experte: id(6), duo: id(7), relExact: id(10), relBegins: id(11), relWord: id(12), relContains: id(13), relPublisher: id(14), hidden: id(20), merged: id(21), genreA: id(101), genreB: id(102), mechanicM1: id(111), mechanicM2: id(112), pubQuarz: id(201), pubNebel: id(202), pubRelevanz: id(203), }; const t = { cafe: `${W} Café Größe: Duel®`, wald: `${W} Schattenwald`, waldAltDe: `${W} Dunkelforst`, waldAltEn: `${W} Shadowwood`, rot: `${W} Rotkehlchen`, blau: `${W} Blaumeise`, familie: `${W} Familienfest`, experte: `${W} Expertenrunde`, duo: `${W} Duo`, relExact: `${W} Relevanz`, relBegins: `${W} Relevanz Deluxe`, relWord: `Die ${W} Relevanz`, relContains: `X${W} Relevanzen`, relPublisher: `${W} Fremdtitel`, hidden: `${W} Verborgen`, merged: `${W} Zusammengefuehrt`, genreA: `${W} Genre A`, genreB: `${W} Genre B`, mechanicM1: `${W} Mechanik 1`, mechanicM2: `${W} Mechanik 2`, pubQuarz: `Quarzfeld ${W} Presse`, pubNebel: `Nebelkamm ${W} Verlag`, pubRelevanz: `Relevanz Verlag ${W}`, }; return { word, W, prefix, ids, titles: t, mass }; } export type CatalogDataset = ReturnType; /** Loescht alle Zeilen des Sets (anhand des UUID-Praefixes). */ export async function cleanupCatalog(c: Client, ds: Pick): Promise { const like = `${ds.prefix}-%`; await c.query(`delete from public.games where id::text like $1`, [like]); await c.query(`delete from public.publishers where id::text like $1`, [like]); await c.query(`delete from public.genres where id::text like $1`, [like]); await c.query(`delete from public.mechanics where id::text like $1`, [like]); } /** Legt das Datenset an (nach Aufraeumen). */ export async function seedCatalog(ds: CatalogDataset): Promise { const { ids: i, titles: t } = ds; await withDb(async (c) => { await cleanupCatalog(c, ds); await c.query( `insert into public.genres (id, name_de, name_en) values ($1, $2, $2), ($3, $4, $4)`, [i.genreA, t.genreA, i.genreB, t.genreB], ); await c.query( `insert into public.mechanics (id, name_de, name_en) values ($1, $2, $2), ($3, $4, $4)`, [i.mechanicM1, t.mechanicM1, i.mechanicM2, t.mechanicM2], ); await c.query( `insert into public.publishers (id, name) values ($1, $2), ($3, $4), ($5, $6)`, [i.pubQuarz, t.pubQuarz, i.pubNebel, t.pubNebel, i.pubRelevanz, t.pubRelevanz], ); // (id, title, year, min_p, max_p, min_min, max_min, min_age) const games: [string, string, number | null, number | null, number | null, number | null, number | null, number | null][] = [ [i.cafe, t.cafe, 2021, 2, 2, 20, 30, 12], [i.wald, t.wald, 2019, 2, 4, 45, 60, 10], [i.rot, t.rot, 2018, 2, 4, 30, 45, 8], [i.blau, t.blau, 2022, 2, 5, 30, 45, 8], [i.familie, t.familie, 2017, 3, 8, 20, 45, 6], [i.experte, t.experte, 2016, 1, 2, 120, 180, 14], [i.duo, t.duo, 2015, 2, 2, 30, 30, 8], [i.relExact, t.relExact, 2000, 2, 4, 30, 60, 10], [i.relBegins, t.relBegins, 2000, 2, 4, 30, 60, 10], [i.relWord, t.relWord, 2000, 2, 4, 30, 60, 10], [i.relContains, t.relContains, 2000, 2, 4, 30, 60, 10], [i.relPublisher, t.relPublisher, 2000, 2, 4, 30, 60, 10], ...ds.mass.map( (m) => [m.id, m.title, 2020, 2, 4, 30, 60, 10] as [string, string, number, number, number, number, number, number], ), ]; for (const g of games) { await c.query( `insert into public.games (id, title, visibility, year, min_players, max_players, min_minutes, max_minutes, min_age) values ($1, $2, 'catalog', $3, $4, $5, $6, $7, $8)`, g, ); } // Ausgeblendet und zusammengefuehrt: duerfen nie erscheinen (KAT-05 Kriterium 4). await c.query( `insert into public.games (id, title, visibility, hidden_at) values ($1, $2, 'catalog', now())`, [i.hidden, t.hidden], ); await c.query( `insert into public.games (id, title, visibility, hidden_at, merged_into_id) values ($1, $2, 'catalog', now(), $3)`, [i.merged, t.merged, i.relExact], ); await c.query( `insert into public.game_alt_titles (game_id, locale, title) values ($1, 'de', $2), ($1, 'en', $3)`, [i.wald, t.waldAltDe, t.waldAltEn], ); // Der alternative Titel des ausgeblendeten Spiels darf es nicht auffindbar machen. await c.query(`insert into public.game_alt_titles (game_id, locale, title) values ($1, 'de', $2)`, [ i.hidden, `${ds.W} Verstecktername`, ]); await c.query( `insert into public.game_publishers (game_id, publisher_id) values ($1, $2), ($3, $2), ($3, $4), ($5, $6)`, [i.rot, i.pubQuarz, i.blau, i.pubNebel, i.relPublisher, i.pubRelevanz], ); await c.query( `insert into public.game_genres (game_id, genre_id) values ($1, $2), ($3, $2), ($4, $2), ($4, $5)`, [i.familie, i.genreA, i.duo, i.experte, i.genreB], ); await c.query( `insert into public.game_mechanics (game_id, mechanic_id) values ($1, $3), ($2, $3)`, [i.experte, i.duo, i.mechanicM1], ); }); } export async function cleanupCatalogDataset(ds: CatalogDataset): Promise { await withDb((c) => cleanupCatalog(c, ds)); }