@@ -7,6 +7,7 @@ const mockFile = {
77 download : jest . fn ( ) ,
88 getMetadata : jest . fn ( ) ,
99 delete : jest . fn ( ) ,
10+ getSignedUrl : jest . fn ( ) ,
1011 name : 'test-file.txt' ,
1112} ;
1213
@@ -65,6 +66,9 @@ describe('ObjectStorage', () => {
6566 } ,
6667 ] ) ;
6768 mockFile . delete . mockResolvedValue ( undefined ) ;
69+ mockFile . getSignedUrl . mockResolvedValue ( [
70+ 'https://storage.googleapis.com/test-bucket/test-file.txt?signed-url-params' ,
71+ ] ) ;
6872
6973 mockBucket . getFiles . mockResolvedValue ( [
7074 [
@@ -281,4 +285,136 @@ describe('ObjectStorage', () => {
281285 expect ( result . error ) . toBe ( 'File not found' ) ;
282286 } ) ;
283287 } ) ;
288+
289+ describe ( 'getPresignedUploadUrl' , ( ) => {
290+ it ( 'should generate a presigned upload URL successfully' , async ( ) => {
291+ const fileName = 'upload-file.txt' ;
292+ const expectedUrl = 'https://storage.googleapis.com/test-bucket/upload-file.txt?signed-url-params' ;
293+
294+ mockFile . getSignedUrl . mockResolvedValueOnce ( [ expectedUrl ] ) ;
295+
296+ const result = await objectStorage . getPresignedUploadUrl ( fileName ) ;
297+
298+ expect ( result . success ) . toBe ( true ) ;
299+ expect ( result . presignedUrl ) . toBe ( expectedUrl ) ;
300+ expect ( result . fileName ) . toBe ( fileName ) ;
301+ expect ( mockFile . getSignedUrl ) . toHaveBeenCalledWith ( {
302+ version : 'v4' ,
303+ action : 'write' ,
304+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
305+ expires : expect . any ( Date ) ,
306+ } ) ;
307+ } ) ;
308+
309+ it ( 'should generate a presigned upload URL with custom expiration' , async ( ) => {
310+ const fileName = 'upload-file.txt' ;
311+ const customExpires = new Date ( '2024-12-31T23:59:59Z' ) ;
312+ const expectedUrl = 'https://storage.googleapis.com/test-bucket/upload-file.txt?signed-url-params' ;
313+
314+ mockFile . getSignedUrl . mockResolvedValueOnce ( [ expectedUrl ] ) ;
315+
316+ const result = await objectStorage . getPresignedUploadUrl ( fileName , { expires : customExpires } ) ;
317+
318+ expect ( result . success ) . toBe ( true ) ;
319+ expect ( result . presignedUrl ) . toBe ( expectedUrl ) ;
320+ expect ( result . fileName ) . toBe ( fileName ) ;
321+ expect ( mockFile . getSignedUrl ) . toHaveBeenCalledWith ( {
322+ version : 'v4' ,
323+ action : 'write' ,
324+ expires : customExpires ,
325+ } ) ;
326+ } ) ;
327+
328+ it ( 'should generate a presigned upload URL with content type restriction' , async ( ) => {
329+ const fileName = 'upload-file.txt' ;
330+ const contentType = 'text/plain' ;
331+ const expectedUrl = 'https://storage.googleapis.com/test-bucket/upload-file.txt?signed-url-params' ;
332+
333+ mockFile . getSignedUrl . mockResolvedValueOnce ( [ expectedUrl ] ) ;
334+
335+ const result = await objectStorage . getPresignedUploadUrl ( fileName , { contentType } ) ;
336+
337+ expect ( result . success ) . toBe ( true ) ;
338+ expect ( result . presignedUrl ) . toBe ( expectedUrl ) ;
339+ expect ( result . fileName ) . toBe ( fileName ) ;
340+ expect ( mockFile . getSignedUrl ) . toHaveBeenCalledWith ( {
341+ version : 'v4' ,
342+ action : 'write' ,
343+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
344+ expires : expect . any ( Date ) ,
345+ contentType : 'text/plain' ,
346+ } ) ;
347+ } ) ;
348+
349+ it ( 'should generate a presigned upload URL with all options' , async ( ) => {
350+ const fileName = 'upload-file.txt' ;
351+ const customExpires = new Date ( '2024-12-31T23:59:59Z' ) ;
352+ const contentType = 'application/json' ;
353+ const expectedUrl = 'https://storage.googleapis.com/test-bucket/upload-file.txt?signed-url-params' ;
354+
355+ mockFile . getSignedUrl . mockResolvedValueOnce ( [ expectedUrl ] ) ;
356+
357+ const result = await objectStorage . getPresignedUploadUrl ( fileName , {
358+ expires : customExpires ,
359+ contentType,
360+ } ) ;
361+
362+ expect ( result . success ) . toBe ( true ) ;
363+ expect ( result . presignedUrl ) . toBe ( expectedUrl ) ;
364+ expect ( result . fileName ) . toBe ( fileName ) ;
365+ expect ( mockFile . getSignedUrl ) . toHaveBeenCalledWith ( {
366+ version : 'v4' ,
367+ action : 'write' ,
368+ expires : customExpires ,
369+ contentType : 'application/json' ,
370+ } ) ;
371+ } ) ;
372+
373+ it ( 'should use default expiration when no expires option is provided' , async ( ) => {
374+ const fileName = 'upload-file.txt' ;
375+ const expectedUrl = 'https://storage.googleapis.com/test-bucket/upload-file.txt?signed-url-params' ;
376+
377+ mockFile . getSignedUrl . mockResolvedValueOnce ( [ expectedUrl ] ) ;
378+
379+ // Mock Date.now to have predictable test results
380+ const mockNow = new Date ( '2023-01-01T12:00:00Z' ) . getTime ( ) ;
381+ const originalDateNow = Date . now ;
382+ Date . now = jest . fn ( ( ) => mockNow ) ;
383+
384+ const result = await objectStorage . getPresignedUploadUrl ( fileName ) ;
385+
386+ expect ( result . success ) . toBe ( true ) ;
387+ expect ( mockFile . getSignedUrl ) . toHaveBeenCalledWith ( {
388+ version : 'v4' ,
389+ action : 'write' ,
390+ expires : new Date ( mockNow + 15 * 60 * 1000 ) , // 15 minutes from mockNow
391+ } ) ;
392+
393+ // Restore original Date.now
394+ Date . now = originalDateNow ;
395+ } ) ;
396+
397+ it ( 'should handle presigned URL generation failure' , async ( ) => {
398+ const fileName = 'upload-file.txt' ;
399+
400+ mockFile . getSignedUrl . mockRejectedValueOnce ( new Error ( 'Signing failed' ) ) ;
401+
402+ const result = await objectStorage . getPresignedUploadUrl ( fileName ) ;
403+
404+ expect ( result . success ) . toBe ( false ) ;
405+ expect ( result . error ) . toContain ( 'Failed to generate presigned upload URL' ) ;
406+ expect ( result . presignedUrl ) . toBeUndefined ( ) ;
407+ } ) ;
408+
409+ it ( 'should handle empty file name gracefully' , async ( ) => {
410+ const fileName = '' ;
411+
412+ mockFile . getSignedUrl . mockRejectedValueOnce ( new Error ( 'Invalid file name' ) ) ;
413+
414+ const result = await objectStorage . getPresignedUploadUrl ( fileName ) ;
415+
416+ expect ( result . success ) . toBe ( false ) ;
417+ expect ( result . error ) . toContain ( 'Failed to generate presigned upload URL' ) ;
418+ } ) ;
419+ } ) ;
284420} ) ;
0 commit comments