fix(server): keep no OSRM stand-in for a day the TREK Valhalla routes

A road trip day OSRM drew while the relay could not answer was cached for five
minutes, and a key added or removed kept serving the old engine's answer. The
stand-in is no longer cached and the key's availability is part of the cache
key. A walk kept off the boats may still go to FOSSGIS, which is now said and
tested.
This commit is contained in:
Maurice
2026-10-29 12:25:50 +01:00
committed by Maurice
parent e3a8c6fb8a
commit 10e0e59c3f
2 changed files with 47 additions and 2 deletions
@@ -422,7 +422,11 @@ export class RoadtripRouterService {
const footOnly = options.footOnly === true && profile === 'walking';
// A tour's own costing goes with a way on foot or by bike; a drive has its avoidances instead.
const hiking = profile === 'walking' || profile === 'cycling' ? options.costingOptions : undefined;
const trekValhalla = this.relay.trekValhalla();
// Whether the TREK services can be asked belongs in the key: a key added or removed
// moves the days from one engine to the other.
const key = JSON.stringify([
trekValhalla !== null,
userId,
tripId,
dayId,
@@ -443,6 +447,9 @@ export class RoadtripRouterService {
let avoidMissed = profile === 'driving' ? [...avoid] : [];
let snapped: SnappedWaypoint[] | undefined;
let ferryLegs: number[] | undefined;
// A day Valhalla routes that OSRM drew instead, kept no longer than this answer: the
// next question goes to Valhalla again, as the planner's does.
let standIn = false;
const plugin = /^plugin:([^/]+)\/(.+)$/.exec(profile);
if (plugin) {
const [, id, name] = plugin;
@@ -463,7 +470,7 @@ export class RoadtripRouterService {
parts = route.legs;
} else {
const mode = profile === 'walking' || profile === 'cycling' ? profile : 'driving';
const { base, valhalla, everyDay } = routersOf(settings, this.relay.trekValhalla());
const { base, valhalla, everyDay } = routersOf(settings, trekValhalla);
let weighted = false;
if (hiking) {
const cycling = mode === 'cycling';
@@ -476,6 +483,8 @@ export class RoadtripRouterService {
({ line, distance, duration, parts, snapped } = routeOfLegs(points, legs));
weighted = true;
} else if (footOnly && valhalla) {
// A walk kept off the boats is something only Valhalla can do, like an avoidance,
// so it may go to FOSSGIS where the relay cannot answer, as the planner's does.
try {
const ferryless = { use_ferry: 0 };
const offBoats = { offBoatLines: true };
@@ -519,6 +528,7 @@ export class RoadtripRouterService {
weighted = true;
} catch {
snapped = undefined;
standIn = true;
}
}
if (!weighted) {
@@ -595,7 +605,7 @@ export class RoadtripRouterService {
avoidMissed,
...(ferryLegs ? { ferryLegs } : {}),
};
this.cache.set(key, { at: Date.now(), route });
if (!standIn) this.cache.set(key, { at: Date.now(), route });
if (this.cache.size > 100) this.cache.delete(this.cache.keys().next().value!);
return route;
}
@@ -921,6 +921,41 @@ describe('server Roadtrip router: every day through Valhalla where it routes the
expect(route.parts).toHaveLength(11);
expect(route.avoidMissed).toEqual([]);
});
it('ROUTER-DAY-008: keeps no OSRM stand-in, so the same day asks the relay again once it is back', async () => {
const { router } = setup();
relay.ask.mockResolvedValueOnce(answer(502, { error: 'The TREK routing service did not answer' }));
const fallen = await settled(router.route(4, 1, 1, points, 'driving', []));
expect(fallen.parts.map((part) => part.duration)).toEqual([100, 200]);
const back = await settled(router.route(4, 1, 1, points, 'driving', []));
expect(back.parts.map((part) => part.duration)).toEqual([60, 60]);
expect(relay.ask).toHaveBeenCalledTimes(2);
expect(await settled(router.route(4, 1, 1, points, 'driving', []))).toBe(back);
expect(relay.ask).toHaveBeenCalledTimes(2);
});
it('ROUTER-DAY-009: a day routed with the key is not served from the cache once the key is gone', async () => {
const { router } = setup();
await settled(router.route(4, 1, 1, points, 'driving', []));
relay.trekValhalla.mockReturnValue(null);
const plain = await settled(router.route(4, 1, 1, points, 'driving', []));
expect(plain.parts.map((part) => part.duration)).toEqual([100, 200]);
expect(relay.ask).toHaveBeenCalledTimes(1);
});
it('ROUTER-DAY-010: a walk kept off the boats goes to FOSSGIS where the relay cannot answer, as only Valhalla can plan it', async () => {
relay.ask.mockReset().mockResolvedValue(answer(503, { error: 'The TREK routing service is resting' }));
vi.mocked(safeFetchAdminConfigured).mockImplementation(
async (_url, init) => new Response(JSON.stringify(legsFor(bodyOf(init)))),
);
const route = await settled(setup().router.route(4, 1, 1, points, 'walking', [], { footOnly: true }));
expect(vi.mocked(safeFetchAdminConfigured).mock.calls.map(([url]) => url)).toEqual([
'https://valhalla1.openstreetmap.de/route',
]);
expect(route.ferryLegs).toEqual([]);
});
});
it('ROUTER-DAY-006: an own Valhalla routes every day in pieces of twenty, and OSRM the day it cannot answer', async () => {