From 6c27e053b7c318673d6f718428b683b3d59fbd04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 28 Jun 2026 15:18:20 +0200 Subject: [PATCH] test: route the global fetch stub onto the undici pinned engine The requirePublicHost egress paths now dispatch through undici's own fetch, so the existing global fetch stubs in the AI, GeoIP, moodlog, send, and ntfy suites no longer intercept them. Delegate undici's fetch back to the installed global stub so each suite keeps asserting its request shape and canned responses without exercising the network. --- .../moodlog/test/__tests__/route.test.ts | 15 +++++++++++++++ src/app/api/send/__tests__/route.test.ts | 15 +++++++++++++++ src/lib/__tests__/geo-asn.test.ts | 15 +++++++++++++++ src/lib/__tests__/geo.test.ts | 15 +++++++++++++++ src/lib/ai/__tests__/anthropic-client.test.ts | 15 +++++++++++++++ src/lib/ai/__tests__/openai-client.test.ts | 15 +++++++++++++++ src/lib/ai/__tests__/provider-contract.test.ts | 15 +++++++++++++++ src/lib/moodlog/__tests__/push.test.ts | 15 +++++++++++++++ .../senders/__tests__/push-attempt-record.test.ts | 15 +++++++++++++++ 9 files changed, 135 insertions(+) diff --git a/src/app/api/integrations/moodlog/test/__tests__/route.test.ts b/src/app/api/integrations/moodlog/test/__tests__/route.test.ts index b2a77a689..33ac0c7e5 100644 --- a/src/app/api/integrations/moodlog/test/__tests__/route.test.ts +++ b/src/app/api/integrations/moodlog/test/__tests__/route.test.ts @@ -1,5 +1,20 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + vi.mock("@/lib/api-handler", () => ({ apiHandler: unknown>(fn: T) => fn, requireAuth: vi.fn(async () => ({ diff --git a/src/app/api/send/__tests__/route.test.ts b/src/app/api/send/__tests__/route.test.ts index 8ed6df78a..874cca183 100644 --- a/src/app/api/send/__tests__/route.test.ts +++ b/src/app/api/send/__tests__/route.test.ts @@ -1,6 +1,21 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + vi.mock("@/lib/api-handler", () => ({ apiHandler: unknown>(fn: T) => fn, })); diff --git a/src/lib/__tests__/geo-asn.test.ts b/src/lib/__tests__/geo-asn.test.ts index 6a59dfc7b..f10903253 100644 --- a/src/lib/__tests__/geo-asn.test.ts +++ b/src/lib/__tests__/geo-asn.test.ts @@ -1,5 +1,20 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + /** * v1.4.27 B3 — ASN + carrier lookup tests. * diff --git a/src/lib/__tests__/geo.test.ts b/src/lib/__tests__/geo.test.ts index dd231e4fb..63af5bb27 100644 --- a/src/lib/__tests__/geo.test.ts +++ b/src/lib/__tests__/geo.test.ts @@ -1,5 +1,20 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + // v1.18.11 (W3): capture wide-event warnings so the non-ok-status path can // be asserted. `getEvent()` returns null outside a request context, so // without this mock the `getEvent()?.addWarning(...)` call is a silent diff --git a/src/lib/ai/__tests__/anthropic-client.test.ts b/src/lib/ai/__tests__/anthropic-client.test.ts index fe637eabe..63bfa5e73 100644 --- a/src/lib/ai/__tests__/anthropic-client.test.ts +++ b/src/lib/ai/__tests__/anthropic-client.test.ts @@ -2,6 +2,21 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { AnthropicClient } from "../anthropic-client"; import { singleUserTurn } from "../types"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + describe("AnthropicClient", () => { beforeEach(() => { vi.restoreAllMocks(); diff --git a/src/lib/ai/__tests__/openai-client.test.ts b/src/lib/ai/__tests__/openai-client.test.ts index 2a94eed33..70bbcf647 100644 --- a/src/lib/ai/__tests__/openai-client.test.ts +++ b/src/lib/ai/__tests__/openai-client.test.ts @@ -2,6 +2,21 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { OpenAIClient } from "../openai-client"; import { singleUserTurn } from "../types"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + describe("OpenAIClient", () => { beforeEach(() => { vi.restoreAllMocks(); diff --git a/src/lib/ai/__tests__/provider-contract.test.ts b/src/lib/ai/__tests__/provider-contract.test.ts index 7b9190b4b..39ed56e34 100644 --- a/src/lib/ai/__tests__/provider-contract.test.ts +++ b/src/lib/ai/__tests__/provider-contract.test.ts @@ -10,6 +10,21 @@ import { type CompletionResult, } from "../types"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + /** * Provider-contract tests: every concrete AIProvider implementation must * satisfy the same shape — return a `CompletionResult` with the four diff --git a/src/lib/moodlog/__tests__/push.test.ts b/src/lib/moodlog/__tests__/push.test.ts index 28e22446a..3c150e981 100644 --- a/src/lib/moodlog/__tests__/push.test.ts +++ b/src/lib/moodlog/__tests__/push.test.ts @@ -9,6 +9,21 @@ */ import { describe, it, expect, beforeEach, vi } from "vitest"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + vi.mock("@/lib/db", () => ({ prisma: { user: { findUnique: vi.fn() } }, })); diff --git a/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts b/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts index 0c9a339e3..b2e5b7cb8 100644 --- a/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts +++ b/src/lib/notifications/senders/__tests__/push-attempt-record.test.ts @@ -16,6 +16,21 @@ */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +// safeFetch's requirePublicHost path runs through undici's own `fetch` +// (version-locked with its dispatcher). Delegate it to the global `fetch` +// stub these tests install so the existing interception still applies. +vi.mock("undici", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + fetch: (input: unknown, init?: unknown) => + (globalThis.fetch as unknown as (i: unknown, n?: unknown) => unknown)( + input, + init, + ), + }; +}); + // vi.mock factories run before module top-level executes, so anything // referenced inside a factory must be created via vi.hoisted to share // the same hoisted scope.