@@ -25,6 +25,7 @@ const {
2525 mockGetUserEntityPermissions,
2626 mockGenerateWorkspaceFileKey,
2727 mockGenerateExecutionFileKey,
28+ mockInsertFileMetadata,
2829} = vi . hoisted ( ( ) => ( {
2930 mockVerifyFileAccess : vi . fn ( ) . mockResolvedValue ( true ) ,
3031 mockVerifyWorkspaceFileAccess : vi . fn ( ) . mockResolvedValue ( true ) ,
@@ -50,6 +51,7 @@ const {
5051 ( ctx : { workspaceId : string ; workflowId : string ; executionId : string } , fileName : string ) =>
5152 `execution/${ ctx . workspaceId } /${ ctx . workflowId } /${ ctx . executionId } /${ fileName } `
5253 ) ,
54+ mockInsertFileMetadata : vi . fn ( ) . mockResolvedValue ( { id : 'wf_test' } ) ,
5355} ) )
5456
5557vi . mock ( '@/app/api/files/authorization' , ( ) => ( {
@@ -89,6 +91,10 @@ vi.mock('@/lib/uploads/contexts/execution/utils', () => ({
8991 generateExecutionFileKey : mockGenerateExecutionFileKey ,
9092} ) )
9193
94+ vi . mock ( '@/lib/uploads/server/metadata' , ( ) => ( {
95+ insertFileMetadata : mockInsertFileMetadata ,
96+ } ) )
97+
9298vi . mock ( '@/lib/uploads/utils/file-utils' , ( ) => ( {
9399 isImageFileType : mockIsImageFileType ,
94100} ) )
@@ -614,6 +620,57 @@ describe('/api/files/presigned', () => {
614620 const response = await POST ( request )
615621 expect ( response . status ) . toBe ( 403 )
616622 } )
623+
624+ it ( 'inserts a workspaceFiles row with context=mothership so previews authorize' , async ( ) => {
625+ setupFileApiMocks ( { cloudEnabled : true , storageProvider : 's3' } )
626+
627+ const request = new NextRequest (
628+ 'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1' ,
629+ {
630+ method : 'POST' ,
631+ body : JSON . stringify ( {
632+ fileName : 'screenshot.png' ,
633+ contentType : 'image/png' ,
634+ fileSize : 4096 ,
635+ } ) ,
636+ }
637+ )
638+
639+ const response = await POST ( request )
640+ const data = await response . json ( )
641+
642+ expect ( response . status ) . toBe ( 200 )
643+ expect ( mockInsertFileMetadata ) . toHaveBeenCalledTimes ( 1 )
644+ expect ( mockInsertFileMetadata ) . toHaveBeenCalledWith ( {
645+ key : data . fileInfo . key ,
646+ userId : 'test-user-id' ,
647+ workspaceId : 'ws-1' ,
648+ context : 'mothership' ,
649+ originalName : 'screenshot.png' ,
650+ contentType : 'image/png' ,
651+ size : 4096 ,
652+ } )
653+ } )
654+
655+ it ( 'returns 500 when insertFileMetadata fails so callers do not get an unauthorizable URL' , async ( ) => {
656+ setupFileApiMocks ( { cloudEnabled : true , storageProvider : 's3' } )
657+ mockInsertFileMetadata . mockRejectedValueOnce ( new Error ( 'DB connection lost' ) )
658+
659+ const request = new NextRequest (
660+ 'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1' ,
661+ {
662+ method : 'POST' ,
663+ body : JSON . stringify ( {
664+ fileName : 'screenshot.png' ,
665+ contentType : 'image/png' ,
666+ fileSize : 4096 ,
667+ } ) ,
668+ }
669+ )
670+
671+ const response = await POST ( request )
672+ expect ( response . status ) . toBe ( 500 )
673+ } )
617674 } )
618675
619676 describe ( 'execution uploads' , ( ) => {
@@ -682,6 +739,70 @@ describe('/api/files/presigned', () => {
682739 const response = await POST ( request )
683740 expect ( response . status ) . toBe ( 400 )
684741 } )
742+
743+ it ( 'inserts a workspaceFiles row with context=execution so previews authorize' , async ( ) => {
744+ setupFileApiMocks ( { cloudEnabled : true , storageProvider : 's3' } )
745+
746+ const request = new NextRequest (
747+ 'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1&workflowId=wf-1&executionId=exec-1' ,
748+ {
749+ method : 'POST' ,
750+ body : JSON . stringify ( {
751+ fileName : 'output.mp4' ,
752+ contentType : 'video/mp4' ,
753+ fileSize : 4096 ,
754+ } ) ,
755+ }
756+ )
757+
758+ const response = await POST ( request )
759+ const data = await response . json ( )
760+
761+ expect ( response . status ) . toBe ( 200 )
762+ expect ( mockInsertFileMetadata ) . toHaveBeenCalledTimes ( 1 )
763+ expect ( mockInsertFileMetadata ) . toHaveBeenCalledWith ( {
764+ key : data . fileInfo . key ,
765+ userId : 'test-user-id' ,
766+ workspaceId : 'ws-1' ,
767+ context : 'execution' ,
768+ originalName : 'output.mp4' ,
769+ contentType : 'video/mp4' ,
770+ size : 4096 ,
771+ } )
772+ } )
773+ } )
774+
775+ describe ( 'workspace-logos uploads' , ( ) => {
776+ it ( 'inserts a workspaceFiles row with context=workspace-logos so logos authorize' , async ( ) => {
777+ setupFileApiMocks ( { cloudEnabled : true , storageProvider : 's3' } )
778+
779+ const request = new NextRequest (
780+ 'http://localhost:3000/api/files/presigned?type=workspace-logos&workspaceId=ws-1' ,
781+ {
782+ method : 'POST' ,
783+ body : JSON . stringify ( {
784+ fileName : 'logo.png' ,
785+ contentType : 'image/png' ,
786+ fileSize : 4096 ,
787+ } ) ,
788+ }
789+ )
790+
791+ const response = await POST ( request )
792+ const data = await response . json ( )
793+
794+ expect ( response . status ) . toBe ( 200 )
795+ expect ( mockInsertFileMetadata ) . toHaveBeenCalledTimes ( 1 )
796+ expect ( mockInsertFileMetadata ) . toHaveBeenCalledWith ( {
797+ key : data . fileInfo . key ,
798+ userId : 'test-user-id' ,
799+ workspaceId : 'ws-1' ,
800+ context : 'workspace-logos' ,
801+ originalName : 'logo.png' ,
802+ contentType : 'image/png' ,
803+ size : 4096 ,
804+ } )
805+ } )
685806 } )
686807
687808 describe ( 'knowledge-base uploads' , ( ) => {
0 commit comments