import { describe, expect, it } from 'vitest'; import { parseProject, sanitizeObject } from '@/domain/model/schema'; import { createStandardIntersectionProject } from '@/domain/model/factory'; import { CURRENT_SCHEMA_VERSION } from '@/domain/model/project'; describe('Projektpruefung beim Einlesen', () => { it('liest ein selbst erzeugtes Projekt unveraendert wieder ein', () => { const original = createStandardIntersectionProject('Test', new Date('2026-01-01T00:00:00Z')); const { project, issues } = parseProject(original); expect(issues.filter((i) => i.severity === 'fehler')).toHaveLength(0); expect(project.signalGroups).toHaveLength(original.signalGroups.length); expect(project.conflicts).toHaveLength(original.conflicts.length); expect(project.phases).toHaveLength(original.phases.length); expect(project.schemaVersion).toBe(CURRENT_SCHEMA_VERSION); }); /* * oneOf() schrieb den eingelesenen Wert klein und stellte ihn dann gegen die * Werteliste. Bei ['HBS','HCM'] traf das nie zu: HCM fiel bei jedem Speichern * auf HBS zurueck, und JEDE Projektdatei meldete beim Einlesen * "Bewertungsskala ist unbekannt" - auch die mit HBS. Der Rundreisetest * darueber hat das nicht gefunden, weil er nur Laengen vergleicht. */ it('behaelt die Bewertungsskala HCM ueber den Hin- und Rueckweg', () => { const original = createStandardIntersectionProject('Skala', new Date('2026-01-01T00:00:00Z')); const mitHcm = { ...original, settings: { ...original.settings, serviceLevelScale: 'HCM' as const }, }; const { project, issues } = parseProject(JSON.parse(JSON.stringify(mitHcm))); expect(project.settings.serviceLevelScale).toBe('HCM'); expect(issues.filter((i) => i.message.includes('Bewertungsverfahren'))).toHaveLength(0); }); it('meldet bei der Regelskala HBS keinen Hinweis', () => { const original = createStandardIntersectionProject('Skala', new Date('2026-01-01T00:00:00Z')); const { project, issues } = parseProject(JSON.parse(JSON.stringify(original))); expect(project.settings.serviceLevelScale).toBe('HBS'); expect(issues.filter((i) => i.message.includes('Bewertungsverfahren'))).toHaveLength(0); }); it('nimmt eine abweichend geschriebene Skala an und meldet erst bei einem echten Fehlwert', () => { const original = createStandardIntersectionProject('Skala', new Date('2026-01-01T00:00:00Z')); const roh = JSON.parse(JSON.stringify(original)) as Record; const einstellungen = roh['settings'] as Record; const nachlaessig = parseProject({ ...roh, settings: { ...einstellungen, serviceLevelScale: ' hcm ' }, }); expect(nachlaessig.project.settings.serviceLevelScale).toBe('HCM'); const unsinn = parseProject({ ...roh, settings: { ...einstellungen, serviceLevelScale: 'HBSX' }, }); expect(unsinn.project.settings.serviceLevelScale).toBe('HBS'); expect(unsinn.issues.some((i) => i.message.includes('Bewertungsverfahren'))).toBe(true); }); it('entfernt gefaehrliche Schluessel aus fremden Daten', () => { const hostile = JSON.parse( '{"a":1,"__proto__":{"polluted":true},"nested":{"constructor":{"x":1}}}', ); const clean = sanitizeObject(hostile) as Record; expect(Object.keys(clean)).toEqual(['a', 'nested']); expect(({} as Record)['polluted']).toBeUndefined(); }); it('stuerzt bei voellig falschen Datentypen nicht ab', () => { const { project, issues } = parseProject({ signalGroups: 'keine Liste', phases: 42, conflicts: null, program: { phaseOrder: 'p1' }, }); expect(project.signalGroups).toEqual([]); expect(project.phases).toEqual([]); expect(Array.isArray(issues)).toBe(true); }); it('entfernt Konflikte, die auf nicht vorhandene Signalgruppen verweisen', () => { const { project, issues } = parseProject({ signalGroups: [{ id: 'a', name: 'K1', mode: 'kfz' }], conflicts: [ { fromId: 'a', toId: 'unbekannt', clearingDistance: 20, enteringDistance: 10 }, { fromId: 'a', toId: 'a', clearingDistance: 20, enteringDistance: 10 }, ], }); expect(project.conflicts).toHaveLength(0); expect(issues.filter((i) => i.path.startsWith('conflicts'))).toHaveLength(2); }); it('vergibt doppelte Kennungen neu, statt Verweise mehrdeutig zu lassen', () => { const { project, issues } = parseProject({ signalGroups: [ { id: 'gleich', name: 'K1', mode: 'kfz' }, { id: 'gleich', name: 'K2', mode: 'kfz' }, ], }); expect(project.signalGroups).toHaveLength(2); expect(project.signalGroups[0]!.id).not.toBe(project.signalGroups[1]!.id); expect(issues.some((i) => i.severity === 'fehler')).toBe(true); }); it('ergaenzt fehlende Phasen in der Reihenfolge und entfernt ungueltige', () => { const { project } = parseProject({ phases: [ { id: 'p1', name: 'Phase 1', signalGroupIds: [] }, { id: 'p2', name: 'Phase 2', signalGroupIds: [] }, ], program: { phaseOrder: ['p2', 'gibtsnicht'] }, }); expect(project.program.phaseOrder).toEqual(['p2', 'p1']); }); it('akzeptiert deutsches Dezimalkomma in Zahlenfeldern', () => { const { project } = parseProject({ signalGroups: [{ id: 'a', name: 'K1', mode: 'kfz' }], conflicts: [{ fromId: 'a', toId: 'b' }], intersection: { vZul: '50' }, demands: [{ signalGroupId: 'a', volume: '620', heavyVehicleShare: '0,05' }], }); expect(project.intersection.vZul).toBe(50); expect(project.demands[0]!.volume).toBe(620); expect(project.demands[0]!.heavyVehicleShare).toBeCloseTo(0.05, 6); }); });