diff --git a/deploy/scripts/common.sh b/deploy/scripts/common.sh index 12b6fef..d834b53 100755 --- a/deploy/scripts/common.sh +++ b/deploy/scripts/common.sh @@ -433,7 +433,56 @@ wm_wait_for_health() { wm_smoke_test_route() { wm_compose exec --no-TTY watermaps node --input-type=module --eval ' - const expectedSource = "local-geofabrik-germany+netherlands"; + // ROUTE_SOURCE_VALIDATOR_START + const localEdgeSource = "local-geofabrik-germany+netherlands"; + const postgisEdgeSource = "postgis-osm"; + const bboxSource = String.raw`-?\d{1,3}\.\d{3}(?:--?\d{1,3}\.\d{3}){3}`; + const localGraphPattern = new RegExp( + `^fairway-graph:local-geofabrik-${bboxSource}$` + ); + const combinedGraphPattern = new RegExp( + `^fairway-graph:combined-postgis-(${bboxSource})\\+local-geofabrik-\\1$` + ); + const hasExpectedRouteSources = (dataSources) => { + if ( + !Array.isArray(dataSources) + || dataSources.length < 2 + || dataSources.some((source) => typeof source !== "string") + || new Set(dataSources).size !== dataSources.length + ) { + return false; + } + + const graphSources = dataSources.filter((source) => + source.startsWith("fairway-graph:") + ); + const edgeSources = dataSources.filter((source) => + !source.startsWith("fairway-graph:") + ); + if ( + graphSources.length !== 1 + || edgeSources.length === 0 + || edgeSources.some( + (source) => source !== localEdgeSource && source !== postgisEdgeSource + ) + ) { + return false; + } + + const graphSource = graphSources[0]; + return ( + localGraphPattern.test(graphSource) + && edgeSources.length === 1 + && edgeSources[0] === localEdgeSource + ) || ( + combinedGraphPattern.test(graphSource) + && ( + edgeSources.includes(localEdgeSource) + || edgeSources.includes(postgisEdgeSource) + ) + ); + }; + // ROUTE_SOURCE_VALIDATOR_END const routeChecks = [ { name: "Emden–Ditzum", @@ -552,8 +601,7 @@ wm_smoke_test_route() { alternative.routingMode === "fairway" && Array.isArray(alternative.geometry?.coordinates) && alternative.geometry.coordinates.length >= 2 - && Array.isArray(alternative.dataSources) - && alternative.dataSources.includes(expectedSource) + && hasExpectedRouteSources(alternative.dataSources) ); const routeSignatures = [ route.geometry?.coordinates, @@ -563,8 +611,7 @@ wm_smoke_test_route() { route.routingMode !== "fairway" || !Array.isArray(routeCoordinates) || routeCoordinates.length < (routeCheck.minimumCoordinates ?? 2) || - !Array.isArray(route.dataSources) || - !route.dataSources.includes(expectedSource) || + !hasExpectedRouteSources(route.dataSources) || alternatives.length < routeCheck.minimumAlternatives || route.distanceNm < (routeCheck.minimumDistanceNm ?? 0) || route.distanceNm > (routeCheck.maximumDistanceNm ?? Number.POSITIVE_INFINITY) || diff --git a/deploy/scripts/tests/route-smoke-sources.test.sh b/deploy/scripts/tests/route-smoke-sources.test.sh new file mode 100755 index 0000000..534db2b --- /dev/null +++ b/deploy/scripts/tests/route-smoke-sources.test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +COMMON_SH="$ROOT_DIR/deploy/scripts/common.sh" +TEST_DIR="$(mktemp -d)" +trap 'rm -rf "$TEST_DIR"' EXIT + +VALIDATOR_JS="$TEST_DIR/route-source-validator.mjs" +awk ' + /ROUTE_SOURCE_VALIDATOR_START/ { capture = 1; next } + /ROUTE_SOURCE_VALIDATOR_END/ { capture = 0 } + capture { + sub(/^ /, "") + print + } +' "$COMMON_SH" >"$VALIDATOR_JS" + +[[ -s "$VALIDATOR_JS" ]] || { + printf 'Der Datenquellen-Validator konnte nicht aus common.sh gelesen werden.\n' >&2 + exit 1 +} + +printf '%s\n' \ + 'const assertSourceValidity = (expected, dataSources, description) => {' \ + ' const actual = hasExpectedRouteSources(dataSources);' \ + ' if (actual !== expected) {' \ + ' console.error(`${description}: erwartet ${expected}, erhalten ${actual}`);' \ + ' process.exit(1);' \ + ' }' \ + '};' \ + 'const bbox = "7.037-53.192-7.623-53.615";' \ + 'const localSource = `fairway-graph:local-geofabrik-${bbox}`;' \ + 'const combinedSource = `fairway-graph:combined-postgis-${bbox}+local-geofabrik-${bbox}`;' \ + 'assertSourceValidity(true, [localSource, "local-geofabrik-germany+netherlands"], "lokaler Index");' \ + 'assertSourceValidity(true, [combinedSource, "postgis-osm"], "kombinierter Produktionsgraph");' \ + 'assertSourceValidity(true, [combinedSource, "local-geofabrik-germany+netherlands"], "kombinierter Graph mit lokalen Kanten");' \ + 'assertSourceValidity(true, [combinedSource, "postgis-osm", "local-geofabrik-germany+netherlands"], "kombinierter Graph mit beiden Kantenquellen");' \ + 'assertSourceValidity(false, ["local-geofabrik-germany+netherlands"], "lokale Kante ohne Graph");' \ + 'assertSourceValidity(false, [combinedSource], "kombinierter Graph ohne Kantenquelle");' \ + 'assertSourceValidity(false, [`fairway-graph:combined-postgis-${bbox}+local-geofabrik-7.000-53.000-8.000-54.000`, "postgis-osm"], "abweichende Ausschnitte");' \ + 'assertSourceValidity(false, ["fairway-graph:combined-postgis-anything+local-geofabrik-anything", "postgis-osm"], "ungültiger Ausschnitt");' \ + 'assertSourceValidity(false, ["postgis-osm"], "fehlender lokaler Graph");' \ + 'assertSourceValidity(false, [combinedSource, "postgis-osm", "osm-overpass"], "unbekannte Kantenquelle");' \ + 'assertSourceValidity(false, [`${combinedSource}+osm-overpass-live`, "postgis-osm"], "zusätzlicher Live-Graph");' \ + 'assertSourceValidity(false, [combinedSource, "postgis-osm", "postgis-osm"], "doppelte Quelle");' \ + 'assertSourceValidity(false, [combinedSource, 42], "Nicht-String-Quelle");' \ + 'assertSourceValidity(false, null, "fehlende Quellenliste");' \ + 'console.log("Routing-Smoke-Test-Datenquellen: OK");' \ + >>"$VALIDATOR_JS" + +node "$VALIDATOR_JS" diff --git a/package.json b/package.json index c3f09e9..edfd8b9 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "setup:local-routing": "./scripts/setup-local-routing.sh", "sync:euris-locks": "node scripts/sync-euris-locks.mjs", "test": "npm run build --workspace @watermaps/shared && npm run test --workspace @watermaps/shared && npm run test --workspace @watermaps/api && npm run test --workspace @watermaps/web && npm run test:local-routing && npm run test:deployment", - "test:deployment": "bash deploy/scripts/tests/image-references.test.sh && bash deploy/scripts/tests/download-geofabrik.test.sh && bash deploy/scripts/tests/auto-deploy.test.sh && bash deploy/scripts/tests/upload-and-deploy.test.sh && bash deploy/scripts/tests/route-data-helpers.test.sh && bash deploy/scripts/tests/update-route-data.test.sh", + "test:deployment": "bash deploy/scripts/tests/image-references.test.sh && bash deploy/scripts/tests/download-geofabrik.test.sh && bash deploy/scripts/tests/route-smoke-sources.test.sh && bash deploy/scripts/tests/auto-deploy.test.sh && bash deploy/scripts/tests/upload-and-deploy.test.sh && bash deploy/scripts/tests/route-data-helpers.test.sh && bash deploy/scripts/tests/update-route-data.test.sh", "test:e2e": "npm run test:e2e --workspace @watermaps/web", "test:local-routing": "PYTHONPATH=.tools/python python3 -m unittest discover -s scripts/tests -p 'test_*.py'", "typecheck": "npm run build --workspace @watermaps/shared && npm run typecheck --workspace @watermaps/shared && npm run typecheck --workspace @watermaps/api && npm run typecheck --workspace @watermaps/web"