lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
| 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 | }); |