diff --git a/src/apps/main/device/service.ts b/src/apps/main/device/service.ts index ccbfd8281..cbf0acd66 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 bb8c9ec3d..a354355a4 100644 --- a/src/apps/renderer/context/DeviceContext.tsx +++ b/src/apps/renderer/context/DeviceContext.tsx @@ -37,7 +37,7 @@ export function DeviceProvider({ children }: { children: ReactNode }) { const refreshDevice = () => { setDeviceState({ status: 'LOADING' }); window.electron.getOrCreateDevice().then(({ error, data: device }) => { - if (error) { + if (error || !device) { setDeviceState({ status: 'ERROR' }); return; } diff --git a/src/backend/features/device/getOrCreateDevice.test.ts b/src/backend/features/device/getOrCreateDevice.test.ts new file mode 100644 index 000000000..fc7747d68 --- /dev/null +++ b/src/backend/features/device/getOrCreateDevice.test.ts @@ -0,0 +1,72 @@ +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); + }); + }); +}); diff --git a/src/backend/features/device/getOrCreateDevice.ts b/src/backend/features/device/getOrCreateDevice.ts index b5b7b421a..101b4f69b 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 }; + } }