|
1 | 1 | import { expect } from "@std/expect/expect"; |
| 2 | +import { fn } from "@std/expect"; |
| 3 | +import { stub } from "@std/testing/mock"; |
2 | 4 | import { App } from "../app.ts"; |
3 | 5 | import { csp } from "./csp.ts"; |
4 | 6 | import { FakeServer } from "../test_utils.ts"; |
@@ -260,3 +262,57 @@ Deno.test("CSP - useNonce replaces unsafe-inline in default-src", async () => { |
260 | 262 | // default-src should have nonce, not unsafe-inline |
261 | 263 | expect(cspHeader).toMatch(/default-src 'self' 'nonce-[a-f0-9]+'/); |
262 | 264 | }); |
| 265 | + |
| 266 | +Deno.test("CSP - warns in development when a response has no nonce", async () => { |
| 267 | + // deno-lint-ignore no-explicit-any |
| 268 | + using warnSpy = stub(console, "warn", fn(() => {}) as any); |
| 269 | + const app = new App({ mode: "development" }) |
| 270 | + .use(csp({ useNonce: true })) |
| 271 | + .get("/api", () => new Response(JSON.stringify({ ok: true }))); |
| 272 | + |
| 273 | + const server = new FakeServer(app.handler()); |
| 274 | + const res = await server.get("/api"); |
| 275 | + await res.body?.cancel(); |
| 276 | + |
| 277 | + expect(res.headers.get("Content-Security-Policy")).toContain( |
| 278 | + "'unsafe-inline'", |
| 279 | + ); |
| 280 | + expect(warnSpy.fake).toHaveBeenCalledTimes(1); |
| 281 | + expect(warnSpy.fake).toHaveBeenLastCalledWith( |
| 282 | + `🍋 %c[WARNING] CSP: "/api" responded without a nonce, so 'unsafe-inline' was kept. Only ctx.render() sets a nonce.`, |
| 283 | + expect.any(String), |
| 284 | + ); |
| 285 | +}); |
| 286 | + |
| 287 | +Deno.test("CSP - warns once per path, not once per request", async () => { |
| 288 | + // deno-lint-ignore no-explicit-any |
| 289 | + using warnSpy = stub(console, "warn", fn(() => {}) as any); |
| 290 | + const app = new App({ mode: "development" }) |
| 291 | + .use(csp({ useNonce: true })) |
| 292 | + .get("/repeated", () => new Response("ok")); |
| 293 | + |
| 294 | + const server = new FakeServer(app.handler()); |
| 295 | + for (let i = 0; i < 3; i++) { |
| 296 | + const res = await server.get("/repeated"); |
| 297 | + await res.body?.cancel(); |
| 298 | + } |
| 299 | + |
| 300 | + expect(warnSpy.fake).toHaveBeenCalledTimes(1); |
| 301 | +}); |
| 302 | + |
| 303 | +Deno.test("CSP - does not warn in production", async () => { |
| 304 | + // deno-lint-ignore no-explicit-any |
| 305 | + using warnSpy = stub(console, "warn", fn(() => {}) as any); |
| 306 | + const app = new App() |
| 307 | + .use(csp({ useNonce: true })) |
| 308 | + .get("/prod-api", () => new Response("ok")); |
| 309 | + |
| 310 | + const server = new FakeServer(app.handler()); |
| 311 | + const res = await server.get("/prod-api"); |
| 312 | + await res.body?.cancel(); |
| 313 | + |
| 314 | + expect(res.headers.get("Content-Security-Policy")).toContain( |
| 315 | + "'unsafe-inline'", |
| 316 | + ); |
| 317 | + expect(warnSpy.fake).not.toHaveBeenCalled(); |
| 318 | +}); |
0 commit comments