aboutsummaryrefslogtreecommitdiff
path: root/src/test/java
diff options
context:
space:
mode:
authorMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-25 22:25:21 +0200
committerMatthias Andreas Benkard <code@mail.matthias.benkard.de>2026-08-25 22:25:21 +0200
commit22f19c3afc4d51d7714647c5635bb068cf32d433 (patch)
treee132af3ba56250ee03a0382bbb79f63d45eeeeda /src/test/java
parent86a16e6e6e71a883708652bf623e51099707ea04 (diff)
Das Erzeugnis gibt seine Fassung heraus und prüft sie selbst
Zwei Leistungen fehlten, die das Werkzeug längst erbringen konnte, aber nicht herausgab. Die erste ist die fortgeschriebene Fassung. Der Schalter --neufassung schreibt sie als kanonischen Klartext; damit steht die Kette: Die Ausgabe des ersten Laufes ist das Stammgesetz des zweiten, und eine konsolidierte Fassung lässt sich aus dem Stammheft und den Änderungsheften zusammensetzen, wo kein Portal sie fertig liefert. Für Thüringen ist das von Hand geschehen. Die zweite ist der Abgleich. Ob ein Lauf gelungen ist, entscheidet nicht die Zahl der angewandten Befehle, sondern der Wortlaut hinterher — und dieser Maßstab stand bislang allein im Testcode. Wer ein Land erschloss, musste die Prüfung ein zweites Mal schreiben. Der Schalter --nachfassung nimmt die amtliche Fassung entgegen, weist fehlende, überzählige und abweichende Normen aus, zeigt die Abweichung in der Synopse wortweise und endet bei einem Fehlschlag mit dem Rückgabewert 3. Denselben Abgleich fahren fortan die Akzeptanzprüfungen; vier handgeschriebene Vergleichsschleifen weichen ihm. Die Überschrift zählt dabei zum Wortlaut, denn auf sie zielen eigene Befehle — der Korpus trägt diese Verschärfung ohne einen einzigen Rückfall. Statt einer sechsten und siebten Fassung der Signatur tritt ein Auftrags-Record an die Stelle der Überladungsleiter der Pipeline. Die Browserfassung bekommt beides: ein Dateifeld für die Nachfassung und ein Ankreuzfeld für die Textausgabe, beide hinter der Klappe „Weitere Angaben“. Der Rundlauf hat dabei einen zweiten Mangel des Lesers zutage gefördert. Das Bundesrecht führt aufgehobene Paragraphen unter einer Sammelbezeichnung („§§ 17 u. 18“, Titel „(weggefallen)“, im gii-XML mit der Platzhalterkennung „(XXXX)“). Der kanonische Klartext kannte diese Kopfzeile nicht; sie wäre in den Wortlaut der Vornorm gefallen und hätte ihn verfälscht. Ohne sie könnte der Klartext kein Bundesgesetz tragen, und die Kette endete beim Bund. Geprüft: 375 Tests (zuvor 373); Berlin gibt 171 von 171 Normen gleich aus, und die für das UWG ausgegebene Fassung ergibt wieder eingelesen dieselbe. Change-Id: Ie3c45e18b561175fe6dd0033c46d3bed69e415bc
Diffstat (limited to 'src/test/java')
-rw-r--r--src/test/java/eu/mulk/aendggner/EndToEndTest.java161
1 files changed, 109 insertions, 52 deletions
diff --git a/src/test/java/eu/mulk/aendggner/EndToEndTest.java b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
index 78a45e9..91a0cca 100644
--- a/src/test/java/eu/mulk/aendggner/EndToEndTest.java
+++ b/src/test/java/eu/mulk/aendggner/EndToEndTest.java
@@ -17,11 +17,13 @@ import eu.mulk.aendggner.aenderung.parse.SuperskriptModus;
import eu.mulk.aendggner.aenderung.parse.TextBereiniger;
import eu.mulk.aendggner.aenderung.parse.ZitatExtraktor;
import eu.mulk.aendggner.anwendung.BefehlAnwender;
+import eu.mulk.aendggner.anwendung.Nachfassungsabgleich;
import eu.mulk.aendggner.gesetz.Absatz;
import eu.mulk.aendggner.gesetz.Gesetz;
import eu.mulk.aendggner.gesetz.gii.GiiXmlLoader;
import eu.mulk.aendggner.synopse.HtmlRenderer;
import eu.mulk.aendggner.synopse.SynopseBuilder;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
@@ -737,13 +739,13 @@ class EndToEndTest {
assumeTrue(Files.exists(pfad), "Nachfassung " + nachfassung + " fehlt");
var soll = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(pfad);
assertThat(soll.normen()).hasSameSizeAs(alt.normen());
- for (var normSoll : soll.normen()) {
- var normIst = anwendung.neu().norm(normSoll.enbez());
- assertThat(normIst).as("Norm %s fehlt", normSoll.enbez()).isPresent();
- assertThat(normIst.orElseThrow().gesamtText().replaceAll("\\s+", " ").strip())
- .as("Norm %s", normSoll.enbez())
- .isEqualTo(normSoll.gesamtText().replaceAll("\\s+", " ").strip());
- }
+
+ // Geprüft wird mit demselben Abgleich, den auch --nachfassung fährt: Werkzeug und Test messen
+ // an einem Maßstab, sonst geht der eine durch, wo der andere anschlüge.
+ var abgleich = Nachfassungsabgleich.vergleiche(soll, anwendung.neu());
+ assertThat(abgleich.fehlende()).isEmpty();
+ assertThat(abgleich.abweichungen()).as("Abweichungen gegen die amtliche Nachfassung").isEmpty();
+ assertThat(abgleich.gehtAuf()).as(abgleich.kurzbericht()).isTrue();
}
/**
@@ -1055,17 +1057,10 @@ class EndToEndTest {
var sollAsog = SAMPLEDATA.resolve("Berlin/ASOG-Bln-neu.txt");
assumeTrue(Files.exists(sollAsog), "ASOG-Nachfassung fehlt");
var soll = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(sollAsog);
- var abweichend = new java.util.ArrayList<String>();
- for (var normSoll : soll.normen()) {
- var normIst = anwendung.neu().norm(normSoll.enbez()).orElseThrow();
- if (!normIst
- .gesamtText()
- .replaceAll("\\s+", " ")
- .strip()
- .equals(normSoll.gesamtText().replaceAll("\\s+", " ").strip())) {
- abweichend.add(normSoll.enbez());
- }
- }
+ var abweichend =
+ Nachfassungsabgleich.vergleiche(soll, anwendung.neu()).abweichungen().stream()
+ .map(Nachfassungsabgleich.Abweichung::enbez)
+ .toList();
// Keine Abweichung mehr: Alle 171 Normen gleichen der amtlichen Nachfassung.
//
// § 67 trug die beiden zuvor benannten Abweichungen: Das Portal setzt in der neuen Fassung
@@ -1148,17 +1143,10 @@ class EndToEndTest {
var soll =
new eu.mulk.aendggner.gesetz.land.LandesRechtLoader()
.load(SAMPLEDATA.resolve("BadenWuerttemberg/KomWO-BW-neu.txt"));
- var abweichend = new java.util.ArrayList<String>();
- for (var normSoll : soll.normen()) {
- var normIst = anwendung.neu().norm(normSoll.enbez()).orElseThrow();
- if (!normIst
- .gesamtText()
- .replaceAll("\\s+", " ")
- .strip()
- .equals(normSoll.gesamtText().replaceAll("\\s+", " ").strip())) {
- abweichend.add(normSoll.enbez());
- }
- }
+ var abweichend =
+ Nachfassungsabgleich.vergleiche(soll, anwendung.neu()).abweichungen().stream()
+ .map(Nachfassungsabgleich.Abweichung::enbez)
+ .toList();
// § 20: Der Befehl ersetzt das Wort „Name“ durch „der vollständige Familienname“; im Zieltext
// steht davor bereits „der“, sodass es doppelt erscheint. Die amtliche Nachfassung räumt das
// auf, der Befehlswortlaut tut es nicht — ÄndGgner wendet den Wortlaut an.
@@ -1353,13 +1341,10 @@ class EndToEndTest {
assumeTrue(Files.exists(soll), "Hessische Nachfassung fehlt");
var amtlich = new eu.mulk.aendggner.gesetz.land.LandesRechtLoader().load(soll);
assertThat(amtlich.normen()).hasSize(52);
- for (var normSoll : amtlich.normen()) {
- var normIst = anwendung.neu().norm(normSoll.enbez());
- assertThat(normIst).as("Norm %s fehlt", normSoll.enbez()).isPresent();
- assertThat(normIst.orElseThrow().gesamtText().replaceAll("\\s+", " ").strip())
- .as("Norm %s", normSoll.enbez())
- .isEqualTo(normSoll.gesamtText().replaceAll("\\s+", " ").strip());
- }
+ var abgleich = Nachfassungsabgleich.vergleiche(amtlich, anwendung.neu());
+ assertThat(abgleich.fehlende()).isEmpty();
+ assertThat(abgleich.abweichungen()).isEmpty();
+ assertThat(abgleich.gleich()).isEqualTo(52);
}
/**
@@ -1527,8 +1512,8 @@ class EndToEndTest {
Files.exists(alt) && Files.exists(entwurfPdf) && Files.exists(antragPdf),
"BayJG-Beispieldaten fehlen");
- var ohne = Pipeline.erzeugeSynopse(alt, List.of(entwurfPdf), null, false);
- var mit = Pipeline.erzeugeSynopse(alt, List.of(entwurfPdf, antragPdf), null, false);
+ var ohne = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(alt, List.of(entwurfPdf)));
+ var mit = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(alt, List.of(entwurfPdf, antragPdf)));
assertThat(mit.anzahlAngewandt()).isEqualTo(ohne.anzahlAngewandt()).isEqualTo(154);
assertThat(mit.anzahlManuell()).isEqualTo(ohne.anzahlManuell()).isEqualTo(0);
@@ -1607,8 +1592,8 @@ class EndToEndTest {
Files.exists(xml) && Files.exists(empfehlung) && Files.exists(entwurf),
"GEG-Beispieldaten fehlen");
- var ausEmpfehlung = Pipeline.erzeugeSynopse(xml, List.of(empfehlung), null, false);
- var ausEntwurf = Pipeline.erzeugeSynopse(xml, List.of(entwurf), null, false);
+ var ausEmpfehlung = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(empfehlung)));
+ var ausEntwurf = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(entwurf)));
// 69 statt der früheren 68: Nennt der Rahmen dieselbe Gliederungseinheit wie der Befehl („…
// Teil 2 wird wie folgt geändert: … die Angabe zur Überschrift von Teil 2 Abschnitt 4 …“),
@@ -1635,7 +1620,7 @@ class EndToEndTest {
var pdf = SAMPLEDATA.resolve("IfSG/1924334.pdf");
assumeTrue(Files.exists(xml) && Files.exists(pdf), "IfSG-Beispieldaten fehlen");
- var ergebnis = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false);
+ var ergebnis = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
// 51 statt der früheren 47: Die Satzzählung folgt jetzt der amtlichen — eine eingerückte
// Aufzählungsmarke beendet keinen Satz, und eine Ordnungszahl vor einem Gliederungswort („nach
@@ -1655,7 +1640,7 @@ class EndToEndTest {
var pdf = SAMPLEDATA.resolve("GEG/BT-Drs-21-7071_Beschlussempfehlung.pdf");
assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen");
- var ergebnis = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false);
+ var ergebnis = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
assertThat(ergebnis.anzahlAngewandt()).isZero();
assertThat(ergebnis.html()).contains("keine Änderungsbefehle");
@@ -1682,7 +1667,7 @@ class EndToEndTest {
assumeTrue(Files.exists(xml) && Files.exists(pdf), "UWG-Beispieldaten fehlen");
// Ohne Stichtag bleibt es beim vollen Bestand — aber nicht stillschweigend.
- var ohne = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false);
+ var ohne = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
assertThat(ohne.anzahlAngewandt()).isEqualTo(19);
assertThat(ohne.html())
.contains("Das Änderungsgesetz tritt gestaffelt in Kraft")
@@ -1690,7 +1675,9 @@ class EndToEndTest {
.contains("<dt>Inkrafttreten</dt><dd>27. September 2026 (gestaffelt, siehe unten)</dd>");
// Am 19. Juni 2026 galt genau ein Befehl: der Buchstabe c der Nummer 2.
- var frueh = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2026, 6, 19));
+ var frueh =
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2026, 6, 19)));
assertThat(frueh.anzahlAngewandt()).isEqualTo(1);
assertThat(frueh.anzahlManuell()).isZero();
assertThat(frueh.html())
@@ -1698,12 +1685,16 @@ class EndToEndTest {
.contains("Am Stichtag noch nicht in Kraft")
.contains("Tritt erst am 27. September 2026 in Kraft");
// Der Tag davor ändert noch gar nichts.
- var davor = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2026, 6, 18));
+ var davor =
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2026, 6, 18)));
assertThat(davor.anzahlAngewandt()).isZero();
// Am 27. September 2026 ist das Gesetz vollständig in Kraft; dann deckt sich die Fassung mit
// der ungefilterten, und der Abschnitt „noch nicht in Kraft“ entfällt.
- var spaet = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2026, 9, 27));
+ var spaet =
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2026, 9, 27)));
assertThat(spaet.anzahlAngewandt()).isEqualTo(19);
assertThat(spaet.html()).doesNotContain("Am Stichtag noch nicht in Kraft");
}
@@ -1720,12 +1711,13 @@ class EndToEndTest {
var pdf = SAMPLEDATA.resolve("GEG/bgbl123s0280_regelungstext.pdf");
assumeTrue(Files.exists(xml) && Files.exists(pdf), "GEG-Beispieldaten fehlen");
- var vollstaendig = Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false);
+ var vollstaendig = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
assertThat(vollstaendig.anzahlAngewandt()).isEqualTo(117);
assertThat(vollstaendig.anzahlManuell()).isEqualTo(2);
var anfang2024 =
- Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 1, 1));
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2024, 1, 1)));
assertThat(anfang2024.anzahlAngewandt()).isEqualTo(116);
assertThat(anfang2024.anzahlManuell()).isEqualTo(2);
assertThat(anfang2024.html())
@@ -1733,7 +1725,8 @@ class EndToEndTest {
.contains("Tritt erst am 1. Oktober 2024 in Kraft");
var oktober2024 =
- Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2024, 10, 1));
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2024, 10, 1)));
assertThat(oktober2024.anzahlAngewandt()).isEqualTo(117);
}
@@ -1753,7 +1746,8 @@ class EndToEndTest {
assumeTrue(Files.exists(xml) && Files.exists(pdf), "IfSG-Beispieldaten fehlen");
var novemberFassung =
- Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2020, 11, 19));
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2020, 11, 19)));
assertThat(novemberFassung.anzahlAngewandt()).isEqualTo(65);
assertThat(novemberFassung.html())
.contains("Am Stichtag noch nicht in Kraft")
@@ -1761,7 +1755,8 @@ class EndToEndTest {
// Am 1. April 2021 kommen die zehn Befehle des Artikels 2 und der Doppelbuchstabe hinzu.
var aprilFassung =
- Pipeline.erzeugeSynopse(xml, List.of(pdf), null, false, LocalDate.of(2021, 4, 1));
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf)).mitStichtag(LocalDate.of(2021, 4, 1)));
assertThat(aprilFassung.anzahlAngewandt()).isEqualTo(75);
assertThat(aprilFassung.html()).doesNotContain("Am Stichtag noch nicht in Kraft");
}
@@ -1812,7 +1807,7 @@ class EndToEndTest {
assertThat(grundregel.wortlaut())
.isEqualTo("Dieses Gesetz tritt am Tag nach der Verkündung in Kraft.");
- var synopse = Pipeline.erzeugeSynopse(alt, List.of(pdf), null, false);
+ var synopse = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(alt, List.of(pdf)));
assertThat(synopse.anzahlAngewandt()).isEqualTo(4);
assertThat(synopse.html()).doesNotContain("tritt gestaffelt in Kraft");
}
@@ -1881,4 +1876,66 @@ class EndToEndTest {
.toList();
assertThat(unerkannt).containsExactly("5. a) aa)", "7. a) aa)", "12.");
}
+
+ /**
+ * Der Abgleich mit der amtlichen Nachfassung ist fortan eine Leistung des Erzeugnisses und nicht
+ * bloß eine des Testcodes: {@code --nachfassung} nimmt dieselben Eingaben an wie das Stammgesetz
+ * und stellt das Ergebnis normweise dagegen. Berlin, Artikel 1: 171 von 171.
+ */
+ @Test
+ void nachfassungWirdNormweiseAbgeglichen() throws Exception {
+ var alt = SAMPLEDATA.resolve("Berlin/ASOG-Bln-alt.txt");
+ var neu = SAMPLEDATA.resolve("Berlin/ASOG-Bln-neu.txt");
+ var pdf = SAMPLEDATA.resolve("Berlin/GVBl-2026-17_ASOG-LAF-AendG.pdf");
+ assumeTrue(
+ Files.exists(alt) && Files.exists(neu) && Files.exists(pdf), "ASOG-Beispieldaten fehlen");
+
+ var ergebnis =
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(alt, List.of(pdf))
+ .mitArtikel("1")
+ .mitNachfassung(Quelle.lies(neu)));
+
+ var abgleich = ergebnis.abgleich();
+ assertThat(abgleich).isNotNull();
+ assertThat(abgleich.gehtAuf()).as(abgleich.kurzbericht()).isTrue();
+ assertThat(abgleich.gleich()).isEqualTo(171);
+ assertThat(abgleich.geprueft()).isEqualTo(171);
+ // Und er steht in der Synopse, wo ihn auch findet, wer keine Befehlszeile liest.
+ assertThat(ergebnis.html())
+ .contains("Abgleich mit der amtlichen Nachfassung")
+ .contains("171 von 171 Normen gleich");
+ }
+
+ /**
+ * Die Kette steht und fällt damit, dass die ausgegebene Fassung wieder eingelesen dasselbe Gesetz
+ * ergibt. Geprüft wird das hier am <em>Bundesrecht</em> — der Rundlauf-Test des Textausgebers
+ * deckt nur die Klartext-Stammfassungen ab, und gerade der Weg vom gii-XML in den kanonischen
+ * Klartext ist der, den die Kette braucht: Wer ein zweites Heft auf das Ergebnis des ersten
+ * anwenden will, hat kein XML mehr, sondern nur diesen Text.
+ */
+ @Test
+ void dieAusgegebeneFassungLaesstSichWiederEinlesen() throws Exception {
+ var xml = SAMPLEDATA.resolve("UWG/BJNR141400004.xml");
+ var pdf = SAMPLEDATA.resolve("UWG/bgbl126s0043_regelungstext.pdf");
+ assumeTrue(Files.exists(xml) && Files.exists(pdf), "UWG-Beispieldaten fehlen");
+
+ var erst = Pipeline.erzeugeSynopse(Pipeline.Auftrag.von(xml, List.of(pdf)));
+ assertThat(erst.anzahlAngewandt()).isEqualTo(19);
+
+ // Der zweite Lauf hält dasselbe Ergebnis gegen den Text, den der erste geschrieben hat. Geht
+ // der Abgleich auf, so trägt der Text die Fassung vollständig — und damit die Kette.
+ var zweit =
+ Pipeline.erzeugeSynopse(
+ Pipeline.Auftrag.von(xml, List.of(pdf))
+ .mitNachfassung(
+ new Quelle("UWG-neu.txt", erst.neufassung().getBytes(StandardCharsets.UTF_8))));
+
+ var abgleich = zweit.abgleich();
+ assertThat(abgleich).isNotNull();
+ assertThat(abgleich.fehlende()).isEmpty();
+ assertThat(abgleich.ueberzaehlige()).isEmpty();
+ assertThat(abgleich.abweichungen()).isEmpty();
+ assertThat(abgleich.gehtAuf()).as(abgleich.kurzbericht()).isTrue();
+ }
}