lsa-planer

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

/ tests domain anlagenartDatei.test.ts

8,1 KB Rohdatei
tests/domain/anlagenartDatei.test.ts — 190 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { createEmptyProject, createStandardIntersectionProject } from '@/domain/model/factory';
3 import { loadProject } from '@/domain/model/migrate';
4 import { parseProject } from '@/domain/model/schema';
5 import { CURRENT_SCHEMA_VERSION } from '@/domain/model/project';
6 import { ANLAGENART_GRENZEN } from '@/domain/rilsa/constants';
7
8 /**
9 * Die Anlagenart auf ihrem Weg durch Vorlage, Datei und Altprojekt.
10 *
11 * WOGEGEN DIESE TESTS STEHEN: Bis Schema 8 galten die Schranken des
12 * Knotenpunkts fuer jede Anlage - vor allem der Raeumweg von hoechstens 500 m.
13 * Bei einer einstreifigen Verkehrsfuehrung IST der Raeumweg die ganze
14 * Engstelle; eine 800 m lange Fuehrung waere beim Einlesen stillschweigend auf
15 * 500 m gekuerzt worden, und die daraus gerechnete Zwischenzeit haette danach
16 * rund 30 s zu kurz dagestanden.
17 *
18 * Die Gegenrichtung ist genauso wichtig: Eine Datei ohne Angabe darf nicht
19 * plotzlich die weiten Schranken der Arbeitsstelle bekommen. Fehlende Angabe
20 * heisst "knotenpunkt", und zwar ohne Meldung - das Feld gab es damals nicht.
21 */
22
23 const FIXED_DATE = new Date('2026-01-01T00:00:00Z');
24
25 /** Zwei Signalgruppen und eine Beziehung mit dem angegebenen Raeumweg. */
26 function dateiMit(anlagenart: unknown, raeumweg: number, schemaVersion = CURRENT_SCHEMA_VERSION) {
27 return {
28 schemaVersion,
29 anlagenart,
30 signalGroups: [
31 { id: 'a', name: 'K1', mode: 'kfz' },
32 { id: 'b', name: 'K2', mode: 'kfz' },
33 ],
34 conflicts: [{ fromId: 'a', toId: 'b', clearingDistance: raeumweg, enteringDistance: 5 }],
35 };
36 }
37
38 describe('Modell und Vorlage', () => {
39 it('fuehrt die Schemaversion 12', () => {
40 // ANPASSUNG (Befund A2): Aufstieg auf 10 fuer die Fahrzeugart
41 // 'strassenbahn' und das Konfliktfeld haltVorKnoten. ANPASSUNG (Befund C1):
42 // Aufstieg auf 11 fuer das Signalgruppenfeld blindenzusatz (Freigabe fuer
43 // die ganze Furt). ANPASSUNG (Fassung 5.5.0, Fragen E1/E2): Aufstieg auf 12
44 // fuer das Konfliktfeld engerRadius und den Ueberfahrzeit-Ansatz.
45 // ANPASSUNG: Aufstieg auf 13 fuer den Lastzuganteil am Schwerverkehr
46 // (TrafficDemand.lastzugAnteil, HBS 2015 Gl. 2-5).
47 expect(CURRENT_SCHEMA_VERSION).toBe(19);
48 });
49
50 it('legt ein leeres Projekt ohne Angabe als Knotenpunkt an', () => {
51 expect(createEmptyProject('Leer', FIXED_DATE).anlagenart).toBe('knotenpunkt');
52 });
53
54 it('nimmt die Anlagenart als Vorgabe entgegen', () => {
55 // Der Assistent legt Fussgaengerschutzanlagen und einstreifige Fuehrungen
56 // an. Die Art gehoert in denselben Schritt wie das Projekt, sonst
57 // durchlaeuft es einen Zustand mit den falschen Schranken.
58 expect(createEmptyProject('Baustelle', FIXED_DATE, 'einstreifig').anlagenart).toBe(
59 'einstreifig',
60 );
61 expect(createEmptyProject('Furt', FIXED_DATE, 'fussgaengerschutzanlage').anlagenart).toBe(
62 'fussgaengerschutzanlage',
63 );
64 });
65
66 it('weist den Beispielknotenpunkt als Knotenpunkt aus', () => {
67 expect(createStandardIntersectionProject('Vorlage', FIXED_DATE).anlagenart).toBe('knotenpunkt');
68 });
69 });
70
71 describe('Einlesen', () => {
72 it('liest ein selbst erzeugtes Projekt mit seiner Art wieder ein', () => {
73 const original = createEmptyProject('Baustelle B7', FIXED_DATE, 'einstreifig');
74 expect(parseProject(original, FIXED_DATE).project.anlagenart).toBe('einstreifig');
75 });
76
77 it('gibt einer Datei ohne Angabe "knotenpunkt", und zwar ohne Meldung', () => {
78 // Eine Datei aus Schema 8 ist nicht fehlerhaft: Sie kannte das Feld nicht,
79 // und etwas anderes als einen Knotenpunkt konnte sie gar nicht beschreiben.
80 const { project, issues } = parseProject(
81 { schemaVersion: 8, signalGroups: [{ id: 'a', name: 'K1', mode: 'kfz' }] },
82 FIXED_DATE,
83 );
84
85 expect(project.anlagenart).toBe('knotenpunkt');
86 expect(issues.filter((i) => i.path === 'anlagenart')).toHaveLength(0);
87 });
88
89 it('faellt bei unbekannter Art auf "knotenpunkt" zurueck und meldet das', () => {
90 // Hier steht etwas, das jemand gemeint hat. Stillschweigend die engsten
91 // Schranken zu setzen waere sicher, aber der Bearbeiter wuesste nicht,
92 // warum seine Engstelle nicht rechnet.
93 const { project, issues } = parseProject(dateiMit('kreisverkehr', 20), FIXED_DATE);
94
95 expect(project.anlagenart).toBe('knotenpunkt');
96 const meldung = issues.find((i) => i.path === 'anlagenart');
97 expect(meldung).toBeDefined();
98 expect(meldung!.severity).toBe('warnung');
99 expect(meldung!.message).toContain('kreisverkehr');
100 });
101 });
102
103 describe('Raeumweg an der Anlagenart', () => {
104 it('nimmt bei einstreifiger Fuehrung eine Engstelle ueber 500 m unveraendert an', () => {
105 // DER FEHLER, UM DEN ES GEHT: Bis Schema 8 stand hier fest 500 m, und die
106 // 800 m waeren beim Oeffnen der Datei verschwunden.
107 const { project, issues } = parseProject(dateiMit('einstreifig', 800), FIXED_DATE);
108
109 expect(project.conflicts[0]!.clearingDistance).toBe(800);
110 expect(issues.filter((i) => i.path.includes('clearingDistance'))).toHaveLength(0);
111 });
112
113 it('begrenzt den Raeumweg auf die Grenze der jeweiligen Art und sagt es', () => {
114 const grenze = ANLAGENART_GRENZEN.einstreifig.maxClearingDistance;
115 const { project, issues } = parseProject(dateiMit('einstreifig', grenze + 500), FIXED_DATE);
116
117 expect(project.conflicts[0]!.clearingDistance).toBe(grenze);
118 const meldung = issues.find((i) => i.path.includes('clearingDistance'));
119 expect(meldung).toBeDefined();
120 // Ohne die Art in der Meldung liest sich die Zahl wie eine Programmgrenze.
121 expect(meldung!.message).toContain('Einstreifige');
122 });
123
124 it('haelt am Knotenpunkt die bisherigen 500 m', () => {
125 const { project, issues } = parseProject(dateiMit('knotenpunkt', 800), FIXED_DATE);
126
127 expect(project.conflicts[0]!.clearingDistance).toBe(
128 ANLAGENART_GRENZEN.knotenpunkt.maxClearingDistance,
129 );
130 expect(issues.some((i) => i.path.includes('clearingDistance'))).toBe(true);
131 });
132
133 it('gibt einer Datei ohne Angabe nicht die weiten Schranken der Arbeitsstelle', () => {
134 // Die Gegenprobe: Der Rueckfall darf nicht die grosszuegigste Art sein.
135 const { project } = parseProject(dateiMit(undefined, 800, 8), FIXED_DATE);
136 expect(project.conflicts[0]!.clearingDistance).toBe(500);
137 });
138 });
139
140 describe('Uebernahme von Altprojekten', () => {
141 const legacyProject = {
142 metadata: { version: '4.3.3' },
143 projektdaten: {
144 projektBezeichnung: 'Alt',
145 // Das Altformat fuehrte Baustellenfelder. Daraus wird NICHT auf eine
146 // einstreifige Fuehrung geschlossen: Auch ein Knotenpunkt in einer
147 // Baustelle wird angemietet, und die Fehlannahme haette seine
148 // Umlaufzeitschranke still von 120 s auf 300 s angehoben.
149 baustellenVon: true,
150 mietkosten: { tagessatz: 40 },
151 },
152 signalgruppen: [
153 { id: 'SG-1', nummer: 'K1', typ: 'kfz' },
154 { id: 'SG-2', nummer: 'K2', typ: 'kfz' },
155 ],
156 sperrmatrix: {
157 matrix: [
158 [false, true],
159 [true, false],
160 ],
161 },
162 zwischenzeiten: { parameter: { raeumweg: 24, einfahrweg: 9 } },
163 };
164
165 it('uebernimmt ein Altprojekt als Knotenpunkt', () => {
166 const result = loadProject(legacyProject, FIXED_DATE);
167
168 expect(result.migrated).toBe(true);
169 expect(result.project.anlagenart).toBe('knotenpunkt');
170 });
171
172 it('sagt beim Oeffnen einer Datei aus Schema 8 nichts zur Anlagenart', () => {
173 // Anders als beim Wegnachweis wird hier nichts behauptet, was unbekannt
174 // waere - eine Meldung wuerde nur das Gewicht der uebrigen verwaessern.
175 const result = loadProject(dateiMit(undefined, 24, 8), FIXED_DATE);
176
177 expect(result.project.anlagenart).toBe('knotenpunkt');
178 expect(result.issues.some((i) => i.path === 'anlagenart')).toBe(false);
179 });
180
181 it('warnt bei einer Datei aus einer neueren Schemaversion', () => {
182 // Der Grund fuer den Aufstieg auf 9: Ein aelterer Programmstand soll eine
183 // Arbeitsstelle mit 250 s Umlauf nicht stillschweigend auf 120 s stutzen.
184 const result = loadProject(
185 { schemaVersion: CURRENT_SCHEMA_VERSION + 1, signalGroups: [] },
186 FIXED_DATE,
187 );
188 expect(result.issues.some((i) => i.path === 'schemaVersion')).toBe(true);
189 });
190 });