@@ -11,6 +11,7 @@ import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/
1111import { generateId } from '@sim/utils/id'
1212import { eq , inArray , sql } from 'drizzle-orm'
1313import { afterAll , beforeAll , describe , expect , it } from 'vitest'
14+ import { MAX_FOLDERS_PER_WORKSPACE } from '@/lib/folders/constants'
1415import { workspaceFileVfsPath } from '@/lib/uploads/contexts/workspace/file-manager'
1516import { deleteWorkspaceFileOperation } from '@/lib/workspace-files/application/delete-workspace-file'
1617import { readWorkspaceFileMetadataWithVersion } from '@/lib/workspace-files/application/read-workspace-file-metadata'
@@ -21,6 +22,7 @@ const bob = generateId()
2122const carol = generateId ( )
2223const workspaceId = generateId ( )
2324const otherWorkspaceId = generateId ( )
25+ const overLimitWorkspaceId = generateId ( )
2426const folders = { docs : generateId ( ) , media : generateId ( ) , foreign : generateId ( ) }
2527const session = ( userId : string ) => ( { kind : 'session' as const , userId, sessionId : generateId ( ) } )
2628
@@ -163,7 +165,10 @@ describe('single-file operations addressed by VFS path', () => {
163165 } )
164166
165167 afterAll ( async ( ) => {
166- await deleteWorkspaceFixture ( db , inArray ( workspace . id , [ workspaceId , otherWorkspaceId ] ) )
168+ await deleteWorkspaceFixture (
169+ db ,
170+ inArray ( workspace . id , [ workspaceId , otherWorkspaceId , overLimitWorkspaceId ] )
171+ )
167172 await db . delete ( user ) . where ( inArray ( user . id , [ alice , bob , carol ] ) )
168173 } )
169174
@@ -321,6 +326,37 @@ describe('single-file operations addressed by VFS path', () => {
321326 }
322327 } )
323328
329+ it ( 'answers a non-member the same for a workspace whose folder tree is too large to read' , async ( ) => {
330+ await insertWorkspaceFixture ( db , [
331+ { id : overLimitWorkspaceId , name : 'Over limit' , ownerId : alice , billedAccountUserId : alice } ,
332+ ] )
333+ await db . insert ( permissions ) . values ( {
334+ id : generateId ( ) ,
335+ userId : alice ,
336+ entityType : 'workspace' ,
337+ entityId : overLimitWorkspaceId ,
338+ permissionType : 'admin' ,
339+ } )
340+ await db . execute ( sql `
341+ INSERT INTO folder (id, name, user_id, workspace_id, resource_type, parent_id)
342+ SELECT ${ generateId ( ) } || '-' || g, 'Folder ' || g, ${ alice } , ${ overLimitWorkspaceId } , 'file', NULL
343+ FROM generate_series(1, ${ MAX_FOLDERS_PER_WORKSPACE + 1 } ) AS g` )
344+ const answer = ( userId : string , fileId : string ) =>
345+ describeFile ( fileId , userId , overLimitWorkspaceId ) . then (
346+ ( ) => null ,
347+ ( error : { code ?: string ; message ?: string } ) => ( {
348+ code : error . code ,
349+ message : error . message ,
350+ } )
351+ )
352+ const nested = await answer ( carol , 'files/Folder 1/report.csv' )
353+ expect ( nested ) . toMatchObject ( { code : 'forbidden' } )
354+ expect ( await answer ( carol , 'files/missing.csv' ) ) . toEqual ( nested )
355+ expect ( await answer ( alice , 'files/Folder 1/report.csv' ) ) . toMatchObject ( {
356+ code : 'payload_too_large' ,
357+ } )
358+ } )
359+
324360 it ( 'resolves no path for a caller that did not opt in' , async ( ) => {
325361 await expect (
326362 deleteWorkspaceFileOperation . execute ( {
0 commit comments