diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5658671..3ede17f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,4 @@ + # Contributing Guide * [Ways to Contribute](#ways-to-contribute) * [Find an Issue](#find-an-issue) diff --git a/packages/common/src/controllers/file-upload.controller.ts b/packages/common/src/controllers/file-upload.controller.ts index bd7a2c5..d3f43cc 100644 --- a/packages/common/src/controllers/file-upload.controller.ts +++ b/packages/common/src/controllers/file-upload.controller.ts @@ -15,7 +15,7 @@ import { FastifyReply } from 'fastify'; @Controller('files') export class FileUploadController { - constructor(private readonly filesService: FileUploadService) {} + constructor(private readonly filesService: FileUploadService) { } @Post('upload-file') @UseInterceptors(FastifyFileInterceptor('file', {})) @@ -29,6 +29,13 @@ export class FileUploadController { file?: { url: string } | undefined; }> { try { + // file.size comes from MultiPartFile Interface defined in file-upload.interface + if (file.size === 0) { + return { + statusCode: 400, + message: 'empty file uploads are not allowed' + } + } const directory = await this.filesService.upload( file, destination, diff --git a/packages/common/src/interceptors/file-upload.interceptor.ts b/packages/common/src/interceptors/file-upload.interceptor.ts index 8180cc1..293b070 100644 --- a/packages/common/src/interceptors/file-upload.interceptor.ts +++ b/packages/common/src/interceptors/file-upload.interceptor.ts @@ -32,6 +32,8 @@ export function FastifyFileInterceptor( next: CallHandler, ): Promise> { const ctx = context.switchToHttp(); + const request = ctx.getRequest(); + request.raw = request.raw || request; await new Promise((resolve, reject) => this.multer.single(fieldName)( diff --git a/packages/common/src/interfaces/file-upload.interface.ts b/packages/common/src/interfaces/file-upload.interface.ts index a460cb1..700938e 100644 --- a/packages/common/src/interfaces/file-upload.interface.ts +++ b/packages/common/src/interfaces/file-upload.interface.ts @@ -14,4 +14,6 @@ export interface MultipartFile { encoding: string; mimetype: string; fields: fastifyMutipart.MultipartFields; + size: number; + buffer: Buffer; } diff --git a/packages/common/test/app.e2e-spec.ts b/packages/common/test/app.e2e-spec.ts index 50cda62..de957ac 100644 --- a/packages/common/test/app.e2e-spec.ts +++ b/packages/common/test/app.e2e-spec.ts @@ -2,6 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; +import * as fs from 'fs'; +import * as path from 'path'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -19,6 +21,38 @@ describe('AppController (e2e)', () => { return request(app.getHttpServer()) .get('/') .expect(200) - .expect('Hello World!'); + .expect('Hello World from file upload.'); + }); + + it('/files/upload-file (POST); for file with content', async () => { + const mockDestination = 'uploads'; + const mockFilename = 'empty.txt'; + const response = await request(app.getHttpServer()) + .post( + `/files/upload-file?destination=${mockDestination}&filename=${mockFilename}`, + ) + .attach('file', Buffer.from('abcd'), mockFilename); + expect(response.body).toEqual({ + message: 'File uploaded successfully', + file: { url: mockDestination }, + }); + + // clean up created file from uploads dir + const emptyFilePath = path.join(__dirname, '../uploads/empty.txt'); + fs.unlinkSync(emptyFilePath); + }); + + it('/files/upload-file (POST); for empty file check', async () => { + const mockDestination = 'uploads'; + const mockFilename = 'empty.txt'; + const response = await request(app.getHttpServer()) + .post( + `/files/upload-file?destination=${mockDestination}&filename=${mockFilename}`, + ) + .attach('file', Buffer.from(''), mockFilename); + expect(response.body).toEqual({ + statusCode: 400, + message: 'empty file uploads are not allowed', + }); }); }); diff --git a/sample/06-file-upload/test/app.e2e-spec.ts b/sample/06-file-upload/test/app.e2e-spec.ts index 6163d49..de957ac 100644 --- a/sample/06-file-upload/test/app.e2e-spec.ts +++ b/sample/06-file-upload/test/app.e2e-spec.ts @@ -2,6 +2,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from './../src/app.module'; +import * as fs from 'fs'; +import * as path from 'path'; describe('AppController (e2e)', () => { let app: INestApplication; @@ -21,4 +23,36 @@ describe('AppController (e2e)', () => { .expect(200) .expect('Hello World from file upload.'); }); + + it('/files/upload-file (POST); for file with content', async () => { + const mockDestination = 'uploads'; + const mockFilename = 'empty.txt'; + const response = await request(app.getHttpServer()) + .post( + `/files/upload-file?destination=${mockDestination}&filename=${mockFilename}`, + ) + .attach('file', Buffer.from('abcd'), mockFilename); + expect(response.body).toEqual({ + message: 'File uploaded successfully', + file: { url: mockDestination }, + }); + + // clean up created file from uploads dir + const emptyFilePath = path.join(__dirname, '../uploads/empty.txt'); + fs.unlinkSync(emptyFilePath); + }); + + it('/files/upload-file (POST); for empty file check', async () => { + const mockDestination = 'uploads'; + const mockFilename = 'empty.txt'; + const response = await request(app.getHttpServer()) + .post( + `/files/upload-file?destination=${mockDestination}&filename=${mockFilename}`, + ) + .attach('file', Buffer.from(''), mockFilename); + expect(response.body).toEqual({ + statusCode: 400, + message: 'empty file uploads are not allowed', + }); + }); });