feat(auth): fetch wrapper with 401 redirect + authApi
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// @vitest-environment happy-dom
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
describe("auth/api fetch wrapper", () => {
|
||||
let originalFetch: typeof globalThis.fetch;
|
||||
let originalLocation: Location;
|
||||
|
||||
beforeEach(() => {
|
||||
originalFetch = globalThis.fetch;
|
||||
originalLocation = window.location;
|
||||
delete (window as any).__navCalls;
|
||||
(window as any).__navCalls = [];
|
||||
});
|
||||
afterEach(() => {
|
||||
globalThis.fetch = originalFetch;
|
||||
window.location = originalLocation as any;
|
||||
});
|
||||
|
||||
it("redirects to /login on 401 response", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
headers: new Headers(),
|
||||
json: async () => ({ error: "session_expired" }),
|
||||
} as Response);
|
||||
|
||||
// Stub window.location.href setter to capture navigation without
|
||||
// actually navigating (jsdom does not implement location.href assignment).
|
||||
let href = originalLocation.href;
|
||||
Object.defineProperty(window, "location", {
|
||||
configurable: true,
|
||||
get: () => ({
|
||||
...originalLocation,
|
||||
get href() {
|
||||
return href;
|
||||
},
|
||||
set href(v: string) {
|
||||
(window as any).__navCalls.push(v);
|
||||
href = v;
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { authedFetch } = await import("./api");
|
||||
await expect(authedFetch("/api/anything")).rejects.toThrow();
|
||||
expect((window as any).__navCalls).toContainEqual(expect.stringContaining("/login"));
|
||||
});
|
||||
|
||||
it("does NOT redirect on 403", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 403,
|
||||
statusText: "Forbidden",
|
||||
headers: new Headers(),
|
||||
json: async () => ({ error: "forbidden" }),
|
||||
} as Response);
|
||||
Object.defineProperty(window, "location", {
|
||||
configurable: true,
|
||||
get: () => ({
|
||||
...originalLocation,
|
||||
set href(_: string) {
|
||||
throw new Error("should not nav");
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const { authedFetch } = await import("./api");
|
||||
await expect(authedFetch("/api/anything")).rejects.toThrow();
|
||||
});
|
||||
|
||||
it("returns parsed JSON on 200", async () => {
|
||||
globalThis.fetch = vi.fn().mockResolvedValue({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Headers(),
|
||||
json: async () => ({ hello: "world" }),
|
||||
} as Response);
|
||||
const { authedFetch } = await import("./api");
|
||||
const data = await authedFetch("/api/anything");
|
||||
expect(data).toEqual({ hello: "world" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user