Unverified Commit 64dc818d authored by Hong Minhee's avatar Hong Minhee
Browse files

Migrate tests from deno-mock-fetch to fetch-mock

Replace @hongminhee/deno-mock-fetch with fetch-mock across test files for
improved compatibility and standardization. Updates test mocking patterns in
nodeinfo, runtime, sig, vocab, and webfinger modules.
parent 6369ac5f
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@
    "@cfworker/json-schema": "npm:@cfworker/json-schema@^4.1.1",
    "@cloudflare/workers-types": "npm:@cloudflare/workers-types@^4.20250529.0",
    "@es-toolkit/es-toolkit": "jsr:@es-toolkit/es-toolkit@^1.38.0",
    "@hongminhee/deno-mock-fetch": "jsr:@hongminhee/deno-mock-fetch@^0.3.2",
    "@hugoalh/http-header-link": "jsr:@hugoalh/http-header-link@^1.0.2",
    "@multiformats/base-x": "npm:@multiformats/base-x@^4.0.1",
    "@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0",
+20 −34
Original line number Diff line number Diff line
import * as mf from "@hongminhee/deno-mock-fetch";
import { assertEquals } from "@std/assert";
import fetchMock from "fetch-mock";
import { test } from "../testing/mod.ts";
import {
  getNodeInfo,
@@ -19,20 +19,17 @@ import type {
} from "./types.ts";

test("getNodeInfo()", async (t) => {
  mf.install();
  fetchMock.spyGlobal();

  mf.mock("GET@/.well-known/nodeinfo", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    return new Response(
      JSON.stringify({
  fetchMock.get("https://example.com/.well-known/nodeinfo", {
    body: {
      links: [
        {
          rel: "http://nodeinfo.diaspora.software/ns/schema/2.1",
          href: "https://example.com/nodeinfo/2.1",
        },
      ],
      }),
    );
    },
  });

  const rawExpected = {
@@ -41,17 +38,8 @@ test("getNodeInfo()", async (t) => {
    usage: { users: {}, localPosts: 123, localComments: 456 },
  };

  mf.mock("GET@/nodeinfo/2.1", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    return new Response(
      JSON.stringify(rawExpected),
    );
  });

  mf.mock("GET@/404", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    return new Response(null, { status: 404 });
  });
  fetchMock.get("https://example.com/nodeinfo/2.1", { body: rawExpected });
  fetchMock.get("https://example.com/404", { status: 404 });

  const expected: NodeInfo = {
    software: {
@@ -78,11 +66,9 @@ test("getNodeInfo()", async (t) => {
    assertEquals(info, expected);
  });

  mf.mock("GET@/.well-known/nodeinfo", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    return new Response(JSON.stringify({
      links: [],
    }));
  fetchMock.removeRoutes();
  fetchMock.get("https://example.com/.well-known/nodeinfo", {
    body: { links: [] },
  });

  await t.step("indirect: no links", async () => {
@@ -90,9 +76,9 @@ test("getNodeInfo()", async (t) => {
    assertEquals(info, undefined);
  });

  mf.mock("GET@/.well-known/nodeinfo", (req) => {
    assertEquals(new URL(req.url).host, "example.com");
    return new Response(null, { status: 404 });
  fetchMock.removeRoutes();
  fetchMock.get("https://example.com/.well-known/nodeinfo", {
    status: 404,
  });

  await t.step("indirect: 404", async () => {
@@ -113,7 +99,7 @@ test("getNodeInfo()", async (t) => {
    assertEquals(info2, undefined);
  });

  mf.uninstall();
  fetchMock.hardReset();
});

test("parseNodeInfo()", () => {
+0 −1
Original line number Diff line number Diff line
@@ -104,7 +104,6 @@
  },
  "devDependencies": {
    "@cloudflare/workers-types": "^4.20250529.0",
    "@hongminhee/deno-mock-fetch": "jsr:^0.3.2",
    "@std/assert": "jsr:^0.226.0",
    "@std/path": "jsr:^1.0.9",
    "@std/url": "jsr:1.0.0-rc.3",
+87 −111
Original line number Diff line number Diff line
import * as mf from "@hongminhee/deno-mock-fetch";
import { assertEquals, assertRejects, assertThrows } from "@std/assert";
import fetchMock from "fetch-mock";
import process from "node:process";
import metadata from "../deno.json" with { type: "json" };
import type { KvKey, KvStore, KvStoreSetOptions } from "../federation/kv.ts";
@@ -29,18 +29,16 @@ test("new FetchError()", () => {
test("getDocumentLoader()", async (t) => {
  const fetchDocumentLoader = getDocumentLoader();

  mf.install();
  fetchMock.spyGlobal();

  mf.mock("GET@/object", (_req) =>
    new Response(
      JSON.stringify({
  fetchMock.get("https://example.com/object", {
    body: {
      "@context": "https://www.w3.org/ns/activitystreams",
      id: "https://example.com/object",
      name: "Fetched object",
      type: "Object",
      }),
      { status: 200 },
    ));
    },
  });

  await t.step("ok", async () => {
    assertEquals(await fetchDocumentLoader("https://example.com/object"), {
@@ -55,67 +53,49 @@ test("getDocumentLoader()", async (t) => {
    });
  });

  mf.mock("GET@/link-ctx", (_req) =>
    new Response(
      JSON.stringify({
  fetchMock.get("https://example.com/link-ctx", {
    body: {
      id: "https://example.com/link-ctx",
      name: "Fetched object",
      type: "Object",
      }),
      {
        status: 200,
    },
    headers: {
      "Content-Type": "application/activity+json",
      Link: "<https://www.w3.org/ns/activitystreams>; " +
        'rel="http://www.w3.org/ns/json-ld#context"; ' +
        'type="application/ld+json"',
    },
      },
    ));
  });

  mf.mock("GET@/link-obj", (_req) =>
    new Response(
      "",
      {
        status: 200,
  fetchMock.get("https://example.com/link-obj", {
    headers: {
      "Content-Type": "text/html; charset=utf-8",
      Link: '<https://example.com/object>; rel="alternate"; ' +
        'type="application/activity+json"',
    },
      },
    ));
  });

  mf.mock("GET@/link-obj-relative", (_req) =>
    new Response(
      "",
      {
        status: 200,
  fetchMock.get("https://example.com/link-obj-relative", {
    headers: {
      "Content-Type": "text/html; charset=utf-8",
      Link: '</object>; rel="alternate"; ' +
        'type="application/activity+json"',
    },
      },
    ));
  });

  mf.mock("GET@/obj-w-wrong-link", (_req) =>
    new Response(
      JSON.stringify({
  fetchMock.get("https://example.com/obj-w-wrong-link", {
    body: {
      "@context": "https://www.w3.org/ns/activitystreams",
      id: "https://example.com/obj-w-wrong-link",
      name: "Fetched object",
      type: "Object",
      }),
      {
        status: 200,
    },
    headers: {
      "Content-Type": "text/html; charset=utf-8",
      Link: '<https://example.com/object>; rel="alternate"; ' +
        'type="application/ld+json; profile="https://www.w3.org/ns/activitystreams""',
    },
      },
    ));
  });

  await t.step("Link header", async () => {
    assertEquals(await fetchDocumentLoader("https://example.com/link-ctx"), {
@@ -182,9 +162,8 @@ test("getDocumentLoader()", async (t) => {
    );
  });

  mf.mock("GET@/html-link", (_req) =>
    new Response(
      `<html>
  fetchMock.get("https://example.com/html-link", {
    body: `<html>
        <head>
          <meta charset=utf-8>
          <link
@@ -193,11 +172,8 @@ test("getDocumentLoader()", async (t) => {
            href="https://example.com/object">
        </head>
      </html>`,
      {
        status: 200,
    headers: { "Content-Type": "text/html; charset=utf-8" },
      },
    ));
  });

  await t.step("HTML <link>", async () => {
    assertEquals(await fetchDocumentLoader("https://example.com/html-link"), {
@@ -212,9 +188,8 @@ test("getDocumentLoader()", async (t) => {
    });
  });

  mf.mock("GET@/html-a", (_req) =>
    new Response(
      `<html>
  fetchMock.get("https://example.com/html-a", {
    body: `<html>
        <head>
          <meta charset=utf-8>
        </head>
@@ -225,11 +200,8 @@ test("getDocumentLoader()", async (t) => {
            href=https://example.com/object>test</a>
        </body>
      </html>`,
      {
        status: 200,
    headers: { "Content-Type": "text/html; charset=utf-8" },
      },
    ));
  });

  await t.step("HTML <a>", async () => {
    assertEquals(await fetchDocumentLoader("https://example.com/html-a"), {
@@ -244,16 +216,15 @@ test("getDocumentLoader()", async (t) => {
    });
  });

  mf.mock("GET@/wrong-content-type", (_req) =>
    new Response(
      JSON.stringify({
  fetchMock.get("https://example.com/wrong-content-type", {
    body: {
      "@context": "https://www.w3.org/ns/activitystreams",
      id: "https://example.com/wrong-content-type",
      name: "Fetched object",
      type: "Object",
      }),
      { status: 200, headers: { "Content-Type": "text/html; charset=utf-8" } },
    ));
    },
    headers: { "Content-Type": "text/html; charset=utf-8" },
  });

  await t.step("Wrong Content-Type", async () => {
    assertEquals(
@@ -271,7 +242,7 @@ test("getDocumentLoader()", async (t) => {
    );
  });

  mf.mock("GET@/404", (_req) => new Response("", { status: 404 }));
  fetchMock.get("https://example.com/404", { status: 404 });

  await t.step("not ok", async () => {
    await assertRejects(
@@ -298,14 +269,13 @@ test("getDocumentLoader()", async (t) => {
    );
  });

  mf.mock(
    "GET@/localhost-redirect",
    (_req) => Response.redirect("https://localhost/object", 302),
  );
  fetchMock.get("https://example.com/localhost-redirect", {
    status: 302,
    headers: { Location: "https://localhost/object" },
  });

  mf.mock("GET@/localhost-link", (_req) =>
    new Response(
      `<html>
  fetchMock.get("https://example.com/localhost-link", {
    body: `<html>
        <head>
          <meta charset=utf-8>
          <link
@@ -314,11 +284,17 @@ test("getDocumentLoader()", async (t) => {
            href="https://localhost/object">
        </head>
      </html>`,
      {
        status: 200,
    headers: { "Content-Type": "text/html; charset=utf-8" },
  });

  fetchMock.get("https://localhost/object", {
    body: {
      "@context": "https://www.w3.org/ns/activitystreams",
      id: "https://localhost/object",
      name: "Fetched object",
      type: "Object",
    },
    ));
  });

  await t.step("allowPrivateAddress: false", async () => {
    await assertRejects(
@@ -343,7 +319,7 @@ test("getDocumentLoader()", async (t) => {
      documentUrl: "https://localhost/object",
      document: {
        "@context": "https://www.w3.org/ns/activitystreams",
        id: "https://example.com/object",
        id: "https://localhost/object",
        name: "Fetched object",
        type: "Object",
      },
@@ -362,7 +338,7 @@ test("getDocumentLoader()", async (t) => {
    );
  });

  mf.uninstall();
  fetchMock.hardReset();
});

test("kvCache()", async (t) => {
+37 −32
Original line number Diff line number Diff line
import * as mf from "@hongminhee/deno-mock-fetch";
import {
  assert,
  assertEquals,
@@ -7,6 +6,7 @@ import {
  assertStringIncludes,
} from "@std/assert";
import { encodeBase64 } from "byte-encodings/base64";
import fetchMock from "fetch-mock";
import { exportSpki } from "../runtime/key.ts";
import { mockDocumentLoader } from "../testing/docloader.ts";
import {
@@ -1163,15 +1163,16 @@ test("verifyRequest() [rfc9421] test vector from Mastodon", async () => {

test("doubleKnock() function with successful first attempt", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // A counter to track the number of times the endpoint is hit
  let requestCount = 0;
  let firstRequestSpec: string | null = null;

  // Mock an endpoint that accepts RFC 9421 signatures
  mf.mock("POST@/inbox-accepts-rfc9421", (req) => {
  fetchMock.post("https://example.com/inbox-accepts-rfc9421", (cl) => {
    requestCount++;
    const req = cl.request!;
    const signatureInputHeader = req.headers.get("Signature-Input");
    const signatureHeader = req.headers.get("Signature");

@@ -1243,12 +1244,12 @@ test("doubleKnock() function with successful first attempt", async () => {
    "Logged request should have RFC 9421 Signature header",
  );

  mf.uninstall();
  fetchMock.hardReset();
});

test("doubleKnock() function with fallback to draft-cavage", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // Track request attempts and specs used
  let requestCount = 0;
@@ -1256,7 +1257,8 @@ test("doubleKnock() function with fallback to draft-cavage", async () => {
  let secondSpec: string | null = null;

  // Mock an endpoint that only accepts draft-cavage signatures
  mf.mock("POST@/inbox-accepts-draft-cavage", (req) => {
  fetchMock.post("https://example.com/inbox-accepts-draft-cavage", (cl) => {
    const req = cl.request!;
    requestCount++;

    // Check which signature format was used
@@ -1331,27 +1333,27 @@ test("doubleKnock() function with fallback to draft-cavage", async () => {
    "Successful spec should be remembered",
  );

  mf.uninstall();
  fetchMock.hardReset();
});

test("doubleKnock() function with redirect handling", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // Track request attempts and redirects
  const requestedUrls: string[] = [];
  const responseCodes: number[] = [];

  // Mock an endpoint that redirects
  mf.mock("POST@/redirect-endpoint", (req) => {
    requestedUrls.push(req.url);
  fetchMock.post("https://example.com/redirect-endpoint", (cl) => {
    requestedUrls.push(cl.url);
    responseCodes.push(302);
    return Response.redirect("https://example.com/final-endpoint", 302);
  });

  // Mock the destination endpoint
  mf.mock("POST@/final-endpoint", (req) => {
    requestedUrls.push(req.url);
  fetchMock.post("https://example.com/final-endpoint", (cl) => {
    requestedUrls.push(cl.url);
    responseCodes.push(202);
    return new Response("", { status: 202 });
  });
@@ -1397,19 +1399,20 @@ test("doubleKnock() function with redirect handling", async () => {
    "Response status codes should match expected sequence",
  );

  mf.uninstall();
  fetchMock.hardReset();
});

test("doubleKnock() function with both specs rejected", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // Track request attempts
  let requestCount = 0;
  const attempts: string[] = [];

  // Mock an endpoint that rejects all signatures
  mf.mock("POST@/inbox-rejects-all", (req) => {
  fetchMock.post("https://example.com/inbox-rejects-all", (cl) => {
    const req = cl.request!;
    requestCount++;

    if (req.headers.has("Signature-Input")) {
@@ -1460,19 +1463,20 @@ test("doubleKnock() function with both specs rejected", async () => {
    "Second attempt should use draft-cavage",
  );

  mf.uninstall();
  fetchMock.hardReset();
});

test("doubleKnock() function with specDeterminer choosing draft-cavage first", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // Track request attempts
  let requestCount = 0;
  let firstSpec: string | null = null;

  // Mock an endpoint that accepts draft-cavage signatures
  mf.mock("POST@/inbox-accepts-any", (req) => {
  fetchMock.post("https://example.com/inbox-accepts-any", (cl) => {
    const req = cl.request!;
    requestCount++;

    if (req.headers.has("Signature-Input")) {
@@ -1525,34 +1529,34 @@ test("doubleKnock() function with specDeterminer choosing draft-cavage first", a
    "First attempt should use draft-cavage",
  );

  mf.uninstall();
  fetchMock.hardReset();
});

test("doubleKnock() complex redirect chain test", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // Track request attempts
  const requestedUrls: string[] = [];

  // Create a redirect chain with 3 redirects
  mf.mock("POST@/redirect1", (req) => {
    requestedUrls.push(req.url);
  fetchMock.post("https://example.com/redirect1", (cl) => {
    requestedUrls.push(cl.url);
    return Response.redirect("https://example.com/redirect2", 302);
  });

  mf.mock("POST@/redirect2", (req) => {
    requestedUrls.push(req.url);
  fetchMock.post("https://example.com/redirect2", (cl) => {
    requestedUrls.push(cl.url);
    return Response.redirect("https://example.com/redirect3", 307);
  });

  mf.mock("POST@/redirect3", (req) => {
    requestedUrls.push(req.url);
  fetchMock.post("https://example.com/redirect3", (cl) => {
    requestedUrls.push(cl.url);
    return Response.redirect("https://example.com/final", 301);
  });

  mf.mock("POST@/final", (req) => {
    requestedUrls.push(req.url);
  fetchMock.post("https://example.com/final", (cl) => {
    requestedUrls.push(cl.url);
    return new Response("Success", { status: 200 });
  });

@@ -1622,19 +1626,20 @@ test("doubleKnock() complex redirect chain test", async () => {
    );
  }

  mf.uninstall();
  fetchMock.hardReset();
});

test("doubleKnock() async specDeterminer test", async () => {
  // Install mock fetch handler
  mf.install();
  fetchMock.spyGlobal();

  // Track request attempts
  let requestCount = 0;
  let specUsed: string | null = null;

  // Mock an endpoint that accepts both types of signatures
  mf.mock("POST@/inbox-async-determiner", (req) => {
  fetchMock.post("https://example.com/inbox-async-determiner", (cl) => {
    const req = cl.request!;
    requestCount++;

    if (req.headers.has("Signature-Input")) {
@@ -1689,7 +1694,7 @@ test("doubleKnock() async specDeterminer test", async () => {
    "Should use spec from async determiner",
  );

  mf.uninstall();
  fetchMock.hardReset();
});

test("timingSafeEqual()", async (t) => {
Loading