218 lines
6.1 KiB
JavaScript
218 lines
6.1 KiB
JavaScript
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();
|
|
});
|
|
});
|