Skip to content
Draft
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
128 changes: 128 additions & 0 deletions src/modules/file/file-version.repository.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createMock } from '@golevelup/ts-jest';
import { QueryTypes } from 'sequelize';
import { SequelizeFileVersionRepository } from './file-version.repository';
import { type FileVersionModel } from './file-version.model';
import { FileVersion, FileVersionStatus } from './file-version.domain';
Expand Down Expand Up @@ -418,6 +419,133 @@ describe('SequelizeFileVersionRepository', () => {
});
});

describe('updateStatusBatch', () => {
it('When updating status for multiple versions, then it calls model update with all ids', async () => {
const ids = ['id-1', 'id-2', 'id-3'];
const status = FileVersionStatus.DELETED;

jest.spyOn(fileVersionModel, 'update').mockResolvedValue([3] as any);

await repository.updateStatusBatch(ids, status);

expect(fileVersionModel.update).toHaveBeenCalledWith(
{ status },
{ where: { id: ids } },
);
});
});

describe('delete', () => {
it('When deleting a version, then it calls model destroy with the id', async () => {
const versionId = 'version-id';

jest.spyOn(fileVersionModel, 'destroy').mockResolvedValue(1 as any);

await repository.delete(versionId);

expect(fileVersionModel.destroy).toHaveBeenCalledWith({
where: { id: versionId },
});
});
});

describe('deleteUserVersionsBatch', () => {
it('When deleting user versions in batch, then it executes the query and returns affected count', async () => {
const userId = 'user-uuid';
const limit = 10;
const affectedRows = 5;

jest
.spyOn(fileVersionModel.sequelize, 'query')
.mockResolvedValue([undefined, affectedRows] as any);

const result = await repository.deleteUserVersionsBatch(userId, limit);

expect(result).toBe(affectedRows);
expect(fileVersionModel.sequelize.query).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
replacements: expect.objectContaining({
userId,
limit,
deletedStatus: FileVersionStatus.DELETED,
existsStatus: FileVersionStatus.EXISTS,
}),
type: QueryTypes.UPDATE,
}),
);
});
});

describe('deleteUserVersionsByLimits', () => {
it('When deleting user versions by limits, then it executes the query and returns affected count', async () => {
const userId = 'user-uuid';
const retentionDays = 30;
const maxVersions = 5;
const limit = 100;
const affectedRows = 3;

jest
.spyOn(fileVersionModel.sequelize, 'query')
.mockResolvedValue([undefined, affectedRows] as any);

const result = await repository.deleteUserVersionsByLimits(
userId,
retentionDays,
maxVersions,
limit,
);

expect(result).toBe(affectedRows);
expect(fileVersionModel.sequelize.query).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
replacements: expect.objectContaining({
userId,
retentionDays,
maxVersions,
limit,
deletedStatus: FileVersionStatus.DELETED,
existsStatus: FileVersionStatus.EXISTS,
}),
type: QueryTypes.UPDATE,
}),
);
});
});

describe('findExpiredVersionIdsByTierLimits', () => {
it('When finding expired version ids, then it returns the version ids', async () => {
const limit = 50;
const queryResults = [{ version_id: 'uuid-1' }, { version_id: 'uuid-2' }];

jest
.spyOn(fileVersionModel.sequelize, 'query')
.mockResolvedValue(queryResults as any);

const result = await repository.findExpiredVersionIdsByTierLimits(limit);

expect(result).toEqual(['uuid-1', 'uuid-2']);
expect(fileVersionModel.sequelize.query).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
replacements: { limit },
type: QueryTypes.SELECT,
}),
);
});

it('When no expired versions are found, then it returns an empty array', async () => {
jest
.spyOn(fileVersionModel.sequelize, 'query')
.mockResolvedValue([] as any);

const result = await repository.findExpiredVersionIdsByTierLimits(50);

expect(result).toEqual([]);
});
});

describe('sumExistingSizesByUser', () => {
it('When user has versions, then it returns the sum', async () => {
const userId = 'user-uuid-123';
Expand Down
151 changes: 151 additions & 0 deletions src/modules/file/file.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
newFolder,
newUser,
newVersioningLimits,
newWorkspace,
} from '../../../test/fixtures';
import { FileUseCases } from './file.usecase';
import { User } from '../user/user.domain';
Expand Down Expand Up @@ -155,6 +156,15 @@ describe('FileController', () => {
offset: 0,
});
});

it('When getRecentFiles is requested with a limit out of range, then it should throw BadRequestException', async () => {
await expect(
fileController.getRecentFiles(
userMocked,
API_LIMITS.FILES.GET.LIMIT.UPPER_BOUND + 1,
),
).rejects.toThrow(BadRequestException);
});
});

describe('get file by path', () => {
Expand Down Expand Up @@ -201,6 +211,20 @@ describe('FileController', () => {
fileController.getFileMetaByPath(userMocked, longPath),
).rejects.toThrow('Path is too deep');
});

it('When get file metadata by path throws an unexpected error, then it should log and not throw', async () => {
const filePath = '/test/file.png';
jest
.spyOn(fileUseCases, 'getFileMetadataByPath')
.mockRejectedValue(new Error('Unexpected error'));

const result = await fileController.getFileMetaByPath(
userMocked,
filePath,
);

expect(result).toBeUndefined();
});
});

describe('update File MetaData by uuid', () => {
Expand Down Expand Up @@ -311,6 +335,29 @@ describe('FileController', () => {
),
).rejects.toThrow(InternalServerErrorException);
});

it('When fileId is provided without fileUuid, then it should log a warning and still create the thumbnail', async () => {
const dtoWithFileIdOnly: CreateThumbnailDto = {
...createThumbnailDto,
fileUuid: undefined,
};
jest
.spyOn(thumbnailUseCases, 'createThumbnail')
.mockResolvedValue(thumbnailDto);

const result = await fileController.createThumbnail(
userMocked,
dtoWithFileIdOnly,
'drive-web',
'1.0.0',
);

expect(result).toEqual(thumbnailDto);
expect(thumbnailUseCases.createThumbnail).toHaveBeenCalledWith(
userMocked,
dtoWithFileIdOnly,
);
});
});

describe('deleteFileByUuid', () => {
Expand Down Expand Up @@ -634,6 +681,28 @@ describe('FileController', () => {
),
).rejects.toThrow(error);
});

it('When replaceFile is called with a workspace context, then it should pass workspace info to use case', async () => {
const workspace = newWorkspace();
const replacedFile = newFile();
jest.spyOn(fileUseCases, 'replaceFile').mockResolvedValue(replacedFile);

await fileController.replaceFile(
userMocked,
validUuid,
replaceFileDto,
clientId,
requester,
workspace,
);

expect(fileUseCases.replaceFile).toHaveBeenCalledWith(
userMocked,
validUuid,
replaceFileDto,
{ workspace, memberId: requester.uuid },
);
});
});

describe('getFiles', () => {
Expand Down Expand Up @@ -748,6 +817,31 @@ describe('FileController', () => {
undefined,
);
});

it('When getFiles returns a file without plainName, then it should decrypt the file name', async () => {
const fileWithoutPlainName = newFile({
attributes: { plainName: null },
});
jest
.spyOn(fileUseCases, 'getNotTrashedFilesUpdatedAfter')
.mockResolvedValue([fileWithoutPlainName]);
jest
.spyOn(fileUseCases, 'decrypFileName')
.mockReturnValue({ plainName: 'decrypted-name' } as any);

const queryParams: GetFilesDto = {
limit: validLimit,
offset: validOffset,
status: FileStatus.EXISTS,
};

const result = await fileController.getFiles(userMocked, queryParams);

expect(fileUseCases.decrypFileName).toHaveBeenCalledWith(
fileWithoutPlainName,
);
expect(result[0].plainName).toBe('decrypted-name');
});
});

describe('getLimits', () => {
Expand Down Expand Up @@ -791,4 +885,61 @@ describe('FileController', () => {
);
});
});

describe('getFileVersions', () => {
it('When getFileVersions is called, then it should return file versions', async () => {
const fileUuid = v4();
jest.spyOn(fileUseCases, 'getFileVersions').mockResolvedValue([]);

const result = await fileController.getFileVersions(userMocked, fileUuid);

expect(result).toEqual([]);
expect(fileUseCases.getFileVersions).toHaveBeenCalledWith(
userMocked,
fileUuid,
);
});
});

describe('deleteFileVersion', () => {
it('When deleteFileVersion is called, then it should call the use case', async () => {
const fileUuid = v4();
const versionId = v4();
jest
.spyOn(fileUseCases, 'deleteFileVersion')
.mockResolvedValue(undefined);

await fileController.deleteFileVersion(userMocked, fileUuid, versionId);

expect(fileUseCases.deleteFileVersion).toHaveBeenCalledWith(
userMocked,
fileUuid,
versionId,
);
});
});

describe('restoreFileVersion', () => {
it('When restoreFileVersion is called, then it should return the restored file', async () => {
const fileUuid = v4();
const versionId = v4();
const restoredFile = newFile();
jest
.spyOn(fileUseCases, 'restoreFileVersion')
.mockResolvedValue(restoredFile);

const result = await fileController.restoreFileVersion(
userMocked,
fileUuid,
versionId,
);

expect(result).toEqual(restoredFile);
expect(fileUseCases.restoreFileVersion).toHaveBeenCalledWith(
userMocked,
fileUuid,
versionId,
);
});
});
});
Loading
Loading