diff --git a/src/shared/utils/__tests__/basePath.test.ts b/src/shared/utils/__tests__/basePath.test.ts deleted file mode 100644 index 2cfa9ecd98..0000000000 --- a/src/shared/utils/__tests__/basePath.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { getDeployBasePath, normalizeBasePath, withBasePath } from "../basePath"; - -describe("normalizeBasePath", () => { - it("normalizes leading/trailing slashes", () => { - expect(normalizeBasePath("omniroute")).toBe("/omniroute"); - expect(normalizeBasePath("/omniroute/")).toBe("/omniroute"); - expect(normalizeBasePath("/omniroute")).toBe("/omniroute"); - expect(normalizeBasePath("")).toBe(""); - expect(normalizeBasePath("/")).toBe(""); - expect(normalizeBasePath(null)).toBe(""); - }); -}); - -describe("getDeployBasePath", () => { - it("reads NEXT_PUBLIC_OMNIROUTE_BASE_PATH first", () => { - expect( - getDeployBasePath({ - NEXT_PUBLIC_OMNIROUTE_BASE_PATH: "/omniroute", - OMNIROUTE_BASE_PATH: "/other", - } as NodeJS.ProcessEnv) - ).toBe("/omniroute"); - }); - - it("falls back to OMNIROUTE_BASE_PATH", () => { - expect( - getDeployBasePath({ - OMNIROUTE_BASE_PATH: "/omniroute", - } as NodeJS.ProcessEnv) - ).toBe("/omniroute"); - }); -}); - -describe("withBasePath", () => { - const base = "/omniroute"; - - it("is a no-op when basePath is empty", () => { - expect(withBasePath("/api/health/ping", "")).toBe("/api/health/ping"); - }); - - it("prefixes absolute app paths", () => { - expect(withBasePath("/api/health/ping", base)).toBe("/omniroute/api/health/ping"); - expect(withBasePath("/v1/models", base)).toBe("/omniroute/v1/models"); - }); - - it("does not double-prefix", () => { - expect(withBasePath("/omniroute/api/health/ping", base)).toBe("/omniroute/api/health/ping"); - expect(withBasePath("/omniroute", base)).toBe("/omniroute"); - }); - - it("rewrites same-origin absolute URLs", () => { - expect(withBasePath("https://host.example/api/x", base, "https://host.example")).toBe( - "https://host.example/omniroute/api/x" - ); - }); - - it("leaves external absolute URLs alone", () => { - expect(withBasePath("https://other.example/api/x", base, "https://host.example")).toBe( - "https://other.example/api/x" - ); - }); - - it("leaves protocol-relative URLs alone", () => { - expect(withBasePath("//cdn.example/app.js", base)).toBe("//cdn.example/app.js"); - }); -}); diff --git a/src/shared/utils/__tests__/basePathFetch.test.ts b/src/shared/utils/__tests__/basePathFetch.test.ts deleted file mode 100644 index c64d7f6796..0000000000 --- a/src/shared/utils/__tests__/basePathFetch.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -// @vitest-environment jsdom -import { afterEach, describe, expect, it, vi } from "vitest"; -import { __resetBasePathFetchForTests, installBasePathFetch } from "../basePathFetch"; - -describe("installBasePathFetch", () => { - afterEach(() => { - __resetBasePathFetchForTests(); - vi.unstubAllGlobals(); - }); - - it("is a no-op when basePath is empty", async () => { - const native = vi.fn(async () => new Response("ok")); - vi.stubGlobal("fetch", native); - - const uninstall = installBasePathFetch(""); - await fetch("/api/health/ping"); - expect(native).toHaveBeenCalledWith("/api/health/ping", undefined); - uninstall(); - }); - - it("prefixes absolute paths on fetch when basePath is set", async () => { - const native = vi.fn(async () => new Response("ok")); - vi.stubGlobal("fetch", native); - - const uninstall = installBasePathFetch("/omniroute"); - await fetch("/api/health/ping"); - expect(native).toHaveBeenCalledWith("/omniroute/api/health/ping", undefined); - uninstall(); - }); - - it("does not double-prefix already-prefixed paths", async () => { - const native = vi.fn(async () => new Response("ok")); - vi.stubGlobal("fetch", native); - - const uninstall = installBasePathFetch("/omniroute"); - await fetch("/omniroute/api/health/ping"); - expect(native).toHaveBeenCalledWith("/omniroute/api/health/ping", undefined); - uninstall(); - }); - - it("restores native fetch after last uninstall", async () => { - const native = vi.fn(async () => new Response("ok")); - vi.stubGlobal("fetch", native); - - const a = installBasePathFetch("/omniroute"); - const b = installBasePathFetch("/omniroute"); - a(); - await fetch("/api/x"); - expect(native).toHaveBeenCalledWith("/omniroute/api/x", undefined); - b(); - native.mockClear(); - await fetch("/api/x"); - expect(native).toHaveBeenCalledWith("/api/x", undefined); - }); -}); diff --git a/tests/unit/shared/basePath.test.ts b/tests/unit/shared/basePath.test.ts new file mode 100644 index 0000000000..8b86e95701 --- /dev/null +++ b/tests/unit/shared/basePath.test.ts @@ -0,0 +1,75 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { + getDeployBasePath, + normalizeBasePath, + withBasePath, +} from "../../../src/shared/utils/basePath"; + +describe("normalizeBasePath", () => { + it("normalizes leading/trailing slashes", () => { + assert.equal(normalizeBasePath("omniroute"), "/omniroute"); + assert.equal(normalizeBasePath("/omniroute/"), "/omniroute"); + assert.equal(normalizeBasePath("/omniroute"), "/omniroute"); + assert.equal(normalizeBasePath(""), ""); + assert.equal(normalizeBasePath("/"), ""); + assert.equal(normalizeBasePath(null), ""); + }); +}); + +describe("getDeployBasePath", () => { + it("reads NEXT_PUBLIC_OMNIROUTE_BASE_PATH first", () => { + assert.equal( + getDeployBasePath({ + NEXT_PUBLIC_OMNIROUTE_BASE_PATH: "/omniroute", + OMNIROUTE_BASE_PATH: "/other", + } as NodeJS.ProcessEnv), + "/omniroute" + ); + }); + + it("falls back to OMNIROUTE_BASE_PATH", () => { + assert.equal( + getDeployBasePath({ + OMNIROUTE_BASE_PATH: "/omniroute", + } as NodeJS.ProcessEnv), + "/omniroute" + ); + }); +}); + +describe("withBasePath", () => { + const base = "/omniroute"; + + it("is a no-op when basePath is empty", () => { + assert.equal(withBasePath("/api/health/ping", ""), "/api/health/ping"); + }); + + it("prefixes absolute app paths", () => { + assert.equal(withBasePath("/api/health/ping", base), "/omniroute/api/health/ping"); + assert.equal(withBasePath("/v1/models", base), "/omniroute/v1/models"); + }); + + it("does not double-prefix", () => { + assert.equal(withBasePath("/omniroute/api/health/ping", base), "/omniroute/api/health/ping"); + assert.equal(withBasePath("/omniroute", base), "/omniroute"); + }); + + it("rewrites same-origin absolute URLs", () => { + assert.equal( + withBasePath("https://host.example/api/x", base, "https://host.example"), + "https://host.example/omniroute/api/x" + ); + }); + + it("leaves external absolute URLs alone", () => { + assert.equal( + withBasePath("https://other.example/api/x", base, "https://host.example"), + "https://other.example/api/x" + ); + }); + + it("leaves protocol-relative URLs alone", () => { + assert.equal(withBasePath("//cdn.example/app.js", base), "//cdn.example/app.js"); + }); +}); diff --git a/tests/unit/shared/basePathFetch.test.ts b/tests/unit/shared/basePathFetch.test.ts new file mode 100644 index 0000000000..5ce000cf90 --- /dev/null +++ b/tests/unit/shared/basePathFetch.test.ts @@ -0,0 +1,78 @@ +import assert from "node:assert/strict"; +import { afterEach, describe, it, mock } from "node:test"; +import { + __resetBasePathFetchForTests, + installBasePathFetch, +} from "../../../src/shared/utils/basePathFetch"; + +describe("installBasePathFetch", () => { + afterEach(() => { + __resetBasePathFetchForTests(); + }); + + it("is a no-op when basePath is empty", async () => { + const native = mock.fn(async () => new Response("ok")); + const originalFetch = globalThis.fetch; + globalThis.fetch = native as unknown as typeof fetch; + try { + const uninstall = installBasePathFetch(""); + await fetch("/api/health/ping"); + // No-op install never wraps fetch, so the call keeps its original single-argument + // shape (the caller passed no `init`) instead of the two-argument shape the wrapper + // produces once basePath is set. + assert.deepEqual(native.mock.calls[0].arguments, ["/api/health/ping"]); + uninstall(); + } finally { + globalThis.fetch = originalFetch; + } + }); + + it("prefixes absolute paths on fetch when basePath is set", async () => { + const native = mock.fn(async () => new Response("ok")); + const originalFetch = globalThis.fetch; + globalThis.fetch = native as unknown as typeof fetch; + try { + const uninstall = installBasePathFetch("/omniroute"); + await fetch("/api/health/ping"); + assert.deepEqual(native.mock.calls[0].arguments, ["/omniroute/api/health/ping", undefined]); + uninstall(); + } finally { + globalThis.fetch = originalFetch; + } + }); + + it("does not double-prefix already-prefixed paths", async () => { + const native = mock.fn(async () => new Response("ok")); + const originalFetch = globalThis.fetch; + globalThis.fetch = native as unknown as typeof fetch; + try { + const uninstall = installBasePathFetch("/omniroute"); + await fetch("/omniroute/api/health/ping"); + assert.deepEqual(native.mock.calls[0].arguments, ["/omniroute/api/health/ping", undefined]); + uninstall(); + } finally { + globalThis.fetch = originalFetch; + } + }); + + it("restores native fetch after last uninstall", async () => { + const native = mock.fn(async () => new Response("ok")); + const originalFetch = globalThis.fetch; + globalThis.fetch = native as unknown as typeof fetch; + try { + const a = installBasePathFetch("/omniroute"); + const b = installBasePathFetch("/omniroute"); + a(); + await fetch("/api/x"); + assert.deepEqual(native.mock.calls[0].arguments, ["/omniroute/api/x", undefined]); + b(); + native.mock.resetCalls(); + await fetch("/api/x"); + // Once the last consumer uninstalls, fetch is native again — same single-argument + // call shape as the no-op case above. + assert.deepEqual(native.mock.calls[0].arguments, ["/api/x"]); + } finally { + globalThis.fetch = originalFetch; + } + }); +});