import { describe, expect, it, vi } from "vitest"; import { ensureFreshSession, refreshSession, } from "@/lib/session-refresh"; import { createSession, getSession, type SessionData } from "@/lib/session-store"; const json = (body: unknown, status = 200) => new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" }, }); // Seeds a session whose profile access token expires at `profileExpiresAt` // (and feed at `feedExpiresAt`), with known "old" tokens. function seedSession( profileExpiresAt: number | null, feedExpiresAt: number | null = null, ): string { const data: SessionData = { profileAccessToken: "old-access", profileAccessTokenExpiresAt: profileExpiresAt, profileRefreshToken: "old-refresh", feedAccessToken: "old-feed", feedAccessTokenExpiresAt: feedExpiresAt, person: { userId: "u1", name: "Pat Rivera", email: "person@example.com", pictureUrl: null }, createdAt: Date.now(), }; return createSession(data); } // Stub for the refresh path: refresh-token -> new profile tokens, feed -> new feed token. function refreshFetchStub( overrides: Partial> = {}, ) { return vi.fn(async (input: RequestInfo | URL): Promise => { const url = String(input); if (url.endsWith("/api/v4/auth/refresh-token")) { return ( overrides.refresh ?? json({ data: { accessToken: "new-access", refreshToken: "new-refresh", expiresIn: 3600 } }) ); } if (url.endsWith("/api/v4/auth/external-services/feed")) { return overrides.feed ?? json({ accessToken: "new-feed" }); } throw new Error(`unexpected fetch to ${url}`); }); } describe("ensureFreshSession", () => { it("an access token expiring within 2 min triggers refresh; tokens change, no password", async () => { const id = seedSession(Date.now() + 60_000); // 1 min out: inside the 2-min window const fetchImpl = refreshFetchStub(); const updated = await ensureFreshSession(id, { fetchImpl: fetchImpl as unknown as typeof fetch }); expect(updated).not.toBeNull(); expect(updated!.profileAccessToken).toBe("new-access"); expect(updated!.profileRefreshToken).toBe("new-refresh"); expect(updated!.feedAccessToken).toBe("new-feed"); // Persisted under the same id. expect(getSession(id)!.profileAccessToken).toBe("new-access"); // Refresh used the stored refresh token, never the login/password route. expect(fetchImpl.mock.calls.some(([u]) => String(u).endsWith("/api/v4/auth/login"))).toBe(false); }); it("a feed token expiring within 2 min also triggers refresh", async () => { const id = seedSession(Date.now() + 60 * 60_000, Date.now() + 30_000); const fetchImpl = refreshFetchStub(); const updated = await ensureFreshSession(id, { fetchImpl: fetchImpl as unknown as typeof fetch }); expect(updated!.feedAccessToken).toBe("new-feed"); expect(updated!.profileAccessToken).toBe("new-access"); }); it("tokens comfortably in the future are not refreshed", async () => { const id = seedSession(Date.now() + 60 * 60_000, Date.now() + 60 * 60_000); const fetchImpl = refreshFetchStub(); const same = await ensureFreshSession(id, { fetchImpl: fetchImpl as unknown as typeof fetch }); expect(same!.profileAccessToken).toBe("old-access"); expect(fetchImpl).not.toHaveBeenCalled(); }); it("no cookie / unknown session is null", async () => { expect(await ensureFreshSession(undefined)).toBeNull(); expect(await ensureFreshSession("does-not-exist")).toBeNull(); }); }); describe("refreshSession failure", () => { it("a failed refresh deletes the session", async () => { const id = seedSession(Date.now() + 60_000); const fetchImpl = refreshFetchStub({ refresh: json({ data: null }, 401) }); const result = await refreshSession(id, { fetchImpl: fetchImpl as unknown as typeof fetch }); expect(result).toBeNull(); expect(getSession(id)).toBeNull(); }); it("a failed feed exchange during refresh also deletes the session", async () => { const id = seedSession(Date.now() + 60_000); const fetchImpl = refreshFetchStub({ feed: json({ data: null }, 500) }); const result = await ensureFreshSession(id, { fetchImpl: fetchImpl as unknown as typeof fetch }); expect(result).toBeNull(); expect(getSession(id)).toBeNull(); }); });