import { readdirSync, readFileSync } from 'node:fs'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; /** * Prüft das hand-geschriebene Backfill-SQL für die DÖE-Bekanntmachungslinks * ohne Datenbank — reiner Textabgleich, gleiches Muster wie * groups/migration-sql.spec.ts. * * Der Adapter allein repariert nur neue Importe. Ohne den Nachlauf behalten * die bereits importierten Ausschreibungen ihre kaputte API-URL, und genau * die sieht der Nutzer heute in Trefferliste, Detailansicht und Alarm-Mail. */ const MIGRATIONS_DIR = join(__dirname, '../../prisma/migrations'); function readMigrationSql(suffix: string): string { const dirs = readdirSync(MIGRATIONS_DIR, { withFileTypes: true }) .filter((entry) => entry.isDirectory() && entry.name.endsWith(suffix)) .map((entry) => entry.name); if (dirs.length !== 1) { throw new Error( `Expected exactly one migration directory ending in "${suffix}", found ${dirs.length}: ${dirs.join(', ')}`, ); } return readFileSync(join(MIGRATIONS_DIR, dirs[0], 'migration.sql'), 'utf-8'); } describe('doe_notice_url_backfill migration.sql', () => { const sql = readMigrationSql('_doe_notice_url_backfill'); it('schreibt die Bekanntmachungsseite, nicht die API-Adresse', () => { expect(sql).toContain( "'https://oeffentlichevergabe.de/ui/de/search/details?noticeId=' || \"sourceNoticeId\"", ); expect(sql).not.toMatch(/SET\s+"sourceUrl"\s*=\s*'[^']*\/api\/notices\//); }); it('fasst beide Tabellen an — sonst bleiben die Quell-Links der zusammengefuehrten Tender kaputt', () => { expect(sql).toMatch(/UPDATE\s+"Tender"/); expect(sql).toMatch(/UPDATE\s+"TenderSource"/); }); it('ist idempotent: nur Zeilen, die noch auf die API zeigen', () => { const updates = sql.match(/UPDATE\s+"[A-Za-z]+"[\s\S]*?;/g) ?? []; expect(updates.length).toBe(2); for (const stmt of updates) { expect(stmt).toContain( `"sourceUrl" LIKE 'https://oeffentlichevergabe.de/api/notices/%'`, ); expect(stmt).toContain(`"sourcePortal" = 'doe-opendata'`); } }); it('baut keine URL aus einer Kennung unerwarteter Form', () => { const updates = sql.match(/UPDATE\s+"[A-Za-z]+"[\s\S]*?;/g) ?? []; for (const stmt of updates) { expect(stmt).toContain(`"sourceNoticeId" ~ '^[A-Za-z0-9-]+$'`); } }); it('fasst ausschliesslich DÖE-Zeilen an — andere Portale behalten ihre Links', () => { expect(sql).not.toMatch(/UPDATE[\s\S]*?service-bund/); expect(sql).not.toMatch(/UPDATE[\s\S]*?cosinex/); }); });