@@ -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 { workspaceFileVfsPath } from '@/lib/uploads/contexts/workspace/file-manager'
1415import { deleteWorkspaceFileOperation } from '@/lib/workspace-files/application/delete-workspace-file'
1516import { readWorkspaceFileMetadataWithVersion } from '@/lib/workspace-files/application/read-workspace-file-metadata'
1617import { renameWorkspaceFile } from '@/lib/workspace-files/application/rename-workspace-file'
@@ -69,6 +70,11 @@ describe('single-file operations addressed by VFS path', () => {
6970 foreignReport : '' ,
7071 chatUpload : '' ,
7172 archived : '' ,
73+ docsContent : '' ,
74+ docsOnly : '' ,
75+ spaced : '' ,
76+ alikeSpaced : '' ,
77+ alikeSingle : '' ,
7278 }
7379
7480 beforeAll ( async ( ) => {
@@ -125,6 +131,11 @@ describe('single-file operations addressed by VFS path', () => {
125131 } )
126132 ids . chatUpload = await insertFile ( { name : 'notes.txt' , context : 'mothership' } )
127133 ids . archived = await insertFile ( { name : 'old.csv' , deleted : true } )
134+ ids . docsContent = await insertFile ( { name : 'content' , folderId : folders . docs } )
135+ ids . docsOnly = await insertFile ( { name : 'only-here.csv' , folderId : folders . docs } )
136+ ids . spaced = await insertFile ( { name : 'my notes.csv' } )
137+ ids . alikeSpaced = await insertFile ( { name : 'a b.csv' , folderId : folders . media } )
138+ ids . alikeSingle = await insertFile ( { name : 'a b.csv' , folderId : folders . media } )
128139 } )
129140
130141 afterAll ( async ( ) => {
@@ -139,6 +150,33 @@ describe('single-file operations addressed by VFS path', () => {
139150 code : 'not_found' ,
140151 } )
141152 await expect ( describeFile ( 'files/Docs' ) ) . rejects . toMatchObject ( { code : 'not_found' } )
153+ await expect ( describeFile ( 'files/only-here.csv' ) ) . rejects . toMatchObject ( {
154+ code : 'not_found' ,
155+ } )
156+ } )
157+
158+ it ( 'treats a trailing `content` or `meta.json` as a name, never as the parent file' , async ( ) => {
159+ expect ( ( await describeFile ( 'files/Docs/content' ) ) . file . id ) . toBe ( ids . docsContent )
160+ await expect ( deleteFile ( 'files/report.csv/content' ) ) . rejects . toMatchObject ( {
161+ code : 'not_found' ,
162+ } )
163+ await expect ( deleteFile ( 'files/report.csv/meta.json' ) ) . rejects . toMatchObject ( {
164+ code : 'not_found' ,
165+ } )
166+ expect ( ( await row ( ids . rootReport ) ) ?. deletedAt ) . toBeNull ( )
167+ } )
168+
169+ it ( 'resolves the path a listing prints for a name it displays normalized' , async ( ) => {
170+ const listed = workspaceFileVfsPath ( ( await describeFile ( ids . spaced ) ) . file )
171+ expect ( ( await describeFile ( listed ) ) . file . id ) . toBe ( ids . spaced )
172+ } )
173+
174+ it ( 'refuses a path two display-alike names share instead of picking one' , async ( ) => {
175+ const listed = workspaceFileVfsPath ( ( await describeFile ( ids . alikeSingle ) ) . file )
176+ await expect ( deleteFile ( listed ) ) . rejects . toMatchObject ( { code : 'validation' } )
177+ for ( const id of [ ids . alikeSpaced , ids . alikeSingle ] ) {
178+ expect ( ( await row ( id ) ) ?. deletedAt ) . toBeNull ( )
179+ }
142180 } )
143181
144182 it ( 'renames and deletes only the file the path names' , async ( ) => {
0 commit comments