import { describe, expect, it } from "vitest"; import { achsenVertauscht, ausGeoJson, ausZeilen, datum, statusErkennen, typErkennen, zahl, } from "@/lib/karte/hydrant-import"; /** * Hydranten einlesen. * * Was hier geprüft wird, ist keine Formatfrage, sondern eine * Erfahrungsfrage: Wehren bekommen Hydrantenlisten in jedem denkbaren Zustand * — mit „DN 100" in der Zahlenspalte, mit vertauschten Achsen, mit deutschen * Datumsangaben und mit Spaltennamen, die sich niemand ausgedacht hat. * * Der Import darf daran nicht scheitern. Er darf aber auch nichts erfinden. */ describe("Typ erkennen", () => { it("erkennt die üblichen Schreibweisen für Unterflur", () => { expect(typErkennen("UF")).toBe("UNTERFLUR"); expect(typErkennen("Unterflurhydrant")).toBe("UNTERFLUR"); expect(typErkennen("hydrant uf")).toBe("UNTERFLUR"); }); it("erkennt Überflur mit und ohne Umlaut", () => { expect(typErkennen("OF")).toBe("UEBERFLUR"); expect(typErkennen("Überflurhydrant")).toBe("UEBERFLUR"); expect(typErkennen("ueberflur")).toBe("UEBERFLUR"); }); it("erkennt offene Gewässer an dem, was die Leute schreiben", () => { expect(typErkennen("Weiher")).toBe("OFFENES_GEWAESSER"); expect(typErkennen("Löschteich")).toBe("OFFENES_GEWAESSER"); expect(typErkennen("Bach hinter dem Hof")).toBe("OFFENES_GEWAESSER"); }); it("erkennt Zisternen und Saugstellen", () => { expect(typErkennen("Zisterne 50 m³")).toBe("ZISTERNE"); expect(typErkennen("Saugstelle A")).toBe("SAUGSTELLE"); }); it("nimmt ohne Angabe den Regelfall an", () => { expect(typErkennen("")).toBe("UNTERFLUR"); expect(typErkennen(null)).toBe("UNTERFLUR"); }); }); describe("Status erkennen", () => { it("erkennt Defekte", () => { expect(statusErkennen("defekt")).toBe("DEFEKT"); expect(statusErkennen("außer Betrieb")).toBe("DEFEKT"); expect(statusErkennen("stillgelegt")).toBe("DEFEKT"); }); it("erkennt Einschränkungen", () => { expect(statusErkennen("eingeschränkt nutzbar")).toBe("EINGESCHRAENKT"); expect(statusErkennen("bedingt")).toBe("EINGESCHRAENKT"); }); it("sagt ohne Angabe „unbekannt“ — nicht „einsatzbereit“", () => { // Der wichtigste Fall: eine Liste ohne Statusspalte darf nicht dazu // führen, dass tausend Hydranten als geprüft gelten. expect(statusErkennen("")).toBe("UNBEKANNT"); expect(statusErkennen(null)).toBe("UNBEKANNT"); expect(statusErkennen("k. A.")).toBe("UNBEKANNT"); }); }); describe("Zahlen lesen", () => { it("liest die Nennweite aus „DN 100“", () => { expect(zahl("DN 100")).toBe(100); }); it("versteht das deutsche Dezimalkomma", () => { expect(zahl("48,512345")).toBeCloseTo(48.512345, 6); }); it("versteht den Tausenderpunkt", () => { expect(zahl("1.234,5")).toBeCloseTo(1234.5, 1); expect(zahl("1.600")).toBe(1600); }); it("lässt echte Zahlen unangetastet", () => { expect(zahl(12.5)).toBe(12.5); }); it("gibt bei Unsinn nichts zurück", () => { expect(zahl("")).toBeNull(); expect(zahl("k. A.")).toBeNull(); expect(zahl(null)).toBeNull(); }); }); describe("Datum lesen", () => { it("liest die deutsche Schreibweise", () => { expect(datum("24.05.2026")).toEqual(new Date("2026-05-24T00:00:00.000Z")); expect(datum("4.5.26")).toEqual(new Date("2026-05-04T00:00:00.000Z")); }); it("liest ISO", () => { expect(datum("2026-05-24")).toEqual(new Date("2026-05-24T00:00:00.000Z")); }); it("gibt bei allem anderen nichts zurück", () => { expect(datum("letztes Jahr")).toBeNull(); expect(datum("")).toBeNull(); }); }); describe("Vertauschte Achsen", () => { it("erkennt vertauschte Werte in Deutschland", () => { // 12,15 als Breite und 48,5 als Länge kann in Bayern nicht stimmen. expect(achsenVertauscht(12.15, 48.5)).toBe(true); }); it("lässt richtige Werte in Ruhe", () => { expect(achsenVertauscht(48.5, 12.15)).toBe(false); }); }); describe("GeoJSON", () => { const geojson = JSON.stringify({ type: "FeatureCollection", features: [ { type: "Feature", geometry: { type: "Point", coordinates: [12.151, 48.502] }, properties: { Nummer: "H-0042", Typ: "Unterflur", DN: "150", Strasse: "Hauptstraße", Status: "in Betrieb", }, }, { type: "Feature", geometry: { type: "LineString", coordinates: [[12, 48]] }, properties: {}, }, { type: "Feature", geometry: { type: "Point", coordinates: [999, 999] }, properties: {}, }, ], }); it("liest Punkte samt Eigenschaften", () => { const ergebnis = ausGeoJson(geojson); expect(ergebnis.hydranten).toHaveLength(1); const [hydrant] = ergebnis.hydranten; expect(hydrant?.externeNummer).toBe("H-0042"); expect(hydrant?.typ).toBe("UNTERFLUR"); expect(hydrant?.nennweite).toBe(150); expect(hydrant?.status).toBe("EINSATZBEREIT"); expect(hydrant?.strasse).toBe("Hauptstraße"); }); it("nimmt die Reihenfolge von GeoJSON ernst: erst Länge, dann Breite", () => { const [hydrant] = ausGeoJson(geojson).hydranten; expect(hydrant?.breitengrad).toBeCloseTo(48.502, 3); expect(hydrant?.laengengrad).toBeCloseTo(12.151, 3); }); it("überspringt, was keine Punktgeometrie ist, und sagt warum", () => { const ergebnis = ausGeoJson(geojson); expect(ergebnis.uebersprungen).toHaveLength(2); expect(ergebnis.uebersprungen[0]?.grund).toContain("Punktgeometrie"); expect(ergebnis.uebersprungen[1]?.grund).toContain("Erde"); }); it("scheitert nicht an einer kaputten Datei", () => { const ergebnis = ausGeoJson("{ das ist kein json"); expect(ergebnis.hydranten).toHaveLength(0); expect(ergebnis.hinweise.join(" ")).toContain("kein gültiges JSON"); }); }); describe("CSV-Zeilen", () => { it("findet die Spalten unter verschiedenen Namen", () => { const ergebnis = ausZeilen([ { "Hydrant-Nr": "4711", Bauart: "Überflur", "DN": "100", Lage: "Bahnhofstraße 3", Latitude: "48,5031", Longitude: "12,1522", Pruefdatum: "12.03.2026", Versorger: "Stadtwerke Musterhausen", }, ]); expect(ergebnis.hydranten).toHaveLength(1); const [hydrant] = ergebnis.hydranten; expect(hydrant?.externeNummer).toBe("4711"); expect(hydrant?.typ).toBe("UEBERFLUR"); expect(hydrant?.nennweite).toBe(100); expect(hydrant?.strasse).toBe("Bahnhofstraße 3"); expect(hydrant?.betreiber).toBe("Stadtwerke Musterhausen"); expect(hydrant?.letztePruefung).toEqual(new Date("2026-03-12T00:00:00.000Z")); expect(hydrant?.breitengrad).toBeCloseTo(48.5031, 4); }); it("dreht vertauschte Achsen zurück und sagt es", () => { const ergebnis = ausZeilen([{ lat: "12,15", lon: "48,50" }]); expect(ergebnis.hydranten[0]?.breitengrad).toBeCloseTo(48.5, 2); expect(ergebnis.hydranten[0]?.laengengrad).toBeCloseTo(12.15, 2); expect(ergebnis.hinweise.join(" ")).toContain("vertauscht"); }); it("überspringt Zeilen ohne Koordinaten und nennt die Zeilennummer", () => { const ergebnis = ausZeilen([ { lat: "48,5", lon: "12,15" }, { Bezeichnung: "Hydrant ohne Ort" }, ]); expect(ergebnis.hydranten).toHaveLength(1); // Zeile 1 ist die Kopfzeile — die zweite Datenzeile ist Zeile 3. expect(ergebnis.uebersprungen).toEqual([{ zeile: 3, grund: "Keine Koordinaten in der Zeile." }]); }); it("warnt, wenn Nummern fehlen — ein zweiter Import legt sonst alles doppelt an", () => { const ergebnis = ausZeilen([ { lat: "48,5", lon: "12,15" }, { lat: "48,51", lon: "12,16" }, ]); expect(ergebnis.hinweise.join(" ")).toContain("keine Nummer"); }); it("kommt mit einer leeren Liste zurecht", () => { const ergebnis = ausZeilen([]); expect(ergebnis.hydranten).toHaveLength(0); expect(ergebnis.uebersprungen).toHaveLength(0); }); });