diff --git a/server/__tests__/fileUpload.test.ts b/server/__tests__/fileUpload.test.ts new file mode 100644 index 000000000..f4cc3070f --- /dev/null +++ b/server/__tests__/fileUpload.test.ts @@ -0,0 +1,138 @@ +import type { Response, Request } from 'express'; +import fs from 'fs'; +import path from 'path'; + +import { uploadFiles } from '../controllers/filesController'; +import type { User } from '../entities/user'; +import { AwsS3 } from '../fileUpload/s3'; +import { AppError } from '../middlewares/handleErrors'; +import { appDataSource, fakeUser } from './mock'; + +beforeAll(() => { + return appDataSource.initialize(); +}); +beforeEach(() => {}); +afterAll(() => { + return appDataSource.destroy(); +}); + +const dummyPdf = fs.readFileSync(path.join(__dirname, 'files/dummy.pdf')); +describe('Upload files', () => { + test('Should return an url array', async () => { + jest.spyOn(AwsS3.prototype, 'uploadFile').mockImplementationOnce(() => { + return Promise.resolve('url/test'); + }); + jest.spyOn(AwsS3.prototype, 'uploadS3File').mockImplementationOnce(() => { + return Promise.resolve('url/test'); + }); + + const mockRequest: Partial = { + user: fakeUser as User, + files: [ + { + buffer: dummyPdf, + fieldname: 'files', + filename: 'dummy.pdf', + mimetype: 'application/pdf', + originalname: 'dummy.pdf', + path: 'server/__tests__/files', + size: 1000, + destination: 'server/__tests__/files', + } as Express.Multer.File, + ], + }; + + const res = {} as unknown as Response; + res.json = jest.fn(); + res.status = jest.fn(() => res); // chained + + await uploadFiles(mockRequest as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith(['url/test']); + }); + test('Should throw - files are missing -', async () => { + const mockRequest: Partial = { + user: fakeUser as User, + files: [], + }; + + const res = {} as unknown as Response; + res.json = jest.fn(); + res.status = jest.fn(() => res); // chained + + await uploadFiles(mockRequest as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith('Files are missing'); + }); + test('User forbiden', async () => { + const mockRequest: Partial = { + files: [], + }; + + const res = {} as unknown as Response; + res.json = jest.fn(); + res.status = jest.fn(() => res); // chained + + await uploadFiles(mockRequest as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith('Forbidden'); + }); + test('Should throw an app error', async () => { + jest.spyOn(AwsS3.prototype, 'uploadFile').mockImplementationOnce(() => { + throw new AppError('Yolo', 0); + }); + + const mockRequest: Partial = { + user: fakeUser as User, + files: [ + { + buffer: dummyPdf, + fieldname: 'files', + filename: 'dummy.pdf', + mimetype: 'application/pdf', + originalname: 'dummy.pdf', + path: 'server/__tests__/files', + size: 1000, + destination: 'server/__tests__/files', + } as Express.Multer.File, + ], + }; + + const res = {} as unknown as Response; + res.json = jest.fn(); + res.status = jest.fn(() => res); // chained + + await uploadFiles(mockRequest as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith('Yolo'); + }); + test('Should throw an unknown error', async () => { + jest.spyOn(AwsS3.prototype, 'uploadFile').mockImplementationOnce(() => { + throw new Error('Yolo'); + }); + + const mockRequest: Partial = { + user: fakeUser as User, + files: [ + { + buffer: dummyPdf, + fieldname: 'files', + filename: 'dummy.pdf', + mimetype: 'application/pdf', + originalname: 'dummy.pdf', + path: 'server/__tests__/files', + size: 1000, + destination: 'server/__tests__/files', + } as Express.Multer.File, + ], + }; + + const res = {} as unknown as Response; + res.json = jest.fn(); + res.status = jest.fn(() => res); // chained + + await uploadFiles(mockRequest as Request, res as Response); + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith('Internal server error'); + }); +}); diff --git a/server/__tests__/files/dummy-image.jpg b/server/__tests__/files/dummy-image.jpg new file mode 100644 index 000000000..1db39e04b Binary files /dev/null and b/server/__tests__/files/dummy-image.jpg differ diff --git a/server/__tests__/files/dummy.pdf b/server/__tests__/files/dummy.pdf new file mode 100644 index 000000000..774c2ea70 Binary files /dev/null and b/server/__tests__/files/dummy.pdf differ diff --git a/server/controllers/filesController.ts b/server/controllers/filesController.ts index bb0d305d9..faeb61b22 100644 --- a/server/controllers/filesController.ts +++ b/server/controllers/filesController.ts @@ -1,26 +1,23 @@ import type { Request, Response } from 'express'; -import fs from 'fs'; -import path from 'path'; import { uploadFile } from '../fileUpload'; import { AppError, ErrorCode } from '../middlewares/handleErrors'; export async function uploadFiles(req: Request, res: Response) { try { - // const user = req.user; - // if (!user) { - // throw new AppError('Forbidden', ErrorCode.UNKNOWN); - // } + const user = req.user; + if (!user) { + throw new AppError('Forbidden', ErrorCode.UNKNOWN); + } const { files } = req; if (!files || !files.length) { throw new AppError('Files are missing', ErrorCode.UNKNOWN); } - const userId = 1; const promises: Promise[] = []; for (const file of files as Express.Multer.File[]) { // making the filename being the path here is a trick to use // upload function... - const filename = `images/${userId}/${file.filename}`; + const filename = `images/${user.id}/${file.filename}`; const promise = uploadFile(filename, file.mimetype); promises.push(promise); } @@ -28,7 +25,7 @@ export async function uploadFiles(req: Request, res: Response) { res.status(200).json(results); } catch (error) { if (error instanceof AppError) { - res.status(error.errorCode).json(error.message); + res.status(400).json(error.message); } else { res.status(500).json('Internal server error'); } diff --git a/server/controllers/multer.ts b/server/controllers/multer.ts index df459d1c3..60bb07db9 100644 --- a/server/controllers/multer.ts +++ b/server/controllers/multer.ts @@ -12,7 +12,6 @@ export const diskStorage = multer.diskStorage({ cb(null, `${uuid}${path.extname(file.originalname)}`); }, }); - export const upload = multer({ storage: diskStorage }); export const diskStorageToImages = multer.diskStorage({ @@ -28,3 +27,20 @@ export const diskStorageToImages = multer.diskStorage({ cb(null, `${uuid}${path.extname(file.originalname)}`); }, }); + +const whitelist = [ + 'application/pdf', + 'application/vnd.ms-powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', +]; +export const fileUplad = multer({ + storage: diskStorageToImages, + fileFilter: (_req, file, cb) => { + if (!whitelist.includes(file.mimetype)) { + return cb(new Error('file is not allowed')); + } + cb(null, true); + }, +}); diff --git a/server/routes/filesRouter.ts b/server/routes/filesRouter.ts index 729cc05cf..d2cc94b25 100644 --- a/server/routes/filesRouter.ts +++ b/server/routes/filesRouter.ts @@ -1,12 +1,11 @@ import { Router } from 'express'; -import multer from 'multer'; import { UserType } from '../../types/user.type'; import { uploadFiles } from '../controllers/filesController'; -import { diskStorageToImages } from '../controllers/multer'; +import { fileUplad } from '../controllers/multer'; import { authenticate } from '../middlewares/authenticate'; import { handleErrors } from '../middlewares/handleErrors'; export const filesRouter = Router(); -filesRouter.post('/', multer({ storage: diskStorageToImages }).array('files'), handleErrors(authenticate(UserType.ADMIN)), uploadFiles); +filesRouter.post('/', fileUplad.array('files'), handleErrors(authenticate(UserType.ADMIN)), uploadFiles);