fix: keep session when refresh fails on network error or 5xx (WealthLedger/frontend#97)
Frontend verify / commits (push) Skipped
Frontend verify / security (push) Successful in 1m3s
Frontend verify / verify (push) Failing after 7m11s

This commit was merged in pull request #100.
This commit is contained in:
2026-10-23 15:06:20 +03:00
parent 7b6f34c776
commit 3af9df0ae2
9 changed files with 154 additions and 15 deletions
+52 -7
View File
@@ -92,8 +92,10 @@ describe("AuthProvider", () => {
);
});
it("settles unauthenticated and clears cached data when cold-start refresh has a transport error", async () => {
server.use(http.post("*/api/v1/auth/refresh", () => HttpResponse.error()));
it("settles unauthenticated and clears cached data when cold-start refresh is rejected with 401", async () => {
server.use(http.post("*/api/v1/auth/refresh", () =>
HttpResponse.json({ code: "UNAUTHORIZED" }, { status: 401 }),
));
const queryClient = createTestQueryClient();
queryClient.setQueryData(["cached-query"], { value: "stale" });
queryClient.setQueryData(bankCardsKey, [{ id: "bank-card-1" }]);
@@ -103,12 +105,54 @@ describe("AuthProvider", () => {
await waitFor(() => expect(result.current.ready).toBe(true));
expect(result.current.user).toBeUndefined();
expect(result.current.unavailable).toBe(false);
expect(await requestAuthorization()).toBeNull();
expect(queryClient.getQueryData(["cached-query"])).toBeUndefined();
expect(queryClient.getQueryData(bankCardsKey)).toBeUndefined();
});
it("keeps the original protected 401 when serialized refresh has a transport error", async () => {
it.each([
["a transport error", () => HttpResponse.error()],
["a 502 from the proxy", () => new HttpResponse(null, { status: 502 })],
["a 503 problem", () => HttpResponse.json({ code: "INTERNAL_SERVER_ERROR" }, { status: 503 })],
])("settles as unavailable and keeps cached data when cold-start refresh gets %s", async (_name, failure) => {
server.use(http.post("*/api/v1/auth/refresh", failure));
const queryClient = createTestQueryClient();
queryClient.setQueryData(["cached-query"], { value: "kept" });
const wrapper: FC<PropsWithChildren> = createAuthWrapper(queryClient);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.ready).toBe(true));
expect(result.current.user).toBeUndefined();
expect(result.current.unavailable).toBe(true);
expect(queryClient.getQueryData(["cached-query"])).toEqual({ value: "kept" });
});
it("restores the session when retry succeeds after an outage", async () => {
let isOutage = true;
server.use(http.post("*/api/v1/auth/refresh", () =>
isOutage
? new HttpResponse(null, { status: 502 })
: HttpResponse.json({
accessToken: "restored-token",
user: { email: "user@example.com", id: "user-1" },
}),
));
const queryClient = createTestQueryClient();
const wrapper: FC<PropsWithChildren> = createAuthWrapper(queryClient);
const { result } = renderHook(() => useAuth(), { wrapper });
await waitFor(() => expect(result.current.unavailable).toBe(true));
isOutage = false;
act(() => result.current.retry());
await waitFor(() => expect(result.current.user?.id).toBe("user-1"));
expect(result.current.unavailable).toBe(false);
expect(await requestAuthorization()).toBe("Bearer restored-token");
});
it("keeps the original protected 401 and the session when serialized refresh has a transport error", async () => {
let refreshCalls = 0;
server.use(
http.post("*/api/v1/auth/refresh", () => {
@@ -137,10 +181,11 @@ describe("AuthProvider", () => {
const response = await apiClient.GET("/api/v1/brokerage-accounts");
expect(response.response.status).toBe(401);
await waitFor(() => expect(result.current.user).toBeUndefined());
expect(await requestAuthorization()).toBeNull();
expect(queryClient.getQueryData(["cached-query"])).toBeUndefined();
expect(queryClient.getQueryData(bankCardsKey)).toBeUndefined();
// An unreachable server does not prove the session ended; the next 401 refreshes again.
await waitFor(() => expect(result.current.unavailable).toBe(true));
expect(result.current.user?.id).toBe("user-1");
expect(queryClient.getQueryData(["cached-query"])).toEqual({ value: "stale" });
expect(queryClient.getQueryData(bankCardsKey)).toEqual([{ id: "bank-card-1" }]);
});
it("clears connection status and financial cache independently on logout", async () => {