diff --git a/packages/api-client/src/core/client.ts b/packages/api-client/src/core/client.ts index 94182169..70ac9d72 100644 --- a/packages/api-client/src/core/client.ts +++ b/packages/api-client/src/core/client.ts @@ -2,7 +2,26 @@ export class APIClient { constructor(private readonly baseUrl: string) {} async request(url: string, options: RequestInit): Promise { - return await fetch(`${this.baseUrl}${url}`, options) + const response = await fetch(`${this.baseUrl}${url}`, options); + + if (response.status === 403) { + // Clear local data and cookies + if (typeof localStorage !== 'undefined') { + localStorage.clear(); + } + if (typeof document !== 'undefined' && typeof document.cookie !== 'undefined') { + document.cookie.split(";").forEach((cookie) => { + const name = cookie.split("=")[0].trim(); + document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/`; + }); + } + + // Redirect to /auth + window.location.href = "/auth"; + return Promise.reject(new Error("User is not authenticated. Redirecting to /auth.")); + } + + return response; } /** diff --git a/packages/api-client/tests/api-client.spec.ts b/packages/api-client/tests/api-client.spec.ts new file mode 100644 index 00000000..ef35316f --- /dev/null +++ b/packages/api-client/tests/api-client.spec.ts @@ -0,0 +1,68 @@ +import { APIClient } from "../src/APIClient"; +import { rest } from "msw"; +import { setupServer } from "msw/node"; + +const backendUrl = process.env.BACKEND_URL +const apiClient = new APIClient(backendUrl); + +// Mock server for API requests +const server = setupServer( + rest.get(`${backendUrl}/test`, (req, res, ctx) => { + return res(ctx.status(200), ctx.json({ message: "Success" })); + }), + rest.get(`${backendUrl}/auth-required`, (req, res, ctx) => { + return res(ctx.status(403), ctx.json({ message: "Forbidden" })); + }) +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); + +describe("APIClient", () => { + beforeEach(() => { + localStorage.clear(); + document.cookie = ""; + }); + + it("should fetch data successfully", async () => { + const response = await apiClient.get("/test"); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.message).toBe("Success"); + }); + + it("should clear local data and redirect on 403 Forbidden", async () => { + // Mock window.location.href + delete (window as any).location; + (window as any).location = { href: "" }; + + await expect(apiClient.get("/auth-required")).rejects.toThrow( + "User is not authenticated. Redirecting to /auth." + ); + + // Verify localStorage is cleared + expect(localStorage.length).toBe(0); + + // Verify cookies are cleared + expect(document.cookie).toBe(""); + + // Verify redirection + expect(window.location.href).toBe("/auth"); + }); + + it("should handle other HTTP errors gracefully", async () => { + server.use( + rest.get(`${baseUrl}/error`, (req, res, ctx) => { + return res(ctx.status(500), ctx.json({ message: "Server Error" })); + }) + ); + + const response = await apiClient.get("/error"); + expect(response.status).toBe(500); + + const data = await response.json(); + expect(data.message).toBe("Server Error"); + }); +});