import { beforeEach, describe, expect, it, vi } from "vitest"; const { mockApi, mockOptima, mockIsInvalidSignatureError, mockRedirect } = vi.hoisted(() => ({ mockApi: { get: vi.fn(), }, mockOptima: { auth: {}, user: { isLoggedIn: vi.fn(), isLoggedInServer: vi.fn(), logout: vi.fn(), refreshSession: vi.fn(), fetchInfo: vi.fn(), checkPermissions: vi.fn(), }, }, mockIsInvalidSignatureError: vi.fn(), mockRedirect: vi.fn(), })); vi.mock("$lib", () => ({ optima: mockOptima })); vi.mock("$lib/optima-api/axios", () => ({ default: mockApi, api: mockApi })); vi.mock("$lib/optima-api/errorHandler", () => ({ isInvalidSignatureError: mockIsInvalidSignatureError, })); vi.mock("@sveltejs/kit", () => ({ redirect: mockRedirect, })); import { handle } from "./hooks.server"; function createMockEvent(overrides: Record = {}) { const cookies: Record = {}; return { url: new URL("http://localhost/"), cookies: { get: vi.fn((name: string) => cookies[name] ?? null), set: vi.fn((name: string, value: string) => { cookies[name] = value; }), delete: vi.fn((name: string) => { delete cookies[name]; }), }, locals: {} as Record, ...overrides, }; } function createResolve(response = new Response("OK")) { return vi.fn().mockResolvedValue(response); } describe("hooks.server handle", () => { beforeEach(() => { vi.clearAllMocks(); mockIsInvalidSignatureError.mockReturnValue(false); }); // ── Health check bypass ─────────────────────────────────────────── it("passes /healthz through without API check", async () => { const event = createMockEvent({ url: new URL("http://localhost/healthz"), }); const resolve = createResolve(); await handle({ event, resolve } as any); expect(mockApi.get).not.toHaveBeenCalled(); expect(resolve).toHaveBeenCalledWith(event); }); // ── API health check ───────────────────────────────────────────── it("returns 503 page when API teapot check fails", async () => { mockApi.get.mockRejectedValueOnce(new Error("Network error")); const event = createMockEvent(); const resolve = createResolve(); const response = await handle({ event, resolve } as any); expect(response.status).toBe(503); const html = await response.text(); expect(html).toContain("Unable to Reach API"); }); it("returns 503 when API teapot returns non-418 status", async () => { mockApi.get.mockResolvedValueOnce({ status: 200 }); const event = createMockEvent(); const resolve = createResolve(); const response = await handle({ event, resolve } as any); expect(response.status).toBe(503); }); // ── Logout path ────────────────────────────────────────────────── it("clears cookies and redirects on /logout", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); const event = createMockEvent({ url: new URL("http://localhost/logout"), }); event.cookies.get = vi .fn() .mockReturnValueOnce("access-tok") .mockReturnValueOnce("refresh-tok"); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(event.cookies.delete).toHaveBeenCalledWith("accessToken", { path: "/", }); expect(event.cookies.delete).toHaveBeenCalledWith("refreshToken", { path: "/", }); expect(mockRedirect).toHaveBeenCalledWith(303, "/login"); }); // ── Login path when already logged in ───────────────────────────── it("redirects to / when visiting /login while already logged in", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); mockOptima.user.isLoggedInServer.mockReturnValue(true); const event = createMockEvent({ url: new URL("http://localhost/login"), }); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(mockRedirect).toHaveBeenCalledWith(303, "/"); }); // ── Login path when not logged in ───────────────────────────────── it("resolves /login normally when user is not logged in", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); mockOptima.user.isLoggedInServer.mockReturnValue(false); const event = createMockEvent({ url: new URL("http://localhost/login"), }); const resolve = createResolve(); await handle({ event, resolve } as any); expect(resolve).toHaveBeenCalledWith(event); }); // ── No tokens — force logout ────────────────────────────────────── it("forces logout when no access or refresh tokens exist", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn().mockReturnValue(null); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(mockOptima.user.logout).toHaveBeenCalled(); expect(mockRedirect).toHaveBeenCalledWith(303, "/login"); }); // ── Valid access token — normal flow ────────────────────────────── it("resolves normally with valid non-expired access token", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); // Create a JWT payload with exp far in the future const payload = { exp: Math.floor(Date.now() / 1000) + 3600, sub: "user-1", }; const encodedPayload = Buffer.from(JSON.stringify(payload)).toString( "base64url" ); const fakeJwt = `header.${encodedPayload}.signature`; const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return fakeJwt; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); await handle({ event, resolve } as any); expect(resolve).toHaveBeenCalledWith(event); // setTokens should have set session on locals expect(event.locals.session).toBeDefined(); }); // ── Expired access token — refresh ──────────────────────────────── it("refreshes expired access token using refresh token", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); // Create an expired JWT const payload = { exp: Math.floor(Date.now() / 1000) - 100, sub: "u1" }; const encodedPayload = Buffer.from(JSON.stringify(payload)).toString( "base64url" ); const fakeJwt = `h.${encodedPayload}.s`; mockOptima.user.refreshSession.mockResolvedValueOnce({ accessToken: "new-access", refreshToken: "new-refresh", }); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return fakeJwt; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); await handle({ event, resolve } as any); expect(mockOptima.user.refreshSession).toHaveBeenCalledWith("refresh-tok"); expect(event.cookies.set).toHaveBeenCalledWith( "accessToken", "new-access", { path: "/" } ); expect(resolve).toHaveBeenCalledWith(event); }); // ── Expired token, no refresh token — force logout ──────────────── it("forces logout when token expired and no refresh token", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); const payload = { exp: Math.floor(Date.now() / 1000) - 100, sub: "u1" }; const encodedPayload = Buffer.from(JSON.stringify(payload)).toString( "base64url" ); const fakeJwt = `h.${encodedPayload}.s`; const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return fakeJwt; if (name === "refreshToken") return null; return null; }); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(mockOptima.user.logout).toHaveBeenCalled(); expect(mockRedirect).toHaveBeenCalledWith(303, "/login"); }); // ── Invalid signature error — force logout ──────────────────────── it("forces logout on invalid signature error during token decode", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); mockIsInvalidSignatureError.mockReturnValue(true); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); // Return a malformed JWT that will throw when parsed event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return "bad.!!!.token"; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(mockOptima.user.logout).toHaveBeenCalled(); expect(mockRedirect).toHaveBeenCalledWith(303, "/login"); }); // ── Malformed token, refresh succeeds ───────────────────────────── it("refreshes when access token is malformed and refresh token exists", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); mockIsInvalidSignatureError.mockReturnValue(false); mockOptima.user.refreshSession.mockResolvedValueOnce({ accessToken: "recovered-access", refreshToken: "recovered-refresh", }); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return "totally.broken"; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); await handle({ event, resolve } as any); expect(mockOptima.user.refreshSession).toHaveBeenCalledWith("refresh-tok"); expect(resolve).toHaveBeenCalled(); }); // ── No access token, has refresh — try refresh ──────────────────── it("attempts refresh when only refresh token exists", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); mockOptima.user.refreshSession.mockResolvedValueOnce({ accessToken: "new-access", refreshToken: "new-refresh", }); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return null; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); await handle({ event, resolve } as any); expect(mockOptima.user.refreshSession).toHaveBeenCalledWith("refresh-tok"); expect(resolve).toHaveBeenCalled(); }); // ── No access token, refresh fails — force logout ───────────────── it("forces logout when only refresh token exists but refresh fails", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); mockIsInvalidSignatureError.mockReturnValue(false); mockOptima.user.refreshSession.mockRejectedValueOnce( new Error("Refresh failed") ); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return null; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(mockOptima.user.logout).toHaveBeenCalled(); expect(mockRedirect).toHaveBeenCalledWith(303, "/login"); }); // ── Refresh fails with invalid signature ────────────────────────── it("logs warning on invalid signature during refresh fallback", async () => { mockApi.get.mockResolvedValueOnce({ status: 418 }); const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {}); // First call: malformed token parse error (not invalid signature) // Second call: refresh also fails with invalid signature mockIsInvalidSignatureError .mockReturnValueOnce(false) .mockReturnValueOnce(true); mockOptima.user.refreshSession.mockRejectedValueOnce( new Error("invalid signature") ); const event = createMockEvent({ url: new URL("http://localhost/dashboard"), }); event.cookies.get = vi.fn((name: string) => { if (name === "accessToken") return "bad.!!!.token"; if (name === "refreshToken") return "refresh-tok"; return null; }); const resolve = createResolve(); try { await handle({ event, resolve } as any); } catch { // redirect throws } expect(warnSpy).toHaveBeenCalledWith( "Invalid refresh token signature — forcing logout." ); expect(mockOptima.user.logout).toHaveBeenCalled(); warnSpy.mockRestore(); }); });