lsa-planer

LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.

/ tests export fremdunterlagenVerweis.test.ts

5,3 KB Rohdatei
tests/export/fremdunterlagenVerweis.test.ts — 121 Zeilen
1 import { inflateSync } from 'node:zlib';
2 import { describe, expect, it } from 'vitest';
3 import { buildProjectPdf, DEFAULT_PDF_OPTIONS } from '@/services/export/pdf';
4 import { buildSignalPlan } from '@/domain/plan/signalPlan';
5 import { validateProject } from '@/domain/validation';
6 import { ASSISTENT_VORGABE, projektAusAngaben } from '@/ui/assistent';
7 import type { Project } from '@/domain/model/project';
8
9 /*
10 * STUFE 1 DES SIGNALLAGEPLAN-ENTWURFS: DIE FREMDEN UNTERLAGEN BEKOMMEN EINEN
11 * NAMEN, DIE LUECKE BLEIBT EINE LUECKE.
12 *
13 * Die Uebersicht "Bestandteile der Anordnungsunterlage" sagt seit Befund C14,
14 * was diese Unterlage NICHT enthaelt - den bemassten Signallageplan und den
15 * Verkehrszeichenplan. Das war richtig und blieb doch die halbe Auskunft: Wer
16 * die Unterlage einreicht, legt einen Verkehrszeichenplan daneben, und der
17 * Sachbearbeiter musste die Zuordnung aus dem Anschreiben rekonstruieren.
18 *
19 * DIE GEFAHR DABEI IST DIE UMGEKEHRTE. Ein Verweis an der falschen Stelle
20 * liest sich wie ein Haken: Stuende die Kennung vorn, uebersaehe ein Pruefer
21 * beim Ueberfliegen das "NICHT Bestandteil". Diese Datei bewacht deshalb nicht
22 * nur, DASS der Verweis erscheint, sondern auch, dass die Zeile weiterhin mit
23 * der Absage beginnt.
24 *
25 * ZWEITE GEFAHR: die Regelplannummer. Das Programm hat die RSA 21 nicht im
26 * Original; jede Nummer, die es selbst vorschluege, waere der Fall des
27 * Befunds D4 (geratene Fundstellennummern) - derselbe, aus dem die Typklassenmeldung
28 * in 5.41.0 ihren Vorbehalt bekam. Die Nummer kommt vom Anwender, und der
29 * Ausdruck sagt das.
30 */
31
32 const DATUM = new Date('2026-09-12T09:00:00Z');
33
34 /** Der sichtbare Text des Ausdrucks, aus den Textbefehlen des PDF gelesen. */
35 function pdfText(bytes: Uint8Array): string {
36 const roh = new TextDecoder('latin1').decode(bytes);
37 const teile: string[] = [];
38 const suche = /stream\r?\n/g;
39 let treffer: RegExpExecArray | null;
40
41 while ((treffer = suche.exec(roh)) !== null) {
42 const start = treffer.index + treffer[0].length;
43 const ende = roh.indexOf('endstream', start);
44 if (ende < 0) continue;
45 let inhalt: string;
46 try {
47 inhalt = inflateSync(Buffer.from(roh.slice(start, ende), 'latin1')).toString('latin1');
48 } catch {
49 continue;
50 }
51 for (const stueck of inhalt.matchAll(/\((?:\\.|[^\\()])*\)\s*Tj/g)) {
52 teile.push(stueck[0].slice(1, stueck[0].lastIndexOf(')')));
53 }
54 }
55 return teile.join(' ').replace(/\s+/g, ' ');
56 }
57
58 /** Eine echte Engstelle aus dem gefuehrten Einstieg, mit geaenderten Projektdaten. */
59 function ausdruck(meta: Partial<Project['meta']>): string {
60 const basis = projektAusAngaben(
61 { ...ASSISTENT_VORGABE, form: 'einstreifig' },
62 'Arbeitsstelle Musterweg',
63 DATUM,
64 );
65 const project: Project = { ...basis, meta: { ...basis.meta, ...meta } };
66 const plan = buildSignalPlan(project);
67 const report = validateProject(project, plan, DATUM);
68 return pdfText(buildProjectPdf(project, plan, report, DEFAULT_PDF_OPTIONS, DATUM));
69 }
70
71 describe('Verweise auf Unterlagen, die dieses Programm nicht erzeugt', () => {
72 it('nennt den Verkehrszeichenplan beim Namen, sobald einer eingetragen ist', () => {
73 expect(ausdruck({ verkehrszeichenplan: 'VZP 2027-14, Stand 03.05.2027' })).toContain(
74 'VZP 2027-14, Stand 03.05.2027',
75 );
76 });
77
78 it('sagt trotz Verweis weiterhin, dass der Plan NICHT Bestandteil ist', () => {
79 /*
80 * Der Kern der Uebersicht. Aus "fehlt" darf nicht "enthalten" werden,
81 * sondern nur "fehlt hier, liegt als X bei" - und die Absage steht vorn,
82 * weil sie sonst beim Ueberfliegen untergeht.
83 */
84 const gedruckt = ausdruck({ verkehrszeichenplan: 'VZP 2027-14' });
85 const verweis = gedruckt.indexOf('bezieht sich auf');
86 expect(verweis, 'der Verweis fehlt').toBeGreaterThanOrEqual(0);
87 const absage = gedruckt.lastIndexOf('NICHT Bestandteil', verweis);
88 expect(absage, 'vor dem Verweis steht keine Absage').toBeGreaterThanOrEqual(0);
89 // In derselben Zelle und nicht irgendwo davor: Die Absage gehoert an den
90 // Anfang der Zeile, die den Verweis traegt.
91 expect(verweis - absage, 'die Absage steht zu weit vor dem Verweis').toBeLessThan(120);
92 expect(gedruckt.slice(verweis, verweis + 40)).toContain('VZP 2027-14');
93 });
94
95 it('sagt ohne Eintrag genau das, was es vorher sagte', () => {
96 // Gegenprobe: Wer nichts eintraegt, bekommt keine neue Zeile und keinen
97 // halben Satz, sondern die bekannte Absage.
98 const gedruckt = ausdruck({});
99 expect(gedruckt).toContain('NICHT Bestandteil dieser Unterlage');
100 expect(gedruckt).not.toContain('bezieht sich auf');
101 });
102
103 it('druckt Regelplan und Zustimmung nur, wenn sie eingetragen sind', () => {
104 const ohne = ausdruck({});
105 expect(ohne).not.toContain('Regelplan der RSA 21');
106 expect(ohne).not.toContain('Zustimmung des Straßenbaulastträgers');
107
108 const mit = ausdruck({ regelplan: 'B I/5', zustimmungBaulasttraeger: 'LBM Az. 4711-2027' });
109 expect(mit).toContain('B I/5');
110 expect(mit).toContain('LBM Az. 4711-2027');
111 });
112
113 it('weist die Regelplannummer als Angabe des Planers aus', () => {
114 /*
115 * Ohne diesen Zusatz laese sich die Zeile als Aussage des Programms ueber
116 * die RSA 21 - und das Programm hat sie nicht im Original. Es gibt die
117 * Nummer unveraendert wieder und stellt sich nicht dahinter.
118 */
119 expect(ausdruck({ regelplan: 'B I/5' })).toContain('Angabe des Planers');
120 });
121 });