Refactor authentication logic and API client

- Update AuthProvider with improved error handling and token management
- Enhance API client with better request/response handling
- Add comprehensive test coverage for auth flows
- Update documentation with current tasks

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-04-25 19:23:12 +02:00
parent 6a062a72a7
commit f84aeef249
5 changed files with 82 additions and 50 deletions

View File

@@ -25,7 +25,7 @@ import {
} from "react";
import { useNavigate } from "react-router-dom";
import * as authApi from "../api/auth";
import { SESSION_EXPIRED_EVENT } from "../api/client";
import { setUnauthorizedHandler } from "../api/client";
// ---------------------------------------------------------------------------
// Context
@@ -75,9 +75,11 @@ export function AuthProvider({
}, [navigate]);
useEffect((): (() => void) => {
window.addEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired);
setUnauthorizedHandler((): void => {
handleSessionExpired();
});
return (): void => {
window.removeEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired);
setUnauthorizedHandler(null);
};
}, [handleSessionExpired]);

View File

@@ -4,7 +4,7 @@ import { type ReactElement } from "react";
import { MemoryRouter, Route, Routes, useLocation } from "react-router-dom";
import { FluentProvider, webLightTheme } from "@fluentui/react-components";
import { AuthProvider } from "../AuthProvider";
import { SESSION_EXPIRED_EVENT } from "../../api/client";
import * as clientModule from "../../api/client";
function CurrentLocation(): ReactElement {
const location = useLocation();
@@ -17,9 +17,35 @@ describe("AuthProvider", () => {
vi.restoreAllMocks();
});
it("clears auth state and redirects to /login when session-expired fires", async () => {
it("registers unauthorized handler on mount and clears on unmount", () => {
const setSpy = vi.spyOn(clientModule, "setUnauthorizedHandler");
const { unmount } = render(
<FluentProvider theme={webLightTheme}>
<MemoryRouter initialEntries={["/private"]}>
<AuthProvider>
<Routes>
<Route path="*" element={<CurrentLocation />} />
</Routes>
</AuthProvider>
</MemoryRouter>
</FluentProvider>,
);
expect(setSpy).toHaveBeenCalledWith(expect.any(Function));
unmount();
expect(setSpy).toHaveBeenCalledWith(null);
});
it("calls handler to clear auth state and redirect to /login", async () => {
sessionStorage.setItem("bangui_authenticated", "true");
let capturedHandler: (() => void) | null = null;
vi.spyOn(clientModule, "setUnauthorizedHandler").mockImplementation((handler) => {
capturedHandler = handler;
});
render(
<FluentProvider theme={webLightTheme}>
<MemoryRouter initialEntries={["/private"]}>
@@ -32,7 +58,10 @@ describe("AuthProvider", () => {
</FluentProvider>,
);
window.dispatchEvent(new Event(SESSION_EXPIRED_EVENT));
// Invoke the handler that was captured during render
if (typeof capturedHandler === "function") {
capturedHandler();
}
await waitFor(() => {
expect(screen.getByTestId("location")).toHaveTextContent("/login");