Skip to content

Commit 813c8dc

Browse files
committed
fix(uploads): not-ready 409 for uptimerobot, real MIME for non-doc, xlsx tests
Address review findings: - uptimerobot create-psp/update-psp now map DocCompileUserError to the shared 409 (Greptile + Cursor flagged the gap alongside slack/teams). - downloadServableFileFromStorage returns the extension-derived MIME (getMimeTypeFromExtension) for non-doc files instead of an empty string when userFile.type is unset. - Add resolveServableDocBytes tests for the three xlsx branches (binary ZIP passthrough, not-ready throw under E2B+beta, no-workspaceId raw passthrough).
1 parent aab1d24 commit 813c8dc

4 files changed

Lines changed: 55 additions & 3 deletions

File tree

apps/sim/app/api/tools/uptimerobot/create-psp/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { parseRequest } from '@/lib/api/server'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
77
import { generateRequestId } from '@/lib/core/utils/request'
88
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
9+
import { docNotReadyResponse } from '@/lib/uploads/utils/servable-file-response'
910
import { forwardPspRequest } from '@/app/api/tools/uptimerobot/server-utils'
1011

1112
export const dynamic = 'force-dynamic'
@@ -39,6 +40,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
3940
logger,
4041
})
4142
} catch (error) {
43+
const notReady = docNotReadyResponse(error)
44+
if (notReady) return notReady
4245
logger.error(`[${requestId}] Unexpected error creating status page:`, error)
4346
return NextResponse.json(
4447
{ success: false, error: getErrorMessage(error, 'Unknown error') },

apps/sim/app/api/tools/uptimerobot/update-psp/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { parseRequest } from '@/lib/api/server'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
77
import { generateRequestId } from '@/lib/core/utils/request'
88
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
9+
import { docNotReadyResponse } from '@/lib/uploads/utils/servable-file-response'
910
import { forwardPspRequest } from '@/app/api/tools/uptimerobot/server-utils'
1011

1112
export const dynamic = 'force-dynamic'
@@ -39,6 +40,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
3940
logger,
4041
})
4142
} catch (error) {
43+
const notReady = docNotReadyResponse(error)
44+
if (notReady) return notReady
4245
logger.error(`[${requestId}] Unexpected error updating status page:`, error)
4346
return NextResponse.json(
4447
{ success: false, error: getErrorMessage(error, 'Unknown error') },

apps/sim/lib/copilot/tools/server/files/doc-servable.test.ts

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
*/
44
import { beforeEach, describe, expect, it, vi } from 'vitest'
55

6-
const { e2bFlag, mockLoadCompiledDoc, mockRunSandboxTask } = vi.hoisted(() => ({
6+
const { e2bFlag, betaFlag, mockLoadCompiledDoc, mockRunSandboxTask } = vi.hoisted(() => ({
77
e2bFlag: { value: true },
8+
betaFlag: { value: false },
89
mockLoadCompiledDoc: vi.fn(),
910
mockRunSandboxTask: vi.fn(),
1011
}))
@@ -28,7 +29,7 @@ vi.mock('./doc-compiled-store', () => ({
2829
storeCompiledDoc: vi.fn(),
2930
}))
3031
vi.mock('@/lib/core/config/feature-flags', () => ({
31-
isFeatureEnabled: vi.fn().mockResolvedValue(false),
32+
isFeatureEnabled: vi.fn(async () => betaFlag.value),
3233
}))
3334
vi.mock('@/lib/core/config/env-flags', () => ({
3435
get isE2BDocEnabled() {
@@ -49,11 +50,14 @@ import { DocCompileUserError, resolveServableDocBytes } from './doc-compile'
4950
const WORKSPACE_ID = '550e8400-e29b-41d4-a716-446655440000'
5051
const PDF_MAGIC = Buffer.from('%PDF-1.7\n...binary...')
5152
const PDF_SOURCE = Buffer.from('from reportlab.pdfgen import canvas\n# generates a PDF', 'utf-8')
53+
const ZIP_MAGIC = Buffer.from([0x50, 0x4b, 0x03, 0x04, 0x00, 0x01])
54+
const XLSX_SOURCE = Buffer.from('from openpyxl import Workbook\n# generates an xlsx', 'utf-8')
5255

5356
describe('resolveServableDocBytes', () => {
5457
beforeEach(() => {
5558
vi.clearAllMocks()
5659
e2bFlag.value = true
60+
betaFlag.value = false
5761
})
5862

5963
it('swaps generated-doc source for the compiled artifact + binary content type', async () => {
@@ -135,4 +139,45 @@ describe('resolveServableDocBytes', () => {
135139
expect(result.contentType).toBe('text/plain')
136140
expect(mockLoadCompiledDoc).not.toHaveBeenCalled()
137141
})
142+
143+
it('passes through a real binary XLSX (ZIP magic) without an artifact lookup', async () => {
144+
const result = await resolveServableDocBytes({
145+
rawBuffer: ZIP_MAGIC,
146+
fileName: 'sheet.xlsx',
147+
workspaceId: WORKSPACE_ID,
148+
})
149+
150+
expect(result.buffer).toBe(ZIP_MAGIC)
151+
expect(mockLoadCompiledDoc).not.toHaveBeenCalled()
152+
})
153+
154+
it('throws when a generated XLSX artifact is not ready (E2B + mothership-beta enabled)', async () => {
155+
mockLoadCompiledDoc.mockResolvedValue(null)
156+
e2bFlag.value = true
157+
betaFlag.value = true
158+
159+
await expect(
160+
resolveServableDocBytes({
161+
rawBuffer: XLSX_SOURCE,
162+
fileName: 'sheet.xlsx',
163+
workspaceId: WORKSPACE_ID,
164+
})
165+
).rejects.toBeInstanceOf(DocCompileUserError)
166+
167+
expect(mockRunSandboxTask).not.toHaveBeenCalled()
168+
})
169+
170+
it('returns raw XLSX source when there is no workspaceId (xlsx has no isolated-vm path)', async () => {
171+
betaFlag.value = true
172+
173+
const result = await resolveServableDocBytes({
174+
rawBuffer: XLSX_SOURCE,
175+
fileName: 'sheet.xlsx',
176+
workspaceId: undefined,
177+
})
178+
179+
expect(result.buffer).toBe(XLSX_SOURCE)
180+
expect(mockLoadCompiledDoc).not.toHaveBeenCalled()
181+
expect(mockRunSandboxTask).not.toHaveBeenCalled()
182+
})
138183
})

apps/sim/lib/uploads/utils/file-utils.server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { isExecutionFile } from '@/lib/uploads/contexts/execution/utils'
1818
import {
1919
extractStorageKey,
2020
getFileExtension,
21+
getMimeTypeFromExtension,
2122
inferContextFromKey,
2223
isInternalFileUrl,
2324
processSingleFileToUserFile,
@@ -342,7 +343,7 @@ export async function downloadServableFileFromStorage(
342343
// the authority on what actually gets swapped.
343344
const ext = getFileExtension(userFile.name)
344345
if (ext !== 'pdf' && ext !== 'docx' && ext !== 'pptx' && ext !== 'xlsx') {
345-
return { buffer, contentType: userFile.type || '' }
346+
return { buffer, contentType: userFile.type || getMimeTypeFromExtension(ext) }
346347
}
347348

348349
const { parseWorkspaceFileKey } = await import(

0 commit comments

Comments
 (0)