95 lines
2.5 KiB
TypeScript
95 lines
2.5 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
const responseMock = vi.hoisted(() => {
|
|
function makeResponseMock() {
|
|
const store = new Map<string, string>();
|
|
return {
|
|
_store: store,
|
|
headers: {
|
|
set: vi.fn((k: string, v: string) => store.set(k, v)),
|
|
get: vi.fn((k: string) => store.get(k) ?? null),
|
|
},
|
|
};
|
|
}
|
|
return { makeResponseMock };
|
|
});
|
|
|
|
vi.mock("next/server", () => ({
|
|
NextResponse: {
|
|
next: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
import type { NextRequest } from "next/server";
|
|
import { NextResponse } from "next/server";
|
|
import { middleware } from "../../middleware";
|
|
|
|
const ALL_SIX_HEADERS = [
|
|
"Strict-Transport-Security",
|
|
"X-Frame-Options",
|
|
"X-Content-Type-Options",
|
|
"Referrer-Policy",
|
|
"Permissions-Policy",
|
|
"Content-Security-Policy",
|
|
] as const;
|
|
|
|
function makeRequest(): NextRequest {
|
|
return { headers: new Headers() } as unknown as NextRequest;
|
|
}
|
|
|
|
describe("web-next middleware", () => {
|
|
let mock: ReturnType<typeof responseMock.makeResponseMock>;
|
|
|
|
beforeEach(() => {
|
|
mock = responseMock.makeResponseMock();
|
|
vi.mocked(NextResponse.next).mockReturnValue(
|
|
mock as unknown as ReturnType<typeof NextResponse.next>,
|
|
);
|
|
});
|
|
|
|
it("sets all six security headers on the response", () => {
|
|
middleware(makeRequest());
|
|
|
|
for (const header of ALL_SIX_HEADERS) {
|
|
expect(mock._store.has(header)).toBe(true);
|
|
}
|
|
});
|
|
|
|
it("sets x-nonce header on the response", () => {
|
|
middleware(makeRequest());
|
|
|
|
const nonce = mock._store.get("x-nonce");
|
|
expect(nonce).toBeDefined();
|
|
expect(typeof nonce).toBe("string");
|
|
expect((nonce as string).length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("CSP contains nonce in production mode", () => {
|
|
vi.stubEnv("NODE_ENV", "production");
|
|
|
|
middleware(makeRequest());
|
|
|
|
const nonce = mock._store.get("x-nonce");
|
|
const csp = mock._store.get("Content-Security-Policy");
|
|
expect(csp).toContain(`'nonce-${nonce}'`);
|
|
});
|
|
|
|
it("CSP is permissive (unsafe-inline) in development mode", () => {
|
|
vi.stubEnv("NODE_ENV", "development");
|
|
|
|
middleware(makeRequest());
|
|
|
|
const csp = mock._store.get("Content-Security-Policy");
|
|
expect(csp).toContain("'unsafe-inline'");
|
|
});
|
|
|
|
it("x-nonce is forwarded in request headers passed to NextResponse.next", () => {
|
|
middleware(makeRequest());
|
|
|
|
const call = vi.mocked(NextResponse.next).mock.calls[0] as [
|
|
{ request?: { headers?: Headers } } | undefined,
|
|
];
|
|
expect(call[0]?.request?.headers?.get("x-nonce")).toBeTruthy();
|
|
});
|
|
});
|