diff --git a/packages/platform-express/multer/multer/multer.utils.ts b/packages/platform-express/multer/multer/multer.utils.ts index b5fb4f8808..e7932f46a1 100644 --- a/packages/platform-express/multer/multer/multer.utils.ts +++ b/packages/platform-express/multer/multer/multer.utils.ts @@ -33,6 +33,11 @@ export function mergeMulterOptions( if (isPlainObject(options.limits) && isPlainObject(localOptions.limits)) { merged.limits = { ...options.limits, ...localOptions.limits }; } + // `dest` and `storage` both say where files go, and multer prefers + // `storage`, so a global storage engine would shadow a local `dest`. + if (localOptions.dest && !localOptions.storage) { + delete merged.storage; + } return merged; } diff --git a/packages/platform-express/test/multer/multer/multer.utils.spec.ts b/packages/platform-express/test/multer/multer/multer.utils.spec.ts index a32365e326..2309a72fd3 100644 --- a/packages/platform-express/test/multer/multer/multer.utils.spec.ts +++ b/packages/platform-express/test/multer/multer/multer.utils.spec.ts @@ -21,6 +21,18 @@ describe('mergeMulterOptions', () => { const merged = mergeMulterOptions({ dest: '/global' }, { dest: '/local' }); expect(merged.dest).toBe('/local'); }); + it('should let a local "dest" override a global "storage"', () => { + const merged = mergeMulterOptions( + { storage: multer.memoryStorage() }, + { dest: '/local' }, + ); + expect(merged).toEqual({ dest: '/local' }); + }); + it('should keep a global "storage" when the route sets no "dest"', () => { + const storage = multer.memoryStorage(); + const merged = mergeMulterOptions({ storage }, { preservePath: true }); + expect(merged.storage).toBe(storage); + }); it('should merge limits key-by-key instead of replacing the whole object', () => { const merged = mergeMulterOptions( { limits: { files: 2, fieldNameSize: 100 } }, diff --git a/packages/platform-fastify/multipart/multipart/multipart.utils.ts b/packages/platform-fastify/multipart/multipart/multipart.utils.ts index 8189631ec4..b855c21040 100644 --- a/packages/platform-fastify/multipart/multipart/multipart.utils.ts +++ b/packages/platform-fastify/multipart/multipart/multipart.utils.ts @@ -30,6 +30,11 @@ export function mergeMultipartOptions( if (isPlainObject(options.limits) && isPlainObject(localOptions.limits)) { merged.limits = { ...options.limits, ...localOptions.limits }; } + // `dest` and `storage` both say where files go, and `resolveStorage()` + // prefers `storage`, so a global storage engine would shadow a local `dest`. + if (localOptions.dest && !localOptions.storage) { + delete merged.storage; + } return merged; } diff --git a/packages/platform-fastify/test/multipart/multipart/multipart.utils.spec.ts b/packages/platform-fastify/test/multipart/multipart/multipart.utils.spec.ts index 7854566848..76d28002ae 100644 --- a/packages/platform-fastify/test/multipart/multipart/multipart.utils.spec.ts +++ b/packages/platform-fastify/test/multipart/multipart/multipart.utils.spec.ts @@ -9,6 +9,7 @@ import { mergeMultipartOptions, transformException, } from '../../../multipart/multipart/multipart.utils.js'; +import { memoryStorage } from '../../../multipart/storage/index.js'; describe('mergeMultipartOptions', () => { it('should let local options override global options', () => { @@ -17,6 +18,18 @@ describe('mergeMultipartOptions', () => { ).toEqual({ dest: '/local' }); }); + it('should let a local "dest" override a global "storage"', () => { + expect( + mergeMultipartOptions({ storage: memoryStorage() }, { dest: '/local' }), + ).toEqual({ dest: '/local' }); + }); + + it('should keep a global "storage" when the route sets no "dest"', () => { + const storage = memoryStorage(); + const merged = mergeMultipartOptions({ storage }, { preservePath: true }); + expect(merged.storage).toBe(storage); + }); + it('should merge limits key-by-key', () => { const merged = mergeMultipartOptions( { limits: { files: 2, fileSize: 1 } },