diff --git a/src/modules/file/file.e2e-spec.ts b/src/modules/file/file.e2e-spec.ts index ec33596f8..79e547170 100644 --- a/src/modules/file/file.e2e-spec.ts +++ b/src/modules/file/file.e2e-spec.ts @@ -2,6 +2,7 @@ import { type NestExpressApplication } from '@nestjs/platform-express'; import { BadRequestException, ConflictException, + HttpStatus, type ArgumentsHost, } from '@nestjs/common'; import { createMock } from '@golevelup/ts-jest'; @@ -26,6 +27,7 @@ import { UniqueConstraintFilter } from '../../common/filters/unique-constraint.f import { AesService } from '../../externals/crypto/aes'; import request from 'supertest'; import { ClientEnum } from '../../common/enums/platform.enum'; +import { SequelizeThumbnailRepository } from '../thumbnail/thumbnail.repository'; describe('File module', () => { let app: NestExpressApplication; @@ -52,6 +54,18 @@ describe('File module', () => { await testUser.cleanup(); }); + const createUserFile = (owner: TestUserContext) => + fileRepository.create( + newFile({ + attributes: { + folderId: owner.rootFolder?.id, + folderUuid: owner.rootFolder?.uuid, + userId: owner.user.id, + status: FileStatus.EXISTS, + }, + }), + ); + afterAll(async () => { await app.close(); }); @@ -746,6 +760,144 @@ describe('File module', () => { }); }); + describe('POST /files', () => { + const buildCreateFileBody = (folderUuid: string) => ({ + bucket: v4().substring(0, 24), + fileId: v4().substring(0, 24), + encryptVersion: '03-aes', + folderUuid, + size: 100, + plainName: `file-${v4()}`, + type: 'txt', + }); + + it('When the user uploads a file to their own folder, then the file is created', async () => { + const body = buildCreateFileBody(testUser.rootFolder?.uuid); + + const response = await request(app.getHttpServer()) + .post('/files') + .set('Authorization', `bearer ${testUser.token}`) + .send(body); + const storedFile = await fileModel.findOne({ + where: { uuid: response.body.uuid ?? null }, + }); + + expect(response.status).toBe(HttpStatus.CREATED); + expect(response.body).toMatchObject({ + uuid: expect.any(String), + plainName: body.plainName, + status: FileStatus.EXISTS, + folderUuid: body.folderUuid, + userId: testUser.user.id, + }); + expect(storedFile).toMatchObject({ + uuid: response.body.uuid, + plainName: body.plainName, + status: FileStatus.EXISTS, + folderUuid: body.folderUuid, + userId: testUser.user.id, + }); + }); + + it('When the user uploads a file to a folder of another user, then it is not allowed', async () => { + const anotherTestUser = await createTestUser(app); + const body = buildCreateFileBody(anotherTestUser.rootFolder?.uuid); + + const response = await request(app.getHttpServer()) + .post('/files') + .set('Authorization', `bearer ${testUser.token}`) + .send(body); + const filesInFolder = await fileModel.count({ + where: { folderUuid: anotherTestUser.rootFolder?.uuid }, + }); + await anotherTestUser.cleanup(); + + expect(response.status).toBe(HttpStatus.FORBIDDEN); + expect(filesInFolder).toBe(0); + }); + }); + + describe('GET /files/sync', () => { + it('When listing files, then only the files of the current user are returned', async () => { + const anotherTestUser = await createTestUser(app); + const requesterFile = await createUserFile(testUser); + const anotherUserFile = await createUserFile(anotherTestUser); + + const response = await request(app.getHttpServer()) + .get('/files/sync') + .query({ + status: FileStatus.EXISTS, + updatedAt: new Date(0).toISOString(), + }) + .set('Authorization', `bearer ${testUser.token}`); + await anotherTestUser.cleanup(); + + expect(response.status).toBe(HttpStatus.OK); + expect(response.body.files).toHaveLength(1); + expect(response.body.files[0]).toMatchObject({ + uuid: requesterFile.uuid, + plainName: requesterFile.plainName, + status: FileStatus.EXISTS, + }); + expect(response.body.files[0].uuid).not.toBe(anotherUserFile.uuid); + }); + }); + + describe('POST /files/thumbnail', () => { + let thumbnailRepository: SequelizeThumbnailRepository; + + beforeAll(() => { + thumbnailRepository = app.get(SequelizeThumbnailRepository); + }); + + const buildThumbnailBody = (fileUuid: string) => ({ + fileUuid, + type: 'png', + size: 100, + maxWidth: 300, + maxHeight: 300, + bucketId: v4().substring(0, 24), + bucketFile: v4().substring(0, 24), + encryptVersion: '03-aes', + }); + + it('When the user adds a thumbnail to their own file, then the thumbnail is created', async () => { + const file = await createUserFile(testUser); + const body = buildThumbnailBody(file.uuid); + + const response = await request(app.getHttpServer()) + .post('/files/thumbnail') + .set('Authorization', `bearer ${testUser.token}`) + .send(body); + const thumbnails = await thumbnailRepository.findAll({ + fileUuid: file.uuid, + }); + await thumbnailRepository.deleteBy({ fileUuid: file.uuid }); + + expect(response.status).toBe(HttpStatus.CREATED); + expect(response.body).toMatchObject({ fileUuid: file.uuid }); + expect(thumbnails).toHaveLength(1); + }); + + it('When the user adds a thumbnail to a file of another user, then it is not allowed', async () => { + const anotherTestUser = await createTestUser(app); + const otherUserFile = await createUserFile(anotherTestUser); + const body = buildThumbnailBody(otherUserFile.uuid); + + const response = await request(app.getHttpServer()) + .post('/files/thumbnail') + .set('Authorization', `bearer ${testUser.token}`) + .send(body); + const thumbnails = await thumbnailRepository.findAll({ + fileUuid: otherUserFile.uuid, + }); + await anotherTestUser.cleanup(); + + expect(response.status).toBe(HttpStatus.FORBIDDEN); + expect(thumbnails).toHaveLength(0); + }); + }); + describe('File unique constraint', () => { it('When a file with a duplicated name is inserted, then the violated constraint is mapped to a conflict', async () => { const fileAttributes = newFile({ diff --git a/src/modules/folder/folder.e2e-spec.ts b/src/modules/folder/folder.e2e-spec.ts index 462508f5f..c9affae71 100644 --- a/src/modules/folder/folder.e2e-spec.ts +++ b/src/modules/folder/folder.e2e-spec.ts @@ -10,9 +10,9 @@ import request from 'supertest'; import { SequelizeFileRepository } from '../file/file.repository'; import { newFile, newFolder } from '../../../test/fixtures'; -import { type File } from '../file/file.domain'; +import { type File, FileStatus } from '../file/file.domain'; import { SequelizeFolderRepository } from './folder.repository'; -import { type Folder } from './folder.domain'; +import { type Folder, FolderStatus } from './folder.domain'; import { CreateFolderDto } from './dto/create-folder.dto'; import { createTestUser, @@ -121,6 +121,8 @@ describe('Folder module', () => { size: file.size, type: file.type, name: file.plainName, + plainName: file.plainName, + status: FileStatus.EXISTS, }); }); @@ -176,6 +178,108 @@ describe('Folder module', () => { expect(response.body).toHaveProperty('folders'); expect(response.body.folders.length).toBe(1); + expect(response.body.folders[0]).toMatchObject({ + uuid: folder.uuid, + plainName: folder.plainName, + status: FolderStatus.EXISTS, + }); + }); + + it('When the user lists the subfolders of a folder of another user, then nothing is returned', async () => { + const anotherTestUser = await createTestUser(app); + const folderUuid = testUser.rootFolder?.uuid; + + const response = await request(app.getHttpServer()) + .get(`/folders/content/${folderUuid}/folders`) + .query({ limit: DEFAULT_LIMIT, offset: DEFAULT_OFFSET }) + .set('Authorization', `bearer ${anotherTestUser.token}`); + await anotherTestUser.cleanup(); + + expect(response.status).toBe(HttpStatus.OK); + expect(response.body.folders.length).toBe(0); + }); + }); + + describe('POST /folders/content/:uuid/files/existence', () => { + let file: File; + + beforeEach(async () => { + file = await fileRepository.create( + newFile({ + attributes: { + folderId: testUser.rootFolder?.id, + folderUuid: testUser.rootFolder?.uuid, + userId: testUser.user.id, + }, + }), + ); + }); + + afterEach(async () => { + await fileRepository.destroyFile({ id: file.id }); + }); + + it('When the user checks a file that exists in their folder, then the file is returned', async () => { + const folderUuid = testUser.rootFolder?.uuid; + const body = { files: [{ plainName: file.plainName, type: file.type }] }; + + const response = await makeRequest( + 'post', + `/folders/content/${folderUuid}/files/existence`, + ) + .send(body) + .expect(HttpStatus.CREATED); + + expect(response.body.existentFiles).toHaveLength(1); + expect(response.body.existentFiles[0]).toMatchObject({ + uuid: file.uuid, + plainName: file.plainName, + status: FileStatus.EXISTS, + }); + }); + + it('When the user checks files in a folder of another user, then an error is thrown', async () => { + const anotherTestUser = await createTestUser(app); + const folderUuid = testUser.rootFolder?.uuid; + const body = { files: [{ plainName: file.plainName, type: file.type }] }; + + const response = await request(app.getHttpServer()) + .post(`/folders/content/${folderUuid}/files/existence`) + .set('Authorization', `bearer ${anotherTestUser.token}`) + .send(body); + await anotherTestUser.cleanup(); + + expect(response.status).toBe(HttpStatus.FORBIDDEN); + }); + }); + + describe('GET /folders/:id/metadata', () => { + it('When the user requests the metadata of their own folder, then it is returned', async () => { + const folderId = testUser.rootFolder?.id; + + const response = await makeRequest( + 'get', + `/folders/${folderId}/metadata`, + ).expect(HttpStatus.OK); + + expect(response.body).toMatchObject({ + id: folderId, + uuid: testUser.rootFolder?.uuid, + plainName: testUser.rootFolder?.plainName, + status: FolderStatus.EXISTS, + }); + }); + + it('When the user requests the metadata of a folder of another user, then it is not found', async () => { + const anotherTestUser = await createTestUser(app); + const folderId = testUser.rootFolder?.id; + + const response = await request(app.getHttpServer()) + .get(`/folders/${folderId}/metadata`) + .set('Authorization', `bearer ${anotherTestUser.token}`); + await anotherTestUser.cleanup(); + + expect(response.status).toBe(HttpStatus.NOT_FOUND); }); });