aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/eu
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/java/eu')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java78
-rw-r--r--src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java88
2 files changed, 166 insertions, 0 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 8c9743d..bb67978 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -1700,6 +1700,84 @@ class EndToEndTest {
.containsOnlyOnce("Fortgeschrieben durch: Änderungsgesetz vom 12. Februar 2026");
}
+ /**
+ * Hamburg: Die Verordnung zur Änderung der Verordnung über den Gutachterausschuss für
+ * Grundstückswerte (HmbGVBl. Nr. 17/2026, S. 147).
+ *
+ * <p>Der Fall bringt eine Gliederung, die im übrigen Landesrecht nicht vorkommt: Das
+ * Änderungsdokument selbst teilt sich in <em>Paragraphen</em> (§ 1 Änderung, § 2 Inkrafttreten),
+ * obgleich dasselbe Heft davor eine Verkündung führt, die sich in Artikel teilt; und seine
+ * Befehle sind <em>dezimal</em> gegliedert (6.1, 7.1.1, 7.2.3) statt a)/aa)/aaa). Dazu vier
+ * Idiome — „die Textstelle“, „Hinter § 7 wird … eingefügt“, „Es wird folgender Absatz angefügt“,
+ * „§§ 6 und 7 erhalten folgende Fassung“ — und ein schmales Leerzeichen zwischen
+ * Paragraphenzeichen und Nummer.
+ *
+ * <p>Vier Normen weichen von der amtlichen Nachfassung ab; drei davon liegen an der Vorlage und
+ * nicht am Werkzeug (siehe {@code Hamburg/SOURCES}).
+ */
+ @Test
+ void gutachterausschussVOHamburg() throws Exception {
+ var alt = SAMPLEDATA.resolve("Hamburg/GAusschV-HH-alt.txt");
+ var pdf = SAMPLEDATA.resolve("Hamburg/HmbGVBl-2026-17_GutachterausschussVO-AendVO.pdf");
+ var neu = SAMPLEDATA.resolve("Hamburg/GAusschV-HH-neu.txt");
+ assumeTrue(Files.exists(alt) && Files.exists(pdf), "Hamburger Beispieldaten fehlen");
+
+ var gesetz = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(alt);
+ assertThat(gesetz.jurabk()).isEqualTo("GAusschV HA 2009");
+ assertThat(gesetz.normen()).hasSize(13);
+
+ var text = TextBereiniger.bereinige(new PatchTextExtraktor().extrahiere(pdf));
+ // Das schmale Leerzeichen (U+2009) zwischen § und Nummer ist normalisiert; ohne das wäre
+ // „§ 1“ kein Kopf, und das ganze Heft bliebe ungelesen.
+ assertThat(text).contains("\n§ 1\n").doesNotContain("§\u2009");
+ // Der Kolumnentitel steht im Inhaltsstrom und fiele sonst hinter das Zitat eines Befehls.
+ assertThat(text).doesNotContain("Dienstag, den 26. Mai 2026 149");
+
+ var parseErgebnis = new AenderungsgesetzParser().parse(text, gesetz, null);
+ // Der Artikel-Modus findet nichts, das die Verordnung beträfe; erst die §-Teilung tut es.
+ assertThat(parseErgebnis.artikel()).containsExactly("1");
+ assertThat(parseErgebnis.befehle()).hasSize(21);
+
+ // Die Dezimalgliederung trägt ihre Herkunft im Label; der Pfad wiederholt sie nicht.
+ assertThat(befehlAn(parseErgebnis, "7.1.4"))
+ .isInstanceOf(Aenderungsbefehl.Umnummerierung.class);
+ assertThat(befehlAn(parseErgebnis, "8.1")).isInstanceOf(Aenderungsbefehl.Neufassung.class);
+
+ // Zwei Befehle bleiben unerkannt, und zwar zu Recht: Die Nummern 2 und 3 des Heftes schreiben
+ // „wir die Textstelle … ersetzt“ statt „wird“. Das ist ein Verkündungsfehler, und geraten wird
+ // nicht.
+ var unerkannt =
+ parseErgebnis.befehle().stream()
+ .filter(b -> b instanceof UnbekannterBefehl)
+ .map(b -> b.provenienz().gliederungsPfad())
+ .toList();
+ assertThat(unerkannt).containsExactly("2.", "3.");
+
+ var anwendung = BefehlAnwender.anwenden(gesetz, parseErgebnis.befehle());
+ assertThat(anwendung.anzahlAngewandt()).isEqualTo(17);
+
+ // Der angefügte Schlusssatz steht hinter der Aufzählung auf eigener Zeile und nicht am Ende
+ // des letzten Aufzählungsgliedes.
+ var neunter = anwendung.neu().norm("§ 9").orElseThrow().absaetze().get(0).text();
+ assertThat(neunter)
+ .contains("\nDie DIN EN ISO/IEC 17024 ist zur kostenfreien Einsicht")
+ // Das Satzzeichen, das an die Stelle eines Wortes tritt, nimmt dessen Zwischenraum mit.
+ .contains("zur Erfüllung ihrer Aufgaben,\n")
+ // Die bisherige Nummer 3 ist erhalten: Die Umnummerierung geht der Neufassung vor, die
+ // ihre Bezeichnung neu vergäbe — sonst ginge jener Wortlaut verloren.
+ .contains("4. weitere Personen und Stellen");
+
+ assumeTrue(Files.exists(neu), "Hamburger Nachfassung fehlt");
+ var soll = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(neu);
+ var abgleich = Nachfassungsabgleich.vergleiche(soll, anwendung.neu());
+ assertThat(abgleich.fehlende()).isEmpty();
+ assertThat(abgleich.ueberzaehlige()).isEmpty();
+ // § 3 und § 4: der Verkündungsfehler „wir“. § 6: das Gesetzblatt setzt „1. in Präsenz“ ohne
+ // Komma, das Portal mit. § 9: die Neufassung der soeben geräumten Nummer 3.
+ assertThat(abgleich.abweichungen().stream().map(a -> a.enbez()).toList())
+ .containsExactly("§ 3", "§ 4", "§ 6", "§ 9");
+ }
+
private static Aenderungsbefehl befehlZu(
AenderungsgesetzParser.ParseErgebnis ergebnis, String gliederungsPfad) {
return ergebnis.befehle().stream()
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 327c8db..4c55154 100644
--- a/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
+++ b/src/test/java/eu/mulk/aendggner/aenderung/parse/BefehlErkennerTest.java
@@ -1544,4 +1544,92 @@ class BefehlErkennerTest {
Stelle.LEER);
assertThat(befehl).containsInstanceOf(Ersetzung.class);
}
+
+ // --- Hamburger Idiome (HmbGVBl. Nr. 17/2026) --------------------------------------------------
+
+ /** „Hinter“ steht dem „nach“ gleich; das hamburgische Blatt setzt es durchweg. */
+ @Test
+ void erkenntEinfuegungHinterEinerNorm() {
+ var befehl = erkenne("Hinter § 7 wird folgender § 7a eingefügt: „§ 7a Aufgaben“", Stelle.LEER);
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.vorher()).isFalse();
+ assertThat(einfuegung.stelle().anzeigeText()).isEqualTo("§ 7");
+ assertThat(einfuegung.bezeichnung()).isEqualTo("7a");
+ }
+
+ @Test
+ void erkenntEinfuegungHinterEinemAbsatz() {
+ var befehl =
+ erkenne(
+ "Hinter Absatz 3 wird folgender neuer Absatz 4 eingefügt: „(4) Nicht"
+ + " grundstücksbezogene Auskünfte sind zu erteilen.“",
+ PARAGRAPH_5);
+
+ assertThat(befehl).containsInstanceOf(StrukturEinfuegung.class);
+ var einfuegung = (StrukturEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.vorher()).isFalse();
+ assertThat(einfuegung.bezeichnung()).isEqualTo("4");
+ }
+
+ /** Die Anfügung ohne Subjekt: Das Ziel steht im Rahmen, der Satz nennt nur das Angefügte. */
+ @Test
+ void erkenntAnfuegungOhneSubjekt() {
+ var befehl =
+ erkenne(
+ "Es wird folgender Absatz 3 angefügt: „(3) Personenbezogene Daten sind zu löschen.“",
+ PARAGRAPH_5);
+
+ assertThat(befehl).isPresent();
+ assertThat(befehl.orElseThrow().stelle().anzeigeText()).isEqualTo("§ 5");
+ }
+
+ @Test
+ void erkenntSatzAnfuegungOhneSubjekt() {
+ var befehl =
+ erkenne("Es wird folgender Satz angefügt: „Die Norm ist niedergelegt.“", PARAGRAPH_5);
+
+ assertThat(befehl).isPresent();
+ }
+
+ /** „Textstelle“ ist die hamburgische Nebenform zu „Wörter“ und „Angabe“. */
+ @Test
+ void erkenntTextstellenErsetzung() {
+ var befehl =
+ erkenne(
+ "In § 3 Absatz 2 Nummer 1 wird die Textstelle „Absatz 5“ durch die Textstelle"
+ + " „Absatz 8“ ersetzt.",
+ Stelle.LEER);
+
+ assertThat(befehl).containsInstanceOf(Ersetzung.class);
+ var ersetzung = (Ersetzung) befehl.orElseThrow();
+ assertThat(ersetzung.alt()).isEqualTo("Absatz 5");
+ assertThat(ersetzung.neu()).isEqualTo("Absatz 8");
+ }
+
+ @Test
+ void erkenntEinfuegungHinterEinerTextstelle() {
+ var befehl =
+ erkenne(
+ "In Satz 3 wird hinter der Textstelle „Sachverständige nach Absatz 1 Nummer 2“ die"
+ + " Textstelle „und 3“ eingefügt.",
+ PARAGRAPH_5);
+
+ assertThat(befehl).containsInstanceOf(WoerterEinfuegung.class);
+ var einfuegung = (WoerterEinfuegung) befehl.orElseThrow();
+ assertThat(einfuegung.woerter()).isEqualTo("und 3");
+ }
+
+ /** „§§ 6 und 7 erhalten folgende Fassung“ — die Neufassung einer Paragraphenfolge. */
+ @Test
+ void erkenntNeufassungMehrererParagraphen() {
+ var befehl =
+ erkenne(
+ "§§ 6 und 7 erhalten folgende Fassung: „§ 6 Verfahren (1) Beraten wird gemeinsam."
+ + " § 7 Aufgaben Das vorsitzende Mitglied ist verantwortlich.“",
+ Stelle.LEER);
+
+ assertThat(befehl).isPresent();
+ }
}