mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(uploads): write workspaceFiles row when issuing presigned URL (#4537)
* fix(uploads): write workspaceFiles row when issuing presigned URL * test(uploads): cover insertFileMetadata failure path in presigned route
This commit is contained in:
@@ -25,6 +25,7 @@ const {
|
||||
mockGetUserEntityPermissions,
|
||||
mockGenerateWorkspaceFileKey,
|
||||
mockGenerateExecutionFileKey,
|
||||
mockInsertFileMetadata,
|
||||
} = vi.hoisted(() => ({
|
||||
mockVerifyFileAccess: vi.fn().mockResolvedValue(true),
|
||||
mockVerifyWorkspaceFileAccess: vi.fn().mockResolvedValue(true),
|
||||
@@ -50,6 +51,7 @@ const {
|
||||
(ctx: { workspaceId: string; workflowId: string; executionId: string }, fileName: string) =>
|
||||
`execution/${ctx.workspaceId}/${ctx.workflowId}/${ctx.executionId}/${fileName}`
|
||||
),
|
||||
mockInsertFileMetadata: vi.fn().mockResolvedValue({ id: 'wf_test' }),
|
||||
}))
|
||||
|
||||
vi.mock('@/app/api/files/authorization', () => ({
|
||||
@@ -89,6 +91,10 @@ vi.mock('@/lib/uploads/contexts/execution/utils', () => ({
|
||||
generateExecutionFileKey: mockGenerateExecutionFileKey,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/uploads/server/metadata', () => ({
|
||||
insertFileMetadata: mockInsertFileMetadata,
|
||||
}))
|
||||
|
||||
vi.mock('@/lib/uploads/utils/file-utils', () => ({
|
||||
isImageFileType: mockIsImageFileType,
|
||||
}))
|
||||
@@ -614,6 +620,57 @@ describe('/api/files/presigned', () => {
|
||||
const response = await POST(request)
|
||||
expect(response.status).toBe(403)
|
||||
})
|
||||
|
||||
it('inserts a workspaceFiles row with context=mothership so previews authorize', async () => {
|
||||
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
|
||||
|
||||
const request = new NextRequest(
|
||||
'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: 'screenshot.png',
|
||||
contentType: 'image/png',
|
||||
fileSize: 4096,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
const response = await POST(request)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
|
||||
expect(mockInsertFileMetadata).toHaveBeenCalledWith({
|
||||
key: data.fileInfo.key,
|
||||
userId: 'test-user-id',
|
||||
workspaceId: 'ws-1',
|
||||
context: 'mothership',
|
||||
originalName: 'screenshot.png',
|
||||
contentType: 'image/png',
|
||||
size: 4096,
|
||||
})
|
||||
})
|
||||
|
||||
it('returns 500 when insertFileMetadata fails so callers do not get an unauthorizable URL', async () => {
|
||||
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
|
||||
mockInsertFileMetadata.mockRejectedValueOnce(new Error('DB connection lost'))
|
||||
|
||||
const request = new NextRequest(
|
||||
'http://localhost:3000/api/files/presigned?type=mothership&workspaceId=ws-1',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: 'screenshot.png',
|
||||
contentType: 'image/png',
|
||||
fileSize: 4096,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
const response = await POST(request)
|
||||
expect(response.status).toBe(500)
|
||||
})
|
||||
})
|
||||
|
||||
describe('execution uploads', () => {
|
||||
@@ -682,6 +739,70 @@ describe('/api/files/presigned', () => {
|
||||
const response = await POST(request)
|
||||
expect(response.status).toBe(400)
|
||||
})
|
||||
|
||||
it('inserts a workspaceFiles row with context=execution so previews authorize', async () => {
|
||||
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
|
||||
|
||||
const request = new NextRequest(
|
||||
'http://localhost:3000/api/files/presigned?type=execution&workspaceId=ws-1&workflowId=wf-1&executionId=exec-1',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: 'output.mp4',
|
||||
contentType: 'video/mp4',
|
||||
fileSize: 4096,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
const response = await POST(request)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
|
||||
expect(mockInsertFileMetadata).toHaveBeenCalledWith({
|
||||
key: data.fileInfo.key,
|
||||
userId: 'test-user-id',
|
||||
workspaceId: 'ws-1',
|
||||
context: 'execution',
|
||||
originalName: 'output.mp4',
|
||||
contentType: 'video/mp4',
|
||||
size: 4096,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('workspace-logos uploads', () => {
|
||||
it('inserts a workspaceFiles row with context=workspace-logos so logos authorize', async () => {
|
||||
setupFileApiMocks({ cloudEnabled: true, storageProvider: 's3' })
|
||||
|
||||
const request = new NextRequest(
|
||||
'http://localhost:3000/api/files/presigned?type=workspace-logos&workspaceId=ws-1',
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
fileName: 'logo.png',
|
||||
contentType: 'image/png',
|
||||
fileSize: 4096,
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
const response = await POST(request)
|
||||
const data = await response.json()
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(mockInsertFileMetadata).toHaveBeenCalledTimes(1)
|
||||
expect(mockInsertFileMetadata).toHaveBeenCalledWith({
|
||||
key: data.fileInfo.key,
|
||||
userId: 'test-user-id',
|
||||
workspaceId: 'ws-1',
|
||||
context: 'workspace-logos',
|
||||
originalName: 'logo.png',
|
||||
contentType: 'image/png',
|
||||
size: 4096,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('knowledge-base uploads', () => {
|
||||
|
||||
@@ -10,6 +10,7 @@ import { USE_BLOB_STORAGE } from '@/lib/uploads/config'
|
||||
import { generateExecutionFileKey } from '@/lib/uploads/contexts/execution/utils'
|
||||
import { generateWorkspaceFileKey } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
|
||||
import { generatePresignedUploadUrl, hasCloudStorage } from '@/lib/uploads/core/storage-service'
|
||||
import { insertFileMetadata } from '@/lib/uploads/server/metadata'
|
||||
import { isImageFileType } from '@/lib/uploads/utils/file-utils'
|
||||
import { validateAttachmentFileType, validateFileType } from '@/lib/uploads/utils/validation'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
@@ -157,6 +158,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
expirationSeconds: 3600,
|
||||
metadata: { workspaceId },
|
||||
})
|
||||
|
||||
await insertFileMetadata({
|
||||
key: presignedUrlResponse.key,
|
||||
userId: sessionUserId,
|
||||
workspaceId,
|
||||
context: 'mothership',
|
||||
originalName: fileName,
|
||||
contentType,
|
||||
size: fileSize,
|
||||
})
|
||||
} else if (uploadType === 'execution') {
|
||||
const workflowId = request.nextUrl.searchParams.get('workflowId')
|
||||
const executionId = request.nextUrl.searchParams.get('executionId')
|
||||
@@ -191,6 +202,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
expirationSeconds: 3600,
|
||||
metadata: { workspaceId, workflowId, executionId },
|
||||
})
|
||||
|
||||
await insertFileMetadata({
|
||||
key: presignedUrlResponse.key,
|
||||
userId: sessionUserId,
|
||||
workspaceId,
|
||||
context: 'execution',
|
||||
originalName: fileName,
|
||||
contentType,
|
||||
size: fileSize,
|
||||
})
|
||||
} else if (uploadType === 'workspace-logos') {
|
||||
const workspaceId = request.nextUrl.searchParams.get('workspaceId')
|
||||
if (!workspaceId?.trim()) {
|
||||
@@ -222,6 +243,16 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
expirationSeconds: 3600,
|
||||
metadata: { workspaceId },
|
||||
})
|
||||
|
||||
await insertFileMetadata({
|
||||
key: presignedUrlResponse.key,
|
||||
userId: sessionUserId,
|
||||
workspaceId,
|
||||
context: 'workspace-logos',
|
||||
originalName: fileName,
|
||||
contentType,
|
||||
size: fileSize,
|
||||
})
|
||||
} else {
|
||||
if (uploadType === 'profile-pictures') {
|
||||
if (!sessionUserId?.trim()) {
|
||||
|
||||
Reference in New Issue
Block a user