diff --git a/server/src/nest/roadtrip/roadtrip-router.service.ts b/server/src/nest/roadtrip/roadtrip-router.service.ts index b3b2450a5..dc747923b 100644 --- a/server/src/nest/roadtrip/roadtrip-router.service.ts +++ b/server/src/nest/roadtrip/roadtrip-router.service.ts @@ -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; } diff --git a/server/tests/unit/nest/roadtrip-router.test.ts b/server/tests/unit/nest/roadtrip-router.test.ts index 351094e72..4eb07459f 100644 --- a/server/tests/unit/nest/roadtrip-router.test.ts +++ b/server/tests/unit/nest/roadtrip-router.test.ts @@ -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 () => {