lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests domain konfliktkennungSchreibstellen.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import { setConflict } from '@/app/actions'; |
| 3 | import { validateProject } from '@/domain/validation/engine'; |
| 4 | import { buildSignalPlan } from '@/domain/plan/signalPlan'; |
| 5 | import { createStandardIntersectionProject } from '@/domain/model/factory'; |
| 6 | import { parseProject } from '@/domain/model/schema'; |
| 7 | import type { Conflict, Project } from '@/domain/model/project'; |
| 8 | |
| 9 | /* |
| 10 | * Befund 2 - die verbliebenen Schreib- und Nachschlagstellen (Fassung 5.9.0). |
| 11 | * |
| 12 | * Der Befund war: Doppelte Konfliktkennungen wurden beim Einlesen nicht |
| 13 | * bemerkt, und das Aendern einer Beziehung aenderte stumm eine zweite mit. |
| 14 | * Die erste Behebung hat die Neuvergabe beim Einlesen eingebaut - damit sind Daten aus |
| 15 | * `parseProject` entschaerft. `setConflict` sucht seine Beziehung aber ueber |
| 16 | * das Paar (fromId, toId) und schreibt danach ueber die KENNUNG: zwei |
| 17 | * Schluessel fuer dieselbe Entscheidung. Trifft die Kennung ein zweites Mal zu, |
| 18 | * schreibt die Funktion an eine Beziehung, die sie nie nachgeschlagen hat. |
| 19 | */ |
| 20 | |
| 21 | const DATUM = new Date('2026-01-01T00:00:00Z'); |
| 22 | |
| 23 | /** |
| 24 | * Zwei Beziehungen mit derselben Kennung, aber verschiedenen Paaren. |
| 25 | * |
| 26 | * Von Hand zusammengesetzt: Genau so entsteht der Zustand auch im Betrieb - |
| 27 | * aus einer Projektdatei fremder Herkunft oder einer Umwandlung, die an |
| 28 | * `parseProject` vorbeifuehrt. |
| 29 | */ |
| 30 | function mitDoppelterKennung(): { projekt: Project; erste: Conflict; zweite: Conflict } { |
| 31 | const roh = createStandardIntersectionProject('Doppelte Kennung', DATUM); |
| 32 | const erste = roh.conflicts[0]!; |
| 33 | const zweite = { ...roh.conflicts[1]!, id: erste.id }; |
| 34 | return { |
| 35 | projekt: { ...roh, conflicts: [erste, zweite] }, |
| 36 | erste, |
| 37 | zweite, |
| 38 | }; |
| 39 | } |
| 40 | |
| 41 | describe('setConflict schreibt ueber das Paar, nicht ueber die Kennung', () => { |
| 42 | it('aendert nur die nachgeschlagene Beziehung', () => { |
| 43 | const { projekt, erste, zweite } = mitDoppelterKennung(); |
| 44 | |
| 45 | const nachher = setConflict(projekt, zweite.fromId, zweite.toId, { clearingDistance: 44 }); |
| 46 | |
| 47 | const geaendert = nachher.conflicts.find( |
| 48 | (c) => c.fromId === zweite.fromId && c.toId === zweite.toId, |
| 49 | ); |
| 50 | const unberuehrt = nachher.conflicts.find( |
| 51 | (c) => c.fromId === erste.fromId && c.toId === erste.toId, |
| 52 | ); |
| 53 | expect(geaendert?.clearingDistance).toBe(44); |
| 54 | // Bis hierher trug auch diese Beziehung 44 m - der Anwender hat einen Weg |
| 55 | // eingetragen und zwei Zwischenzeiten veraendert. |
| 56 | expect(unberuehrt?.clearingDistance).toBe(erste.clearingDistance); |
| 57 | }); |
| 58 | |
| 59 | it('zieht den Herkunftsnachweis nur an der nachgeschlagenen Beziehung nach', () => { |
| 60 | // Der Nachweis sagt aus, dass die Zeile im Ausdruck aus dem Lageplan |
| 61 | // stammt. An der mitgeaenderten Beziehung war er ebenfalls entwertet - |
| 62 | // ohne dass dort jemand etwas eingetragen haette. |
| 63 | const { projekt, erste, zweite } = mitDoppelterKennung(); |
| 64 | |
| 65 | const nachher = setConflict(projekt, zweite.fromId, zweite.toId, { clearingDistance: 44 }); |
| 66 | |
| 67 | const unberuehrt = nachher.conflicts.find( |
| 68 | (c) => c.fromId === erste.fromId && c.toId === erste.toId, |
| 69 | ); |
| 70 | expect(unberuehrt?.wegnachweis).toBe(erste.wegnachweis); |
| 71 | }); |
| 72 | |
| 73 | it('legt eine noch nicht erfasste Beziehung weiterhin an', () => { |
| 74 | const roh = createStandardIntersectionProject('Neu', DATUM); |
| 75 | const k1 = roh.signalGroups.find((g) => g.name === 'K1')!; |
| 76 | const k2 = roh.signalGroups.find((g) => g.name === 'K2')!; |
| 77 | |
| 78 | const nachher = setConflict(roh, k1.id, k2.id, { clearingDistance: 17 }); |
| 79 | |
| 80 | expect(nachher.conflicts).toHaveLength(roh.conflicts.length + 1); |
| 81 | const neu = nachher.conflicts.find((c) => c.fromId === k1.id && c.toId === k2.id); |
| 82 | expect(neu?.clearingDistance).toBe(17); |
| 83 | }); |
| 84 | }); |
| 85 | |
| 86 | /* |
| 87 | * Die zweite Stelle des Befundes: Der Sprungknopf des Pruefberichts loest die |
| 88 | * Notiz ueber `c.id === note.konfliktId` auf. Das traegt - aber nur, solange |
| 89 | * die Kennungen eindeutig sind. Genau das ist hier festgehalten: Faellt die |
| 90 | * Neuvergabe beim Einlesen weg, ist die Aufloesung ueber die Kennung nicht mehr |
| 91 | * eindeutig, und der Bericht schickt den Anwender an die falsche Beziehung. |
| 92 | */ |
| 93 | describe('Voraussetzung der Aufloesung ueber die Kennung', () => { |
| 94 | it('vergibt eine doppelte Konfliktkennung beim Einlesen neu', () => { |
| 95 | const roh = createStandardIntersectionProject('Einlesen', DATUM); |
| 96 | const rohDatei = JSON.parse(JSON.stringify(roh)) as Record<string, unknown>; |
| 97 | const konflikte = rohDatei['conflicts'] as Record<string, unknown>[]; |
| 98 | konflikte[1]!['id'] = konflikte[0]!['id']; |
| 99 | |
| 100 | const ergebnis = parseProject(rohDatei); |
| 101 | |
| 102 | expect(ergebnis.project).not.toBeNull(); |
| 103 | const kennungen = (ergebnis.project?.conflicts ?? []).map((c) => c.id); |
| 104 | expect(kennungen.length).toBeGreaterThan(1); |
| 105 | expect(new Set(kennungen).size).toBe(kennungen.length); |
| 106 | }); |
| 107 | |
| 108 | it('schickt den Sprungknopf an die Beziehung, aus der die Meldung stammt', () => { |
| 109 | // Eine von Hand auf 1 s gekuerzte Zwischenzeit K1 nach K3: Die Meldung |
| 110 | // traegt die Kennung dieser Beziehung, und das Sprungziel muss sie nennen. |
| 111 | const roh = createStandardIntersectionProject('Sprungziel', DATUM); |
| 112 | const k1 = roh.signalGroups.find((g) => g.name === 'K1')!; |
| 113 | const k3 = roh.signalGroups.find((g) => g.name === 'K3')!; |
| 114 | const projekt: Project = { |
| 115 | ...roh, |
| 116 | conflicts: roh.conflicts.map((c) => |
| 117 | c.fromId === k1.id && c.toId === k3.id ? { ...c, manualIntergreen: 1 } : c, |
| 118 | ), |
| 119 | }; |
| 120 | const gemeint = projekt.conflicts.find((c) => c.fromId === k1.id && c.toId === k3.id)!; |
| 121 | |
| 122 | const bericht = validateProject(projekt, buildSignalPlan(projekt), DATUM); |
| 123 | |
| 124 | const befund = bericht.findings.find( |
| 125 | (f) => f.rule === 'signalplan.zwischenzeit-vorgabe-unter-rechenwert', |
| 126 | ); |
| 127 | expect(befund?.target?.kind).toBe('konflikt'); |
| 128 | expect(befund?.target?.id).toBe(gemeint.id); |
| 129 | expect(befund?.target?.label).toBe('K1 nach K3'); |
| 130 | }); |
| 131 | }); |