Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class BackupConfiguration {
const { error, data } = await DeviceModule.getOrCreateDevice();
if (error) return [];

const enabledBackupEntries = await DeviceModule.getBackupsFromDevice(data, true);
const { error: backupsError, data: enabledBackupEntries } = await DeviceModule.getBackupsFromDevice(data, true);
if (backupsError || !enabledBackupEntries) return [];

return this.map(enabledBackupEntries, data.bucket);
}
Expand Down
4 changes: 2 additions & 2 deletions src/apps/main/interface.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export interface IElectronAPI {

getOrCreateDevice: () => Promise<Result<Device, Error>>;

getBackupsFromDevice: (device: Device, isCurrent?: boolean) => Promise<Array<BackupInfo>>;
getBackupsFromDevice: (device: Device, isCurrent?: boolean) => Promise<Result<Array<BackupInfo>, Error>>;

addBackup: () => Promise<Result<BackupInfo, Error>>;

Expand All @@ -62,7 +62,7 @@ export interface IElectronAPI {

abortDownloadBackups: (deviceId: string) => void;

renameDevice: (deviceName: string) => Promise<Device>;
renameDevice: (deviceName: string) => Promise<Result<Device, Error>>;
devices: {
getDevices: () => Promise<Array<Device>>;
};
Expand Down
15 changes: 12 additions & 3 deletions src/apps/main/preload.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,26 @@ declare interface Window {

path: typeof import('path');

getOrCreateDevice: typeof import('../../backend/features/device/device.module').DeviceModule.getOrCreateDevice;
getOrCreateDevice: () => Promise<
import('../../context/shared/domain/Result').Result<import('../main/device/service').Device, Error>
>;

renameDevice: typeof import('../../backend/features/device/device.module').DeviceModule.renameDevice;
renameDevice: (
deviceName: string,
) => Promise<import('../../context/shared/domain/Result').Result<import('../main/device/service').Device, Error>>;

devices: {
getDevices: () => Promise<Array<Device>>;
};

onDeviceCreated(func: (value: Device) => void): () => void;

getBackupsFromDevice: typeof import('../../backend/features/device/device.module').DeviceModule.getBackupsFromDevice;
getBackupsFromDevice: (
device: import('../main/device/service').Device,
isCurrent?: boolean,
) => Promise<
import('../../context/shared/domain/Result').Result<import('../backups/BackupInfo').BackupInfo[], Error>
>;

addBackup: typeof import('../../backend/features/backup/add-backup').addBackup;

Expand Down
17 changes: 7 additions & 10 deletions src/apps/renderer/context/DeviceContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,15 @@
const deviceRename = async (deviceName: string) => {
setDeviceState({ status: 'LOADING' });

try {
const updatedDevice = await window.electron.renameDevice(deviceName);
setDeviceState({ status: 'SUCCESS', device: updatedDevice });
setCurrent(updatedDevice);
setSelected(updatedDevice);
} catch (err) {
window.electron.logger.error({
msg: '[RENDERER] Failed to rename device',
error: err,
});
const { error, data: updatedDevice } = await window.electron.renameDevice(deviceName);

Check warning on line 61 in src/apps/renderer/context/DeviceContext.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop-linux&issues=AZ2OXW_MBvXQJmScjZZC&open=AZ2OXW_MBvXQJmScjZZC&pullRequest=311
if (error || !updatedDevice) {
setDeviceState({ status: 'ERROR' });
return;
}

setDeviceState({ status: 'SUCCESS', device: updatedDevice });
setCurrent(updatedDevice);
setSelected(updatedDevice);
};

return (
Expand Down
24 changes: 16 additions & 8 deletions src/apps/renderer/hooks/backups/useBackups.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@
const [backups, setBackups] = useState<Array<BackupInfo>>([]);
const [hasExistingBackups, setHasExistingBackups] = useState(false);

async function fetchBackups(): Promise<void> {
if (!selected) return;
const backups = await window.electron.getBackupsFromDevice(selected, selected === current);
setBackups(backups);
async function fetchBackups(): Promise<boolean> {
if (!selected) return true;

const { error, data } = await window.electron.getBackupsFromDevice(selected, selected === current);

Check warning on line 29 in src/apps/renderer/hooks/backups/useBackups.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-desktop-linux&issues=AZ2OXXCaBvXQJmScjZZD&open=AZ2OXXCaBvXQJmScjZZD&pullRequest=311
if (error || !data) {
setBackups([]);
return false;
}

setBackups(data);
return true;
}

const validateIfBackupExists = async () => {
Expand All @@ -38,13 +45,14 @@
setBackupsState('LOADING');
setBackups([]);

try {
await fetchBackups();
setBackupsState('SUCCESS');
} catch {
const isLoaded = await fetchBackups();
if (!isLoaded) {
setBackupsState('ERROR');
setBackups([]);
return;
}

setBackupsState('SUCCESS');
}

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/features/backup/change-backup-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('change-backup-path', () => {
mockedConfigStoreGet.mockReturnValue(backupList);
mockedGetBackupFolderUuid.mockResolvedValue({ data: 'remote-folder-uuid' });
mockedRenameFolder.mockResolvedValue({ data: {} });
mockedMigrateBackupEntryIfNeeded.mockResolvedValue(migratedBackup);
mockedMigrateBackupEntryIfNeeded.mockResolvedValue({ data: migratedBackup });

const result = await changeBackupPath({ currentPath, newPath });

Expand Down
6 changes: 4 additions & 2 deletions src/backend/features/backup/change-backup-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,13 @@ export async function changeBackupPath({ currentPath, newPath }: Props): Promise

delete backupsList[currentPath];

const migratedExistingBackup = await migrateBackupEntryIfNeeded({
const { error, data } = await migrateBackupEntryIfNeeded({
pathname: newPath,
backup: existingBackup,
});
backupsList[newPath] = migratedExistingBackup;
if (error) return { error };

backupsList[newPath] = data;

configStore.set('backupList', backupsList);

Expand Down
8 changes: 4 additions & 4 deletions src/backend/features/backup/delete-device-backups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ describe('delete-device-backups', () => {
},
];

getBackupsFromDeviceMock.mockResolvedValue(backups);
deleteBackupMock.mockResolvedValue(undefined);
getBackupsFromDeviceMock.mockResolvedValue({ data: backups });
deleteBackupMock.mockResolvedValue({ data: undefined });
getBackupFolderTreeSnapshotMock.mockResolvedValue({
data: {
tree: {
Expand Down Expand Up @@ -68,8 +68,8 @@ describe('delete-device-backups', () => {
},
];

getBackupsFromDeviceMock.mockResolvedValue(backups);
deleteBackupMock.mockResolvedValue(undefined);
getBackupsFromDeviceMock.mockResolvedValue({ data: backups });
deleteBackupMock.mockResolvedValue({ data: undefined });
getBackupFolderTreeSnapshotMock.mockResolvedValue({
data: { tree: { children: [{ id: 10, uuid: 'folder-uuid-1' }] } },
} as never);
Expand Down
7 changes: 6 additions & 1 deletion src/backend/features/backup/delete-device-backups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ type Props = {
};

export async function deleteDeviceBackups({ device, isCurrent }: Props) {
const backups = await DeviceModule.getBackupsFromDevice(device, isCurrent);
const { error: getBackupsError, data: backups } = await DeviceModule.getBackupsFromDevice(device, isCurrent);
if (getBackupsError) {
logger.error({ tag: 'BACKUPS', msg: 'Error fetching backups from device', error: getBackupsError });
return;
}

logger.debug({ tag: 'BACKUPS', msg: '[BACKUPS] Deleting backups from device', count: backups.length });
logger.debug({ tag: 'BACKUPS', msg: '[BACKUPS] Backups details', backups });

Expand Down
3 changes: 3 additions & 0 deletions src/backend/features/backup/download-backup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('download-backup', () => {
it('should download backup and broadcast progress when not aborted', async () => {
downloadDeviceBackupZipMock.mockImplementation(async ({ updateProgress }) => {
updateProgress(33);
return { data: true };
});

await downloadBackup({ device, pathname });
Expand Down Expand Up @@ -86,6 +87,7 @@ describe('download-backup', () => {
const abortListener = ipcMainOnMock.mock.calls[0]?.[1];
abortListener?.({} as never, device.uuid);
updateProgress(90);
return { data: true };
});

await downloadBackup({ device, pathname });
Expand All @@ -98,6 +100,7 @@ describe('download-backup', () => {
const abortListener = ipcMainOnMock.mock.calls[0]?.[1];
abortListener?.({} as never, 'other-device-uuid');
updateProgress(12);
return { data: true };
});

await downloadBackup({ device, pathname });
Expand Down
2 changes: 1 addition & 1 deletion src/backend/features/backup/enable-existing-backup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { migrateBackupEntryIfNeeded } from './migrate-backup-entry-if-needed';
import { PATHS } from '../../../core/electron/paths';
import { createAbsolutePath } from '../../../context/local/localFile/infrastructure/AbsolutePath';
import { DriveServerError } from 'src/infra/drive-server/drive-server.error';
import { GetFolderContentDto } from 'src/infra/drive-server/out/dto';
import { GetFolderContentDto } from '../../../infra/drive-server/out/dto';

vi.mock('../../../apps/main/config');
vi.mock('../../../infra/drive-server/services/folder/services/fetch-folder');
Expand Down
103 changes: 103 additions & 0 deletions src/backend/features/device/createAndSetupNewDevice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { BrowserWindow } from 'electron';
import { broadcastToWindows } from '../../../apps/main/windows';
import { DependencyInjectionUserProvider } from '../../../apps/shared/dependency-injection/DependencyInjectionUserProvider';
import { createNewDevice } from './createNewDevice';
import { createAndSetupNewDevice } from './createAndSetupNewDevice';
import { getDeviceIdentifier } from './getDeviceIdentifier';

vi.mock('electron', async (importOriginal) => {
const actual = await importOriginal<typeof import('electron')>();

return {
...actual,
app: {
...actual.app,
getPath: vi.fn().mockReturnValue('/tmp/backups'),
},
ipcMain: {
...actual.ipcMain,
on: vi.fn(),
handle: vi.fn(),
removeHandler: vi.fn(),
},
BrowserWindow: {
...actual.BrowserWindow,
getAllWindows: vi.fn(),
},
};
});
vi.mock('./getDeviceIdentifier');
vi.mock('./createNewDevice');
vi.mock('../../../apps/main/windows', () => ({
broadcastToWindows: vi.fn(),
}));
vi.mock('../../../apps/shared/dependency-injection/DependencyInjectionUserProvider', () => ({
DependencyInjectionUserProvider: { get: vi.fn(), updateUser: vi.fn() },
}));

describe('createAndSetupNewDevice', () => {
const mockedGetDeviceIdentifier = vi.mocked(getDeviceIdentifier);
const mockedCreateNewDevice = vi.mocked(createNewDevice);
const mockedBroadcastToWindows = vi.mocked(broadcastToWindows);
const mockedBrowserWindowGetAllWindows = vi.mocked(BrowserWindow.getAllWindows);
const mockedUserProviderGet = vi.mocked(DependencyInjectionUserProvider.get);
const mockedUserProviderUpdate = vi.mocked(DependencyInjectionUserProvider.updateUser);

beforeEach(() => {
vi.clearAllMocks();
mockedUserProviderGet.mockReturnValue({ backupsBucket: '' } as never);
mockedBrowserWindowGetAllWindows.mockReturnValue([] as never);
});

it('should return only error when the device identifier is unavailable', async () => {
const error = new Error('Missing device identifier');
mockedGetDeviceIdentifier.mockReturnValue({ error });

const result = await createAndSetupNewDevice();

expect(result).toStrictEqual({ error });
expect(mockedCreateNewDevice).not.toHaveBeenCalled();
expect(mockedBroadcastToWindows).not.toHaveBeenCalled();
});

it('should return only error when the device creation fails', async () => {
const error = new Error('Create device failed');
mockedGetDeviceIdentifier.mockReturnValue({
data: { key: 'key', platform: 'linux', hostname: 'host' },
});
mockedCreateNewDevice.mockResolvedValue({ error });

const result = await createAndSetupNewDevice();

expect(result).toStrictEqual({ error });
expect(mockedBroadcastToWindows).not.toHaveBeenCalled();
expect(mockedUserProviderUpdate).not.toHaveBeenCalled();
});

it('should update the user and notify windows when the device is created', async () => {
const user = { backupsBucket: '' };
const send = vi.fn();
const device = {
id: 1,
uuid: 'device-uuid',
name: 'Laptop',
bucket: 'bucket-1',
removed: false,
hasBackups: true,
};
mockedUserProviderGet.mockReturnValue(user as never);
mockedBrowserWindowGetAllWindows.mockReturnValue([{ webContents: { send } }] as never);
mockedGetDeviceIdentifier.mockReturnValue({
data: { key: 'key', platform: 'linux', hostname: 'host' },
});
mockedCreateNewDevice.mockResolvedValue({ data: device });

const result = await createAndSetupNewDevice();

expect(result).toStrictEqual({ data: device });
expect(user.backupsBucket).toBe('bucket-1');
expect(mockedUserProviderUpdate).toHaveBeenCalledWith(user);
expect(send).toHaveBeenCalledWith('reinitialize-backups');
expect(mockedBroadcastToWindows).toHaveBeenCalledWith('device-created', device);
});
});
9 changes: 4 additions & 5 deletions src/backend/features/device/createAndSetupNewDevice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,16 @@ export async function createAndSetupNewDevice() {
const { error, data: deviceIdentifier } = getDeviceIdentifier();
if (error) return { error };

const createNewDeviceEither = await createNewDevice(deviceIdentifier);
if (createNewDeviceEither.isLeft()) {
const { error: createDeviceError, data: device } = await createNewDevice(deviceIdentifier);
if (createDeviceError) {
logger.error({
tag: 'BACKUPS',
msg: '[DEVICE] Error creating new device',
error: createNewDeviceEither.getLeft(),
error: createDeviceError,
});
return { error: createNewDeviceEither.getLeft() };
return { error: createDeviceError };
}

const device = createNewDeviceEither.getRight();
const user = DependencyInjectionUserProvider.get();
user.backupsBucket = device.bucket;
DependencyInjectionUserProvider.updateUser(user);
Expand Down
42 changes: 42 additions & 0 deletions src/backend/features/device/createNewDevice.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createNewDevice } from './createNewDevice';
import { createUniqueDevice } from './createUniqueDevice';
import { saveDeviceToConfig } from './saveDeviceToConfig';

vi.mock('./createUniqueDevice');
vi.mock('./saveDeviceToConfig');

describe('createNewDevice', () => {
const mockedCreateUniqueDevice = vi.mocked(createUniqueDevice);
const mockedSaveDeviceToConfig = vi.mocked(saveDeviceToConfig);

beforeEach(() => {
vi.clearAllMocks();
});

it('should return only error when creating a unique device fails', async () => {
const error = new Error('Could not create device');
mockedCreateUniqueDevice.mockResolvedValue({ error });

const result = await createNewDevice({ key: 'key', platform: 'linux', hostname: 'host' });

expect(result).toStrictEqual({ error });
expect(mockedSaveDeviceToConfig).not.toHaveBeenCalled();
});

it('should save the device to config when creating the device succeeds', async () => {
const device = {
id: 1,
uuid: 'device-uuid',
name: 'Laptop',
bucket: 'bucket-1',
removed: false,
hasBackups: true,
};
mockedCreateUniqueDevice.mockResolvedValue({ data: device });

const result = await createNewDevice({ key: 'key', platform: 'linux', hostname: 'host' });

expect(result).toStrictEqual({ data: device });
expect(mockedSaveDeviceToConfig).toHaveBeenCalledWith(device);
});
});
Loading
Loading