Initial Watermaps import
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
MAX_DETAIL_LIMIT,
|
||||
buildLockRecord,
|
||||
collectEurisLocks,
|
||||
compactLocksFilter,
|
||||
normalizePhones,
|
||||
parseCountries,
|
||||
parseDetailLimit,
|
||||
requestJson,
|
||||
risIndexFilter,
|
||||
runEurisSync,
|
||||
} from "../../../scripts/sync-euris-locks.mjs";
|
||||
|
||||
function jsonResponse(payload, init = {}) {
|
||||
return new Response(JSON.stringify(payload), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
...init,
|
||||
});
|
||||
}
|
||||
|
||||
function paginatedFixtureFetch({ recordRequests = [] } = {}) {
|
||||
const compact = [
|
||||
{
|
||||
locode: "DELOCK001",
|
||||
objectName: "Testschleuse Nord",
|
||||
waterwayName: "Testkanal",
|
||||
contactPhone: "0049 201 12345",
|
||||
comcha: " 18 ",
|
||||
},
|
||||
{ locode: "DELOCK002", objectName: "Testschleuse Süd" },
|
||||
];
|
||||
const ris = [
|
||||
{
|
||||
isrs: "DELOCK001",
|
||||
objectName: "Testschleuse Nord",
|
||||
countryCode: "DE",
|
||||
lon: 7.1,
|
||||
lat: 51.5,
|
||||
source: "WSV, Wadaba",
|
||||
},
|
||||
{
|
||||
isrs: "DELOCK002",
|
||||
objectName: "Testschleuse Süd",
|
||||
countryCode: "DE",
|
||||
lon: 7.2,
|
||||
lat: 51.6,
|
||||
source: "WSV, Wadaba",
|
||||
},
|
||||
{
|
||||
isrs: "DELOCK003",
|
||||
objectName: "Nur im RIS-Index",
|
||||
countryCode: "DE",
|
||||
lon: 7.3,
|
||||
lat: 51.7,
|
||||
source: "WSV",
|
||||
},
|
||||
];
|
||||
|
||||
return async (input, init) => {
|
||||
const url = new URL(String(input));
|
||||
recordRequests.push({ url, init });
|
||||
const skip = Number(url.searchParams.get("$skip"));
|
||||
const top = Number(url.searchParams.get("$top"));
|
||||
|
||||
if (url.pathname.endsWith("/GetCompactLocks")) {
|
||||
return jsonResponse({ count: compact.length, items: compact.slice(skip, skip + top) });
|
||||
}
|
||||
if (url.pathname.endsWith("/GetRISIndexObjects")) {
|
||||
return jsonResponse({ count: ris.length, items: ris.slice(skip, skip + top) });
|
||||
}
|
||||
throw new Error(`Unerwartete Test-URL: ${url}`);
|
||||
};
|
||||
}
|
||||
|
||||
describe("EuRIS lock synchronization", () => {
|
||||
it("validates country filters and caps optional detail requests", () => {
|
||||
expect(parseCountries(" de,NL de ")).toEqual(["DE", "NL"]);
|
||||
expect(() => parseCountries("DEU")).toThrow(/ISO-Ländercode/u);
|
||||
expect(parseDetailLimit("999")).toBe(MAX_DETAIL_LIMIT);
|
||||
expect(compactLocksFilter(["DE", "NL"])).toContain("startswith(locode,'DE')");
|
||||
expect(risIndexFilter(["DE", "NL"])).toBe(
|
||||
"(countryCode eq 'DE' or countryCode eq 'NL') and function eq 'lokare'",
|
||||
);
|
||||
});
|
||||
|
||||
it("normalizes EuRIS contact data and always uses the RIS coordinate", () => {
|
||||
expect(normalizePhones("0049 201 12345; +49 201 67890")).toEqual([
|
||||
"+49 201 12345",
|
||||
"+49 201 67890",
|
||||
]);
|
||||
|
||||
const record = buildLockRecord({
|
||||
compact: {
|
||||
locode: "DELOCK001",
|
||||
objectName: "Lock",
|
||||
waterwayName: "Canal",
|
||||
contactPhone: "0049 201 12345",
|
||||
comcha: " 18 ",
|
||||
},
|
||||
ris: {
|
||||
isrs: "DELOCK001",
|
||||
lon: "7.123",
|
||||
lat: "51.456",
|
||||
source: "WSV, Wadaba",
|
||||
countryCode: "DE",
|
||||
},
|
||||
detail: {
|
||||
facility: {
|
||||
street: "Uferstraße 1",
|
||||
postCode: "12345",
|
||||
city: "Teststadt",
|
||||
country: "DE",
|
||||
contacts: [
|
||||
{
|
||||
company: "Wasserstraßenverwaltung",
|
||||
emails: ["lock@example.test"],
|
||||
phones: ["+49 201 999"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
fetchedAt: "2026-07-20T10:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(record).toMatchObject({
|
||||
sourceId: "DELOCK001",
|
||||
longitude: 7.123,
|
||||
latitude: 51.456,
|
||||
properties: {
|
||||
phone: "+49 201 12345",
|
||||
vhf: "18",
|
||||
waterway_name: "Canal",
|
||||
operator: "Wasserstraßenverwaltung",
|
||||
email: "lock@example.test",
|
||||
address: "Uferstraße 1, 12345, Teststadt, DE",
|
||||
upstream_source: "WSV, Wadaba",
|
||||
fetched_at: "2026-07-20T10:00:00.000Z",
|
||||
},
|
||||
});
|
||||
expect(record.properties.source_url).toContain("isrs=DELOCK001");
|
||||
});
|
||||
|
||||
it("honors Retry-After for throttled requests and sends an optional bearer token", async () => {
|
||||
const waits = [];
|
||||
const headers = [];
|
||||
let calls = 0;
|
||||
const fetchImpl = async (_url, init) => {
|
||||
calls += 1;
|
||||
headers.push(new Headers(init.headers));
|
||||
if (calls === 1) {
|
||||
return new Response("rate limited", {
|
||||
status: 429,
|
||||
headers: { "retry-after": "2" },
|
||||
});
|
||||
}
|
||||
return jsonResponse({ ok: true });
|
||||
};
|
||||
|
||||
await expect(
|
||||
requestJson("https://example.test/euris", {
|
||||
fetchImpl,
|
||||
token: "secret-token",
|
||||
sleepImpl: async (milliseconds) => waits.push(milliseconds),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true });
|
||||
expect(waits).toEqual([2_000]);
|
||||
expect(headers.every((entry) => entry.get("authorization") === "Bearer secret-token")).toBe(true);
|
||||
});
|
||||
|
||||
it("paginates compact and RIS data stably, joins by ISRS and keeps RIS-only locks", async () => {
|
||||
const requests = [];
|
||||
const result = await collectEurisLocks({
|
||||
countries: ["DE"],
|
||||
detailLimit: 0,
|
||||
pageSize: 1,
|
||||
fetchImpl: paginatedFixtureFetch({ recordRequests: requests }),
|
||||
token: "token",
|
||||
fetchedAt: "2026-07-20T10:00:00.000Z",
|
||||
});
|
||||
|
||||
expect(result.records.map((record) => record.sourceId)).toEqual([
|
||||
"DELOCK001",
|
||||
"DELOCK002",
|
||||
"DELOCK003",
|
||||
]);
|
||||
expect(result.stats).toMatchObject({
|
||||
compactLocks: 2,
|
||||
risLocks: 3,
|
||||
joinedLocks: 2,
|
||||
risOnlyLocks: 1,
|
||||
storedLocks: 3,
|
||||
});
|
||||
expect(requests.every(({ url }) => Number(url.searchParams.get("$top")) <= 100)).toBe(true);
|
||||
expect(requests.every(({ url }) => url.searchParams.has("$orderby"))).toBe(true);
|
||||
expect(
|
||||
requests.every(({ init }) => new Headers(init.headers).get("authorization") === "Bearer token"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not invoke the database writer in dry-run mode", async () => {
|
||||
const writer = vi.fn();
|
||||
const result = await runEurisSync({
|
||||
dryRun: true,
|
||||
writer,
|
||||
collectorOptions: {
|
||||
countries: ["DE"],
|
||||
pageSize: 100,
|
||||
fetchImpl: paginatedFixtureFetch(),
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.records).toHaveLength(3);
|
||||
expect(writer).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user