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
63 changes: 63 additions & 0 deletions src/app/network/UploadFolderManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getUniqueFolderName } from 'app/store/slices/storage/folderUtils/getUni
import tasksService from 'app/tasks/services/tasks.service';
import { beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { TaskFolder, UploadFoldersManager, uploadFoldersWithManager } from './UploadFolderManager';
import * as networkInformation from './networkInformation';

vi.mock('app/drive/services/new-storage.service', () => ({
default: {
Expand Down Expand Up @@ -68,6 +69,10 @@ vi.mock('services/referral.service', () => ({
},
}));

vi.mock('./networkInformation', () => ({
logNetworkInfoForUpload: vi.fn(),
}));

vi.mock('services/error.service', () => ({
default: {
castError: vi.fn().mockImplementation((e) => e),
Expand Down Expand Up @@ -300,6 +305,64 @@ describe('checkUploadFolders', () => {
expect(renameFolderSpy).not.toHaveBeenCalled();
});

it('should log network information on successful folder upload', async () => {
const logNetworkInfoMock = networkInformation.logNetworkInfoForUpload as Mock;
const mockFolder: DriveFolderData = {
id: 0,
uuid: 'uuid',
name: 'MyFolder',
bucket: 'bucket',
parentId: 0,
parent_id: 0,
parentUuid: 'parentUuid',
userId: 0,
user_id: 0,
icon: null,
iconId: null,
icon_id: null,
isFolder: true,
color: null,
encrypt_version: null,
plain_name: 'MyFolder',
deleted: false,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
const taskId = 'task-id';

(createFolder as Mock).mockResolvedValueOnce(mockFolder);
(checkFolderDuplicated as Mock).mockResolvedValueOnce({
duplicatedFoldersResponse: [],
foldersWithDuplicates: [],
foldersWithoutDuplicates: [mockFolder],
});
vi.spyOn(tasksService, 'create').mockReturnValue(taskId);
vi.spyOn(tasksService, 'updateTask').mockReturnValue();
vi.spyOn(tasksService, 'addListener').mockReturnValue();
vi.spyOn(tasksService, 'removeListener').mockReturnValue();

await uploadFoldersWithManager({
payload: [
{
currentFolderId: 'currentFolderId',
root: {
folderId: mockFolder.uuid,
childrenFiles: [],
childrenFolders: [],
name: mockFolder.name,
fullPathEdited: 'path1',
},
options: { taskId },
},
],
selectedWorkspace: null,
dispatch: mockDispatch,
});

expect(logNetworkInfoMock).toHaveBeenCalledOnce();
expect(logNetworkInfoMock).toHaveBeenCalledWith({ folderName: 'MyFolder' });
});

it('should abort the upload if abortController is called', async () => {
const mockParentFolder: DriveFolderData = {
id: 1,
Expand Down
2 changes: 2 additions & 0 deletions src/app/network/UploadFolderManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { QueueUtilsService } from 'utils/queueUtils';
import { wait } from 'utils/timeUtils';
import { ConnectionLostError } from './requests';
import referralService from 'services/referral.service';
import { logNetworkInfoForUpload } from './networkInformation';

interface UploadFolderPayload {
root: IRoot;
Expand Down Expand Up @@ -403,6 +404,7 @@ export class UploadFoldersManager {

options.onSuccess?.();
referralService.trackFolderUpload();
logNetworkInfoForUpload({ folderName: root.name });

setTimeout(() => {
this.dispatch(planThunks.fetchUsageThunk());
Expand Down
75 changes: 75 additions & 0 deletions src/app/network/UploadManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DriveFileData } from 'app/drive/types';
import RetryManager from './RetryManager';
import { TaskStatus } from 'app/tasks/types';
import { ErrorMessages } from 'app/core/constants';
import * as networkInformation from './networkInformation';

vi.mock('app/drive/services/file.service/uploadFile', () => ({
default: vi.fn(() => Promise.resolve({} as DriveFileData)),
Expand Down Expand Up @@ -51,6 +52,16 @@ vi.mock('app/repositories/DatabaseUploadRepository', () => {

vi.mock('i18next', () => ({ default: { language: 'en' }, t: () => 'Translation message' }));

vi.mock('./networkInformation', () => ({
logNetworkInfoForUpload: vi.fn(),
}));

vi.mock('services/referral.service', () => ({
default: {
trackFileUpload: vi.fn(),
},
}));

const openMaxSpaceOccupiedDialogMock = vi.fn();

const mockFile1 = {
Expand Down Expand Up @@ -622,6 +633,70 @@ describe('checkUploadFiles', () => {
);
});

it('should log network information on successful file upload', async () => {
const logNetworkInfoMock = networkInformation.logNetworkInfoForUpload as Mock;
(uploadFile as Mock).mockResolvedValueOnce(mockFile1);
vi.spyOn(tasksService, 'create').mockReturnValue('taskId');
vi.spyOn(tasksService, 'updateTask').mockReturnValue();
vi.spyOn(tasksService, 'addListener').mockReturnValue();
vi.spyOn(tasksService, 'removeListener').mockReturnValue();

await uploadFileWithManager(
[
{
taskId: 'taskId',
filecontent: {
content: 'file-content' as unknown as File,
type: 'text/plain',
name: 'file.txt',
size: 1024,
parentFolderId: 'folder-1',
},
userEmail: '',
parentFolderId: '',
},
],
openMaxSpaceOccupiedDialogMock,
DatabaseUploadRepository.getInstance(),
);

expect(logNetworkInfoMock).toHaveBeenCalledOnce();
expect(logNetworkInfoMock).toHaveBeenCalledWith({ fileName: 'file.txt', fileSize: 1024 });
});

it('should not log network information when upload fails', async () => {
const logNetworkInfoMock = networkInformation.logNetworkInfoForUpload as Mock;
(uploadFile as Mock).mockRejectedValue(new AppError('Upload failed'));
vi.spyOn(tasksService, 'create').mockReturnValue('taskId');
vi.spyOn(tasksService, 'updateTask').mockReturnValue();
vi.spyOn(tasksService, 'addListener').mockReturnValue();
vi.spyOn(tasksService, 'removeListener').mockReturnValue();
vi.spyOn(errorService, 'reportError').mockReturnValue();

await expect(
uploadFileWithManager(
[
{
taskId: 'taskId',
filecontent: {
content: 'file-content' as unknown as File,
type: 'text/plain',
name: 'file.txt',
size: 1024,
parentFolderId: 'folder-1',
},
userEmail: '',
parentFolderId: '',
},
],
openMaxSpaceOccupiedDialogMock,
DatabaseUploadRepository.getInstance(),
),
).rejects.toThrow();

expect(logNetworkInfoMock).not.toHaveBeenCalled();
});

it('When uploading a personal file, then it uses personal credentials', async () => {
const uploadFileSpy = (uploadFile as Mock).mockResolvedValueOnce(mockFile1);

Expand Down
2 changes: 2 additions & 0 deletions src/app/network/UploadManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ErrorMessages } from 'app/core/constants';
import { MAX_UPLOAD_ATTEMPTS, TWENTY_MEGABYTES, USE_MULTIPART_THRESHOLD_BYTES } from './networkConstants';
import { OwnerUserAuthenticationData } from './types';
import referralService from 'services/referral.service';
import { logNetworkInfoForUpload } from './networkInformation';

enum FileSizeType {
Big = 'big',
Expand Down Expand Up @@ -229,6 +230,7 @@ class UploadManager {

fileData.onFinishUploadFile?.(driveFileDataWithNameParsed, taskId);
referralService.trackFileUpload();
logNetworkInfoForUpload({ fileName: file.name, fileSize: file.size });

if (this.onFileUploadCallback) {
this.onFileUploadCallback(driveFileDataWithNameParsed);
Expand Down
128 changes: 128 additions & 0 deletions src/app/network/networkInformation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { getNetworkInformation, logNetworkInfoForUpload } from './networkInformation';

const defineNavigatorConnection = (value: unknown) => {
Object.defineProperty(navigator, 'connection', { value, writable: true, configurable: true });
};

describe('getNetworkInformation', () => {
beforeEach(() => {
defineNavigatorConnection(undefined);
});

it('returns null when the Network Information API is not supported', () => {
expect(getNetworkInformation()).toBeNull();
});

it('returns all network info fields when navigator.connection is available', () => {
defineNavigatorConnection({
type: 'wifi',
effectiveType: '4g',
downlink: 10,
downlinkMax: 100,
rtt: 50,
saveData: false,
});

expect(getNetworkInformation()).toEqual({
type: 'wifi',
effectiveType: '4g',
downlink: 10,
downlinkMax: 100,
rtt: 50,
saveData: false,
});
});

it('returns undefined fields when the connection object has no values', () => {
defineNavigatorConnection({});

expect(getNetworkInformation()).toEqual({
type: undefined,
effectiveType: undefined,
downlink: undefined,
downlinkMax: undefined,
rtt: undefined,
saveData: undefined,
});
});

it('captures saveData: true when the user has data saving enabled', () => {
defineNavigatorConnection({
type: 'cellular',
effectiveType: '2g',
downlink: 0.5,
downlinkMax: 1,
rtt: 800,
saveData: true,
});

expect(getNetworkInformation()?.saveData).toBe(true);
});
});

describe('logNetworkInfoForUpload', () => {
beforeEach(() => {
defineNavigatorConnection(undefined);
vi.restoreAllMocks();
});

it('does not call console.log when the Network Information API is not supported', () => {
const consoleSpy = vi.spyOn(console, 'log');

logNetworkInfoForUpload({ fileName: 'file.txt', fileSize: 1024 });

expect(consoleSpy).not.toHaveBeenCalled();
});

it('logs context and all network info fields together when the API is available', () => {
defineNavigatorConnection({
type: 'wifi',
effectiveType: '4g',
downlink: 10,
downlinkMax: 100,
rtt: 50,
saveData: false,
});
const consoleSpy = vi.spyOn(console, 'log');

logNetworkInfoForUpload({ fileName: 'file.txt', fileSize: 1024 });

expect(consoleSpy).toHaveBeenCalledOnce();
expect(consoleSpy).toHaveBeenCalledWith('[Upload] Network information:', {
fileName: 'file.txt',
fileSize: 1024,
type: 'wifi',
effectiveType: '4g',
downlink: 10,
downlinkMax: 100,
rtt: 50,
saveData: false,
});
});

it('logs context and network info for folder uploads', () => {
defineNavigatorConnection({
type: 'cellular',
effectiveType: '3g',
downlink: 2,
downlinkMax: 10,
rtt: 200,
saveData: true,
});
const consoleSpy = vi.spyOn(console, 'log');

logNetworkInfoForUpload({ folderName: 'MyFolder' });

expect(consoleSpy).toHaveBeenCalledOnce();
expect(consoleSpy).toHaveBeenCalledWith('[Upload] Network information:', {
folderName: 'MyFolder',
type: 'cellular',
effectiveType: '3g',
downlink: 2,
downlinkMax: 10,
rtt: 200,
saveData: true,
});
});
});
47 changes: 47 additions & 0 deletions src/app/network/networkInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
interface NetworkInformation {
type?: string;
effectiveType?: string;
downlink?: number;
downlinkMax?: number;
rtt?: number;
saveData?: boolean;
}

interface NavigatorWithConnection extends Navigator {
connection?: NetworkInformation;
mozConnection?: NetworkInformation;
webkitConnection?: NetworkInformation;
}

export type NetworkInfo = {
type: string | undefined;
effectiveType: string | undefined;
downlink: number | undefined;
downlinkMax: number | undefined;
rtt: number | undefined;
saveData: boolean | undefined;
};

export const getNetworkInformation = (): NetworkInfo | null => {
const nav = navigator as NavigatorWithConnection;
const connection = nav.connection ?? nav.mozConnection ?? nav.webkitConnection;

if (!connection) return null;

return {
type: connection.type,
effectiveType: connection.effectiveType,
downlink: connection.downlink,
downlinkMax: connection.downlinkMax,
rtt: connection.rtt,
saveData: connection.saveData,
};
};

export const logNetworkInfoForUpload = (context: Record<string, unknown>): void => {
const networkInfo = getNetworkInformation();

if (!networkInfo) return;

console.log('[Upload] Network information:', { ...context, ...networkInfo });
};
Loading
Loading