import { describe, expect, it } from 'vitest'; import { isLegacyProject, loadProject } from '@/domain/model/migrate'; import { createStandardIntersectionProject } from '@/domain/model/factory'; const FIXED_DATE = new Date('2026-01-01T00:00:00Z'); /** Ein Projekt im Format der Version 4.x, wie es der Altbestand gespeichert hat. */ const legacyProject = { metadata: { id: 'LSA-1731600000000-abc123def', version: '4.3.3', createdAt: '2025-11-14T10:00:00.000Z', modifiedAt: '2025-11-15T12:30:00.000Z', }, projektdaten: { projektNummer: 'VP-2025-018', projektBezeichnung: 'Hauptstrasse / Bahnhofstrasse', kreuzungstyp: 'Vierarmiger Knotenpunkt', steuerungstyp: 'Festzeitsteuerung', strasse: 'Hauptstrasse', plz: '12345', ort: 'Musterstadt', auftraggeber: 'Stadt Musterstadt', bearbeiter: 'O. Willerding', bemerkungen: 'Bestandsaufnahme 2025', anordnungsnummer: 'A-2025-77', }, signalgruppen: [ { id: 'SG-1', nummer: 'K1', typ: 'kfz', richtung: 'geradeaus', minGruen: 5, maxGruen: 40, gelb: 3, rotGelb: 2, geschwindigkeit: 50, }, { id: 'SG-2', nummer: 'K2', typ: 'kfz', richtung: 'links', minGruen: 5, maxGruen: 30, gelb: 3, rotGelb: 2, geschwindigkeit: 50, }, { id: 'SG-3', nummer: 'F1', typ: 'fussgaenger', richtung: '', minGruen: 6, maxGruen: 20, gelb: 3, rotGelb: 2, geschwindigkeit: 50, }, ], sperrmatrix: { matrix: [ [false, false, true], [false, false, true], [true, true, false], ], autoDetect: false, }, zwischenzeiten: { parameter: { berechnungsmethode: 'rilsa2015', vRaeumKfz: 50, vRaeumRad: 20, vRaeumFuss: 1.2, vEinfahrKfz: 40, raeumweg: 24, einfahrweg: 9, }, matrix: [ [0, 0, 4], [0, 0, 4], [7, 7, 0], ], }, phasen: [ { id: 'PH-1', nummer: 1, bezeichnung: 'Phase 1', signalgruppen: ['SG-1', 'SG-2'], dauer: 35 }, { id: 'PH-2', nummer: 2, bezeichnung: 'Phase 2', signalgruppen: ['SG-3'], dauer: 15 }, ], signalplan: { umlaufzeit: 70, signalbilder: [] }, umlaufzeit: { verfahren: 'webster', auslastung: 85, verlustzeiten: 12, eingabe: 70 }, lageplan: null, pruefungen: {}, }; describe('Uebernahme von Altprojekten', () => { it('erkennt das Altformat', () => { expect(isLegacyProject(legacyProject)).toBe(true); expect(isLegacyProject(createStandardIntersectionProject('x', FIXED_DATE))).toBe(false); }); it('uebernimmt Projektdaten, Signalgruppen und Phasen', () => { const result = loadProject(legacyProject, FIXED_DATE); expect(result.migrated).toBe(true); expect(result.sourceVersion).toBe('4.3.3'); expect(result.project.meta.name).toBe('Hauptstrasse / Bahnhofstrasse'); expect(result.project.meta.projectNumber).toBe('VP-2025-018'); expect(result.project.meta.client).toBe('Stadt Musterstadt'); expect(result.project.meta.location).toBe('Hauptstrasse, 12345 Musterstadt'); expect(result.project.meta.planner).toBe('O. Willerding'); expect(result.project.meta.description).toContain('Anordnungsnummer: A-2025-77'); expect(result.project.signalGroups.map((g) => g.name)).toEqual(['K1', 'K2', 'F1']); expect(result.project.signalGroups[1]!.movement).toBe('links'); expect(result.project.signalGroups[2]!.mode).toBe('fuss'); expect(result.project.signalGroups[2]!.movement).toBe('querung'); expect(result.project.phases.map((p) => p.name)).toEqual(['Phase 1', 'Phase 2']); expect(result.project.phases[0]!.manualGreen).toBe(35); }); it('bildet die Phasenzuordnung ueber die neuen Kennungen ab', () => { const result = loadProject(legacyProject, FIXED_DATE); const k1 = result.project.signalGroups.find((g) => g.name === 'K1')!; const k2 = result.project.signalGroups.find((g) => g.name === 'K2')!; const f1 = result.project.signalGroups.find((g) => g.name === 'F1')!; expect(result.project.phases[0]!.signalGroupIds).toEqual([k1.id, k2.id]); expect(result.project.phases[1]!.signalGroupIds).toEqual([f1.id]); }); it('leitet die Konfliktbeziehungen aus der Sperrmatrix ab', () => { const result = loadProject(legacyProject, FIXED_DATE); const names = result.project.conflicts.map((c) => { const from = result.project.signalGroups.find((g) => g.id === c.fromId)!.name; const to = result.project.signalGroups.find((g) => g.id === c.toId)!.name; return `${from}->${to}`; }); expect(names).toContain('K1->F1'); expect(names).toContain('F1->K1'); expect(names).toContain('K2->F1'); expect(names).not.toContain('K1->K2'); }); it('uebernimmt die alten Zwischenzeiten NICHT, sondern rechnet neu', () => { const result = loadProject(legacyProject, FIXED_DATE); for (const conflict of result.project.conflicts) { expect(conflict.manualIntergreen).toBeNull(); expect(conflict.clearingDistance).toBe(24); expect(conflict.enteringDistance).toBe(9); } expect(result.intergreenComparison.length).toBeGreaterThan(0); expect(result.issues.some((i) => i.path === 'zwischenzeiten')).toBe(true); expect(result.issues.some((i) => i.path === 'conflicts')).toBe(true); }); it('uebernimmt das Verfahren der Umlaufzeitermittlung', () => { const result = loadProject(legacyProject, FIXED_DATE); expect(result.project.program.method).toBe('webster'); expect(result.project.program.phaseOrder).toHaveLength(2); }); it('uebernimmt eine feste Umlaufzeit, wenn sie im Altprojekt gewaehlt war', () => { const result = loadProject( { ...legacyProject, umlaufzeit: { ...legacyProject.umlaufzeit, verfahren: 'manuell' } }, FIXED_DATE, ); expect(result.project.program.method).toBe('manuell'); expect(result.project.program.manualCycleTime).toBe(70); }); it('kommt mit einer luecken- und fehlerhaften Altdatei zurecht', () => { const result = loadProject( { projektdaten: {}, signalgruppen: null, phasen: 'kaputt' }, FIXED_DATE, ); expect(result.migrated).toBe(true); expect(result.project.signalGroups).toEqual([]); expect(result.project.phases).toEqual([]); }); it('warnt bei einer Datei aus einer neueren Programmversion', () => { const result = loadProject({ schemaVersion: 99, signalGroups: [] }, FIXED_DATE); expect(result.migrated).toBe(false); expect(result.issues.some((i) => i.path === 'schemaVersion')).toBe(true); }); });