import { http, HttpResponse } from "msw"; import { describe, expect, it, vi } from "vitest"; import { resource } from "@/test/resource"; import { server } from "@/test/server"; import { apiClient, configureAuthClient } from "./client"; describe("apiClient authentication", () => { it("refreshes once and retries an unauthorized protected request", async () => { let token = "expired"; const refresh = vi.fn(() => { token = "fresh"; return Promise.resolve(true); }); const authorization: string[] = []; server.use( http.get("*/api/v1/brokerage-accounts", ({ request }) => { authorization.push(request.headers.get("Authorization") ?? ""); return authorization.length === 1 ? HttpResponse.json({ code: "UNAUTHORIZED" }, { status: 401 }) : HttpResponse.json(resource([])); }), ); configureAuthClient({ accessToken: () => token, clear: vi.fn(), refresh }); const { data } = await apiClient.GET("/api/v1/brokerage-accounts"); expect(data?.data).toEqual([]); expect(refresh).toHaveBeenCalledOnce(); expect(authorization).toEqual(["Bearer expired", "Bearer fresh"]); }); it("clears the session when refresh cannot restore it", async () => { const clear = vi.fn(); server.use( http.get("*/api/v1/brokerage-accounts", () => HttpResponse.json({ code: "UNAUTHORIZED" }, { status: 401 }), ), ); configureAuthClient({ accessToken: () => "expired", clear, refresh: () => Promise.resolve(false), }); await apiClient.GET("/api/v1/brokerage-accounts"); expect(clear).toHaveBeenCalledOnce(); }); it("does not attach Bearer auth to authentication endpoints", async () => { let authorization: null | string = null; server.use( http.post("*/api/v1/auth/refresh", ({ request }) => { authorization = request.headers.get("Authorization"); return HttpResponse.json({ code: "UNAUTHORIZED" }, { status: 401 }); }), ); configureAuthClient({ accessToken: () => "token", clear: vi.fn(), refresh: vi.fn(), }); await apiClient.POST("/api/v1/auth/refresh"); expect(authorization).toBeNull(); }); it("discards a retried request response after another session takes over", async () => { let generation = 0; let calls = 0; const { promise: pending, resolve: release } = Promise.withResolvers(); server.use( http.get("*/api/v1/brokerage-accounts", async () => { if (++calls === 1) return HttpResponse.json({ code: "UNAUTHORIZED" }, { status: 401 }); await pending; return HttpResponse.json(resource([{ id: "previous-user-account" }])); }), ); configureAuthClient({ accessToken: () => "synthetic", clear: vi.fn(), generation: () => generation, refresh: () => Promise.resolve(true), }); // eslint-disable-next-line vitest/valid-expect -- Attach rejection handling before releasing the request; the assertion is awaited below. const response = expect( apiClient.GET("/api/v1/brokerage-accounts"), ).rejects.toMatchObject({ name: "AbortError" }); await vi.waitFor(() => expect(calls).toBe(2)); generation += 1; release(); await response; }); });