'use strict'; const assert = require('assert/strict'); const fs = require('fs'); const os = require('os'); const path = require('path'); const { nativeRoadEdits } = require('../src'); const { EDITS_SCHEMA, CONSTRAINT_ANCHORS, CONSTRAINT_KINDS, emptyEditDocument, validateEditDocument } = nativeRoadEdits; const { loadEditDocument, saveEditDocument } = nativeRoadEdits; const DIGEST = 'a'.repeat(64); function snapshot() { return { coordinate: [114.1, 22.5], tangentAzimuth: 87.5, roadLengthMeters: 240.75, osmNodeIds: ['node/1', 'node/2'], }; } function anchorFor(kind) { const type = CONSTRAINT_ANCHORS[kind]; if (type === 'road-interval') return { type, roadId: 'road:way/1:forward', startStation: 0.25, endStation: 0.75, side: 'left' }; if (type === 'junction-approach') return { type, nodeId: 'node/9', segmentId: 'road:way/1:segment/0' }; return { type, nodeId: 'node/9', incomingRoadId: 'road:way/1:forward', outgoingRoadId: 'road:way/2:forward' }; } function valueFor(kind) { if (kind === 'road-edge-offset') return { offsetMeters: 0.8, transition: 'smoothstep' }; if (kind === 'road-sidewalk-width') return { widthMeters: 2.5, transition: 'linear' }; if (kind === 'road-lane-divider') return { boundaryIndex: 2, offsetMeters: -1.6, transition: 'smoothstep' }; if (kind === 'junction-approach-width') return { widthMeters: 12.5 }; if (kind === 'junction-cutback') return { cutbackMeters: 4 }; return { radiusMeters: 6 }; } function constraintFor(kind, id = `c-${kind}`) { return { id, kind, anchor: anchorFor(kind), anchorSnapshot: snapshot(), value: valueFor(kind), enabled: true, status: 'exact', provenance: { operationId: 'op-1', createdAt: '2026-08-26T10:00:00.000Z', author: 'dingkang' }, }; } // A document exercising all six kinds at once, so a taxonomy drift breaks here. function fullDocument() { const document = emptyEditDocument({ osmSha256: DIGEST, areaConfigSha256: DIGEST, compilerGeometryVersion: 'native-road-package/v1.1', }); document.constraints = CONSTRAINT_KINDS.map((kind) => constraintFor(kind)); document.operations = [ { id: 'op-1', createdAt: '2026-08-26T10:00:00.000Z', constraintIds: document.constraints.map((constraint) => constraint.id), author: 'dingkang', }, ]; return document; } function rejects(document, pattern, what) { assert.throws(() => validateEditDocument(document), pattern, what); } // --- taxonomy is the one defined in design.md, no drift ----------------------- assert.equal(EDITS_SCHEMA, 'native-road-edits/v2'); assert.deepEqual(CONSTRAINT_KINDS, [ 'road-edge-offset', 'road-sidewalk-width', 'road-lane-divider', 'junction-approach-width', 'junction-cutback', 'junction-corner-radius', ]); // --- round trip is lossless -------------------------------------------------- const document = fullDocument(); assert.deepEqual(validateEditDocument(document), document); assert.deepEqual(JSON.parse(JSON.stringify(document)), document); assert.equal(emptyEditDocument().documentVersion, 0); assert.deepEqual(emptyEditDocument().constraints, []); // --- unknown kind is rejected and the message names the constraint ----------- const unknownKind = fullDocument(); unknownKind.constraints[0].kind = 'corridor-profile'; rejects(unknownKind, /c-road-edge-offset: kind must be one of .*got corridor-profile/, 'unknown kind'); // --- a kind may only carry its own anchor type ------------------------------- const wrongAnchor = fullDocument(); wrongAnchor.constraints[0].anchor = anchorFor('junction-cutback'); rejects(wrongAnchor, /anchor\.type must be road-interval for kind road-edge-offset/, 'anchor/kind mismatch'); // --- station range and ordering --------------------------------------------- for (const [station, label] of [ [-0.1, 'below zero'], [1.5, 'above one'], ]) { const bad = fullDocument(); bad.constraints[0].anchor.startStation = station; rejects(bad, /anchor\.startStation must be a normalized station in 0\.\.1/, `station ${label}`); } const inverted = fullDocument(); inverted.constraints[0].anchor.startStation = 0.8; inverted.constraints[0].anchor.endStation = 0.2; rejects(inverted, /anchor\.startStation must be strictly less than anchor\.endStation/, 'inverted interval'); const degenerate = fullDocument(); degenerate.constraints[0].anchor.startStation = 0.5; degenerate.constraints[0].anchor.endStation = 0.5; rejects(degenerate, /strictly less than/, 'zero-length interval'); // --- boundaryIndex: schema owns "< 1" and "not an integer"; the lane-count // bound is the solver's job and surfaces as `stale`, per design.md ------------ for (const [boundaryIndex, label] of [ [0, 'zero'], [-2, 'negative'], [1.5, 'fractional'], ]) { const bad = fullDocument(); bad.constraints[2].value.boundaryIndex = boundaryIndex; rejects(bad, /c-road-lane-divider: value\.boundaryIndex must be an integer >= 1/, `boundaryIndex ${label}`); } // --- per-kind value ranges --------------------------------------------------- const negativeSidewalk = fullDocument(); negativeSidewalk.constraints[1].value.widthMeters = -1; rejects(negativeSidewalk, /c-road-sidewalk-width: value\.widthMeters must be a non-negative/, 'negative sidewalk'); const zeroSidewalk = fullDocument(); zeroSidewalk.constraints[1].value.widthMeters = 0; assert.deepEqual(validateEditDocument(zeroSidewalk), zeroSidewalk, 'width 0 disables the sidewalk, it is legal'); const zeroApproach = fullDocument(); zeroApproach.constraints[3].value.widthMeters = 0; rejects(zeroApproach, /c-junction-approach-width: value\.widthMeters must be a positive/, 'zero approach width'); const badTransition = fullDocument(); badTransition.constraints[0].value.transition = 'ease'; rejects(badTransition, /value\.transition must be smoothstep or linear/, 'unknown transition'); const nonFinite = fullDocument(); nonFinite.constraints[0].value.offsetMeters = Number.NaN; rejects(nonFinite, /value\.offsetMeters must be a finite/, 'NaN offset'); // --- status enum ------------------------------------------------------------ const badStatus = fullDocument(); badStatus.constraints[0].status = 'unknown'; rejects(badStatus, /status must be exact, recheck, pending, conflicted or stale/, 'unknown status'); // --- ids, provenance and referential integrity ------------------------------ const duplicate = fullDocument(); duplicate.constraints.push(constraintFor('junction-cutback', 'c-road-edge-offset')); rejects(duplicate, /id is duplicated \(c-road-edge-offset\)/, 'duplicate constraint id'); const danglingProvenance = fullDocument(); danglingProvenance.constraints[0].provenance.operationId = 'op-missing'; rejects(danglingProvenance, /provenance\.operationId references an operation that is not recorded/, 'dangling op'); const danglingOperation = fullDocument(); danglingOperation.operations[0].constraintIds.push('c-missing'); rejects( danglingOperation, /operation op-1: constraintIds references an unknown constraint \(c-missing\)/, 'dangling c', ); const missingSnapshot = fullDocument(); delete missingSnapshot.constraints[0].anchorSnapshot; rejects(missingSnapshot, /anchorSnapshot is required so a reimport can relocate the anchor/, 'missing snapshot'); // --- base digests ----------------------------------------------------------- const badDigest = fullDocument(); badDigest.base.osmSha256 = 'not-a-digest'; rejects(badDigest, /base\.osmSha256 must be a 64-character lowercase sha256 digest or null/, 'bad digest'); // --- schema guard ----------------------------------------------------------- rejects({ ...fullDocument(), schema: 'native-road-edits/v1' }, /Edits must use native-road-edits\/v2/, 'old schema'); rejects(null, /Edits must use native-road-edits\/v2/, 'null document'); // --- persistence: version bumps once per write, writes are atomic ----------- const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'road-edits-')); const file = path.join(directory, 'nested', 'native-road-edits.json'); assert.equal(loadEditDocument(file).documentVersion, 0, 'a missing file reads as an empty document'); const firstWrite = saveEditDocument(file, fullDocument()); assert.equal(firstWrite.documentVersion, 1); assert.equal(loadEditDocument(file).documentVersion, 1); const secondWrite = saveEditDocument(file, firstWrite); assert.equal(secondWrite.documentVersion, 2); assert.equal(loadEditDocument(file).documentVersion, 2); assert.deepEqual(loadEditDocument(file).constraints, fullDocument().constraints, 'constraints survive the round trip'); assert.equal(fs.existsSync(`${file}.staging`), false, 'the staging file is renamed away, not left behind'); // An invalid document must not reach disk at all. const invalid = fullDocument(); invalid.constraints[0].value.offsetMeters = 'wide'; assert.throws(() => saveEditDocument(file, invalid), /value\.offsetMeters must be a finite/); assert.equal(loadEditDocument(file).documentVersion, 2, 'a rejected write leaves the stored version untouched'); fs.rmSync(directory, { recursive: true, force: true }); console.log('native road edits document tests passed');