lsa-planer

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

/ tests domain schema.test.ts

5,6 KB Rohdatei
tests/domain/schema.test.ts — 138 Zeilen
1 import { describe, expect, it } from 'vitest';
2 import { parseProject, sanitizeObject } from '@/domain/model/schema';
3 import { createStandardIntersectionProject } from '@/domain/model/factory';
4 import { CURRENT_SCHEMA_VERSION } from '@/domain/model/project';
5
6 describe('Projektpruefung beim Einlesen', () => {
7 it('liest ein selbst erzeugtes Projekt unveraendert wieder ein', () => {
8 const original = createStandardIntersectionProject('Test', new Date('2026-01-01T00:00:00Z'));
9 const { project, issues } = parseProject(original);
10
11 expect(issues.filter((i) => i.severity === 'fehler')).toHaveLength(0);
12 expect(project.signalGroups).toHaveLength(original.signalGroups.length);
13 expect(project.conflicts).toHaveLength(original.conflicts.length);
14 expect(project.phases).toHaveLength(original.phases.length);
15 expect(project.schemaVersion).toBe(CURRENT_SCHEMA_VERSION);
16 });
17
18 /*
19 * oneOf() schrieb den eingelesenen Wert klein und stellte ihn dann gegen die
20 * Werteliste. Bei ['HBS','HCM'] traf das nie zu: HCM fiel bei jedem Speichern
21 * auf HBS zurueck, und JEDE Projektdatei meldete beim Einlesen
22 * "Bewertungsskala ist unbekannt" - auch die mit HBS. Der Rundreisetest
23 * darueber hat das nicht gefunden, weil er nur Laengen vergleicht.
24 */
25 it('behaelt die Bewertungsskala HCM ueber den Hin- und Rueckweg', () => {
26 const original = createStandardIntersectionProject('Skala', new Date('2026-01-01T00:00:00Z'));
27 const mitHcm = {
28 ...original,
29 settings: { ...original.settings, serviceLevelScale: 'HCM' as const },
30 };
31
32 const { project, issues } = parseProject(JSON.parse(JSON.stringify(mitHcm)));
33
34 expect(project.settings.serviceLevelScale).toBe('HCM');
35 expect(issues.filter((i) => i.message.includes('Bewertungsverfahren'))).toHaveLength(0);
36 });
37
38 it('meldet bei der Regelskala HBS keinen Hinweis', () => {
39 const original = createStandardIntersectionProject('Skala', new Date('2026-01-01T00:00:00Z'));
40 const { project, issues } = parseProject(JSON.parse(JSON.stringify(original)));
41
42 expect(project.settings.serviceLevelScale).toBe('HBS');
43 expect(issues.filter((i) => i.message.includes('Bewertungsverfahren'))).toHaveLength(0);
44 });
45
46 it('nimmt eine abweichend geschriebene Skala an und meldet erst bei einem echten Fehlwert', () => {
47 const original = createStandardIntersectionProject('Skala', new Date('2026-01-01T00:00:00Z'));
48 const roh = JSON.parse(JSON.stringify(original)) as Record<string, unknown>;
49 const einstellungen = roh['settings'] as Record<string, unknown>;
50
51 const nachlaessig = parseProject({
52 ...roh,
53 settings: { ...einstellungen, serviceLevelScale: ' hcm ' },
54 });
55 expect(nachlaessig.project.settings.serviceLevelScale).toBe('HCM');
56
57 const unsinn = parseProject({
58 ...roh,
59 settings: { ...einstellungen, serviceLevelScale: 'HBSX' },
60 });
61 expect(unsinn.project.settings.serviceLevelScale).toBe('HBS');
62 expect(unsinn.issues.some((i) => i.message.includes('Bewertungsverfahren'))).toBe(true);
63 });
64
65 it('entfernt gefaehrliche Schluessel aus fremden Daten', () => {
66 const hostile = JSON.parse(
67 '{"a":1,"__proto__":{"polluted":true},"nested":{"constructor":{"x":1}}}',
68 );
69 const clean = sanitizeObject(hostile) as Record<string, unknown>;
70
71 expect(Object.keys(clean)).toEqual(['a', 'nested']);
72 expect(({} as Record<string, unknown>)['polluted']).toBeUndefined();
73 });
74
75 it('stuerzt bei voellig falschen Datentypen nicht ab', () => {
76 const { project, issues } = parseProject({
77 signalGroups: 'keine Liste',
78 phases: 42,
79 conflicts: null,
80 program: { phaseOrder: 'p1' },
81 });
82
83 expect(project.signalGroups).toEqual([]);
84 expect(project.phases).toEqual([]);
85 expect(Array.isArray(issues)).toBe(true);
86 });
87
88 it('entfernt Konflikte, die auf nicht vorhandene Signalgruppen verweisen', () => {
89 const { project, issues } = parseProject({
90 signalGroups: [{ id: 'a', name: 'K1', mode: 'kfz' }],
91 conflicts: [
92 { fromId: 'a', toId: 'unbekannt', clearingDistance: 20, enteringDistance: 10 },
93 { fromId: 'a', toId: 'a', clearingDistance: 20, enteringDistance: 10 },
94 ],
95 });
96
97 expect(project.conflicts).toHaveLength(0);
98 expect(issues.filter((i) => i.path.startsWith('conflicts'))).toHaveLength(2);
99 });
100
101 it('vergibt doppelte Kennungen neu, statt Verweise mehrdeutig zu lassen', () => {
102 const { project, issues } = parseProject({
103 signalGroups: [
104 { id: 'gleich', name: 'K1', mode: 'kfz' },
105 { id: 'gleich', name: 'K2', mode: 'kfz' },
106 ],
107 });
108
109 expect(project.signalGroups).toHaveLength(2);
110 expect(project.signalGroups[0]!.id).not.toBe(project.signalGroups[1]!.id);
111 expect(issues.some((i) => i.severity === 'fehler')).toBe(true);
112 });
113
114 it('ergaenzt fehlende Phasen in der Reihenfolge und entfernt ungueltige', () => {
115 const { project } = parseProject({
116 phases: [
117 { id: 'p1', name: 'Phase 1', signalGroupIds: [] },
118 { id: 'p2', name: 'Phase 2', signalGroupIds: [] },
119 ],
120 program: { phaseOrder: ['p2', 'gibtsnicht'] },
121 });
122
123 expect(project.program.phaseOrder).toEqual(['p2', 'p1']);
124 });
125
126 it('akzeptiert deutsches Dezimalkomma in Zahlenfeldern', () => {
127 const { project } = parseProject({
128 signalGroups: [{ id: 'a', name: 'K1', mode: 'kfz' }],
129 conflicts: [{ fromId: 'a', toId: 'b' }],
130 intersection: { vZul: '50' },
131 demands: [{ signalGroupId: 'a', volume: '620', heavyVehicleShare: '0,05' }],
132 });
133
134 expect(project.intersection.vZul).toBe(50);
135 expect(project.demands[0]!.volume).toBe(620);
136 expect(project.demands[0]!.heavyVehicleShare).toBeCloseTo(0.05, 6);
137 });
138 });