diff --git a/apps/web/src/stores/connection-store.ts b/apps/web/src/stores/connection-store.ts index cb9159e9..3dff6e46 100644 --- a/apps/web/src/stores/connection-store.ts +++ b/apps/web/src/stores/connection-store.ts @@ -51,6 +51,10 @@ export const useConnectionStore = create((set, get) => ({ } else { set({ lastHealthCheck: Date.now() }); } + } else { + if (get().status === "connected") { + get().setDisconnected(); + } } } catch { if (get().status === "connected") { diff --git a/tests/unit/web/connection-store.test.ts b/tests/unit/web/connection-store.test.ts index 601d435a..4527e894 100644 --- a/tests/unit/web/connection-store.test.ts +++ b/tests/unit/web/connection-store.test.ts @@ -81,6 +81,24 @@ describe("connection-store", () => { expect(fetchMock).toHaveBeenCalledTimes(1); }); + it("checkHealth transitions connected → disconnected on fetch failure", async () => { + fetchMock.mockImplementation(failHealth); + expect(useConnectionStore.getState().status).toBe("connected"); + await useConnectionStore.getState().checkHealth(); + expect(useConnectionStore.getState().status).toBe("disconnected"); + expect(useConnectionStore.getState().failedSince).toBeTypeOf("number"); + }); + + it("checkHealth transitions offline → reconnected on success", async () => { + fetchMock.mockImplementation(okHealth); + useConnectionStore.getState().setOffline(); + expect(useConnectionStore.getState().status).toBe("offline"); + await useConnectionStore.getState().checkHealth(); + expect(useConnectionStore.getState().status).toBe("reconnected"); + expect(useConnectionStore.getState().failedSince).toBeNull(); + expect(useConnectionStore.getState().lastHealthCheck).toBeTypeOf("number"); + }); + it("startPolling is idempotent", () => { useConnectionStore.getState().setDisconnected(); useConnectionStore.getState().startPolling();