diff --git a/src/__tests__/components/MaintenanceBypass.test.tsx b/src/__tests__/components/MaintenanceBypass.test.tsx new file mode 100644 index 00000000..98198fc6 --- /dev/null +++ b/src/__tests__/components/MaintenanceBypass.test.tsx @@ -0,0 +1,55 @@ +import { render } from "@testing-library/react"; +import MaintenanceBypass from "@/components/MaintenanceBypass"; +import { useWallet } from "@/components/WalletContext"; +import { BYPASS_COOKIE_MAX_AGE, MAINTENANCE_COOKIE } from "@/lib/maintenanceConfig"; + +jest.mock("@/components/WalletContext", () => ({ + useWallet: jest.fn(), +})); + +const mockUseWallet = useWallet as jest.MockedFunction; + +function mockWallet(publicKey: string | null) { + mockUseWallet.mockReturnValue({ publicKey } as unknown as ReturnType); +} + +// Reproduces the cookie string MaintenanceBypass.tsx builds on the client. Testing this +// pure logic directly (rather than the full env-derived-allowlist component render) keeps +// the test environment simple, matching the existing convention in maintenanceMode.test.ts. +function buildBypassCookie(publicKey: string, allowlist: string[]): string | null { + if (!allowlist.includes(publicKey.toLowerCase())) return null; + return `${MAINTENANCE_COOKIE}=${publicKey.toLowerCase()}; path=/; max-age=${BYPASS_COOKIE_MAX_AGE}; SameSite=Lax`; +} + +describe("MaintenanceBypass cookie construction", () => { + it("embeds BYPASS_COOKIE_MAX_AGE (24h) as the cookie's max-age for an allowlisted wallet", () => { + const cookie = buildBypassCookie("GALLOWED1", ["gallowed1"]); + expect(cookie).toBe(`${MAINTENANCE_COOKIE}=gallowed1; path=/; max-age=86400; SameSite=Lax`); + expect(BYPASS_COOKIE_MAX_AGE).toBe(60 * 60 * 24); + }); + + it("does not build a cookie for a wallet outside the allowlist", () => { + expect(buildBypassCookie("GNOTALLOWED", ["gallowed1"])).toBeNull(); + }); +}); + +describe("MaintenanceBypass component", () => { + beforeEach(() => { + jest.clearAllMocks(); + document.cookie = `${MAINTENANCE_COOKIE}=; path=/; max-age=0`; + }); + + it("does not set the cookie when no wallet is connected", () => { + mockWallet(null); + + render(); + + expect(document.cookie).not.toContain(MAINTENANCE_COOKIE); + }); + + it("renders nothing", () => { + mockWallet(null); + const { container } = render(); + expect(container).toBeEmptyDOMElement(); + }); +}); diff --git a/src/__tests__/unit/maintenanceConfig.test.ts b/src/__tests__/unit/maintenanceConfig.test.ts new file mode 100644 index 00000000..1d217d6b --- /dev/null +++ b/src/__tests__/unit/maintenanceConfig.test.ts @@ -0,0 +1,11 @@ +import { MAINTENANCE_COOKIE, BYPASS_COOKIE_MAX_AGE } from "@/lib/maintenanceConfig"; + +describe("maintenanceConfig", () => { + it("exposes a 24 hour max age, in seconds", () => { + expect(BYPASS_COOKIE_MAX_AGE).toBe(60 * 60 * 24); + }); + + it("exposes the maintenance bypass cookie name", () => { + expect(MAINTENANCE_COOKIE).toBe("maintenance_bypass"); + }); +}); diff --git a/src/components/MaintenanceBypass.tsx b/src/components/MaintenanceBypass.tsx index 9b038c7d..9f9e44a2 100644 --- a/src/components/MaintenanceBypass.tsx +++ b/src/components/MaintenanceBypass.tsx @@ -1,7 +1,7 @@ "use client"; import { useEffect } from "react"; import { useWallet } from "@/components/WalletContext"; -import { MAINTENANCE_COOKIE, BYPASS_COOKIE_MAX_AGE } from "@/middleware"; +import { MAINTENANCE_COOKIE, BYPASS_COOKIE_MAX_AGE } from "@/lib/maintenanceConfig"; const ALLOWLIST = (process.env.NEXT_PUBLIC_MAINTENANCE_ALLOWLIST ?? "") .split(",") diff --git a/src/lib/maintenanceConfig.ts b/src/lib/maintenanceConfig.ts new file mode 100644 index 00000000..7815ce97 --- /dev/null +++ b/src/lib/maintenanceConfig.ts @@ -0,0 +1,7 @@ +// Shared maintenance-mode constants used by both the edge middleware and the +// client-side bypass component. Kept in a dedicated module (rather than +// exported from middleware.ts) so client components don't pull in +// next-intl/next/server middleware code via the import graph. + +export const MAINTENANCE_COOKIE = "maintenance_bypass"; +export const BYPASS_COOKIE_MAX_AGE = 60 * 60 * 24; // 24 hours, in seconds diff --git a/src/middleware.ts b/src/middleware.ts index 7f459d4e..2b1e684c 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,12 +1,10 @@ import createIntlMiddleware from "next-intl/middleware"; import { NextRequest, NextResponse } from "next/server"; import { routing } from "./i18n/routing"; +import { MAINTENANCE_COOKIE, BYPASS_COOKIE_MAX_AGE } from "./lib/maintenanceConfig"; const intlMiddleware = createIntlMiddleware(routing); -const MAINTENANCE_COOKIE = "maintenance_bypass"; -const BYPASS_COOKIE_MAX_AGE = 60 * 60 * 24; // 24 h - function isMaintenanceEnabled(): boolean { return process.env.NEXT_PUBLIC_MAINTENANCE_MODE === "true"; }