From 2f9845338a1c7fff8393e793d3d4ecf0ecf9e5a9 Mon Sep 17 00:00:00 2001 From: DaKheera47 Date: Tue, 20 Jan 2026 23:37:47 +0000 Subject: [PATCH] useSettings error handling --- .../src/client/hooks/useSettings.test.ts | 14 ++++++++ orchestrator/src/client/hooks/useSettings.ts | 32 ++++++++++++++++--- .../src/server/api/routes/test-utils.ts | 9 +++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/orchestrator/src/client/hooks/useSettings.test.ts b/orchestrator/src/client/hooks/useSettings.test.ts index e8aff35..6fba317 100644 --- a/orchestrator/src/client/hooks/useSettings.test.ts +++ b/orchestrator/src/client/hooks/useSettings.test.ts @@ -63,4 +63,18 @@ describe('useSettings', () => { expect(result.current.settings).toEqual(updatedSettings); expect(result.current.showSponsorInfo).toBe(false); }); + + it('handles errors when fetching settings', async () => { + const mockError = new Error('Failed to fetch'); + (api.getSettings as any).mockRejectedValue(mockError); + + const { result } = renderHook(() => useSettings()); + + await waitFor(() => { + expect(result.current.error).toEqual(mockError); + }); + + expect(result.current.isLoading).toBe(false); + expect(result.current.settings).toBeNull(); + }); }); diff --git a/orchestrator/src/client/hooks/useSettings.ts b/orchestrator/src/client/hooks/useSettings.ts index cb8fae3..bf1c7cd 100644 --- a/orchestrator/src/client/hooks/useSettings.ts +++ b/orchestrator/src/client/hooks/useSettings.ts @@ -3,29 +3,41 @@ import type { AppSettings } from '../../shared/types'; import * as api from '../api'; let settingsCache: AppSettings | null = null; -let subscribers: Set<(settings: AppSettings) => void> = new Set(); +let settingsError: Error | null = null; +let subscribers: Set<(settings: AppSettings | null, error: Error | null) => void> = new Set(); let isFetching = false; export function useSettings() { const [settings, setSettings] = useState(settingsCache); + const [error, setError] = useState(settingsError); useEffect(() => { if (settingsCache) { setSettings(settingsCache); } + if (settingsError) { + setError(settingsError); + } - const handleUpdate = (newSettings: AppSettings) => { + const handleUpdate = (newSettings: AppSettings | null, newError: Error | null) => { setSettings(newSettings); + setError(newError); }; subscribers.add(handleUpdate); if (!settingsCache && !isFetching) { isFetching = true; + settingsError = null; api.getSettings() .then((data) => { settingsCache = data; - subscribers.forEach(sub => sub(data)); + settingsError = null; + subscribers.forEach(sub => sub(data, null)); + }) + .catch((err) => { + settingsError = err instanceof Error ? err : new Error(String(err)); + subscribers.forEach(sub => sub(settingsCache, settingsError)); }) .finally(() => { isFetching = false; @@ -39,11 +51,19 @@ export function useSettings() { const refreshSettings = async () => { isFetching = true; + settingsError = null; + subscribers.forEach(sub => sub(settingsCache, null)); + try { const data = await api.getSettings(); settingsCache = data; - subscribers.forEach(sub => sub(data)); + settingsError = null; + subscribers.forEach(sub => sub(data, null)); return data; + } catch (err) { + settingsError = err instanceof Error ? err : new Error(String(err)); + subscribers.forEach(sub => sub(settingsCache, settingsError)); + throw settingsError; } finally { isFetching = false; } @@ -51,7 +71,8 @@ export function useSettings() { return { settings, - isLoading: !settings && isFetching, + error, + isLoading: !settings && isFetching && !error, showSponsorInfo: settings?.showSponsorInfo ?? true, refreshSettings, }; @@ -60,6 +81,7 @@ export function useSettings() { /** @internal For testing only */ export function _resetSettingsCache() { settingsCache = null; + settingsError = null; isFetching = false; subscribers.clear(); } diff --git a/orchestrator/src/server/api/routes/test-utils.ts b/orchestrator/src/server/api/routes/test-utils.ts index ec6bd96..246f0aa 100644 --- a/orchestrator/src/server/api/routes/test-utils.ts +++ b/orchestrator/src/server/api/routes/test-utils.ts @@ -28,7 +28,7 @@ vi.mock('../../pipeline/index.js', () => { getPipelineStatus: vi.fn(() => ({ isRunning: false })), subscribeToProgress: vi.fn((listener: (data: unknown) => void) => { listener(progress); - return () => {}; + return () => { }; }), }; }); @@ -54,6 +54,13 @@ vi.mock('../../services/visa-sponsors/index.js', () => ({ searchSponsors: vi.fn(), getOrganizationDetails: vi.fn(), downloadLatestCsv: vi.fn(), + calculateSponsorMatchSummary: vi.fn((results) => { + if (!results || results.length === 0) return { sponsorMatchScore: 0, sponsorMatchNames: null }; + return { + sponsorMatchScore: results[0].score, + sponsorMatchNames: JSON.stringify(results.map((r: any) => r.sponsor.organisationName)) + }; + }), })); const originalEnv = { ...process.env };