Route ground legs along real roads via an OSRM directions proxy

- GET /api/directions proxies OSRM (OSRM_URL env, default public demo
  server) with validation, Leaflet-order geometry, 24h capped cache
- Map draws straight lines first, then upgrades ground legs to
  road-following polylines with routed km (marked road) as directions
  arrive; silent fallback to great-circle on failure; air legs unchanged
- README documents OSRM_URL
This commit is contained in:
2026-07-20 10:54:04 +07:00
parent 65480fd892
commit 4cedab0cf6
7 changed files with 268 additions and 35 deletions
+93
View File
@@ -514,6 +514,99 @@ test('geocode requires auth and a query', async () => {
assert.equal(noQuery.status, 400);
});
// ---------------------------------------------------------------------------
// Directions (mocked upstream — never hits the real OSRM)
// ---------------------------------------------------------------------------
test('directions proxies and caches results (mocked fetch)', async () => {
const { agent } = await createAccount();
const original = global.fetch;
let calls = 0;
global.fetch = async () => {
calls += 1;
return {
ok: true,
json: async () => ({
code: 'Ok',
routes: [
{
distance: 12345.6,
geometry: {
coordinates: [
[98.9853, 18.7883],
[99.0, 18.8],
],
},
},
],
}),
};
};
try {
const first = await agent.get('/api/directions?from=18.7883,98.9853&to=18.8,99.0');
assert.equal(first.status, 200);
assert.equal(first.body.km, 12.3);
assert.deepEqual(first.body.geometry, [
[18.7883, 98.9853],
[18.8, 99.0],
]);
// Second identical request is served from cache -> fetch not called again.
const second = await agent.get('/api/directions?from=18.7883,98.9853&to=18.8,99.0');
assert.equal(second.status, 200);
assert.equal(calls, 1);
} finally {
global.fetch = original;
}
});
test('directions validates from/to params', async () => {
const { agent } = await createAccount();
const missing = await agent.get('/api/directions?from=18.7883,98.9853');
assert.equal(missing.status, 400);
assert.deepEqual(missing.body, { error: 'from and to must be lat,lng' });
const malformed = await agent.get('/api/directions?from=abc&to=18.8,99.0');
assert.equal(malformed.status, 400);
const outOfRange = await agent.get('/api/directions?from=999,98.9853&to=18.8,99.0');
assert.equal(outOfRange.status, 400);
});
test('directions returns 502 on non-Ok OSRM code and on fetch failure', async () => {
const { agent } = await createAccount();
const original = global.fetch;
global.fetch = async () => ({
ok: true,
json: async () => ({ code: 'NoRoute', routes: [] }),
});
try {
const noRoute = await agent.get('/api/directions?from=18.7883,98.9853&to=18.8,99.0');
assert.equal(noRoute.status, 502);
assert.deepEqual(noRoute.body, { error: 'directions unavailable' });
} finally {
global.fetch = original;
}
global.fetch = async () => {
throw new Error('network down');
};
try {
const res = await agent.get('/api/directions?from=18.7883,98.9853&to=18.8,99.0');
assert.equal(res.status, 502);
assert.deepEqual(res.body, { error: 'directions unavailable' });
} finally {
global.fetch = original;
}
});
test('directions requires auth', async () => {
const noAuth = await request(app).get('/api/directions?from=18.7883,98.9853&to=18.8,99.0');
assert.equal(noAuth.status, 401);
});
// ---------------------------------------------------------------------------
// Unknown /api route -> JSON 404
// ---------------------------------------------------------------------------