feat(flags): serve /api/flags, and keep the public proxy off it
The endpoint and its exposure control ship together on purpose. The moment /api/flags exists, app/api/[...path] forwards it — and the response names every unreleased feature the codebase knows about, along with whether it is on. Publishing that is the opposite of shipping dark. Denied on an exact first-segment match, not a prefix, so /api/flagship does not go down with /api/flags. Next reads the endpoint server-side over the Docker network, which never transits the public proxy. jest.setup.js now guards its browser globals. It runs for every suite, including the one that declares @jest-environment node to exercise the route handler — NextRequest needs Fetch API globals jsdom lacks, and there is no window there to define matchMedia on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj
This commit is contained in:
1 parent
7424cef7c6
commit
c30ad1db07
5 files changed
+93
No files matched your search
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* The /api/* proxy is public. Anything it forwards is on the internet.
|
||||
*
|
||||
* @jest-environment node
|
||||
*/
|
||||
// The docblock above is load-bearing. jest.config.js sets jsdom globally, and
|
||||
// NextRequest/NextResponse need the Web Fetch API globals that only the node
|
||||
// environment provides — under jsdom this suite fails on import, not on an
|
||||
// assertion.
|
||||
import { NextRequest } from 'next/server';
|
||||
import { GET } from '@/app/api/[...path]/route';
|
||||
|
||||
function request(path: string) {
|
||||
return new NextRequest(`http://localhost:3000/api/${path}`);
|
||||
}
|
||||
|
||||
describe('public API proxy', () => {
|
||||
it('refuses to forward internal-only paths', async () => {
|
||||
// /api/flags names every unreleased feature and its state. Forwarding it
|
||||
// publishes the thing shipping dark exists to keep quiet.
|
||||
const res = await GET(request('flags'), { params: Promise.resolve({ path: ['flags'] }) });
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('does not deny a path that merely starts with the same letters', async () => {
|
||||
// A prefix match would take /api/flagship down with /api/flags.
|
||||
const res = await GET(
|
||||
request('flagship'), { params: Promise.resolve({ path: ['flagship'] }) });
|
||||
expect(res.status).not.toBe(404);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user