import { inflateSync } from 'node:zlib'; import { describe, expect, it } from 'vitest'; 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 { toWinAnsi } from '@/render/pdfSurface'; import * as fmt from '@/ui/format'; import type { Project } from '@/domain/model/project'; /* * Befund 62: "Erstellt am" bezeichnete in derselben Unterlage zwei * verschiedene Zeitpunkte - in der Fusszeile jeder Seite den Zeitpunkt der * Ausgabe, auf dem Deckblatt das Anlagedatum des Projekts. Daneben stand auf * demselben Deckblatt "Ausgedruckt am" mit genau dem Wert, den die Fusszeile * "Erstellt am" nannte. * * Was der Anwender sonst erlebt: Er legt ein Projekt im August an, druckt es * im September, und die Unterlage traegt auf Blatt 1 zwei verschiedene * Erstellungsdaten. Welches die Behoerde in ihre Anordnung uebernimmt, bleibt * offen. */ const ANGELEGT = new Date('2026-08-14T09:00:00Z'); const AUSGEDRUCKT = new Date('2026-09-03T20:15:00Z'); function entpacke(roh: string, von: number): string | null { const anfang = roh.indexOf('stream', von); if (anfang < 0) return null; const start = anfang + (roh.startsWith('stream\r\n', anfang) ? 8 : 7); const ende = roh.indexOf('endstream', start); if (ende < 0) return null; try { return inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1'); } catch { return null; } } /** Alle Textstuecke der Datei, in der Reihenfolge des Inhaltsstroms. */ function textstuecke(bytes: Uint8Array): readonly string[] { const roh = new TextDecoder('latin1').decode(bytes); const raus: string[] = []; for (const treffer of roh.matchAll(/\/Type \/Page\b[^>]*?\/Contents (\d+) 0 R/g)) { const objekt = roh.indexOf(`\n${treffer[1]!} 0 obj`); const inhalt = objekt < 0 ? null : entpacke(roh, objekt); if (inhalt === null) continue; for (const t of inhalt.matchAll(/\(((?:[^()\\]|\\.)*)\)\s*Tj/g)) { raus.push(t[1]!.replace(/\\([()\\])/g, '$1')); } } return raus; } function unterlage(): Uint8Array { const basis = createStandardIntersectionProject('Musterkreuzung', ANGELEGT); const projekt: Project = { ...basis, meta: { ...basis.meta, planner: 'O. Willerding' }, }; const plan = buildSignalPlan(projekt); return buildProjectPdf( projekt, plan, validateProject(projekt, plan, AUSGEDRUCKT), DEFAULT_PDF_OPTIONS, AUSGEDRUCKT, ); } describe('Zeitangaben der Unterlage', () => { it('nennt den Zeitpunkt der Ausgabe in der Fusszeile "Ausgedruckt am", nicht "Erstellt am"', () => { const stuecke = textstuecke(unterlage()); const fusszeilen = stuecke.filter((s) => s.includes('Bearbeiter:')); expect(fusszeilen.length, 'Vorbedingung: jede Seite traegt eine Fusszeile').toBeGreaterThan(3); for (const zeile of fusszeilen) { expect(zeile.startsWith(toWinAnsi('Ausgedruckt am '))).toBe(true); } }); it('traegt das Anlagedatum unter derselben Beschriftung wie die Oberflaeche', () => { const stuecke = textstuecke(unterlage()); // Die Ansicht "Projekt" nennt dasselbe Feld "Angelegt am". expect(stuecke).toContain(toWinAnsi('Angelegt am')); expect(stuecke).toContain(toWinAnsi(fmt.dateTime(ANGELEGT.toISOString()))); }); it('benutzt "Erstellt am" nirgends mehr - die Beschriftung war doppelt belegt', () => { const stuecke = textstuecke(unterlage()); const treffer = stuecke.filter((s) => s.includes(toWinAnsi('Erstellt am'))); expect(treffer, `noch vorhanden: ${treffer.join(' | ')}`).toEqual([]); }); });