const { describe, it } = require('node:test'); const assert = require('node:assert'); const axios = require('axios'); const routingRouter = require('../routes/routing'); const placesRouter = require('../routes/places'); describe('Routing Input Hardening & Validations', () => { const calcLayer = routingRouter.stack.find((l) => l.route && l.route.path === '/calculate'); assert.ok(calcLayer, '/calculate endpoint must exist'); const calcHandler = calcLayer.route.stack[calcLayer.route.stack.length - 1].handle; it('rejects less than 2 stops with 400 invalidRequest', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { body: { locations: [{ lat: 37.77, lon: -122.42 }] } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); assert.match(body?.message, /At least 2 locations/); } finally { delete process.env.STADIA_API_KEY; } }); it('rejects more than 25 stops with 400 invalidRequest', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const stops = Array.from({ length: 26 }, (_, i) => ({ lat: 37.0 + i * 0.01, lon: -122.0 })); const req = { body: { locations: stops } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); assert.match(body?.message, /Maximum of 25/); } finally { delete process.env.STADIA_API_KEY; } }); it('accepts exactly 25 stops and passes motorcycle costing to provider', async () => { process.env.STADIA_API_KEY = 'test-secret-key-123'; const originalPost = axios.post; try { let interceptedUrl = null; let interceptedPayload = null; let interceptedParams = null; axios.post = async (url, payload, config) => { interceptedUrl = url; interceptedPayload = payload; interceptedParams = config?.params; return { data: { trip: { legs: [ { summary: { length: 15.5, time: 1200 }, shape: 'test_encoded_polyline' } ], warnings: [] } } }; }; const stops = Array.from({ length: 25 }, (_, i) => ({ lat: 37.0 + i * 0.01, lon: -122.0 })); const req = { body: { locations: stops } }; let responseData = null; const res = { json: (b) => { responseData = b; } }; await calcHandler(req, res); assert.strictEqual(interceptedUrl, 'https://api.stadiamaps.com/route/v1'); assert.strictEqual(interceptedParams?.api_key, 'test-secret-key-123'); assert.strictEqual(interceptedPayload?.costing, 'motorcycle'); assert.strictEqual(interceptedPayload?.locations?.length, 25); assert.strictEqual(responseData?.distanceMeters, 15500); assert.strictEqual(responseData?.durationSeconds, 1200); assert.strictEqual(responseData?.encodedPolyline, 'test_encoded_polyline'); // Verify API key is NOT leaked in client response assert.strictEqual(JSON.stringify(responseData).includes('test-secret-key-123'), false); } finally { axios.post = originalPost; delete process.env.STADIA_API_KEY; } }); it('sanitizes upstream timeout error to 504 providerTimeout without leaking internals', async () => { process.env.STADIA_API_KEY = 'test-key'; const originalPost = axios.post; try { axios.post = async () => { const err = new Error('connect ECONNABORTED'); err.code = 'ECONNABORTED'; throw err; }; const req = { body: { locations: [ { lat: 37.7, lon: -122.4 }, { lat: 37.8, lon: -122.4 } ] } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 504); assert.strictEqual(body?.error, 'providerTimeout'); assert.strictEqual(JSON.stringify(body).includes('ECONNABORTED'), false); } finally { axios.post = originalPost; delete process.env.STADIA_API_KEY; } }); it('rejects invalid latitude bounds (< -90 or > 90)', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { body: { locations: [ { lat: 95.0, lon: -122.4 }, { lat: 37.7, lon: -122.4 } ] } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); assert.match(body?.message, /out of geographic bounds/); } finally { delete process.env.STADIA_API_KEY; } }); it('rejects invalid longitude bounds (< -180 or > 180)', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { body: { locations: [ { lat: 37.7, lon: -185.0 }, { lat: 37.7, lon: -122.4 } ] } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); assert.match(body?.message, /out of geographic bounds/); } finally { delete process.env.STADIA_API_KEY; } }); it('rejects non-finite coordinates (NaN/Infinity)', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { body: { locations: [ { lat: NaN, lon: -122.4 }, { lat: 37.7, lon: -122.4 } ] } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); assert.match(body?.message, /Invalid or non-finite/); } finally { delete process.env.STADIA_API_KEY; } }); it('fails with 503 providerUnavailable if STADIA_API_KEY is not configured', async () => { delete process.env.STADIA_API_KEY; const req = { body: { locations: [{ lat: 37.7, lon: -122.4 }, { lat: 37.8, lon: -122.4 }] } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await calcHandler(req, res); assert.strictEqual(statusCode, 503); assert.strictEqual(body?.error, 'providerUnavailable'); }); }); describe('Places Input Hardening & Validations', () => { const searchLayer = placesRouter.stack.find((l) => l.route && l.route.path === '/search'); assert.ok(searchLayer, '/search endpoint must exist'); const searchHandler = searchLayer.route.stack[searchLayer.route.stack.length - 1].handle; const resolveLayer = placesRouter.stack.find((l) => l.route && l.route.path === '/resolve'); assert.ok(resolveLayer, '/resolve endpoint must exist'); const resolveHandler = resolveLayer.route.stack[resolveLayer.route.stack.length - 1].handle; it('search rejects empty or short query with 400', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { query: { query: 'a' } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await searchHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); } finally { delete process.env.STADIA_API_KEY; } }); it('search rejects excessive query (>200 chars) with 400', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { query: { query: 'A'.repeat(201) } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await searchHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); } finally { delete process.env.STADIA_API_KEY; } }); it('search normalizes Pelias GeoJSON features into place results with coordinates', async () => { process.env.STADIA_API_KEY = 'test-places-key'; const originalGet = axios.get; try { axios.get = async (url, config) => { assert.strictEqual(url, 'https://api.stadiamaps.com/geocoding/v1/autocomplete'); assert.strictEqual(config?.params?.api_key, 'test-places-key'); return { data: { features: [ { properties: { id: 'venue_123', name: 'Skyline Boulevard', label: 'Skyline Boulevard, Oakland, CA', region: 'California', country: 'USA' }, geometry: { coordinates: [-122.18, 37.81] } } ] } }; }; const req = { query: { query: 'Skyline' } }; let responseData = null; const res = { json: (d) => { responseData = d; } }; await searchHandler(req, res); assert.ok(Array.isArray(responseData?.results)); assert.strictEqual(responseData.results.length, 1); const place = responseData.results[0]; assert.strictEqual(place.providerId, 'venue_123'); assert.strictEqual(place.primaryText, 'Skyline Boulevard'); assert.strictEqual(place.latitude, 37.81); assert.strictEqual(place.longitude, -122.18); } finally { axios.get = originalGet; delete process.env.STADIA_API_KEY; } }); it('resolve rejects missing or empty placeId with 400', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { query: { placeId: ' ' } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await resolveHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); } finally { delete process.env.STADIA_API_KEY; } }); it('resolve rejects excessive placeId (>200 chars) with 400', async () => { process.env.STADIA_API_KEY = 'test-key'; try { const req = { query: { placeId: 'x'.repeat(201) } }; let statusCode = null; let body = null; const res = { status: (c) => { statusCode = c; return { json: (b) => { body = b; } }; } }; await resolveHandler(req, res); assert.strictEqual(statusCode, 400); assert.strictEqual(body?.error, 'invalidRequest'); } finally { delete process.env.STADIA_API_KEY; } }); });