Skip to content
Merged
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
@@ -1,33 +1,36 @@
import { calls, mockProps } from '@/tests/vitest/utils.helper.test';
import { loggerMock } from '@/tests/vitest/mocks.helper.test';
import { mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test';
import { getDeletedItems } from './get-deleted-items';
import { FileUuid } from '@/apps/main/database/entities/DriveFile';
import * as isItemDeletedModule from './is-item-deleted';

describe('get-deleted-items', () => {
const isItemDeletedMock = partialSpyOn(isItemDeletedModule, 'isItemDeleted');

let props: Parameters<typeof getDeletedItems>[0];

beforeEach(() => {
props = mockProps<typeof getDeletedItems>({
remotes: [{ uuid: 'uuid' as FileUuid }],
locals: [{ uuid: 'uuid' as FileUuid }],
checkpoint: {},
remotes: [],
locals: [{ uuid: 'uuid' as FileUuid, updatedAt: 'datetime' }],
});
});

it('should return item if not exists remotely', () => {
it('should return empty if item is not deleted', () => {
// Given
props.remotes = [];
isItemDeletedMock.mockReturnValue(false);
// When
const res = getDeletedItems(props);
// Then
expect(res).toHaveLength(1);
calls(loggerMock.error).toMatchObject([{ msg: 'Remote item does not exist' }]);
expect(res).toHaveLength(0);
});

it('should not return item if updatedAt and status are equal', () => {
it('should return local if item is deleted', () => {
// Given
isItemDeletedMock.mockReturnValue(true);
// When
const res = getDeletedItems(props);
// Then
expect(res).toHaveLength(0);
calls(loggerMock.error).toHaveLength(0);
expect(res).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -1,31 +1,18 @@
import { SimpleDriveFile } from '@/apps/main/database/entities/DriveFile';
import { SimpleDriveFolder } from '@/apps/main/database/entities/DriveFolder';
import { FileProps, FolderProps } from '../recovery-sync.types';
import { isItemDeleted } from './is-item-deleted';

type Props = FileProps | FolderProps;

export function getDeletedItems(props: FolderProps): SimpleDriveFolder[];
export function getDeletedItems(props: FileProps): SimpleDriveFile[];

export function getDeletedItems({ ctx, type, remotes, locals }: Props) {
const remotesMap = new Map(remotes.map((item) => [item.uuid, item]));

const itemsToDelete = locals.filter((local) => {
const remote = remotesMap.get(local.uuid);

if (!remote) {
ctx.logger.error({
msg: 'Remote item does not exist',
type,
name: local.name,
updatedAt: local.updatedAt,
});
export function getDeletedItems({ ctx, type, remotes, locals, checkpoint }: Props) {
const checkpointDate = new Date(checkpoint.updatedAt);

return true;
}

return false;
});
const remotesMap = new Map(remotes.map((item) => [item.uuid, item]));
const deletedItems = locals.filter((local) => isItemDeleted({ ctx, type, local, remotesMap, checkpointDate }));

return itemsToDelete;
return deletedItems;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,34 @@ import { mockProps, partialSpyOn } from '@/tests/vitest/utils.helper.test';
import { getItemsToSync } from './get-items-to-sync';
import { FileUuid } from '@/apps/main/database/entities/DriveFile';
import * as isItemToSyncModule from './is-item-to-sync';
import { SqliteModule } from '@/infra/sqlite/sqlite.module';

describe('get-items-to-sync', () => {
const getCheckpointMock = partialSpyOn(SqliteModule.CheckpointModule, 'getCheckpoint');
const isItemToSyncMock = partialSpyOn(isItemToSyncModule, 'isItemToSync');

let props: Parameters<typeof getItemsToSync>[0];

beforeEach(() => {
getCheckpointMock.mockResolvedValue({ data: { updatedAt: 'datetime' } });

props = mockProps<typeof getItemsToSync>({
checkpoint: {},
remotes: [{ uuid: 'uuid' as FileUuid, updatedAt: 'datetime' }],
locals: [],
});
});

it('should return empty if there is no checkpoint', async () => {
// Given
getCheckpointMock.mockResolvedValue({ data: undefined });
// When
const res = await getItemsToSync(props);
// Then
expect(res).toHaveLength(0);
});

it('should return empty if it is not item to sync', async () => {
it('should return empty if item is synced', () => {
// Given
isItemToSyncMock.mockReturnValue(false);
// When
const res = await getItemsToSync(props);
const res = getItemsToSync(props);
// Then
expect(res).toHaveLength(0);
});

it('should return remote if it is item to sync', async () => {
it('should return remote if item is not synced', () => {
// Given
isItemToSyncMock.mockReturnValue(true);
// When
const res = await getItemsToSync(props);
const res = getItemsToSync(props);
// Then
expect(res).toHaveLength(1);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
import { ParsedFileDto, ParsedFolderDto } from '@/infra/drive-server-wip/out/dto';
import { FileProps, FolderProps } from '../recovery-sync.types';
import { isItemToSync } from './is-item-to-sync';
import { SqliteModule } from '@/infra/sqlite/sqlite.module';

type Props = FileProps | FolderProps;

export async function getItemsToSync(props: FolderProps): Promise<ParsedFolderDto[]>;
export async function getItemsToSync(props: FileProps): Promise<ParsedFileDto[]>;

export async function getItemsToSync({ ctx, type, remotes, locals }: Props) {
const { data: checkpoint } = await SqliteModule.CheckpointModule.getCheckpoint({
userUuid: ctx.userUuid,
workspaceId: ctx.workspaceId,
type,
});

if (!checkpoint) return [];
export function getItemsToSync(props: FolderProps): ParsedFolderDto[];
export function getItemsToSync(props: FileProps): ParsedFileDto[];

export function getItemsToSync({ ctx, type, remotes, locals, checkpoint }: Props) {
const checkpointDate = new Date(checkpoint.updatedAt);

const localsMap = new Map(locals.map((file) => [file.uuid, file]));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { calls, mockProps } from '@/tests/vitest/utils.helper.test';
import { loggerMock } from '@/tests/vitest/mocks.helper.test';
import { FileUuid } from '@/apps/main/database/entities/DriveFile';
import { isItemDeleted } from './is-item-deleted';

describe('is-item-deleted', () => {
let props: Parameters<typeof isItemDeleted>[0];

beforeEach(() => {
props = mockProps<typeof isItemDeleted>({
checkpointDate: new Date('2024-01-03'),
local: { uuid: 'uuid' as FileUuid },
remotesMap: new Map([['uuid' as FileUuid, { updatedAt: '2024-01-01' }]]),
});
});

it('should return false is updatedAt is equal than checkpoint', () => {
// Given
props.local.updatedAt = '2024-01-03';
// When
const res = isItemDeleted(props);
// Then
expect(res).toBe(false);
calls(loggerMock.error).toHaveLength(0);
});

it('should return false is updatedAt is greater than checkpoint', () => {
// Given
props.local.updatedAt = '2024-01-04';
// When
const res = isItemDeleted(props);
// Then
expect(res).toBe(false);
calls(loggerMock.error).toHaveLength(0);
});

it('should return true if remote item does not exist', () => {
// Given
props.remotesMap = new Map();
// When
const res = isItemDeleted(props);
// Then
expect(res).toBe(true);
calls(loggerMock.error).toMatchObject([{ msg: 'Remote item does not exist' }]);
});

it('should return false if updatedAt is equal', () => {
// Given
props.local.updatedAt = '2024-01-01';
// When
const res = isItemDeleted(props);
// Then
expect(res).toBe(false);
calls(loggerMock.error).toHaveLength(0);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { FileUuid, SimpleDriveFile } from '@/apps/main/database/entities/DriveFile';
import { FolderUuid, SimpleDriveFolder } from '@/apps/main/database/entities/DriveFolder';
import { SyncContext } from '@/apps/sync-engine/config';
import { ParsedFileDto, ParsedFolderDto } from '@/infra/drive-server-wip/out/dto';

type Props = {
ctx: SyncContext;
type: 'file' | 'folder';
checkpointDate: Date;
local: SimpleDriveFile | SimpleDriveFolder;
remotesMap: Map<FileUuid | FolderUuid, ParsedFileDto | ParsedFolderDto>;
};

export function isItemDeleted({ ctx, type, local, remotesMap, checkpointDate }: Props) {
if (new Date(local.updatedAt) >= checkpointDate) {
return false;
}

const remote = remotesMap.get(local.uuid);

if (!remote) {
ctx.logger.error({
msg: 'Remote item does not exist',
type,
name: local.name,
updatedAt: local.updatedAt,
});

return true;
}

return false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe('is-item-to-sync', () => {
calls(loggerMock.error).toHaveLength(0);
});

it('should return true if not exists locally', () => {
it('should return true if local item does not exists', () => {
// Given
props.localsMap = new Map();
// When
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import * as getLocalFilesModule from './get-local-files';
import { SqliteModule } from '@/infra/sqlite/sqlite.module';

describe('files-recovery-sync', () => {
const getCheckpointMock = partialSpyOn(SqliteModule.CheckpointModule, 'getCheckpoint');
const getFilesMock = partialSpyOn(DriveServerWipModule.FileModule, 'getFiles');
const getLocalFilesMock = partialSpyOn(getLocalFilesModule, 'getLocalFiles');
const getItemsToSyncMock = partialSpyOn(getItemsToSyncModule, 'getItemsToSync');
Expand All @@ -21,12 +22,22 @@ describe('files-recovery-sync', () => {
});

beforeEach(() => {
getCheckpointMock.mockResolvedValue({ data: { updatedAt: 'datetime' } });
getFilesMock.mockResolvedValue({ data: [{ uuid: 'uuid' as FileUuid }] });
getLocalFilesMock.mockResolvedValue([{ uuid: 'uuid' as FileUuid }]);
getItemsToSyncMock.mockResolvedValue([{ uuid: 'create' as FileUuid }]);
getItemsToSyncMock.mockReturnValue([{ uuid: 'create' as FileUuid }]);
getDeletedItemsMock.mockReturnValue([{ uuid: 'deleted' as FileUuid, parentUuid: 'parentUuid' }]);
});

it('should return empty if no checkpoint', async () => {
// Given
getCheckpointMock.mockResolvedValue({ data: undefined });
// When
const res = await filesRecoverySync(props);
// Then
expect(res).toHaveLength(0);
});

it('should return empty if no remote files', async () => {
// Given
getFilesMock.mockResolvedValue({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ type Props = {
};

export async function filesRecoverySync({ ctx, offset }: Props) {
const { data: checkpoint } = await SqliteModule.CheckpointModule.getCheckpoint({
userUuid: ctx.userUuid,
workspaceId: ctx.workspaceId,
type: 'file',
});

if (!checkpoint) return [];

const query: GetFilesQuery = {
limit: FETCH_LIMIT_1000,
offset,
Expand All @@ -35,8 +43,8 @@ export async function filesRecoverySync({ ctx, offset }: Props) {

if (!locals) return [];

const filesToSync = await getItemsToSync({ ctx, type: 'file', remotes, locals });
const deletedFiles = getDeletedItems({ ctx, type: 'file', remotes, locals });
const filesToSync = getItemsToSync({ ctx, type: 'file', remotes, locals, checkpoint });
const deletedFiles = getDeletedItems({ ctx, type: 'file', remotes, locals, checkpoint });

const filesToSyncPromises = createOrUpdateFiles({ ctx, fileDtos: filesToSync });
const deletedFilesPromises = deletedFiles.map(async (file) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SqliteModule } from '@/infra/sqlite/sqlite.module';
import { FileUuid } from '@/apps/main/database/entities/DriveFile';

describe('folders-recovery-sync', () => {
const getCheckpointMock = partialSpyOn(SqliteModule.CheckpointModule, 'getCheckpoint');
const getFoldersMock = partialSpyOn(DriveServerWipModule.FolderModule, 'getFolders');
const getLocalFoldersMock = partialSpyOn(getLocalFoldersModule, 'getLocalFolders');
const getItemsToSyncMock = partialSpyOn(getItemsToSyncModule, 'getItemsToSync');
Expand All @@ -22,12 +23,22 @@ describe('folders-recovery-sync', () => {
});

beforeEach(() => {
getCheckpointMock.mockResolvedValue({ data: { updatedAt: 'datetime' } });
getFoldersMock.mockResolvedValue({ data: [{ uuid: 'uuid' as FolderUuid }] });
getLocalFoldersMock.mockResolvedValue([{ uuid: 'uuid' as FolderUuid }]);
getItemsToSyncMock.mockResolvedValue([{ uuid: 'create' as FileUuid }]);
getItemsToSyncMock.mockReturnValue([{ uuid: 'create' as FileUuid }]);
getDeletedItemsMock.mockReturnValue([{ uuid: 'deleted' as FileUuid, parentUuid: 'parentUuid' }]);
});

it('should return empty if no checkpoint', async () => {
// Given
getCheckpointMock.mockResolvedValue({ data: undefined });
// When
const res = await foldersRecoverySync(props);
// Then
expect(res).toHaveLength(0);
});

it('should return empty if no remote folders', async () => {
// Given
getFoldersMock.mockResolvedValue({});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ type Props = {
};

export async function foldersRecoverySync({ ctx, offset }: Props) {
const { data: checkpoint } = await SqliteModule.CheckpointModule.getCheckpoint({
userUuid: ctx.userUuid,
workspaceId: ctx.workspaceId,
type: 'folder',
});

if (!checkpoint) return [];

const query: GetFoldersQuery = {
limit: FETCH_LIMIT_1000,
offset,
Expand All @@ -35,8 +43,8 @@ export async function foldersRecoverySync({ ctx, offset }: Props) {

if (!locals) return [];

const foldersToSync = await getItemsToSync({ ctx, type: 'folder', remotes, locals });
const deletedFolders = getDeletedItems({ ctx, type: 'folder', remotes, locals });
const foldersToSync = getItemsToSync({ ctx, type: 'folder', remotes, locals, checkpoint });
const deletedFolders = getDeletedItems({ ctx, type: 'folder', remotes, locals, checkpoint });

const foldersToSyncPromises = createOrUpdateFolders({ ctx, folderDtos: foldersToSync });
const deletedFoldersPromises = deletedFolders.map(async (folder) => {
Expand Down
Loading
Loading