aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java5
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java277
-rw-r--r--src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java339
3 files changed, 616 insertions, 5 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 483f06a..d4b0b17 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -89,9 +89,12 @@ class EndToEndTest {
assertThat(parseErgebnis.befehle().size()).isGreaterThanOrEqualTo(10);
var typisiert =
parseErgebnis.befehle().stream().filter(b -> !(b instanceof UnbekannterBefehl)).count();
- assertThat(typisiert).isGreaterThan(parseErgebnis.befehle().size() / 2);
+ assertThat(typisiert).isEqualTo(parseErgebnis.befehle().size());
+ // Referenzfall des Ausbaus: alle Befehle (auch die Anhang-Änderungen) werden angewandt.
var anwendung = BefehlAnwender.anwenden(gesetz, parseErgebnis.befehle());
+ assertThat(anwendung.anzahlManuell()).isZero();
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(parseErgebnis.befehle().size());
var synopse = SynopseBuilder.baue(gesetz, anwendung, parseErgebnis.warnungen(), false);
assertThat(HtmlRenderer.rendere(synopse, "E2E-Test")).contains("Neue Fassung");
}
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
index bbee916..f8cfe82 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
@@ -11,6 +11,7 @@ import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Neufassung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Sammelbefehl;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Streichung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.StrukturEinfuegung;
+import eu.mulk.aendggner.aenderung.Aenderungsbefehl.StrukturErsetzung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Umnummerierung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WoerterEinfuegung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.WortAnker;
@@ -222,8 +223,12 @@ class BefehlErkennerTest {
+ " „§ 28a Besondere Schutzmaßnahmen“.",
Stelle.LEER);
- assertThat(befehl).containsInstanceOf(WoerterEinfuegung.class);
- assertThat(befehl.orElseThrow().stelle().betrifftInhaltsuebersicht()).isTrue();
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.stelle().betrifftInhaltsuebersicht()).isTrue();
+ assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 28");
+ assertThat(einfuegung.vorher()).isFalse();
+ assertThat(einfuegung.text()).isEqualTo("§ 28a Besondere Schutzmaßnahmen");
}
@Test
@@ -344,8 +349,11 @@ class BefehlErkennerTest {
"Nach der Angabe zu § 9 wird folgende Angabe eingefügt: „§ 9a" + " Länderregelung“.",
kontext);
- assertThat(befehl).containsInstanceOf(WoerterEinfuegung.class);
- assertThat(befehl.orElseThrow().stelle().betrifftInhaltsuebersicht()).isTrue();
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.stelle().betrifftInhaltsuebersicht()).isTrue();
+ assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 9");
+ assertThat(einfuegung.text()).isEqualTo("§ 9a Länderregelung");
}
@Test
@@ -757,4 +765,265 @@ class BefehlErkennerTest {
assertThat(teile).extracting(t -> ((Ersetzung) t).alt()).containsExactly("a", "c");
assertThat(teile).extracting(t -> ((Ersetzung) t).neu()).containsExactly("b", "d");
}
+ // --- Welle-4-Formen --------------------------------------------------------------------------
+
+ @Test
+ void erkenntAngabenBereichsErsetzungInDerInhaltsuebersicht() {
+ var kontext = new Stelle(List.of(new Stelle.Inhaltsuebersicht()));
+ var befehl =
+ erkenne(
+ "Die Angaben zu den §§ 34 bis § 45 werden durch die folgenden Angaben ersetzt:"
+ + " „§ 34 (weggefallen) § 35 (weggefallen)“.",
+ kontext);
+
+ assertThat(befehl).containsInstanceOf(StrukturErsetzung.class);
+ var ersetzung = (StrukturErsetzung) befehl.orElseThrow();
+ assertThat(ersetzung.stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 34");
+ assertThat(ersetzung.bisStelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 45");
+ }
+
+ @Test
+ void erkenntAngabeStreichungInDerInhaltsuebersicht() {
+ var befehl =
+ erkenne("In der Inhaltsübersicht wird die Angabe zu § 5a gestrichen.", Stelle.LEER);
+
+ assertThat(befehl).containsInstanceOf(Aufhebung.class);
+ assertThat(befehl.orElseThrow().stelle().anzeigeText()).isEqualTo("Inhaltsübersicht § 5a");
+ }
+
+ @Test
+ void erkenntAngabeMitOrdinalerGliederung() {
+ var kontext = new Stelle(List.of(new Stelle.Inhaltsuebersicht()));
+ var befehl =
+ erkenne(
+ "Die Angabe zum zweiten Abschnitt wird wie folgt gefasst: „2. Abschnitt"
+ + " Koordinierung und epidemische Lage von nationaler Tragweite“.",
+ kontext);
+
+ assertThat(befehl).containsInstanceOf(Neufassung.class);
+ assertThat(befehl.orElseThrow().stelle().anzeigeText()).isEqualTo("Inhaltsübersicht Abschnitt 2");
+ }
+
+ @Test
+ void erkenntVoranstellung() {
+ var befehl =
+ erkenne(
+ "Der Nummer 1 wird folgende Nummer 1 vorangestellt: „1. eine Umwälzpumpe nach § 64"
+ + " Absatz 2 auszutauschen ist,“.",
+ new Stelle(List.of(new Stelle.Paragraph("64"))));
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.vorher()).isTrue();
+ assertThat(einfuegung.ebene()).isEqualTo(Ebene.NUMMER);
+ assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("§ 64 Nummer 1");
+ }
+
+ @Test
+ void erkenntVoranstellungOhneAnker() {
+ var befehl =
+ erkenne(
+ "Folgende Nummer 1 wird vorangestellt: „1. einer vollziehbaren Anordnung nach § 5"
+ + " Absatz 2 Nummer 1 oder 2 zuwiderhandelt,“.",
+ Stelle.LEER);
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ assertThat(((StrukturEinfuegung) befehl.orElseThrow()).vorher()).isTrue();
+ }
+
+ @Test
+ void erkenntNummernBereichsErsetzungMitKardinalitaetswechsel() {
+ var befehl =
+ erkenne(
+ "Satz 1 Nummer 3 bis 6 wird durch die folgenden Nummern 3 und 4 ersetzt: 3. „ bei"
+ + " Wärmeverteilungs- und Warmwasserleitungen die Wärmeabgabe begrenzt ist und"
+ + " 4. die Anforderungen eingehalten werden.“.",
+ new Stelle(List.of(new Stelle.Paragraph("61"))));
+
+ assertThat(befehl).containsInstanceOf(StrukturErsetzung.class);
+ var ersetzung = (StrukturErsetzung) befehl.orElseThrow();
+ assertThat(ersetzung.ebene()).isEqualTo(Ebene.NUMMER);
+ assertThat(ersetzung.stelle().anzeigeText()).isEqualTo("§ 61 Satz 1 Nummer 3");
+ assertThat(ersetzung.bisStelle().anzeigeText()).isEqualTo("§ 61 Satz 1 Nummer 6");
+ assertThat(ersetzung.text()).startsWith("3. ");
+ }
+
+ @Test
+ void erkenntKommaMehrfachErsetzung() {
+ var befehl =
+ erkenne(
+ "In Satz 1 wird die Angabe „2025“ durch die Angabe „2030“, die Angabe „§ 50 Absatz 1"
+ + " in Verbindung mit § 48“ durch die Angabe „§ 38 Absatz 1 in Verbindung mit"
+ + " § 36“ und die Angabe „§ 50 Absatz 1“ durch die Angabe „§ 38 Absatz 1“"
+ + " ersetzt.",
+ new Stelle(List.of(new Stelle.Paragraph("109"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile).hasSize(3).allMatch(t -> t instanceof Ersetzung);
+ assertThat(((Ersetzung) teile.get(0)).alt()).isEqualTo("2025");
+ assertThat(((Ersetzung) teile.get(0)).neu()).isEqualTo("2030");
+ }
+
+ @Test
+ void erkenntMehrfachEinfuegepaare() {
+ var befehl =
+ erkenne(
+ "In Nummer 24 werden nach den Wörtern „einer Rechtsverordnung nach“ die Wörter"
+ + " „§ 5 Absatz 2 Nummer 4,“ und nach der Angabe „§ 23 Absatz 8 Satz 1“ ein"
+ + " Komma und die Angabe „§ 32 Satz 1“ eingefügt.",
+ new Stelle(List.of(new Stelle.Paragraph("73"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile).hasSize(2).allMatch(t -> t instanceof WoerterEinfuegung);
+ assertThat(((WoerterEinfuegung) teile.get(1)).woerter()).isEqualTo(", § 32 Satz 1");
+ }
+
+ @Test
+ void erkenntKommaUndWoerterVorDemPunktAmEnde() {
+ var befehl =
+ erkenne(
+ "In Absatz 3 Satz 1 wird vor dem Punkt am Ende ein Komma und werden die Wörter"
+ + " „oder wenn der Nachweis erfolgt ist“ eingefügt.",
+ new Stelle(List.of(new Stelle.Paragraph("8"))));
+
+ assertThat(befehl).containsInstanceOf(Ersetzung.class);
+ var ersetzung = (Ersetzung) befehl.orElseThrow();
+ assertThat(ersetzung.alt()).isEqualTo(".");
+ assertThat(ersetzung.neu()).isEqualTo(", oder wenn der Nachweis erfolgt ist.");
+ assertThat(ersetzung.amEnde()).isTrue();
+ }
+
+ @Test
+ void erkenntKoordinierteUmnummerierung() {
+ var befehl =
+ erkenne(
+ "Die bisherigen Absätze 6 und 7 werden die Absätze 1 und 2.",
+ new Stelle(List.of(new Stelle.Paragraph("5"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile).hasSize(2).allMatch(t -> t instanceof Umnummerierung);
+ // Absteigend: 7 → 2 zuerst, damit die Labels nicht kollidieren.
+ assertThat(teile.get(0).stelle().anzeigeText()).isEqualTo("§ 5 Absatz 7");
+ }
+
+ @Test
+ void erkenntVerbundMitUmnummerierungUndRueckbezug() {
+ var befehl =
+ erkenne(
+ "Die bisherige Nummer 1 wird Nummer 2 und in ihr werden die Wörter „§ 72 Absatz 1"
+ + " bis 3,“ durch die Wörter „Ablauf der Übergangsfristen,“ ersetzt.",
+ new Stelle(List.of(new Stelle.Paragraph("96"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile).hasSize(2);
+ assertThat(teile.get(0)).isInstanceOf(Umnummerierung.class);
+ assertThat(teile.get(1)).isInstanceOf(Ersetzung.class);
+ assertThat(teile.get(1).stelle().anzeigeText()).isEqualTo("§ 96 Nummer 2");
+ }
+
+ @Test
+ void erkenntVerbundMitUmnummerierungUndNeufassung() {
+ var befehl =
+ erkenne(
+ "Die bisherige Nummer 3 wird Nummer 4 und wird wie folgt gefasst: 4. „ die"
+ + " Abrechnungen und Bestätigungen nach § 96 Absatz 5 vorliegen.“",
+ new Stelle(List.of(new Stelle.Paragraph("96"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile.get(0)).isInstanceOf(Umnummerierung.class);
+ assertThat(teile.get(1)).isInstanceOf(Neufassung.class);
+ assertThat(((Neufassung) teile.get(1)).neuerText()).startsWith("4. ");
+ }
+
+ @Test
+ void erkenntParagraphAnfuegungNachAnker() {
+ var befehl =
+ erkenne(
+ "Nach § 114 wird folgender § 115 angefügt: „§ 115 Übergangsvorschriften für"
+ + " Geldbußen Text.“",
+ Stelle.LEER);
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.ebene()).isEqualTo(Ebene.PARAGRAPH);
+ assertThat(einfuegung.bezeichnung()).isEqualTo("115");
+ }
+
+ @Test
+ void erkenntAnkerloseAbsatzEinfuegung() {
+ var befehl =
+ erkenne(
+ "Folgender Absatz 2 wird eingefügt: „(2) In einem Wohngebäude gilt dies nicht.“",
+ new Stelle(List.of(new Stelle.Paragraph("72"))));
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.vorher()).isFalse();
+ assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("§ 72 Absatz 1");
+ }
+
+ @Test
+ void erkenntNummernBlockAnfuegung() {
+ var befehl =
+ erkenne(
+ "Die folgenden Nummern 9 bis 11 werden angefügt: 9. „ Durchführung hydraulischer"
+ + " Abgleiche, 10. Einbau von Messausstattungen, 11. Sonstiges.“",
+ new Stelle(List.of(new Stelle.Paragraph("60"))));
+
+ assertThat(befehl).containsInstanceOf(Anfuegung.class);
+ var anfuegung = (Anfuegung) befehl.orElseThrow();
+ assertThat(anfuegung.ebene()).isEqualTo(Ebene.NUMMER);
+ assertThat(anfuegung.text()).startsWith("9. ");
+ }
+
+ @Test
+ void erkenntPunktErsetzungDurchFolgendeWoerter() {
+ var befehl =
+ erkenne(
+ "In Absatz 1 Satz 2 wird der Punkt am Ende durch folgende Wörter ersetzt: „, das"
+ + " heißt, wenn die Investitionen unangemessen sind.“",
+ new Stelle(List.of(new Stelle.Paragraph("102"))));
+
+ assertThat(befehl).containsInstanceOf(Ersetzung.class);
+ var ersetzung = (Ersetzung) befehl.orElseThrow();
+ assertThat(ersetzung.alt()).isEqualTo(".");
+ assertThat(ersetzung.amEnde()).isTrue();
+ }
+
+ @Test
+ void erkenntWortVoranstellungImVerbund() {
+ var befehl =
+ erkenne(
+ "In Buchstabe a werden die Wörter „des § 10“ durch die Wörter „der §§ 71 bis 71h“"
+ + " ersetzt, wird dem Wort „Anforderungen“ das Wort „dortigen“ vorangestellt und"
+ + " werden die Wörter „nach den §§ 35 bis 41“ gestrichen.",
+ new Stelle(List.of(new Stelle.Paragraph("105"))));
+
+ assertThat(befehl).containsInstanceOf(Sammelbefehl.class);
+ var teile = ((Sammelbefehl) befehl.orElseThrow()).teilbefehle();
+ assertThat(teile).hasSize(3);
+ assertThat(teile.get(1)).isInstanceOf(WoerterEinfuegung.class);
+ assertThat(((WoerterEinfuegung) teile.get(1)).woerter()).isEqualTo("dortigen");
+ assertThat(teile.get(2)).isInstanceOf(Streichung.class);
+ }
+
+ @Test
+ void erkenntErsetzungMitPositionsanker() {
+ var befehl =
+ erkenne(
+ "In Nummer 2 werden nach den Wörtern „jeweils auch in Verbindung mit“ die Wörter"
+ + " „einer Rechtsverordnung nach § 14,“ durch die Wörter „§ 14 Absatz 8,“"
+ + " ersetzt.",
+ new Stelle(List.of(new Stelle.Paragraph("73"))));
+
+ assertThat(befehl).containsInstanceOf(Ersetzung.class);
+ var ersetzung = (Ersetzung) befehl.orElseThrow();
+ assertThat(ersetzung.alt()).isEqualTo("einer Rechtsverordnung nach § 14,");
+ assertThat(ersetzung.neu()).isEqualTo("§ 14 Absatz 8,");
+ }
}
diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
index 9b9c18d..aa658dd 100644
--- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
+++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
@@ -6,6 +6,7 @@ import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Anfuegung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Aufhebung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Ebene;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Ersetzung;
+import eu.mulk.aendggner.aenderung.Aenderungsbefehl.GliederungsUeberschriften;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Neufassung;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Sammelbefehl;
import eu.mulk.aendggner.aenderung.Aenderungsbefehl.Streichung;
@@ -642,6 +643,344 @@ class BefehlAnwenderTest {
assertThat(text).contains("1. (weggefallen)").contains("2. (weggefallen)").contains("3. (weggefallen)");
}
+ // --- Anhang/Anlage als Norm-Ziel -----------------------------------------------------------
+
+ /** Ein Gesetz mit Anhang-Norm nach dem Muster des UWG (mehrere Absätze, Nummern mit Kindern). */
+ private static Gesetz gesetzMitAnhang() {
+ return new Gesetz(
+ "TestG",
+ null,
+ null,
+ List.of(
+ new Norm(
+ "§ 1",
+ "Zweck",
+ null,
+ List.of(new Absatz("1", "Es gilt der Anhang.")),
+ false),
+ new Norm(
+ "Anhang",
+ "(zu § 1)",
+ null,
+ List.of(
+ new Absatz(null, "Folgende Handlungen sind stets unzulässig:"),
+ new Absatz(
+ null,
+ " 1. die erste Handlung;\n"
+ + " 2. die zweite Handlung,\n"
+ + " a) wenn sie morgens geschieht, oder\n"
+ + " b) wenn sie abends geschieht;\n"
+ + " 3. die dritte Handlung;"),
+ new Absatz(
+ null,
+ " 31. die aggressive Handlung,\n"
+ + " a) wenn sie laut geschieht, oder\n"
+ + " b) wenn sie leise gemacht wird.\n"
+ + " 32. die letzte Handlung;")),
+ false)));
+ }
+
+ private static Stelle anhangStelle(Stelle.Komponente... feinere) {
+ var komponenten = new java.util.ArrayList<Stelle.Komponente>();
+ komponenten.add(new Stelle.Gliederungseinheit("Anhang", ""));
+ komponenten.addAll(List.of(feinere));
+ return new Stelle(komponenten);
+ }
+
+ @Test
+ void fuegtNummerImAnhangNachNummerMitKindernEin() {
+ // „Nach Nummer 2 wird die folgende Nummer 2a eingefügt“ — Nummer 2 hat Buchstaben a)/b);
+ // die neue Nummer muss hinter deren Block landen, nicht zwischen Nummer und Buchstaben.
+ var befehl =
+ new StrukturEinfuegung(
+ anhangStelle(new Stelle.NummerNr("2")),
+ false,
+ Ebene.NUMMER,
+ "2a",
+ "2a. die eingeschobene Handlung;",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitAnhang(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Anhang", 1))
+ .isEqualTo(
+ " 1. die erste Handlung;\n"
+ + " 2. die zweite Handlung,\n"
+ + " a) wenn sie morgens geschieht, oder\n"
+ + " b) wenn sie abends geschieht;\n"
+ + " 2a. die eingeschobene Handlung;\n"
+ + " 3. die dritte Handlung;");
+ }
+
+ @Test
+ void fuegtNummernBlockImAnhangEin() {
+ // Mehrere Einheiten in einem Einfügeblock bleiben eigene Zeilen.
+ var befehl =
+ new StrukturEinfuegung(
+ anhangStelle(new Stelle.NummerNr("3")),
+ false,
+ Ebene.NUMMER,
+ null,
+ "3a. die vierte Handlung;\n3b. die fünfte Handlung;",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitAnhang(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Anhang", 1))
+ .endsWith(
+ " 3. die dritte Handlung;\n"
+ + " 3a. die vierte Handlung;\n"
+ + " 3b. die fünfte Handlung;");
+ }
+
+ @Test
+ void loestNummerBuchstabeKetteImAnhangAuf() {
+ // „b)“ existiert in Nummer 2 und Nummer 31 — die Kette „Nummer 31 Buchstabe b“ ist trotzdem
+ // eindeutig, weil der Buchstabe im Block der Nummer 31 gesucht wird.
+ var befehl =
+ new Ersetzung(
+ anhangStelle(new Stelle.NummerNr("31"), new Stelle.BuchstabeNr("b")),
+ "gemacht wird.",
+ "gemacht wird;",
+ false,
+ false,
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitAnhang(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Anhang", 2))
+ .contains("wenn sie leise gemacht wird;")
+ .contains(" 32. die letzte Handlung;");
+ // Nummer 2 Buchstabe b bleibt unangetastet.
+ assertThat(absatzText(ergebnis.neu(), "Anhang", 1)).contains("wenn sie abends geschieht;");
+ }
+
+ @Test
+ void streichtWoerterInDerUeberschriftEinerNorm() {
+ var befehl =
+ new Streichung(
+ stelle(new Stelle.Paragraph("3"), new Stelle.Ueberschrift()),
+ "Schlussvorschriften",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(ergebnis.neu().norm("§ 3").orElseThrow().titel()).isEmpty();
+ }
+
+ @Test
+ void meldetMehrdeutigeNummerOhneAbsatzangabeImAnhang() {
+ // Gäbe es dieselbe Nummer in mehreren Absätzen, bliebe der Befehl manuell.
+ var gesetz =
+ new Gesetz(
+ "TestG",
+ null,
+ null,
+ List.of(
+ new Norm(
+ "Anhang",
+ null,
+ null,
+ List.of(
+ new Absatz(null, " 1. erstens;"),
+ new Absatz(null, " 1. nochmal erstens;")),
+ false)));
+ var befehl =
+ new Ersetzung(
+ anhangStelle(new Stelle.NummerNr("1")), "erstens", "zuerst", false, false, PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz, List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.MANUELL_PRUEFEN);
+ assertThat(ergebnis.protokoll().get(0).begruendung()).contains("nicht eindeutig");
+ }
+
+ // --- Inhaltsübersicht ------------------------------------------------------------------------
+
+ private static Gesetz gesetzMitInhaltsuebersicht() {
+ return new Gesetz(
+ "TestG",
+ null,
+ null,
+ List.of(
+ new Norm(
+ "Inhaltsübersicht",
+ null,
+ null,
+ List.of(
+ new Absatz(
+ null,
+ "Teil 1 | Allgemeines\n"
+ + "§ 1 | Zweck\n"
+ + "§ 2 | Begriffe\n"
+ + "Teil 2 | Verfahren\n"
+ + "Abschnitt 1 | Grundsätze\n"
+ + "§ 3 | Ablauf\n"
+ + "§ 4 | Fristen\n"
+ + "§ 5 | Schluss")),
+ false),
+ new Norm("§ 1", "Zweck", null, List.of(new Absatz(null, "Text.")), false)));
+ }
+
+ private static Stelle iuStelle(Stelle.Komponente... feinere) {
+ var komponenten = new java.util.ArrayList<Stelle.Komponente>();
+ komponenten.add(new Stelle.Inhaltsuebersicht());
+ komponenten.addAll(List.of(feinere));
+ return new Stelle(komponenten);
+ }
+
+ @Test
+ void fasstAngabeInDerInhaltsuebersichtNeu() {
+ var befehl =
+ new Neufassung(iuStelle(new Stelle.Paragraph("2")), "§ 2 Begriffsbestimmungen", PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0))
+ .contains("§ 2 | Begriffsbestimmungen")
+ .doesNotContain("§ 2 | Begriffe\n");
+ }
+
+ @Test
+ void fuegtAngabeInDerInhaltsuebersichtEin() {
+ var befehl =
+ new StrukturEinfuegung(
+ iuStelle(new Stelle.Paragraph("2")),
+ false,
+ Ebene.PARAGRAPH,
+ null,
+ "§ 2a Anwendungsbereich",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0))
+ .contains("§ 2 | Begriffe\n§ 2a | Anwendungsbereich\nTeil 2 | Verfahren");
+ }
+
+ @Test
+ void ersetztAngabenBereichInDerInhaltsuebersicht() {
+ // „Die Angaben zu den §§ 3 bis 4 werden durch die folgenden Angaben ersetzt: …“
+ var befehl =
+ new StrukturErsetzung(
+ iuStelle(new Stelle.Paragraph("3")),
+ iuStelle(new Stelle.Paragraph("4")),
+ Ebene.PARAGRAPH,
+ "§ 3 (weggefallen) § 4 (weggefallen)",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0))
+ .contains("§ 3 | (weggefallen)\n§ 4 | (weggefallen)")
+ .doesNotContain("Ablauf");
+ }
+
+ @Test
+ void streichtAngabeUndLoestGliederungsKetteAuf() {
+ // „Die Angabe zu Teil 2 Abschnitt 1 wird gestrichen.“ — Kette grenzt das Fenster ein.
+ var befehl =
+ new Aufhebung(
+ iuStelle(
+ new Stelle.Gliederungseinheit("Teil", "2"),
+ new Stelle.Gliederungseinheit("Abschnitt", "1")),
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitInhaltsuebersicht(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(absatzText(ergebnis.neu(), "Inhaltsübersicht", 0))
+ .doesNotContain("Abschnitt 1 | Grundsätze")
+ .contains("Teil 2 | Verfahren\n§ 3 | Ablauf");
+ }
+
+ // --- Gliederungs-Überschriften ---------------------------------------------------------------
+
+ private static Gesetz gesetzMitGliederungen() {
+ var teil1 = new Gliederung("010", "Teil 1", "Allgemeines");
+ var teil2 = new Gliederung("020", "Teil 2", "Anforderungen");
+ return new Gesetz(
+ "TestG",
+ null,
+ null,
+ List.of(
+ new Norm("§ 1", "Zweck", teil1, List.of(new Absatz(null, "Eins.")), false),
+ new Norm("§ 2", "Begriffe", teil1, List.of(new Absatz(null, "Zwei.")), false),
+ new Norm("§ 3", "Pflichten", teil2, List.of(new Absatz(null, "Drei.")), false),
+ new Norm("§ 4", "Nachweise", teil2, List.of(new Absatz(null, "Vier.")), false)),
+ List.of(teil1, teil2));
+ }
+
+ @Test
+ void fuegtGliederungsUeberschriftenEin() {
+ // „Nach § 2 werden die folgenden Überschriften zu Teil 3 und zu Teil 3 Abschnitt 1
+ // eingefügt: „Teil 3 Modernisierung Abschnitt 1 Grundpflichten“.“
+ var befehl =
+ new GliederungsUeberschriften(
+ stelle(new Stelle.Paragraph("2")),
+ List.of(
+ new Stelle.Gliederungseinheit("Teil", "3"),
+ new Stelle.Gliederungseinheit("Abschnitt", "1")),
+ List.of(),
+ "Teil 3 Modernisierung Abschnitt 1 Grundpflichten",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitGliederungen(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(ergebnis.neu().gliederungen())
+ .extracting(Gliederung::bezeichnung)
+ .containsExactly("Teil 1", "Teil 3", "Abschnitt 1", "Teil 2");
+ var abschnitt1 = ergebnis.neu().gliederungen().get(2);
+ assertThat(abschnitt1.titel()).isEqualTo("Grundpflichten");
+ // §§ 3 und 4 (der zusammenhängende Block nach dem Anker) hängen jetzt unter Abschnitt 1.
+ assertThat(ergebnis.neu().norm("§ 3").orElseThrow().gliederung()).isEqualTo(abschnitt1);
+ assertThat(ergebnis.neu().norm("§ 4").orElseThrow().gliederung()).isEqualTo(abschnitt1);
+ assertThat(ergebnis.neu().norm("§ 2").orElseThrow().gliederung().bezeichnung())
+ .isEqualTo("Teil 1");
+ }
+
+ @Test
+ void ersetztGliederungsUeberschriften() {
+ // „Die bisherigen Überschriften zu Teil 2 werden durch die folgende Überschrift zu
+ // Abschnitt 2 ersetzt: „Abschnitt 2 Neue Anforderungen“.“
+ var befehl =
+ new GliederungsUeberschriften(
+ Stelle.LEER,
+ List.of(new Stelle.Gliederungseinheit("Abschnitt", "2")),
+ List.of(List.of(new Stelle.Gliederungseinheit("Teil", "2"))),
+ "Abschnitt 2 Neue Anforderungen",
+ PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetzMitGliederungen(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(ergebnis.neu().gliederungen())
+ .extracting(Gliederung::bezeichnung)
+ .containsExactly("Teil 1", "Abschnitt 2");
+ assertThat(ergebnis.neu().norm("§ 3").orElseThrow().gliederung().titel())
+ .isEqualTo("Neue Anforderungen");
+ }
+
+ @Test
+ void ersetztGesetzesUeberschrift() {
+ var befehl =
+ new Neufassung(
+ stelle(new Stelle.Ueberschrift()), "Gesetz zur gründlichen Erprobung", PROV);
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(befehl));
+
+ assertThat(ergebnis.protokoll().get(0).status()).isEqualTo(Status.ANGEWANDT);
+ assertThat(ergebnis.neu().langue()).isEqualTo("Gesetz zur gründlichen Erprobung");
+ }
+
private static String absatzText(Gesetz gesetz, String enbez, int index) {
return gesetz.norm(enbez).orElseThrow().absaetze().get(index).text();
}