diff --git a/server/src/nest/routing-relay/routing-relay.service.ts b/server/src/nest/routing-relay/routing-relay.service.ts index 42927de2d..4ffb7a6b0 100644 --- a/server/src/nest/routing-relay/routing-relay.service.ts +++ b/server/src/nest/routing-relay/routing-relay.service.ts @@ -9,7 +9,12 @@ import { Inject, Injectable } from '@nestjs/common'; import type { ConfigType } from '@nestjs/config'; import { VALHALLA_RELAY_REQUEST_SCHEMAS, compactValhallaRequest, type ValhallaRelayAction } from '@trek/shared'; -/** Questions one user may put to the TREK Valhalla a minute, the browser and the server's own routing together. */ +/** + * Questions one user may put to the TREK Valhalla a minute, the browser and the server's + * own routing together. A day is one question of up to twenty stops however long it is, + * and the road trip rail asks one a second at most, so a whole trip fits; past it the + * refusal says when to come back (see `ask`). + */ const QUESTIONS_PER_MINUTE = 60; /** * Questions the whole installation may put a minute. The TREK services allow @@ -93,7 +98,9 @@ export class RoutingRelayService { RATE_WINDOW_MS, now, ); - if (!allowed) throw new DomainError(429, 'Too many routing requests, try again in a minute'); + // With the wait it asks for: the planner leaves the relay alone that long and routes + // the days with OSRM meanwhile, rather than for the ten minutes a refusal without one costs. + if (!allowed) return refusal(429, 'Too many routing requests, try again in a minute', RATE_WINDOW_MS); const withinInstallation = await this.rateLimit.check( 'valhalla-relay-installation', 'all', diff --git a/server/tests/unit/nest/routing-relay/routing-relay.service.test.ts b/server/tests/unit/nest/routing-relay/routing-relay.service.test.ts index 3be3333d1..c9630b966 100644 --- a/server/tests/unit/nest/routing-relay/routing-relay.service.test.ts +++ b/server/tests/unit/nest/routing-relay/routing-relay.service.test.ts @@ -124,12 +124,12 @@ describe('RoutingRelayService.ask', () => { expect(safeFetchAdminConfigured).not.toHaveBeenCalled(); }); - it('RELAY-006: holds every user to 60 questions a minute, each user apart', async () => { + it('RELAY-006: holds every user to 60 questions a minute, each user apart, and says when to come back', async () => { vi.mocked(safeFetchAdminConfigured).mockImplementation(async () => upstream(200, '{}')); await oneByOne(times(60), () => relay.ask(1, 'height', { shape: [{ lat: 47, lon: 11 }] })); - const refused = await relay.ask(1, 'route', ROUTE).catch((err: unknown) => err); - expect(refused).toBeInstanceOf(DomainError); - expect(refused).toMatchObject({ status: 429, message: 'Too many routing requests, try again in a minute' }); + const refused = await relay.ask(1, 'route', ROUTE); + expect(refused).toMatchObject({ status: 429, retryAfter: '60' }); + expect(JSON.parse(refused.body.toString())).toEqual({ error: 'Too many routing requests, try again in a minute' }); expect((await relay.ask(2, 'route', ROUTE)).status).toBe(200); expect(safeFetchAdminConfigured).toHaveBeenCalledTimes(61); });