Repository: denoland/fresh
File: src/middlewares/csrf_test.ts (TypeScript)

Code before:
import { csrf } from "./csrf.ts";
import { beforeEach, describe, it } from "@std/testing/bdd";
import { expect } from "@std/expect";
import { spy } from "@std/testing/mock";

const rawSimplePostHandler = async (ctx: FreshContext) => {
  if (ctx.req.headers.get("content-type") === "application/json") {
    return new Response((await ctx.req.json())["name"]);
  } else {
    return new Response((await ctx.req.formData()).get("name"));
  }
};

describe("CSRF by Middleware", () => {
  let simplePostHandler = spy(rawSimplePostHandler);

  beforeEach(() => {
    simplePostHandler = spy(rawSimplePostHandler);
  });

  describe("simple usage", () => {
    const app = new App();

    //
    app.all("*", csrf());
    app.get("/form", (_ctx: FreshContext) => new Response("<form></form>"));
    app.post("/form", simplePostHandler);
    app.put("/form", (_ctx: FreshContext) => new Response("OK"));
    app.delete("/form", (_ctx: FreshContext) => new Response("OK"));
    app.patch("/form", (_ctx: FreshContext) => new Response("OK"));
    app.head("/form", (_ctx: FreshContext) => new Response("OK"));

    const handler = app.handler();

    describe("GET /form", () => {
      it("should be 200 for any request", async () => {
        const res = await handler(new Request("http://localhost/form"));

        expect(res.status).toBe(200);
        expect(await res.text()).toBe("<form></form>");
      });
    });

    describe("HEAD /form", () => {
      it("should be 200 for any request", async () => {
        const res = await handler(
          new Request("http://localhost/form", {
            method: "HEAD",
          }),
        );


Review comment (suggestion):
Nit: If you don't care about the context arg, you can leave it out. Also no need to type it explicitely.

```ts
app.get("/form", () => new Response("<form></form>"));
```

Code after:
import { csrf, type CsrfOptions } from "./csrf.ts";
import { expect } from "@std/expect";

function testHandler<T>(
  options?: CsrfOptions<T>,
): (request: Request) => Promise<Response> {
  return new App<T>()
    .use(csrf(options))
    .all("/", () => new Response("hello"))
    .handler();
}

function createTests(trusted: CsrfOptions["origin"]) {
  return async function runTest(
    method: Method,
    expected: 200 | 403,
    test: {
      secFetchSite?: "cross-site" | "same-origin" | "same-site" | "none";
      origin?: string;
    } = {},
  ) {
    const handler = testHandler({ origin: trusted });

    const headers = new Headers();
    if (test.secFetchSite) headers.append("Sec-Fetch-Site", test.secFetchSite);
    if (test.origin) {
      headers.append("Origin", test.origin);
    }

    const res = await handler(
      new Request("https://example.com", { method, headers }),
    );
    await res.body?.cancel();
    expect(res.status).toEqual(expected);
  };
}

Deno.test("CSRF - allow GET/HEAD/OPTIONS", async () => {
  const runTest = createTests("https://example.com");

  await runTest("GET", 200);
  await runTest("HEAD", 200);
  await runTest("OPTIONS", 200);
});

Deno.test("CSRF - Sec-Fetch-Site", async () => {
  const runTest = createTests("https://example.com");

  await runTest("POST", 200, { secFetchSite: "same-origin" });
  await runTest("POST", 200, { secFetchSite: "none" });
  await runTest("POST", 403, { secFetchSite: "cross-site" });