-
Notifications
You must be signed in to change notification settings - Fork 10.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
regression: undefined fileBuffer in getUploadFormData when onFile is …
…called before onField (#34091)
- Loading branch information
1 parent
a33b209
commit dee6c28
Showing
2 changed files
with
188 additions
and
11 deletions.
There are no files selected for viewing
178 changes: 178 additions & 0 deletions
178
apps/meteor/app/api/server/lib/getUploadFormData.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
import { Readable } from 'stream'; | ||
|
||
import { expect } from 'chai'; | ||
import type { Request } from 'express'; | ||
|
||
import { getUploadFormData } from './getUploadFormData'; | ||
|
||
const createMockRequest = ( | ||
fields: Record<string, string>, | ||
file?: { | ||
fieldname: string; | ||
filename: string; | ||
content: string | Buffer; | ||
mimetype?: string; | ||
}, | ||
): Readable & { headers: Record<string, string> } => { | ||
const boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW'; | ||
const parts: string[] = []; | ||
|
||
if (file) { | ||
parts.push( | ||
`--${boundary}`, | ||
`Content-Disposition: form-data; name="${file.fieldname}"; filename="${file.filename}"`, | ||
`Content-Type: ${file.mimetype || 'application/octet-stream'}`, | ||
'', | ||
file.content.toString(), | ||
); | ||
} | ||
|
||
for (const [name, value] of Object.entries(fields)) { | ||
parts.push(`--${boundary}`, `Content-Disposition: form-data; name="${name}"`, '', value); | ||
} | ||
|
||
parts.push(`--${boundary}--`); | ||
|
||
const mockRequest: any = new Readable({ | ||
read() { | ||
this.push(Buffer.from(parts.join('\r\n'))); | ||
this.push(null); | ||
}, | ||
}); | ||
|
||
mockRequest.headers = { | ||
'content-type': `multipart/form-data; boundary=${boundary}`, | ||
}; | ||
|
||
return mockRequest as Readable & { headers: Record<string, string> }; | ||
}; | ||
|
||
describe('getUploadFormData', () => { | ||
it('should successfully parse a single file upload and fields', async () => { | ||
const mockRequest = createMockRequest( | ||
{ fieldName: 'fieldValue' }, | ||
{ | ||
fieldname: 'fileField', | ||
filename: 'test.txt', | ||
content: 'Hello, this is a test file!', | ||
mimetype: 'text/plain', | ||
}, | ||
); | ||
|
||
const result = await getUploadFormData({ request: mockRequest as Request }, { field: 'fileField' }); | ||
|
||
expect(result).to.deep.include({ | ||
fieldname: 'fileField', | ||
filename: 'test.txt', | ||
mimetype: 'text/plain', | ||
fields: { fieldName: 'fieldValue' }, | ||
}); | ||
|
||
expect(result.fileBuffer).to.not.be.undefined; | ||
expect(result.fileBuffer.toString()).to.equal('Hello, this is a test file!'); | ||
}); | ||
it('should parse a file upload with multiple additional fields', async () => { | ||
const mockRequest = createMockRequest( | ||
{ | ||
fieldName: 'fieldValue', | ||
extraField1: 'extraValue1', | ||
extraField2: 'extraValue2', | ||
}, | ||
{ | ||
fieldname: 'fileField', | ||
filename: 'test_with_fields.txt', | ||
content: 'This file has additional fields!', | ||
mimetype: 'text/plain', | ||
}, | ||
); | ||
|
||
const result = await getUploadFormData({ request: mockRequest as Request }, { field: 'fileField' }); | ||
|
||
expect(result).to.deep.include({ | ||
fieldname: 'fileField', | ||
filename: 'test_with_fields.txt', | ||
mimetype: 'text/plain', | ||
fields: { | ||
fieldName: 'fieldValue', | ||
extraField1: 'extraValue1', | ||
extraField2: 'extraValue2', | ||
}, | ||
}); | ||
|
||
expect(result.fileBuffer).to.not.be.undefined; | ||
expect(result.fileBuffer.toString()).to.equal('This file has additional fields!'); | ||
}); | ||
|
||
it('should handle a file upload when fileOptional is true', async () => { | ||
const mockRequest = createMockRequest( | ||
{ fieldName: 'fieldValue' }, | ||
{ | ||
fieldname: 'fileField', | ||
filename: 'optional.txt', | ||
content: 'This file is optional!', | ||
mimetype: 'text/plain', | ||
}, | ||
); | ||
|
||
const result = await getUploadFormData({ request: mockRequest as Request }, { fileOptional: true }); | ||
|
||
expect(result).to.deep.include({ | ||
fieldname: 'fileField', | ||
filename: 'optional.txt', | ||
mimetype: 'text/plain', | ||
fields: { fieldName: 'fieldValue' }, | ||
}); | ||
|
||
expect(result.fileBuffer).to.not.be.undefined; | ||
expect(result.fileBuffer?.toString()).to.equal('This file is optional!'); | ||
}); | ||
|
||
it('should throw an error when no file is uploaded and fileOptional is false', async () => { | ||
const mockRequest = createMockRequest({ fieldName: 'fieldValue' }); | ||
|
||
try { | ||
await getUploadFormData({ request: mockRequest as Request }, { fileOptional: false }); | ||
throw new Error('Expected function to throw'); | ||
} catch (error) { | ||
expect((error as Error).message).to.equal('[No file uploaded]'); | ||
} | ||
}); | ||
|
||
it('should return fields without errors when no file is uploaded but fileOptional is true', async () => { | ||
const mockRequest = createMockRequest({ fieldName: 'fieldValue' }); // No file | ||
|
||
const result = await getUploadFormData({ request: mockRequest as Request }, { fileOptional: true }); | ||
|
||
expect(result).to.deep.equal({ | ||
fields: { fieldName: 'fieldValue' }, | ||
file: undefined, | ||
fileBuffer: undefined, | ||
fieldname: undefined, | ||
filename: undefined, | ||
encoding: undefined, | ||
mimetype: undefined, | ||
}); | ||
}); | ||
|
||
it('should reject an oversized file', async () => { | ||
const mockRequest = createMockRequest( | ||
{}, | ||
{ | ||
fieldname: 'fileField', | ||
filename: 'large.txt', | ||
content: 'x'.repeat(1024 * 1024 * 2), // 2 MB file | ||
mimetype: 'text/plain', | ||
}, | ||
); | ||
|
||
try { | ||
await getUploadFormData( | ||
{ request: mockRequest as Request }, | ||
{ sizeLimit: 1024 * 1024 }, // 1 MB limit | ||
); | ||
throw new Error('Expected function to throw'); | ||
} catch (error) { | ||
expect((error as Error).message).to.equal('[error-file-too-large]'); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters