#!/usr/bin/env node "use strict"; const assert = require("assert"); const fs = require("fs"); const os = require("os"); const path = require("path"); const { compileRoadModel, compileGeometry, validateOverrides } = require("./lib/native-road"); const { compileArea } = require("./compile-native-roads"); const osm = ``; const empty = { schema: "native-road-overrides/v1", overrides: [] }; const initial = compileRoadModel(osm, empty); assert.equal(initial.roads.length, 3); const target = initial.roads.find((road) => road.id === "road:way/10:forward"); const overrides = validateOverrides({ schema: "native-road-overrides/v1", overrides: [{ id: "road-width", kind: "road", roadId: target.id, widthMeters: 9, laneCount: 2, sidewalkLeft: false }] }, initial); const model = compileRoadModel(osm, overrides); const edited = model.roads.find((road) => road.id === target.id); assert.equal(edited.widthMeters, 9); assert.equal(edited.provenance.widthMeters, "override:road-width"); assert.equal(edited.sidewalkLeft, false); const geometry = compileGeometry(model); assert.equal(geometry.roadSurface.features.length, 2); assert.ok(geometry.roadSurface.features.every((feature) => feature.geometry.coordinates[0].length >= 5)); assert.equal(geometry.laneCenterlines.features.length, model.roads.reduce((sum, road) => sum + road.laneCount, 0)); assert.ok(geometry.connectors.features.length > 0); assert.ok(geometry.connectors.features.every((feature) => feature.geometry.coordinates.length === 13)); assert.ok(geometry.connectors.features.every((feature) => feature.properties.node_id)); assert.ok(geometry.movements.length >= geometry.connectors.features.length); assert.ok(geometry.movements.every((movement) => movement.id.startsWith("movement:") && movement.connectorId.startsWith("connector:"))); assert.ok(geometry.movements.every((movement) => ["connector", "continuous", "deferred-too-long"].includes(movement.geometryStatus))); assert.ok(geometry.intersectionSurface.features.every((feature) => feature.properties.rule === "junction-approach-envelope/v2")); assert.ok(geometry.intersectionSurface.features.every((feature) => ["approach-envelope", "connector-convex-fallback"].includes(feature.properties.boundary_mode))); const connection = initial.connections[0]; assert.ok(initial.connections.every((item) => item.fromEndpointId.endsWith(":end") && item.toEndpointId.endsWith(":start"))); assert.equal(initial.connections.length, new Set(initial.connections.map((item) => `${item.fromEndpointId}->${item.toEndpointId}`)).size); const connectionOverrides = validateOverrides({ schema: "native-road-overrides/v1", overrides: [{ id: "disconnect", kind: "junction-connection", fromEndpointId: connection.fromEndpointId, toEndpointId: connection.toEndpointId, enabled: false }] }, initial); assert.equal(validateOverrides(connectionOverrides).overrides.length, 1); assert.equal(compileRoadModel(osm, connectionOverrides).connections.find((item) => item.id === connection.id).enabled, false); assert.ok(compileGeometry(compileRoadModel(osm, connectionOverrides)).connectors.features.length < geometry.connectors.features.length); const disconnectedOsm = ``; const disconnected = compileRoadModel(disconnectedOsm, empty); const from = disconnected.endpoints.find((endpoint) => endpoint.roadId === "road:way/20:forward" && endpoint.side === "end"); const to = disconnected.endpoints.find((endpoint) => endpoint.roadId === "road:way/21:forward" && endpoint.side === "start"); const manualOverrides = validateOverrides({ schema: "native-road-overrides/v1", overrides: [{ id: "manual", kind: "junction-connection", fromEndpointId: from.id, toEndpointId: to.id, enabled: true }] }, disconnected); assert.ok(compileRoadModel(disconnectedOsm, manualOverrides).connections.some((item) => item.fromEndpointId === from.id && item.toEndpointId === to.id)); assert.throws(() => validateOverrides({ schema: "native-road-overrides/v1", overrides: [{ id: "same-way", kind: "junction-connection", fromEndpointId: initial.endpoints.find((endpoint) => endpoint.roadId === "road:way/10:forward" && endpoint.side === "end").id, toEndpointId: initial.endpoints.find((endpoint) => endpoint.roadId === "road:way/10:backward" && endpoint.side === "start").id, enabled: true }] }, initial), /manual junction connection/); const turnOsm = ``; const turnModel = compileRoadModel(turnOsm, empty); const turnGeometry = compileGeometry(turnModel); assert.equal(turnGeometry.connectors.features.length, 1); assert.match(turnGeometry.connectors.features[0].properties.from_lane_id, /road:way\/30:forward:1$/); assert.match(turnGeometry.connectors.features[0].properties.to_lane_id, /road:way\/31:forward:1$/); const laneOverrides = validateOverrides({ schema: "native-road-overrides/v1", overrides: [{ id: "block-left", kind: "lane-connection", fromLaneId: turnGeometry.connectors.features[0].properties.from_lane_id, toLaneId: turnGeometry.connectors.features[0].properties.to_lane_id, enabled: false }] }, turnModel); assert.equal(compileGeometry(turnModel, laneOverrides).connectors.features.length, 0); assert.equal(compileGeometry(turnModel, laneOverrides).movements.length, 0); assert.throws(() => validateOverrides({ schema: "native-road-overrides/v1", overrides: [{ id: "bad", kind: "road", roadId: "missing", widthMeters: 4 }] }, initial), /Unknown road/); const freshArea = fs.mkdtempSync(path.join(os.tmpdir(), "native-road-fresh-area-")); try { const input = path.join(freshArea, "input.osm"); const outputRoot = path.join(freshArea, "outputs"); const config = path.join(freshArea, "area.json"); fs.writeFileSync(input, osm); fs.writeFileSync(config, JSON.stringify({ id: "fresh", input, outputRoot })); const compiledArea = compileArea(config); assert.equal(compiledArea.result.areaId, "fresh"); assert.ok(fs.existsSync(path.join(outputRoot, "fresh", "native-road", "compiled.json"))); assert.equal(compiledArea.comparison.schema, "native-road-comparison/v2"); assert.equal(compiledArea.comparison.nativeRoadCount, compiledArea.result.model.roads.length); assert.equal(compiledArea.comparison.nativePublishedMovementCount, compiledArea.result.movements.filter((movement) => movement.geometryPublished).length); } finally { fs.rmSync(freshArea, { recursive: true, force: true }); } console.log("native road tests passed");