import { describe, expect, it } from "vitest"; import { deduplicateMarineContactFeatures, FeatureService, normalizeDepthFeatureProperties, normalizeMarineFeatureProperties } from "../src/services/features.js"; describe("marine feature data modes", () => { it("does not expose demo markers when production disables demo data without PostGIS", async () => { const service = new FeatureService({ databaseUrl: undefined, demoData: false }); const result = await service.getFeatures({ bbox: [5, 50, 15, 56], layers: ["seamarks", "locks", "harbours"] }); expect(result.features).toEqual([]); expect(result.metadata.source).toBe("unavailable"); await service.close(); }); }); describe("marine feature normalization", () => { it("formats bridge clearance labels from known OSM height tags", () => { const properties = normalizeMarineFeatureProperties({ layer: "bridges", name: "Kaiser-Wilhelm-Brücke", source: "osm", sourceId: "w123", properties: { bridge: "movable", maxheight: "3" } }); expect(properties.clearance_m).toBe(3); expect(properties.clearance_label).toBe("H 3 m"); expect(properties.label).toBe("Kaiser-Wilhelm-Brücke H 3 m"); }); it("ignores non-numeric default bridge heights", () => { const properties = normalizeMarineFeatureProperties({ layer: "bridges", name: null, source: "osm", sourceId: "w124", properties: { bridge: "yes", maxheight: "default" } }); expect(properties.clearance_m).toBeNull(); expect(properties.label).toBeNull(); }); it("normalizes contact aliases, address and database timestamps", () => { const properties = normalizeMarineFeatureProperties({ layer: "locks", name: "Schleuse Hamm", source: "osm", sourceId: "w166568834", updatedAt: new Date("2026-07-19T08:30:00.000Z"), properties: { "contact:phone": "+49 2381 9019280", "contact:website": "https://example.test/schleuse-hamm", "contact:email": "schleuse@example.test", "seamark:lock_basin:communication_channel": "18", opening_hours: "24/7", operator: "WSV", "addr:street": "Fährstraße", "addr:housenumber": "1", "addr:postcode": "59071", "addr:city": "Hamm", "addr:country": "DE" } }); expect(properties.phone).toBe("+49 2381 9019280"); expect(properties.website).toBe("https://example.test/schleuse-hamm"); expect(properties.email).toBe("schleuse@example.test"); expect(properties.vhf).toBe("18"); expect(properties.openingHours).toBe("24/7"); expect(properties.operator).toBe("WSV"); expect(properties.address).toBe("Fährstraße 1, 59071 Hamm, DE"); expect(properties.source).toBe("osm"); expect(properties.sourceId).toBe("w166568834"); expect(properties.updatedAt).toBe("2026-07-19T08:30:00.000Z"); }); it("prefers direct contact fields and returns stable null values when details are absent", () => { const properties = normalizeMarineFeatureProperties({ layer: "harbours", name: "Marina Emden", source: "osm", sourceId: "n1", properties: { phone: "+49 4921 123", "contact:phone": "+49 4921 999" } }); expect(properties.phone).toBe("+49 4921 123"); expect(properties.website).toBeNull(); expect(properties.email).toBeNull(); expect(properties.vhf).toBeNull(); expect(properties.openingHours).toBeNull(); expect(properties.operator).toBeNull(); expect(properties.address).toBeNull(); expect(properties.updatedAt).toBeNull(); }); it("keeps navigation details but removes unrelated bulk OSM tags from viewport features", () => { const properties = normalizeMarineFeatureProperties({ layer: "harbours", name: "Testhafen", source: "osm", sourceId: "w42", properties: { leisure: "marina", electricity: "yes", "contact:phone": "+49 40 123", "source:geometry": "survey", note: "A very large unrelated note that is not consumed by the client" } }); expect(properties.leisure).toBe("marina"); expect(properties.electricity).toBe("yes"); expect(properties.phone).toBe("+49 40 123"); expect(properties).not.toHaveProperty("source:geometry"); expect(properties).not.toHaveProperty("note"); }); it("formats fairway depth labels", () => { const properties = normalizeDepthFeatureProperties({ name: "Nord-Ostsee-Kanal", source: "osm", sourceId: "w456", minDepthM: "14", properties: { depth: "14" } }); expect(properties.depth_m).toBe(14); expect(properties.depth_label).toBe("14 m"); expect(properties.label).toBe("Nord-Ostsee-Kanal 14 m"); }); }); describe("marine contact feature deduplication", () => { it("returns one stable facility feature and reports how many raw objects were merged", () => { const result = deduplicateMarineContactFeatures([ { type: "Feature", id: "12", geometry: { type: "Point", coordinates: [7.867, 51.695] }, properties: { layer: "locks", source: "osm", sourceId: "w12", name: "Schleuse Werries", website: "https://example.test/werries" } }, { type: "Feature", id: "99", geometry: { type: "Point", coordinates: [7.86708, 51.69508] }, properties: { layer: "locks", source: "euris", sourceId: "DEHMM00301LOCKS00404", name: "Werries", phone: "+49 2381 9019-290", vhf: "22", "ref:EU:RIS": "DEHMM00301LOCKS00404" } }, { type: "Feature", id: "bridge-1", geometry: { type: "LineString", coordinates: [[7.8, 51.6], [7.9, 51.7]] }, properties: { layer: "bridges", source: "osm", name: "Testbrücke" } } ]); expect(result.metadata).toEqual({ inputPoiCount: 2, outputPoiCount: 1, mergedObjectCount: 1 }); expect(result.features).toHaveLength(2); expect(result.features.find((feature) => feature.properties.layer === "locks")).toMatchObject({ id: "marine-poi:locks:euris:DEHMM00301LOCKS00404", properties: { name: "Werries", phone: "+49 2381 9019-290", website: "https://example.test/werries", dedupeMemberCount: 2 } }); }); });