Skip to content

Commit 037c4f5

Browse files
fix(files): keep an overwrite create on its exact path
1 parent e658863 commit 037c4f5

2 files changed

Lines changed: 25 additions & 2 deletions

File tree

‎apps/sim/lib/internal/file/operations.test.ts‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ vi.mock('@/app/api/files/authorization', () => ({
164164

165165
import { fileManageBodySchema } from '@/lib/api/contracts/tools/file'
166166
import { executeFileManageOperation } from '@/lib/internal/file/operations'
167+
import { FileConflictError } from '@/lib/uploads/contexts/workspace'
167168
import { createWorkspaceFileDelegatedPrincipal } from '@/lib/workspace-files/application/delegated-principal'
168169

169170
async function POST(request: Request): Promise<Response> {
@@ -676,10 +677,29 @@ describe('file manage operations', () => {
676677
Buffer.from('fresh'),
677678
'report.txt',
678679
'text/plain',
679-
expect.objectContaining({ exactName: false, folderId: null })
680+
// Exact, so a path created by a concurrent write conflicts instead of being suffixed.
681+
expect.objectContaining({ exactName: true, folderId: null })
680682
)
681683
})
682684

685+
it('surfaces a conflict when a concurrent write claims the overwrite path', async () => {
686+
mockResolveWorkspaceFileReference.mockResolvedValue(null)
687+
mockUploadWorkspaceFile.mockRejectedValue(new FileConflictError('report.txt'))
688+
689+
const response = await POST(
690+
createMockRequest('POST', {
691+
operation: 'write',
692+
workspaceId: 'workspace-1',
693+
fileName: 'report.txt',
694+
content: 'fresh',
695+
overwrite: true,
696+
})
697+
)
698+
699+
expect(response.status).toBe(409)
700+
await expect(response.json()).resolves.toMatchObject({ success: false })
701+
})
702+
683703
it('never overwrites a same-named file resolved outside the target folder', async () => {
684704
mockResolveWorkspaceFileReference.mockResolvedValue({
685705
...workspaceFile('report'),

‎apps/sim/lib/internal/file/operations.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,10 @@ export async function executeFileManageOperation(
974974
content: sourceContent,
975975
encoding: sourceEncoding,
976976
folderId,
977-
exactName: false,
977+
// An overwrite that found no target must land on the exact path or fail. Suffixing
978+
// would silently satisfy the request at the wrong name when a concurrent write
979+
// created that path in between; exactName surfaces the race as a conflict instead.
980+
exactName: Boolean(overwrite),
978981
...(writeProvenance ? { secretProvenance: writeProvenance } : {}),
979982
},
980983
})

0 commit comments

Comments
 (0)