import { inflateSync } from 'node:zlib'; import { describe, expect, it } from 'vitest'; import { wegherkunftZelle } from '@/services/export/wegherkunft'; import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { validateProject } from '@/domain/validation'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import type { Conflict, Project, Wegnachweis } from '@/domain/model/project'; import * as fmt from '@/ui/format'; /* * Herkunft der Wege ohne ueberlieferten Zeitpunkt (Befund 52). * * `wegherkunftZelle` gab bei herkunft = 'lageplan' und uebernommenAm = null die * blanke Beschriftung "Aus dem Lageplan vermessen" aus. Der Erlaeuterungsabsatz * unter der Tabelle (src/services/export/pdf.ts) sagt daneben unbedingt zu, die * Angabe nenne den Zeitpunkt der Uebernahme - fuer diese Zeile eine falsche * Aussage der Planunterlage ueber ihre eigene Tabelle. Der Zustand entsteht * beim Einlesen einer fremd erzeugten Datei und ist dort ausdruecklich * vorgesehen (parseWegnachweis in src/domain/model/schema.ts sammelt nur eine * Warnung und laesst den Nachweis stehen). * * Der Zeitstempel hat einen benannten Zweck: Er soll gegen * `lageplan.bild.geladenAm` gehalten werden. Fehlt er wortlos, kann ein Pruefer * nicht unterscheiden, ob er nie ueberliefert wurde oder ob der Druck ihn * verschluckt hat. Die Zelle sagt es deshalb selbst. */ const DATUM = new Date('2026-01-01T12:00:00Z'); function beziehung(wegnachweis: Wegnachweis): Conflict { const basis = createStandardIntersectionProject('Testknoten', DATUM).conflicts[0]; expect(basis, 'Vorbedingung: der Regelknotenpunkt fuehrt Beziehungen').toBeDefined(); return { ...basis!, wegnachweis }; } /** Sichtbaren Text aus dem PDF lesen; die Inhaltsstroeme sind komprimiert. */ function pdfText(bytes: Uint8Array): string { const roh = new TextDecoder('latin1').decode(bytes); const teile: string[] = []; const suche = /stream\r?\n/g; let treffer: RegExpExecArray | null; while ((treffer = suche.exec(roh)) !== null) { const start = treffer.index + treffer[0].length; const ende = roh.indexOf('endstream', start); if (ende < 0) continue; let inhalt: string; try { inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1'); } catch { continue; } for (const stueck of inhalt.matchAll(/\((?:\\.|[^\\()])*\)\s*Tj/g)) { teile.push(stueck[0].slice(1, stueck[0].lastIndexOf(')'))); } } // Lange Absaetze und Tabellenzellen sind auf Zeilen umbrochen; fuer die Suche // wieder zusammenziehen. Klammern und Schraegstriche stehen im Inhaltsstrom // maskiert - die Zelle traegt Klammern, also wird die Maskierung entfernt. return teile .join(' ') .replace(/\\([()\\])/g, '$1') .replace(/\s+/g, ' '); } function ausdruck(project: Project): string { const plan = buildSignalPlan(project); const report = validateProject(project, plan, DATUM); return pdfText( buildProjectPdf( project, plan, report, { ...DEFAULT_PDF_OPTIONS, includeCalculationDetails: true }, DATUM, ), ); } describe('Herkunft der Wege: Vermessung ohne ueberlieferten Zeitpunkt', () => { it('vermerkt in der Zelle, dass der Zeitpunkt der Uebernahme nicht ueberliefert ist', () => { const zelle = wegherkunftZelle(beziehung({ herkunft: 'lageplan', uebernommenAm: null })); expect(zelle).toBe('Aus dem Lageplan vermessen (Zeitpunkt nicht überliefert)'); // Zuvor stand hier die blanke Beschriftung - und der Absatz daneben // behauptete, sie nenne den Zeitpunkt. expect(zelle).not.toBe('Aus dem Lageplan vermessen'); }); it('nennt den Zeitpunkt unveraendert, wenn er ueberliefert ist', () => { const am = '2026-03-04T09:15:00.000Z'; const zelle = wegherkunftZelle(beziehung({ herkunft: 'lageplan', uebernommenAm: am })); expect(zelle).toBe(`Aus dem Lageplan vermessen (${fmt.dateTime(am)})`); }); it('laesst die beiden anderen Herkunftsstufen unberuehrt', () => { expect(wegherkunftZelle(beziehung({ herkunft: 'handeingabe', uebernommenAm: null }))).toBe( 'Von Hand eingetragen', ); expect(wegherkunftZelle(beziehung({ herkunft: 'unbestimmt', uebernommenAm: null }))).toBe( 'Herkunft nicht nachgewiesen', ); }); it('traegt den Vermerk bis auf das Papier, wo der Erlaeuterungsabsatz den Zeitpunkt zusagt', () => { const basis = createStandardIntersectionProject('Testknoten', DATUM); const erste = basis.conflicts[0]; expect(erste).toBeDefined(); const project: Project = { ...basis, conflicts: basis.conflicts.map((c) => c.id === erste!.id ? { ...c, wegnachweis: { herkunft: 'lageplan', uebernommenAm: null } } : c, ), }; const text = ausdruck(project); // Der Absatz sagt den Zeitpunkt weiterhin unbedingt zu ... expect(text).toContain('"Aus dem Lageplan vermessen" nennt den Zeitpunkt der Übernahme'); // ... die Zeile, fuer die er nicht ueberliefert ist, sagt es selbst. expect(text).toContain('(Zeitpunkt nicht überliefert)'); }); });