blob: 4909d496291c7f49f17ffb305eb9cbb73f7c10ae [file] [log] [blame]
// SPDX-FileCopyrightText: 2026 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
// SPDX-License-Identifier: AGPL-3.0-or-later
package eu.mulk.aendggner;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.junit.jupiter.api.Test;
/**
* Die Archive werden zur Laufzeit gebaut statt als Binärdatei abgelegt: So steht neben jedem Fall,
* was ihn ausmacht, und der Quellbaum bleibt frei von undurchsichtigen Beilagen.
*/
class ZipAuspackerTest {
private static final Path MINI_GII = Path.of("src/test/resources/eu/mulk/aendggner/mini-gii.xml");
@Test
void packtDenXmlEintragAus() throws Exception {
var xml = Files.readAllBytes(MINI_GII);
var archiv = zip(Map.of("BJNR000000000.xml", xml), ZipEntry.DEFLATED);
var ausgepackt = ZipAuspacker.auspacken(new Quelle("xml.zip", archiv));
assertThat(ausgepackt.inhalt()).isEqualTo(xml);
// Der Name folgt dem Eintrag, nicht der Verpackung: Protokoll und Ladefehler sollen das
// Gesetz benennen.
assertThat(ausgepackt.name()).isEqualTo("BJNR000000000.xml");
}
@Test
void packtAuchUnkomprimierteEintraegeAus() throws Exception {
var xml = Files.readAllBytes(MINI_GII);
var archiv = zip(Map.of("BJNR000000000.xml", xml), ZipEntry.STORED);
assertThat(ZipAuspacker.auspacken(new Quelle("xml.zip", archiv)).inhalt()).isEqualTo(xml);
}
@Test
void uebergehtBeiwerkNebenDemXml() throws Exception {
var xml = Files.readAllBytes(MINI_GII);
// So sehen die Archive der Gesetze mit Anlagen aus: neben dem Norm-XML liegen Bilddateien.
var eintraege = new LinkedHashMap<String, byte[]>();
eintraege.put("__MACOSX/", new byte[0]);
eintraege.put("BJNR000000000.xml", xml);
eintraege.put("anlage_1.gif", new byte[] {'G', 'I', 'F', '8'});
assertThat(ZipAuspacker.auspacken(new Quelle("xml.zip", zip(eintraege, ZipEntry.DEFLATED))))
.extracting(Quelle::name)
.isEqualTo("BJNR000000000.xml");
}
@Test
void reichtNichtArchiveUnveraendertDurch() throws Exception {
var quelle = new Quelle("stamm.xml", Files.readAllBytes(MINI_GII));
assertThat(ZipAuspacker.auspacken(quelle)).isSameAs(quelle);
}
@Test
void meldetArchivOhneXml() {
var archiv = zip(Map.of("liesmich.txt", "nichts hierin".getBytes(StandardCharsets.UTF_8)));
assertThatThrownBy(() -> ZipAuspacker.auspacken(new Quelle("xml.zip", archiv)))
.isInstanceOf(IOException.class)
.hasMessageContaining("keine XML-Datei");
}
@Test
void meldetArchivMitMehrerenXml() {
var eintraege = new LinkedHashMap<String, byte[]>();
eintraege.put("eins.xml", "<a/>".getBytes(StandardCharsets.UTF_8));
eintraege.put("zwei.xml", "<b/>".getBytes(StandardCharsets.UTF_8));
assertThatThrownBy(() -> ZipAuspacker.auspacken(new Quelle("xml.zip", zip(eintraege))))
.isInstanceOf(IOException.class)
.hasMessageContaining("eins.xml, zwei.xml");
}
@Test
void meldetBeschaedigtesArchiv() throws Exception {
var archiv = zip(Map.of("BJNR000000000.xml", Files.readAllBytes(MINI_GII)));
// Das Zentralverzeichnis steht am Ende; ein abgeschnittenes Archiv verliert es.
var abgeschnitten = new byte[archiv.length / 2];
System.arraycopy(archiv, 0, abgeschnitten, 0, abgeschnitten.length);
assertThatThrownBy(() -> ZipAuspacker.auspacken(new Quelle("xml.zip", abgeschnitten)))
.isInstanceOf(IOException.class)
.hasMessageContaining("beschädigt");
}
@Test
void gezipptesStammgesetzErgibtDasselbeGesetz() throws Exception {
var xml = Files.readAllBytes(MINI_GII);
var direkt = Pipeline.ladeStammgesetz(new Quelle("mini-gii.xml", xml));
var gezippt = Pipeline.ladeStammgesetz(new Quelle("xml.zip", zip(Map.of("mini-gii.xml", xml))));
assertThat(gezippt.normen()).isEqualTo(direkt.normen());
assertThat(gezippt.jurabk()).isEqualTo(direkt.jurabk());
}
private static byte[] zip(Map<String, byte[]> eintraege) {
return zip(eintraege, ZipEntry.DEFLATED);
}
private static byte[] zip(Map<String, byte[]> eintraege, int verfahren) {
var aus = new ByteArrayOutputStream();
try (var zip = new ZipOutputStream(aus)) {
for (var eintrag : eintraege.entrySet()) {
var kopf = new ZipEntry(eintrag.getKey());
kopf.setMethod(verfahren);
if (verfahren == ZipEntry.STORED) {
// Gespeicherte Einträge verlangen Größe und Prüfsumme vorab.
var pruefsumme = new CRC32();
pruefsumme.update(eintrag.getValue());
kopf.setSize(eintrag.getValue().length);
kopf.setCompressedSize(eintrag.getValue().length);
kopf.setCrc(pruefsumme.getValue());
}
zip.putNextEntry(kopf);
zip.write(eintrag.getValue());
zip.closeEntry();
}
} catch (IOException e) {
throw new AssertionError("Das Testarchiv ließ sich nicht bauen", e);
}
return aus.toByteArray();
}
}