aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/eu
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-07-25 10:35:45 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-07-25 10:35:45 +0200
commit83ba9f0db3462a4c2d5122ba836f646b055156ca (patch)
tree82b786836898972cf1291f2b8739098f762e3baf /src/test/java/eu
parent35e90f4ab0f9b40d4752c9b8cba57c9452a8189f (diff)
Support the two Lower Saxony insertion forms; NEFG now applies fully
The NEFG acceptance case left two commands as "check manually". Both are now supported, so Lower Saxony applies 7 of 7 with no remainder. * Lower Saxony cites inserted paragraphs with a space before the sub number ("§ 2 a"); the rest of German legal usage writes "§ 2a", which is what the Stelle and level parsers expect. TextBereiniger now pulls the form together, leaving enumeration markers of the amendment act alone ("… nach § 8 c) In Absatz 2 …"). * "In Kapitel 4 wird nach § 12 der folgende neue § 13 angefügt" — the division only names the section the new paragraph lands in; the anchor governs the position, as in the plain form. The optional "neue" is now accepted in both. Applying that insertion needed one more thing: the paragraph it creates is only vacated by the *following* command ("Der bisherige § 13 wird § 14"), so in document order the two collided and the insertion was rejected. "Bisherig" denotes the state before the amendment, so the renumbering logically precedes the reoccupation and is now pulled ahead. The log still lists commands in the order of the amendment act. 226 tests green; all other pinned figures unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Change-Id: I0e2ea5b0389f8002157eeb8471800da51febe739
Diffstat (limited to 'src/test/java/eu')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java38
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java21
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java12
-rw-r--r--src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java33
4 files changed, 89 insertions, 15 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index b89a367..e7f7a00 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -402,21 +402,18 @@ class EndToEndTest {
assertThat(parseErgebnis.artikel()).containsExactly("1");
assertThat(parseErgebnis.befehle()).hasSize(7);
- // Fünf der sieben Befehle werden angewandt. Zwei bleiben als „manuell prüfen“ stehen — zwei
- // Einfügeformen, die ÄndGgner noch nicht beherrscht (bewusst dokumentierte Grenze, nicht
- // stillschweigend verworfen):
- // * Nr. 3 „Nach § 2 wird der folgende § 2 a eingefügt“ — Einfügung eines §-Blocks mit einer
- // durch Leerzeichen getrennten Sachnummer („§ 2 a“).
- // * Nr. 5 „In Kapitel 4 wird nach § 12 der folgende neue § 13 angefügt“ — kapitelbezogene
- // §-Block-Einfügung.
+ // Alle sieben Befehle werden angewandt, darunter die beiden niedersächsischen Einfügeformen:
+ // * Nr. 3 „Nach § 2 wird der folgende § 2 a eingefügt“ — Sachnummer mit Leerzeichen.
+ // * Nr. 5 „In Kapitel 4 wird nach § 12 der folgende neue § 13 angefügt“ — gliederungs-
+ // bezogene Einfügung, deren Zielbezeichnung erst der nachfolgende Befehl Nr. 6 freimacht.
var anwendung = BefehlAnwender.anwenden(gesetz, parseErgebnis.befehle());
- assertThat(anwendung.anzahlAngewandt()).isEqualTo(5);
- var manuellPfade =
- anwendung.protokoll().stream()
- .filter(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN)
- .map(a -> a.befehl().provenienz().gliederungsPfad())
- .toList();
- assertThat(manuellPfade).containsExactlyInAnyOrder("3.", "5.");
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(7);
+ assertThat(anwendung.protokoll())
+ .noneMatch(a -> a.status() == BefehlAnwender.Status.MANUELL_PRUEFEN);
+ // Protokolliert wird trotz vorgezogener Umnummerierung in der Reihenfolge des Gesetzes.
+ assertThat(anwendung.protokoll())
+ .extracting(a -> a.befehl().provenienz().gliederungsPfad())
+ .containsExactly("1. a)", "1. b)", "2.", "3.", "4.", "5.", "6.");
// Stichproben: „erhält folgende Fassung“ (§ 1 Abs. 1, § 2), Angaben-Ersetzung (§ 1 Abs. 5),
// Wörter-Einfügung (§ 6 Abs. 1) und §-Umnummerierung (§ 13 → § 14).
@@ -426,7 +423,18 @@ class EndToEndTest {
assertThat(neu.norm("§ 1").orElseThrow().absaetze().get(4).text()).contains("§ 14 Abs. 3");
assertThat(neu.norm("§ 2").orElseThrow().titel()).isEqualTo("Registriernummer");
assertThat(neu.norm("§ 6").orElseThrow().gesamtText()).contains("8 bis 10");
- assertThat(neu.norm("§ 14")).isPresent();
+
+ // Die beiden neuen Paragraphen stehen an der richtigen Stelle, der bisherige § 13 ist § 14.
+ assertThat(neu.normen()).hasSize(15);
+ assertThat(neu.normen())
+ .extracting(n -> n.enbez())
+ .containsSubsequence("§ 2", "§ 2a", "§ 3")
+ .containsSubsequence("§ 12", "§ 13", "§ 14");
+ assertThat(neu.norm("§ 2a").orElseThrow().titel())
+ .isEqualTo("Anwendung bundesrechtlicher Vorschriften");
+ assertThat(neu.norm("§ 13").orElseThrow().titel())
+ .isEqualTo("Entbehrlichkeit von Vergabeverfahren im Unterschwellenbereich");
+ assertThat(neu.norm("§ 14").orElseThrow().titel()).isEqualTo("Verordnungsermächtigungen");
var synopse = SynopseBuilder.baue(gesetz, anwendung, parseErgebnis.warnungen(), false);
assertThat(HtmlRenderer.rendere(synopse, "E2E-Test NEFG")).contains("§ 1");
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 d89c898..5213030 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
@@ -126,6 +126,27 @@ class BefehlErkennerTest {
}
@Test
+ void erkenntGliederungsbezogeneParagraphEinfuegung() {
+ // Echter Befehl aus Art. 1 Nr. 5 des Änderungsgesetzes zum NEFG (Nds. GVBl. 2026 Nr. 10). Die
+ // Kapitelangabe nennt nur den Abschnitt, in dem der neue Paragraph landet; maßgeblich für die
+ // Position ist der Anker „nach § 12“.
+ var befehl =
+ erkenne(
+ "In Kapitel 4 wird nach § 12 der folgende neue § 13 angefügt: „§ 13 Entbehrlichkeit"
+ + " von Vergabeverfahren im Unterschwellenbereich Das Gesetz ist nicht"
+ + " anzuwenden.“",
+ Stelle.LEER);
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("§ 12");
+ assertThat(einfuegung.vorher()).isFalse();
+ assertThat(einfuegung.ebene()).isEqualTo(Ebene.PARAGRAPH);
+ assertThat(einfuegung.bezeichnung()).isEqualTo("13");
+ assertThat(einfuegung.text()).startsWith("§ 13 Entbehrlichkeit");
+ }
+
+ @Test
void erkenntSatzEinfuegung() {
var befehl =
erkenne(
diff --git a/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java b/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java
index f49e41b..4da5c79 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/TextBereinigerTest.java
@@ -224,6 +224,18 @@ class TextBereinigerTest {
}
@Test
+ void ziehtSachnummernMitLeerzeichenZusammen() {
+ // Niedersachsen zitiert eingeschobene Paragraphen als „§ 2 a“; kanonisch ist „§ 2a“.
+ assertThat(TextBereiniger.bereinige("Nach § 2 wird der folgende § 2 a eingefügt:"))
+ .isEqualTo("Nach § 2 wird der folgende § 2a eingefügt:");
+ assertThat(TextBereiniger.bereinige("Art. 28 a Abs. 1")).isEqualTo("Art. 28a Abs. 1");
+ // Aufzählungsmarker des Änderungsgesetzes bleiben unberührt, ebenso echte Folgewörter.
+ assertThat(TextBereiniger.bereinige("Nach § 8 c) In Absatz 2"))
+ .isEqualTo("Nach § 8 c) In Absatz 2");
+ assertThat(TextBereiniger.bereinige("§ 5 des Gesetzes")).isEqualTo("§ 5 des Gesetzes");
+ }
+
+ @Test
void entferntSeitenkopfDesGvoblSchleswigHolstein() {
var roh =
"""
diff --git a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
index a23f444..a8f3daa 100644
--- a/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
+++ b/src/test/java/eu/mulk/aendggner/anwendung/BefehlAnwenderTest.java
@@ -1134,4 +1134,37 @@ class BefehlAnwenderTest {
assertThat(absatzText(ergebnis.neu(), "Art. 2", 0))
.isEqualTo("Erprobung ist die Prüfung der Tauglichkeit; sie schließt mit einem Bericht.");
}
+
+ @Test
+ void ziehtUmnummerierungVorDieNeubesetzendeEinfuegung() {
+ // „Nach § 2 wird der folgende neue § 3 eingefügt“ + „Der bisherige § 3 wird § 4“: In der
+ // Textreihenfolge angewandt kollidierten beide Befehle auf „§ 3“. Die Umnummerierung
+ // beschreibt den Stand vor der Änderung und geht der Neubesetzung sachlich voraus.
+ var einfuegung =
+ new StrukturEinfuegung(
+ stelle(new Stelle.Paragraph("2")),
+ false,
+ Ebene.PARAGRAPH,
+ "3",
+ "§ 3 Zwischennorm (1) Der neue Text.",
+ PROV);
+ var umnummerierung =
+ new Umnummerierung(
+ stelle(new Stelle.Paragraph("3")),
+ stelle(new Stelle.Paragraph("4")),
+ new Provenienz("1", "2.", "(Test)"));
+
+ var ergebnis = BefehlAnwender.anwenden(gesetz(), List.of(einfuegung, umnummerierung));
+
+ assertThat(ergebnis.anzahlAngewandt()).isEqualTo(2);
+ // Protokolliert wird weiterhin in der Reihenfolge des Änderungsgesetzes.
+ assertThat(ergebnis.protokoll())
+ .extracting(a -> a.befehl().provenienz().gliederungsPfad())
+ .containsExactly("1.", "2.");
+ assertThat(ergebnis.neu().normen())
+ .extracting(n -> n.enbez())
+ .containsExactly("§ 1", "§ 2", "§ 3", "§ 4");
+ assertThat(ergebnis.neu().norm("§ 3").orElseThrow().titel()).isEqualTo("Zwischennorm");
+ assertThat(ergebnis.neu().norm("§ 4").orElseThrow().titel()).isEqualTo("Schlussvorschriften");
+ }
}