/** * Component: API Token Constants Tests * Documentation: documentation/backend/services/api-tokens.md */ import { describe, expect, it } from 'vitest'; import { API_TOKEN_ALLOWED_ENDPOINTS, API_TOKEN_ENDPOINT_DOCS, isEndpointAllowed, } from '@/lib/constants/api-tokens'; describe('isEndpointAllowed', () => { describe('positive matches (every allowlisted endpoint)', () => { const cases: Array<[string, string]> = [ ['GET', '/api/auth/me'], ['GET', '/api/audiobooks/search'], ['GET', '/api/requests'], ['POST', '/api/requests'], ['GET', '/api/requests/abc-uuid-123'], ['GET', '/api/requests/00000000-0000-0000-0000-000000000000'], ['GET', '/api/admin/metrics'], ['GET', '/api/admin/downloads/active'], ['GET', '/api/admin/requests/recent'], ]; it.each(cases)('%s %s is allowed', (method, path) => { expect(isEndpointAllowed(method, path)).toBe(true); }); }); describe('negative matches', () => { it('rejects unrelated paths', () => { expect(isEndpointAllowed('GET', '/api/admin/settings')).toBe(false); expect(isEndpointAllowed('GET', '/api/audiobooks/popular')).toBe(false); }); it('rejects wrong HTTP method', () => { expect(isEndpointAllowed('DELETE', '/api/requests')).toBe(false); expect(isEndpointAllowed('POST', '/api/requests/abc')).toBe(false); expect(isEndpointAllowed('PATCH', '/api/requests/abc')).toBe(false); }); it('rejects sibling sub-routes of /api/requests/:id', () => { // The :id placeholder must match a SINGLE segment — anything deeper is excluded. expect(isEndpointAllowed('GET', '/api/requests/abc/select-torrent')).toBe(false); expect(isEndpointAllowed('GET', '/api/requests/abc/download-token')).toBe(false); expect(isEndpointAllowed('GET', '/api/requests/abc/interactive-search')).toBe(false); expect(isEndpointAllowed('GET', '/api/requests/abc/manual-search')).toBe(false); expect(isEndpointAllowed('GET', '/api/requests/abc/select-ebook')).toBe(false); expect(isEndpointAllowed('POST', '/api/requests/abc/select-torrent')).toBe(false); }); it('rejects partial / extended paths', () => { expect(isEndpointAllowed('GET', '/api/request')).toBe(false); expect(isEndpointAllowed('GET', '/api/requests/')).toBe(false); expect(isEndpointAllowed('GET', '/api/auth/me/extra')).toBe(false); }); it('does not allow empty :id segment to match', () => { // `/api/requests/:id` requires at least one char in the segment; // a literal `/api/requests` is matched separately. expect(isEndpointAllowed('GET', '/api/requests/')).toBe(false); }); }); describe('method case-insensitivity', () => { it('accepts lowercase and mixed-case methods', () => { expect(isEndpointAllowed('get', '/api/requests')).toBe(true); expect(isEndpointAllowed('Get', '/api/requests')).toBe(true); expect(isEndpointAllowed('post', '/api/requests')).toBe(true); expect(isEndpointAllowed('PoSt', '/api/requests')).toBe(true); }); }); describe('docs / allowlist parity', () => { it('every documented endpoint is on the allowlist', () => { for (const doc of API_TOKEN_ENDPOINT_DOCS) { const found = API_TOKEN_ALLOWED_ENDPOINTS.some( (ep) => ep.method === doc.method && ep.path === doc.path ); expect(found, `${doc.method} ${doc.path} missing from allowlist`).toBe(true); } }); it('every allowlisted endpoint has a docs entry', () => { for (const ep of API_TOKEN_ALLOWED_ENDPOINTS) { const found = API_TOKEN_ENDPOINT_DOCS.some( (doc) => doc.method === ep.method && doc.path === ep.path ); expect(found, `${ep.method} ${ep.path} missing from docs`).toBe(true); } }); }); });