@@ -11,6 +11,7 @@ import { initPayloadInt } from '../helpers/initPayloadInt.js'
1111import {
1212 mediaSlug ,
1313 mediaWithAlwaysInsertFieldsSlug ,
14+ mediaWithClientUploadsSlug ,
1415 mediaWithDirectAccessSlug ,
1516 mediaWithDynamicPrefixSlug ,
1617 mediaWithPrefixSlug ,
@@ -187,6 +188,92 @@ describe('@payloadcms/storage-s3', () => {
187188 it . todo ( 'can upload' )
188189 } )
189190
191+ describe ( 'client uploads with size limits' , ( ) => {
192+ const signedURLEndpoint = '/storage-s3-generate-signed-url'
193+
194+ it ( 'should generate signed URL for file within size limit' , async ( ) => {
195+ const filename = 'small-file.png'
196+ const filesize = 500_000 // 500KB (within 1MB limit)
197+ const mimeType = 'image/png'
198+
199+ const response = await restClient . POST ( signedURLEndpoint , {
200+ body : JSON . stringify ( {
201+ collectionSlug : mediaWithClientUploadsSlug ,
202+ filename,
203+ filesize,
204+ mimeType,
205+ } ) ,
206+ } )
207+
208+ expect ( response . status ) . toBe ( 200 )
209+ const { url } = ( await response . json ( ) ) as any
210+ expect ( url ) . toBeDefined ( )
211+ expect ( url ) . toContain ( process . env . S3_BUCKET )
212+ expect ( url ) . toContain ( filename )
213+ } )
214+
215+ it ( 'should reject file exceeding size limit' , async ( ) => {
216+ const filename = 'large-file.png'
217+ const filesize = 2_000_000 // 2MB (exceeds 1MB limit)
218+ const mimeType = 'image/png'
219+
220+ const response = await restClient . POST ( signedURLEndpoint , {
221+ body : JSON . stringify ( {
222+ collectionSlug : mediaWithClientUploadsSlug ,
223+ filename,
224+ filesize,
225+ mimeType,
226+ } ) ,
227+ } )
228+
229+ expect ( response . status ) . toBe ( 400 )
230+ const { errors } = ( await response . json ( ) ) as any
231+ expect ( errors ) . toBeDefined ( )
232+ expect ( errors [ 0 ] . message ) . toContain ( 'Exceeded file size limit' )
233+ expect ( errors [ 0 ] . message ) . toMatch ( / L i m i t : 0 \. 9 \d M B / ) // 1,000,000 bytes ≈ 0.95MB
234+ expect ( errors [ 0 ] . message ) . toMatch ( / g o t : 1 \. 9 \d M B / ) // 2,000,000 bytes ≈ 1.91MB
235+ } )
236+
237+ it ( 'should reject file exactly at limit boundary' , async ( ) => {
238+ const filename = 'boundary-file.png'
239+ const filesize = 1_000_001 // Just over 1MB limit
240+ const mimeType = 'image/png'
241+
242+ const response = await restClient . POST ( signedURLEndpoint , {
243+ body : JSON . stringify ( {
244+ collectionSlug : mediaWithClientUploadsSlug ,
245+ filename,
246+ filesize,
247+ mimeType,
248+ } ) ,
249+ } )
250+
251+ expect ( response . status ) . toBe ( 400 )
252+ const { errors } = ( await response . json ( ) ) as any
253+ expect ( errors ) . toBeDefined ( )
254+ expect ( errors [ 0 ] . message ) . toContain ( 'Exceeded file size limit' )
255+ } )
256+
257+ it ( 'should accept file exactly at limit' , async ( ) => {
258+ const filename = 'exact-limit.png'
259+ const filesize = 1_000_000 // Exactly 1MB
260+ const mimeType = 'image/png'
261+
262+ const response = await restClient . POST ( signedURLEndpoint , {
263+ body : JSON . stringify ( {
264+ collectionSlug : mediaWithClientUploadsSlug ,
265+ filename,
266+ filesize,
267+ mimeType,
268+ } ) ,
269+ } )
270+
271+ expect ( response . status ) . toBe ( 200 )
272+ const { url } = ( await response . json ( ) ) as any
273+ expect ( url ) . toBeDefined ( )
274+ } )
275+ } )
276+
190277 describe ( 'prefix collision detection' , ( ) => {
191278 beforeEach ( async ( ) => {
192279 // Clear S3 bucket before each test
0 commit comments