From 766f2d43a92deb0834f39f75d9c5cf060acb2193 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Tue, 17 Mar 2026 17:48:42 -0500 Subject: [PATCH 1/3] Fix: handle device creation errors and improve error handling in device fetching --- src/apps/renderer/context/DeviceContext.tsx | 17 ++++-- .../renderer/hooks/devices/useDevices.tsx | 8 ++- .../features/device/getOrCreateDevice.ts | 57 +++++++++++-------- 3 files changed, 51 insertions(+), 31 deletions(-) diff --git a/src/apps/renderer/context/DeviceContext.tsx b/src/apps/renderer/context/DeviceContext.tsx index bb8c9ec3db..3300353c66 100644 --- a/src/apps/renderer/context/DeviceContext.tsx +++ b/src/apps/renderer/context/DeviceContext.tsx @@ -36,13 +36,18 @@ export function DeviceProvider({ children }: { children: ReactNode }) { const refreshDevice = () => { setDeviceState({ status: 'LOADING' }); - window.electron.getOrCreateDevice().then(({ error, data: device }) => { - if (error) { + window.electron + .getOrCreateDevice() + .then(({ error, data: device }) => { + if (error || !device) { + setDeviceState({ status: 'ERROR' }); + return; + } + setCurrentDevice(device); + }) + .catch(() => { setDeviceState({ status: 'ERROR' }); - return; - } - setCurrentDevice(device); - }); + }); }; const setCurrentDevice = (newDevice: Device) => { diff --git a/src/apps/renderer/hooks/devices/useDevices.tsx b/src/apps/renderer/hooks/devices/useDevices.tsx index 958a0741f9..bc69a320eb 100644 --- a/src/apps/renderer/hooks/devices/useDevices.tsx +++ b/src/apps/renderer/hooks/devices/useDevices.tsx @@ -5,8 +5,12 @@ export function useDevices() { const [devices, setDevices] = useState>([]); const getDevices = async () => { - const devices = await window.electron.devices.getDevices(); - setDevices(devices); + try { + const devices = await window.electron.devices.getDevices(); + setDevices(devices); + } catch { + setDevices([]); + } }; useEffect(() => { diff --git a/src/backend/features/device/getOrCreateDevice.ts b/src/backend/features/device/getOrCreateDevice.ts index b5b7b421ae..101b4f69bb 100644 --- a/src/backend/features/device/getOrCreateDevice.ts +++ b/src/backend/features/device/getOrCreateDevice.ts @@ -30,29 +30,40 @@ async function handleFetchDeviceResult(deviceResult: Result) { } export async function getOrCreateDevice(): Promise> { - const { error, data } = getDeviceIdentifier(); - if (error) return { error }; - - const legacyId = configStore.get('deviceId'); - const savedUUID = configStore.get('deviceUUID'); - logger.debug({ - tag: 'BACKUPS', - msg: '[DEVICE] Checking saved device identifiers', - legacyId, - savedUUID, - }); - - const hasLegacyId = legacyId !== -1; - const hasUuid = savedUUID !== ''; - if (!hasLegacyId && !hasUuid) { - const result = await fetchDevice({ deviceIdentifier: data }); - return await handleFetchDeviceResult(result); - } + try { + const { error, data } = getDeviceIdentifier(); + if (error) return { error }; + + const legacyId = configStore.get('deviceId'); + const savedUUID = configStore.get('deviceUUID'); + logger.debug({ + tag: 'BACKUPS', + msg: '[DEVICE] Checking saved device identifiers', + legacyId, + savedUUID, + }); - /* eventually, this whole if section is going to be replaced - when all the users naturaly migrated to the new identification mechanism */ - const prop = hasUuid ? { uuid: savedUUID } : { legacyId: legacyId.toString() }; + const hasLegacyId = legacyId !== -1; + const hasUuid = savedUUID !== ''; + if (!hasLegacyId && !hasUuid) { + const result = await fetchDevice({ deviceIdentifier: data }); + return await handleFetchDeviceResult(result); + } - const deviceResult = await fetchDeviceLegacyAndMigrate(prop); - return await handleFetchDeviceResult(deviceResult); + /* eventually, this whole if section is going to be replaced + when all the users naturaly migrated to the new identification mechanism */ + const prop = hasUuid ? { uuid: savedUUID } : { legacyId: legacyId.toString() }; + + const deviceResult = await fetchDeviceLegacyAndMigrate(prop); + return await handleFetchDeviceResult(deviceResult); + } catch (error) { + const unknownError = error instanceof Error ? error : new Error('Unexpected error in getOrCreateDevice'); + logger.error({ + tag: 'BACKUPS', + msg: '[DEVICE] Unexpected error in getOrCreateDevice', + error: unknownError, + }); + addUnknownDeviceIssue(unknownError); + return { error: unknownError }; + } } From 42d235d470181f434617b68e1b656c2d9ca7b018 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Thu, 19 Mar 2026 11:02:02 -0500 Subject: [PATCH 2/3] Fix: improve error handling in device fetching and UI state management --- src/apps/main/device/service.ts | 14 ++-- src/apps/renderer/context/DeviceContext.tsx | 3 - .../renderer/hooks/devices/useDevices.tsx | 8 +- .../features/device/getOrCreateDevice.test.ts | 73 +++++++++++++++++++ 4 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 src/backend/features/device/getOrCreateDevice.test.ts diff --git a/src/apps/main/device/service.ts b/src/apps/main/device/service.ts index ccbfd82811..cbf0acd667 100644 --- a/src/apps/main/device/service.ts +++ b/src/apps/main/device/service.ts @@ -34,12 +34,16 @@ export type Device = { }; export async function getDevices(): Promise> { - const response = await driveServerModule.backup.getDevices(); - if (response.isLeft()) { + try { + const response = await driveServerModule.backup.getDevices(); + if (response.isLeft()) { + return []; + } else { + const devices = response.getRight(); + return devices.filter(({ removed, hasBackups }) => !removed && hasBackups).map((device) => device); + } + } catch { return []; - } else { - const devices = response.getRight(); - return devices.filter(({ removed, hasBackups }) => !removed && hasBackups).map((device) => device); } } diff --git a/src/apps/renderer/context/DeviceContext.tsx b/src/apps/renderer/context/DeviceContext.tsx index 3300353c66..5d81d98dfe 100644 --- a/src/apps/renderer/context/DeviceContext.tsx +++ b/src/apps/renderer/context/DeviceContext.tsx @@ -44,9 +44,6 @@ export function DeviceProvider({ children }: { children: ReactNode }) { return; } setCurrentDevice(device); - }) - .catch(() => { - setDeviceState({ status: 'ERROR' }); }); }; diff --git a/src/apps/renderer/hooks/devices/useDevices.tsx b/src/apps/renderer/hooks/devices/useDevices.tsx index bc69a320eb..958a0741f9 100644 --- a/src/apps/renderer/hooks/devices/useDevices.tsx +++ b/src/apps/renderer/hooks/devices/useDevices.tsx @@ -5,12 +5,8 @@ export function useDevices() { const [devices, setDevices] = useState>([]); const getDevices = async () => { - try { - const devices = await window.electron.devices.getDevices(); - setDevices(devices); - } catch { - setDevices([]); - } + const devices = await window.electron.devices.getDevices(); + setDevices(devices); }; useEffect(() => { diff --git a/src/backend/features/device/getOrCreateDevice.test.ts b/src/backend/features/device/getOrCreateDevice.test.ts new file mode 100644 index 0000000000..2311da9389 --- /dev/null +++ b/src/backend/features/device/getOrCreateDevice.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { getOrCreateDevice } from './getOrCreateDevice'; +import { getDeviceIdentifier } from './getDeviceIdentifier'; +import { addUnknownDeviceIssue } from './addUnknownDeviceIssue'; +import { fetchDevice } from './fetchDevice'; +import configStore from '../../../apps/main/config'; + +vi.mock('./getDeviceIdentifier'); +vi.mock('./addUnknownDeviceIssue'); +vi.mock('./fetchDevice'); +vi.mock('./fetchDeviceLegacyAndMigrate'); +vi.mock('./createAndSetupNewDevice'); +vi.mock('../../../apps/main/config', () => ({ + default: { get: vi.fn() }, +})); +vi.mock('../../../apps/shared/dependency-injection/DependencyInjectionUserProvider', () => ({ + DependencyInjectionUserProvider: { get: vi.fn(), updateUser: vi.fn() }, +})); + +describe('getOrCreateDevice', () => { + const mockedGetDeviceIdentifier = vi.mocked(getDeviceIdentifier); + const mockedAddUnknownDeviceIssue = vi.mocked(addUnknownDeviceIssue); + const mockedFetchDevice = vi.mocked(fetchDevice); + const mockedConfigStore = vi.mocked(configStore); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('when an unexpected error is thrown', () => { + it('should return the error and call addUnknownDeviceIssue', async () => { + const unexpectedError = new Error('Unexpected failure'); + mockedGetDeviceIdentifier.mockImplementation(() => { + throw unexpectedError; + }); + + const result = await getOrCreateDevice(); + + expect(result.error).toBe(unexpectedError); + expect(mockedAddUnknownDeviceIssue).toHaveBeenCalledWith(unexpectedError); + }); + + it('should wrap non-Error throws in an Error instance', async () => { + mockedGetDeviceIdentifier.mockImplementation(() => { + throw 'something went wrong'; + }); + + const result = await getOrCreateDevice(); + + expect(result.error).toBeInstanceOf(Error); + expect(result.error?.message).toBe('Unexpected error in getOrCreateDevice'); + expect(mockedAddUnknownDeviceIssue).toHaveBeenCalledWith(result.error); + }); + + it('should return the error when fetchDevice throws', async () => { + mockedGetDeviceIdentifier.mockReturnValue({ + data: { key: 'key', platform: 'linux', hostname: 'host' }, + }); + mockedConfigStore.get.mockImplementation((key: string) => { + if (key === 'deviceId') return -1; + if (key === 'deviceUUID') return ''; + return undefined; + }); + const fetchError = new Error('Network error'); + mockedFetchDevice.mockRejectedValue(fetchError); + + const result = await getOrCreateDevice(); + + expect(result.error).toBe(fetchError); + expect(mockedAddUnknownDeviceIssue).toHaveBeenCalledWith(fetchError); + }); + }); +}); From 1cafae79e971e874abf2ba1e6a506fee7664a7b2 Mon Sep 17 00:00:00 2001 From: Esteban Galvis Date: Thu, 19 Mar 2026 11:07:09 -0500 Subject: [PATCH 3/3] Fix: streamline device creation error handling in DeviceProvider --- src/apps/renderer/context/DeviceContext.tsx | 16 +++++++--------- .../features/device/getOrCreateDevice.test.ts | 1 - 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/apps/renderer/context/DeviceContext.tsx b/src/apps/renderer/context/DeviceContext.tsx index 5d81d98dfe..a354355a4f 100644 --- a/src/apps/renderer/context/DeviceContext.tsx +++ b/src/apps/renderer/context/DeviceContext.tsx @@ -36,15 +36,13 @@ export function DeviceProvider({ children }: { children: ReactNode }) { const refreshDevice = () => { setDeviceState({ status: 'LOADING' }); - window.electron - .getOrCreateDevice() - .then(({ error, data: device }) => { - if (error || !device) { - setDeviceState({ status: 'ERROR' }); - return; - } - setCurrentDevice(device); - }); + window.electron.getOrCreateDevice().then(({ error, data: device }) => { + if (error || !device) { + setDeviceState({ status: 'ERROR' }); + return; + } + setCurrentDevice(device); + }); }; const setCurrentDevice = (newDevice: Device) => { diff --git a/src/backend/features/device/getOrCreateDevice.test.ts b/src/backend/features/device/getOrCreateDevice.test.ts index 2311da9389..fc7747d68c 100644 --- a/src/backend/features/device/getOrCreateDevice.test.ts +++ b/src/backend/features/device/getOrCreateDevice.test.ts @@ -1,4 +1,3 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; import { getOrCreateDevice } from './getOrCreateDevice'; import { getDeviceIdentifier } from './getDeviceIdentifier'; import { addUnknownDeviceIssue } from './addUnknownDeviceIssue';