import { describe, expect, it } from 'vitest'; import { validateProject } from '@/domain/validation/engine'; import { buildSignalPlan } from '@/domain/plan/signalPlan'; import { createPhase, createStandardIntersectionProject } from '@/domain/model/factory'; import type { Project } from '@/domain/model/project'; /* * Befund 16 (Fassung 5.11.0): Der Pruefbericht verschwieg ein zweites * feindliches Paar mit gleichzeitiger Freigabe. * * `checkSimultaneousGreen` und `pruefeFeindlichInPhase` teilen sich eine Menge * bereits gemeldeter Paare. Der Schluessel dieser Menge entstand aus * `[a, b].sort().join('|')` - genau der zusammengeklebte Schluessel, den * Befund 61 in `intergreenKey` und in `parseConflicts` beseitigt hat. * * Eine Signalgruppenkennung kommt ungeprueft aus der Projektdatei: Das * Einlesen beanstandet nur die leere und die doppelt vergebene Kennung, kein * Zeichenrepertoire. Die Paare ("a|b", "c") und ("a", "b|c") ergaben damit * beide den Schluessel "a|b|c"; das zuerst gemeldete Paar sperrte das zweite * ueber `if (reported.has(key)) continue;`. Weil beide Teilpruefungen dieselbe * Menge benutzen, stand das zweite Paar danach ueberhaupt nicht mehr im * Bericht - weder als 'phasen.feindlich-in-phase' noch als * 'signalplan.feindliche-freigabe' -, und die Fehlerzahl im Kopf des Berichts * nannte eine Ursache zu wenig. * * Das ist die gefaehrliche Richtung: Die unterdrueckte Meldung ist die, deren * Wortlaut lautet "Dieser Plan darf nicht geschaltet werden". * * GEGEN DEN ALTSTAND schlagen die beiden Faelle mit "|" in der Kennung fehl * (zwei statt drei Paare, eine Fehlerzahl zu wenig); die Gegenprobe mit * gewoehnlichen Kennungen besteht dort ebenso. */ const STICHTAG = new Date('2026-01-01T00:00:00Z'); /** * Vier Signalgruppen des Regelknotenpunkts in EINER Phase, die Kennungen frei * gewaehlt. Erhalten bleiben die drei Konfliktpaare K1/K3, K1/F2 und K3/F1 - * jedes in beiden Richtungen erfasst. */ function vierGruppenEinePhase(kennungen: Readonly>): Project { const roh = createStandardIntersectionProject('Paarschluessel', STICHTAG); const behalten = ['K1', 'K3', 'F1', 'F2']; const gruppen = roh.signalGroups.filter((g) => behalten.includes(g.name)); const neueId = new Map(gruppen.map((g) => [g.id, kennungen[g.name] ?? g.id])); const signalGroups = gruppen.map((g) => ({ ...g, id: neueId.get(g.id)! })); const conflicts = roh.conflicts .filter((c) => neueId.has(c.fromId) && neueId.has(c.toId)) .map((c) => ({ ...c, fromId: neueId.get(c.fromId)!, toId: neueId.get(c.toId)!, })); const phase = createPhase( 'Phase 1', signalGroups.map((g) => g.id), ); return { ...roh, signalGroups, conflicts, phases: [phase], program: { ...roh.program, phaseOrder: [phase.id] }, demands: [], }; } /** Titel aller Meldungen, die ein feindliches Paar mit Freigabe beanstanden. */ function feindlicheMeldungen(projekt: Project): string[] { const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG); return bericht.findings .filter( (f) => f.rule === 'phasen.feindlich-in-phase' || f.rule === 'signalplan.feindliche-freigabe', ) .map((f) => f.title); } /* * "a|b" (Kfz), "c" (Furt), "a" (Kfz), "b|c" (Furt): Die Paare ("a|b", "c") und * ("a", "b|c") fielen auf denselben zusammengeklebten Schluessel "a|b|c". */ const MIT_STRICH = { K1: 'a|b', F2: 'c', K3: 'a', F1: 'b|c' } as const; describe('Paarschluessel des Pruefberichts bei einer Kennung mit "|"', () => { it('meldet jedes feindliche Paar einer gemeinsamen Phase, auch bei Kennungen mit "|"', () => { const gewoehnlich = feindlicheMeldungen(vierGruppenEinePhase({})); // Voraussetzung des Aufbaus: drei feindliche Paare in einer Phase. expect(gewoehnlich).toHaveLength(3); const mitStrich = feindlicheMeldungen(vierGruppenEinePhase(MIT_STRICH)); expect( mitStrich, 'Das Paar ("a", "b|c") fiel auf denselben Schluessel wie ("a|b", "c") und fehlte ganz', ).toHaveLength(3); }); it('zaehlt die verschwiegene Ursache auch im Kopf des Berichts mit', () => { const projekt = vierGruppenEinePhase(MIT_STRICH); const bericht = validateProject(projekt, buildSignalPlan(projekt), STICHTAG); const gewoehnlichesProjekt = vierGruppenEinePhase({}); const vergleich = validateProject( gewoehnlichesProjekt, buildSignalPlan(gewoehnlichesProjekt), STICHTAG, ); // Dieselbe Anlage, nur andere Kennungen: Die Fehlerzahl darf nicht davon // abhaengen, welche Zeichen der Bearbeiter in einer Kennung verwendet. expect(bericht.errorCount).toBe(vergleich.errorCount); }); });