11import { describe , test , expect , vi } from "vitest" ;
2+ import { Request } from "express" ;
3+ import {
4+ FileExtension ,
5+ fileTypeFromBuffer ,
6+ type FileTypeResult ,
7+ type MimeType ,
8+ } from "file-type" ;
29import { createMockFile } from "../test-utils/mockFile" ;
310import { FileValidationError , validateFile } from "../../src/middleware/upload" ;
4- import { fileTypeFromBuffer , type FileTypeResult } from "file-type" ;
5- import { Request } from "express" ;
611import { mock } from "vitest-mock-extended" ;
712
813// Mock file-type
@@ -16,15 +21,15 @@ describe("Upload Middleware", () => {
1621 test ( "accepts valid file" , async ( ) => {
1722 const mockFile = createMockFile ( "test content" , "test.txt" , "text/plain" ) ;
1823 vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( {
19- mime : "text/plain" as const ,
20- ext : "txt" ,
24+ mime : "text/plain" as MimeType ,
25+ ext : "txt" as FileExtension ,
2126 } satisfies FileTypeResult ) ;
2227
2328 await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
2429 } ) ;
2530
2631 test ( "rejects oversized file" , async ( ) => {
27- const largeContent = "x" . repeat ( 11 * 1024 * 1024 ) ; // 11MB
32+ const largeContent = "x" . repeat ( 12 * 1024 * 1024 ) ; // 12MB
2833 const mockFile = createMockFile ( largeContent , "large.txt" , "text/plain" ) ;
2934
3035 await expect ( validateFile ( mockReq , mockFile ) ) . rejects . toThrow (
@@ -47,8 +52,8 @@ describe("Upload Middleware", () => {
4752 test ( "rejects file with mismatched content type" , async ( ) => {
4853 const mockFile = createMockFile ( "<html></html>" , "fake.txt" , "text/plain" ) ;
4954 vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( {
50- mime : "text/html" as const ,
51- ext : "html" ,
55+ mime : "text/html" as MimeType ,
56+ ext : "html" as FileExtension ,
5257 } satisfies FileTypeResult ) ;
5358
5459 await expect ( validateFile ( mockReq , mockFile ) ) . rejects . toThrow (
@@ -58,9 +63,9 @@ describe("Upload Middleware", () => {
5863
5964 test ( "handles different allowed file types" , async ( ) => {
6065 const testCases = [
61- { content : "test" , name : "test.txt" , type : "text/plain" as const } ,
62- { content : "{}" , name : "test.json" , type : "application/json" as const } ,
63- { content : "PDF" , name : "test.pdf" , type : "application/pdf" as const } ,
66+ { content : "test" , name : "test.txt" , type : "text/plain" as MimeType } ,
67+ { content : "{}" , name : "test.json" , type : "application/json" } ,
68+ { content : "PDF" , name : "test.pdf" , type : "application/pdf" as MimeType } ,
6469 ] ;
6570
6671 for ( const testCase of testCases ) {
@@ -69,18 +74,22 @@ describe("Upload Middleware", () => {
6974 testCase . name ,
7075 testCase . type ,
7176 ) ;
72- vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( {
73- mime : testCase . type ,
74- ext : testCase . name . split ( "." ) [ 1 ] ,
75- } satisfies FileTypeResult ) ;
77+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue (
78+ testCase . type === "application/json"
79+ ? undefined // JSON files typically return undefined
80+ : ( {
81+ mime : testCase . type as MimeType ,
82+ ext : testCase . name . split ( "." ) [ 1 ] as FileExtension ,
83+ } satisfies FileTypeResult ) ,
84+ ) ;
7685
7786 await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
7887 }
7988 } ) ;
8089
8190 test ( "handles null file type detection" , async ( ) => {
8291 const mockFile = createMockFile ( "test" , "test.txt" , "text/plain" ) ;
83- vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( null ) ;
92+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( undefined ) ;
8493
8594 await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
8695 } ) ;
@@ -93,4 +102,66 @@ describe("Upload Middleware", () => {
93102
94103 await expect ( validateFile ( mockReq , mockFile ) ) . rejects . toThrow ( ) ;
95104 } ) ;
105+
106+ test ( "accepts valid image files" , async ( ) => {
107+ const mockFile = createMockFile (
108+ "fake-image-data" ,
109+ "photo.jpg" ,
110+ "image/jpeg" ,
111+ ) ;
112+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( {
113+ mime : "image/jpeg" as MimeType ,
114+ ext : "jpg" ,
115+ } satisfies FileTypeResult ) ;
116+
117+ await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
118+ } ) ;
119+
120+ test ( "accepts valid JSON file" , async ( ) => {
121+ const mockFile = createMockFile (
122+ JSON . stringify ( { test : "data" } ) ,
123+ "data.json" ,
124+ "application/json" ,
125+ ) ;
126+ // For JSON files, fileTypeFromBuffer typically returns undefined
127+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( undefined ) ;
128+
129+ await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
130+ } ) ;
131+
132+ test ( "accepts file at exact size limit" , async ( ) => {
133+ const content = Buffer . alloc ( 11 * 1024 * 1024 ) ; // Exactly 11MB
134+ const mockFile = createMockFile ( content , "at-limit.txt" , "text/plain" ) ;
135+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( {
136+ mime : "text/plain" as MimeType ,
137+ ext : "txt" as FileExtension ,
138+ } satisfies FileTypeResult ) ;
139+
140+ await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
141+ } ) ;
142+
143+ test ( "accepts empty file with valid type" , async ( ) => {
144+ const mockFile = createMockFile ( "" , "empty.txt" , "text/plain" ) ;
145+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( undefined ) ;
146+
147+ await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
148+ } ) ;
149+
150+ test ( "handles file without extension" , async ( ) => {
151+ const mockFile = createMockFile ( "content" , "readme" , "text/plain" ) ;
152+ vi . mocked ( fileTypeFromBuffer ) . mockResolvedValue ( {
153+ mime : "text/plain" as MimeType ,
154+ ext : "txt" as FileExtension ,
155+ } satisfies FileTypeResult ) ;
156+
157+ await expect ( validateFile ( mockReq , mockFile ) ) . resolves . not . toThrow ( ) ;
158+ } ) ;
159+
160+ test ( "rejects file with missing buffer" , async ( ) => {
161+ const mockFile = createMockFile ( "test" , "test.txt" , "text/plain" ) ;
162+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
163+ mockFile . buffer = undefined as any ;
164+
165+ await expect ( validateFile ( mockReq , mockFile ) ) . rejects . toThrow ( ) ;
166+ } ) ;
96167} ) ;
0 commit comments