@@ -13,7 +13,12 @@ import {
1313 createSessionPrincipal ,
1414} from '@sim/testing/factories/principal.factory'
1515import { createRouteContext } from '@sim/testing/helpers/http'
16+ import { fileReadReceiptMock } from '@sim/testing/mocks/file-read-receipt.mock'
1617import { fileUtilsMock , fileUtilsMockFns } from '@sim/testing/mocks/file-utils.mock'
18+ import {
19+ fileUtilsServerMock ,
20+ fileUtilsServerMockFns ,
21+ } from '@sim/testing/mocks/file-utils-server.mock'
1722import {
1823 filesAuthorizationMock ,
1924 filesAuthorizationMockFns ,
@@ -36,6 +41,8 @@ const {
3641 mockReadFile,
3742 mockAuthenticateWorkspaceFile,
3843 mockReadWorkspaceFileContentByKey,
44+ mockReadWorkspaceFileRecordByKey,
45+ mockAuthorizeWorkspaceFileRecordByKey,
3946 mockResolveServableDocBytes,
4047 mockGetContentType,
4148 mockFindLocalFile,
@@ -57,6 +64,8 @@ const {
5764 mockReadFile : vi . fn ( ) ,
5865 mockAuthenticateWorkspaceFile : vi . fn ( ) ,
5966 mockReadWorkspaceFileContentByKey : vi . fn ( ) ,
67+ mockReadWorkspaceFileRecordByKey : vi . fn ( ) ,
68+ mockAuthorizeWorkspaceFileRecordByKey : vi . fn ( async ( ) => undefined ) ,
6069 mockResolveServableDocBytes : vi . fn ( ) ,
6170 mockGetContentType : vi . fn ( ) ,
6271 mockFindLocalFile : vi . fn ( ) ,
@@ -86,6 +95,9 @@ vi.mock('@/lib/uploads/core/storage-service', () => storageServiceMock)
8695
8796vi . mock ( '@/lib/uploads/utils/file' , ( ) => fileUtilsMock )
8897
98+ vi . mock ( '@/lib/uploads/utils/file.server' , ( ) => fileUtilsServerMock )
99+ vi . mock ( '@/lib/workspace-files/read-receipt' , ( ) => fileReadReceiptMock )
100+
89101vi . mock ( '@/lib/uploads/server/metadata' , ( ) => uploadsMetadataMock )
90102
91103vi . mock ( '@/lib/uploads/setup.server' , ( ) => ( { } ) )
@@ -106,6 +118,10 @@ vi.mock('@/lib/workspace-files/api', () => ({
106118
107119vi . mock ( '@/lib/workspace-files/application/read-workspace-file-content-by-key' , ( ) => ( {
108120 readWorkspaceFileContentByKey : { execute : mockReadWorkspaceFileContentByKey } ,
121+ readWorkspaceFileRecordByKey : {
122+ execute : mockReadWorkspaceFileRecordByKey ,
123+ authorize : mockAuthorizeWorkspaceFileRecordByKey ,
124+ } ,
109125} ) )
110126
111127vi . mock ( '@/lib/uploads/documents/compile' , ( ) => ( {
@@ -161,6 +177,17 @@ describe('File Serve API Route', () => {
161177 } ,
162178 content : Buffer . from ( 'generated source' ) ,
163179 } )
180+ mockReadWorkspaceFileRecordByKey . mockResolvedValue ( {
181+ file : {
182+ id : 'file-1' ,
183+ workspaceId : 'test-workspace-id' ,
184+ name : 'report.pdf' ,
185+ key : 'workspace/test-workspace-id/report.pdf' ,
186+ type : 'text/x-pdflibjs' ,
187+ size : 16 ,
188+ contentUpdatedAt : new Date ( '2026-09-06' ) ,
189+ } ,
190+ } )
164191 mockResolveServableDocBytes . mockImplementation (
165192 async ( { rawBuffer, fileName } : { rawBuffer : Buffer ; fileName : string } ) => ( {
166193 buffer : rawBuffer ,
@@ -173,24 +200,23 @@ describe('File Serve API Route', () => {
173200 mockReadFile ( filePath )
174201 )
175202 mockCreateFileResponse . mockImplementation (
176- ( file : { buffer : Buffer ; contentType : string ; filename : string } ) => {
203+ ( file : { buffer : Buffer ; contentType : string ; filename : string ; cacheControl ?: string } ) => {
177204 return new Response ( file . buffer , {
178205 status : 200 ,
179206 headers : {
180207 'Content-Type' : file . contentType ,
208+ ...( file . cacheControl ? { 'Cache-Control' : file . cacheControl } : { } ) ,
181209 'Content-Disposition' : `inline; filename="${ file . filename } "` ,
182210 } ,
183211 } )
184212 }
185213 )
186- // Delegates so the existing assertions on the response payload — including its
187- // Cache-Control — read the same call list whichever helper the route reached for.
188214 mockCreateConditionalFileResponse . mockImplementation ( ( file : unknown ) =>
189215 mockCreateFileResponse ( file )
190216 )
191- mockFileErrorResponse . mockImplementation ( ( error : Error ) => {
217+ mockFileErrorResponse . mockImplementation ( ( error : Error , status = 500 ) => {
192218 return new Response ( JSON . stringify ( { error : error . name , message : error . message } ) , {
193- status : error . name === 'FileNotFoundError' ? 404 : 500 ,
219+ status : error . name === 'FileNotFoundError' ? 404 : status ,
194220 headers : { 'Content-Type' : 'application/json' } ,
195221 } )
196222 } )
@@ -284,9 +310,9 @@ describe('File Serve API Route', () => {
284310 // The real createFileErrorResponse owns the status mapping; mirror it here so the
285311 // route's own error path is what decides, not the mock's default 500.
286312 mockFileErrorResponse . mockImplementation (
287- ( error : Error ) =>
313+ ( error : Error , status = 500 ) =>
288314 new Response ( JSON . stringify ( { error : error . name } ) , {
289- status : error . name === 'PayloadSizeLimitError' ? 413 : 500 ,
315+ status : error . name === 'PayloadSizeLimitError' ? 413 : status ,
290316 } )
291317 )
292318
@@ -316,7 +342,7 @@ describe('File Serve API Route', () => {
316342 mockResolveStoredFileContext . mockResolvedValue ( 'workspace' )
317343 mockParseWorkspaceFileKey . mockReturnValue ( 'test-workspace-id' )
318344 mockAuthenticateWorkspaceFile . mockResolvedValue ( principal )
319- mockResolveServableDocBytes . mockResolvedValue ( {
345+ fileUtilsServerMockFns . mockDownloadServableFileFromStorage . mockResolvedValue ( {
320346 buffer : Buffer . from ( 'compiled' ) ,
321347 contentType : 'application/pdf' ,
322348 ...( dependsOnReferencedFiles ? { dependsOnReferencedFiles : true } : { } ) ,
@@ -325,19 +351,22 @@ describe('File Serve API Route', () => {
325351 const req = createMockRequest ( {
326352 url : 'http://localhost:3000/api/files/serve/workspace/test-workspace-id/report.pdf?v=1756684800000' ,
327353 } )
328- await GET ( req , createRouteContext ( { path : [ 'workspace' , 'test-workspace-id' , 'report.pdf' ] } ) )
329- return mockCreateFileResponse . mock . calls . at ( - 1 ) ?. [ 0 ]
354+ const response = await GET (
355+ req ,
356+ createRouteContext ( { path : [ 'workspace' , 'test-workspace-id' , 'report.pdf' ] } )
357+ )
358+ expect ( response . status ) . toBe ( 200 )
359+ return response
330360 }
331361
332362 it ( 'caches a versioned document immutably when its bytes derive from the stored source alone' , async ( ) => {
333- expect ( await serveVersionedDoc ( false ) ) . toEqual (
334- expect . objectContaining ( { cacheControl : 'private, max-age=31536000, immutable' } )
335- )
363+ const response = await serveVersionedDoc ( false )
364+ expect ( response . headers . get ( 'Cache-Control' ) ) . toBe ( 'private, max-age=31536000, immutable' )
336365 } )
337366
338- it ( 'attaches a validator to a revalidated response so the next check can be answered 304 ' , async ( ) => {
339- await serveVersionedDoc ( true )
340- expect ( mockCreateConditionalFileResponse ) . toHaveBeenCalled ( )
367+ it ( 'requires revalidation for a versioned document with referenced inputs ' , async ( ) => {
368+ const response = await serveVersionedDoc ( true )
369+ expect ( response . headers . get ( 'Cache-Control' ) ) . toBe ( 'private, no-cache, must-revalidate' )
341370 } )
342371 } )
343372} )
0 commit comments