|
| 1 | +const test = require('node:test'); |
| 2 | +const assert = require('node:assert/strict'); |
| 3 | +const express = require('express'); |
| 4 | +const APIv2 = require('../modules/apiv2/index.js'); |
| 5 | + |
| 6 | +const FIXTURE_SPOTS = [ |
| 7 | + { spotted: 'W1AW', spotter: 'K1ABC', frequency: 14074, band: '20m', source: 'cluster' } |
| 8 | +]; |
| 9 | + |
| 10 | +function startApp(config, rateLimiter) { |
| 11 | + const apiv2 = new APIv2(config); |
| 12 | + const app = express(); |
| 13 | + app.use('/api/v2', apiv2.createRouter(rateLimiter)); |
| 14 | + return new Promise((resolve) => { |
| 15 | + const server = app.listen(0, () => resolve(server)); |
| 16 | + }); |
| 17 | +} |
| 18 | + |
| 19 | +function stopApp(server) { |
| 20 | + return new Promise((resolve) => server.close(resolve)); |
| 21 | +} |
| 22 | + |
| 23 | +const ROUTES = ['/bands', '/sources', '/heatmap', '/info']; |
| 24 | + |
| 25 | +test('rate limiter applies to /bands, /sources, /heatmap, /info', async (t) => { |
| 26 | + const rateLimiter = (req, res, next) => { |
| 27 | + res.status(429).json({ error: 'rate limited' }); |
| 28 | + }; |
| 29 | + const server = await startApp({ getSpotsData: () => FIXTURE_SPOTS }, rateLimiter); |
| 30 | + const base = `http://localhost:${server.address().port}/api/v2`; |
| 31 | + |
| 32 | + t.after(() => stopApp(server)); |
| 33 | + |
| 34 | + for (const route of ROUTES) { |
| 35 | + const res = await fetch(`${base}${route}`); |
| 36 | + assert.equal(res.status, 429, `${route} should be rate limited`); |
| 37 | + } |
| 38 | + |
| 39 | + // Regression guard: existing rate-limited route still behaves as before |
| 40 | + const spotsRes = await fetch(`${base}/spots`); |
| 41 | + assert.equal(spotsRes.status, 429, '/spots should still be rate limited'); |
| 42 | +}); |
| 43 | + |
| 44 | +test('global auth middleware protects /bands, /sources, /heatmap, /info when apiKey is configured', async (t) => { |
| 45 | + const server = await startApp({ getSpotsData: () => FIXTURE_SPOTS, apiKey: 'secret' }); |
| 46 | + const base = `http://localhost:${server.address().port}/api/v2`; |
| 47 | + |
| 48 | + t.after(() => stopApp(server)); |
| 49 | + |
| 50 | + for (const route of ROUTES) { |
| 51 | + const noKey = await fetch(`${base}${route}`); |
| 52 | + assert.equal(noKey.status, 401, `${route} without X-API-Key should be 401`); |
| 53 | + |
| 54 | + const wrongKey = await fetch(`${base}${route}`, { headers: { 'x-api-key': 'wrong' } }); |
| 55 | + assert.equal(wrongKey.status, 403, `${route} with wrong X-API-Key should be 403`); |
| 56 | + |
| 57 | + const correctKey = await fetch(`${base}${route}`, { headers: { 'x-api-key': 'secret' } }); |
| 58 | + assert.equal(correctKey.status, 200, `${route} with correct X-API-Key should be 200`); |
| 59 | + } |
| 60 | +}); |
0 commit comments