Skip to content

Commit b64a138

Browse files
authored
fix(settings): restore SSO recovery and lifecycle test isolation (#7317)
* fix(settings): restore recovery and test isolation * fix(settings): scope SSO retry state to failed queries
1 parent 8e9aeb9 commit b64a138

3 files changed

Lines changed: 242 additions & 151 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-workflow-execution.test.tsx

Lines changed: 153 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -414,151 +414,26 @@ describe('useWorkflowExecution cancellation', () => {
414414
})
415415
})
416416

417-
describe('useWorkflowExecution attachment uploads', () => {
418-
beforeEach(() => {
419-
vi.clearAllMocks()
420-
mockEndScopedExecution.mockReset().mockReturnValue(true)
421-
terminalStoreState._hasHydrated = false
422-
executionStoreState.workflowExecutions.set('workflow-1', idleExecution)
423-
executionStoreState.getWorkflowExecution.mockReturnValue(idleExecution)
424-
executionStoreState.getCurrentExecutionId.mockReturnValue(null)
425-
mockAdoptScopedExecution.mockReturnValue(undefined)
426-
mockLoadExecutionPointer.mockResolvedValue(null)
427-
mockReconnect.mockResolvedValue(undefined)
428-
mockResolveStartCandidates.mockReturnValue([])
429-
mockSelectBestTrigger.mockReturnValue([])
430-
vi.stubGlobal('fetch', mockFetch)
431-
mockUploadInternalFileSession.mockRejectedValue(
432-
new Error('Workspace file storage limit exceeded')
433-
)
434-
mockFetch.mockResolvedValue(
435-
new Response(JSON.stringify({ error: 'Workspace file storage limit exceeded' }), {
436-
status: 413,
437-
headers: { 'Content-Type': 'application/json' },
438-
})
439-
)
440-
mockExecute.mockResolvedValue(undefined)
441-
mockExecuteFromBlock.mockResolvedValue(undefined)
442-
workflowStoreState.edges.length = 0
443-
})
444-
445-
afterEach(() => {
446-
vi.unstubAllGlobals()
447-
})
448-
449-
it('does not execute and reports the exact server error when an explicit attachment fails', async () => {
450-
const { result, unmount } = renderWorkflowExecutionHook()
451-
const contextFile = new File(['context'], 'context.txt', { type: 'text/plain' })
452-
const file = new File(['report'], 'report.pdf', { type: 'application/pdf' })
453-
let uploadError: unknown
454-
455-
mockUploadInternalFileSession.mockResolvedValueOnce({
456-
id: 'attachment-context',
457-
key: 'executions/context.txt',
458-
url: '/uploads/context.txt',
459-
name: contextFile.name,
460-
size: contextFile.size,
461-
type: contextFile.type,
462-
context: 'execution',
463-
})
464-
465-
await act(async () => {
466-
try {
467-
await result().handleRunWorkflow({
468-
input: 'Summarize this report',
469-
conversationId: 'conversation-1',
470-
files: [
471-
{
472-
name: contextFile.name,
473-
size: contextFile.size,
474-
type: contextFile.type,
475-
file: contextFile,
476-
},
477-
{
478-
name: file.name,
479-
size: file.size,
480-
type: file.type,
481-
file,
482-
},
483-
],
484-
})
485-
} catch (error) {
486-
uploadError = error
487-
}
488-
})
489-
490-
expect(uploadError).toBeInstanceOf(WorkflowAttachmentUploadError)
491-
expect((uploadError as Error).message).toBe(
492-
'Failed to upload report.pdf: Workspace file storage limit exceeded'
493-
)
494-
expect(mockExecute).not.toHaveBeenCalled()
495-
496-
unmount()
497-
})
498-
499-
it('returns uploaded metadata without mutating or leaking local input into execution', async () => {
500-
const { result, unmount } = renderWorkflowExecutionHook()
501-
const file = new File(['diagram'], 'diagram.png', { type: 'image/png' })
502-
const workflowInput = {
503-
input: 'Describe this diagram',
504-
conversationId: 'conversation-1',
505-
files: [
506-
{
507-
name: file.name,
508-
size: file.size,
509-
type: file.type,
510-
file,
511-
},
512-
],
513-
}
514-
let runResult: unknown
515-
516-
mockUploadInternalFileSession.mockResolvedValueOnce({
517-
id: 'attachment-diagram',
518-
key: 'execution/diagram.png',
519-
url: '/api/files/serve/execution%2Fdiagram.png',
520-
name: file.name,
521-
size: file.size,
522-
type: file.type,
523-
context: 'execution',
524-
})
525-
526-
await act(async () => {
527-
runResult = await result().handleRunWorkflow(workflowInput)
528-
await drainStream(runResult)
529-
})
530-
531-
expect(isChatWorkflowRunResult(runResult)).toBe(true)
532-
if (!isChatWorkflowRunResult(runResult)) {
533-
throw new Error('Expected a chat workflow run result')
534-
}
535-
expect(runResult.uploadedAttachments).toEqual([
536-
expect.objectContaining({
537-
name: 'diagram.png',
538-
url: '/api/files/serve/execution%2Fdiagram.png',
539-
size: file.size,
540-
type: 'image/png',
541-
key: 'execution/diagram.png',
542-
}),
543-
])
544-
expect(workflowInput.files[0].file).toBe(file)
545-
expect(mockExecute).toHaveBeenCalledWith(
546-
expect.objectContaining({
547-
input: expect.objectContaining({
548-
input: 'Describe this diagram',
549-
conversationId: 'conversation-1',
550-
files: [
551-
expect.objectContaining({
552-
name: 'diagram.png',
553-
url: '/api/files/serve/execution%2Fdiagram.png',
554-
}),
555-
],
556-
}),
557-
})
558-
)
417+
function resetWorkflowExecutionTestState() {
418+
vi.clearAllMocks()
419+
mockBeginScopedExecution.mockReset().mockReturnValue({})
420+
mockAdoptScopedExecution.mockReset().mockReturnValue(undefined)
421+
mockEndScopedExecution.mockReset().mockReturnValue(true)
422+
mockLoadExecutionPointer.mockReset().mockResolvedValue(null)
423+
mockReconnect.mockReset().mockResolvedValue(undefined)
424+
mockResolveStartCandidates.mockReset().mockReturnValue([])
425+
mockSelectBestTrigger.mockReset().mockReturnValue([])
426+
mockExecute.mockReset().mockResolvedValue(undefined)
427+
mockExecuteFromBlock.mockReset().mockResolvedValue(undefined)
428+
terminalStoreState._hasHydrated = false
429+
executionStoreState.workflowExecutions.set('workflow-1', idleExecution)
430+
executionStoreState.getWorkflowExecution.mockReturnValue(idleExecution)
431+
executionStoreState.getCurrentExecutionId.mockReturnValue(null)
432+
workflowStoreState.edges.length = 0
433+
}
559434

560-
unmount()
561-
})
435+
describe('useWorkflowExecution lifecycle ownership', () => {
436+
beforeEach(resetWorkflowExecutionTestState)
562437

563438
it('does not let an overlapping run without lifecycle ownership end the active run', async () => {
564439
const persistenceExecution = {}
@@ -776,6 +651,140 @@ describe('useWorkflowExecution attachment uploads', () => {
776651

777652
unmount()
778653
})
654+
})
655+
656+
describe('useWorkflowExecution attachment uploads', () => {
657+
beforeEach(() => {
658+
resetWorkflowExecutionTestState()
659+
vi.stubGlobal('fetch', mockFetch)
660+
mockUploadInternalFileSession.mockRejectedValue(
661+
new Error('Workspace file storage limit exceeded')
662+
)
663+
mockFetch.mockResolvedValue(
664+
new Response(JSON.stringify({ error: 'Workspace file storage limit exceeded' }), {
665+
status: 413,
666+
headers: { 'Content-Type': 'application/json' },
667+
})
668+
)
669+
})
670+
671+
afterEach(() => {
672+
vi.unstubAllGlobals()
673+
})
674+
675+
it('does not execute and reports the exact server error when an explicit attachment fails', async () => {
676+
const { result, unmount } = renderWorkflowExecutionHook()
677+
const contextFile = new File(['context'], 'context.txt', { type: 'text/plain' })
678+
const file = new File(['report'], 'report.pdf', { type: 'application/pdf' })
679+
let uploadError: unknown
680+
681+
mockUploadInternalFileSession.mockResolvedValueOnce({
682+
id: 'attachment-context',
683+
key: 'executions/context.txt',
684+
url: '/uploads/context.txt',
685+
name: contextFile.name,
686+
size: contextFile.size,
687+
type: contextFile.type,
688+
context: 'execution',
689+
})
690+
691+
await act(async () => {
692+
try {
693+
await result().handleRunWorkflow({
694+
input: 'Summarize this report',
695+
conversationId: 'conversation-1',
696+
files: [
697+
{
698+
name: contextFile.name,
699+
size: contextFile.size,
700+
type: contextFile.type,
701+
file: contextFile,
702+
},
703+
{
704+
name: file.name,
705+
size: file.size,
706+
type: file.type,
707+
file,
708+
},
709+
],
710+
})
711+
} catch (error) {
712+
uploadError = error
713+
}
714+
})
715+
716+
expect(uploadError).toBeInstanceOf(WorkflowAttachmentUploadError)
717+
expect((uploadError as Error).message).toBe(
718+
'Failed to upload report.pdf: Workspace file storage limit exceeded'
719+
)
720+
expect(mockExecute).not.toHaveBeenCalled()
721+
722+
unmount()
723+
})
724+
725+
it('returns uploaded metadata without mutating or leaking local input into execution', async () => {
726+
const { result, unmount } = renderWorkflowExecutionHook()
727+
const file = new File(['diagram'], 'diagram.png', { type: 'image/png' })
728+
const workflowInput = {
729+
input: 'Describe this diagram',
730+
conversationId: 'conversation-1',
731+
files: [
732+
{
733+
name: file.name,
734+
size: file.size,
735+
type: file.type,
736+
file,
737+
},
738+
],
739+
}
740+
let runResult: unknown
741+
742+
mockUploadInternalFileSession.mockResolvedValueOnce({
743+
id: 'attachment-diagram',
744+
key: 'execution/diagram.png',
745+
url: '/api/files/serve/execution%2Fdiagram.png',
746+
name: file.name,
747+
size: file.size,
748+
type: file.type,
749+
context: 'execution',
750+
})
751+
752+
await act(async () => {
753+
runResult = await result().handleRunWorkflow(workflowInput)
754+
await drainStream(runResult)
755+
})
756+
757+
expect(isChatWorkflowRunResult(runResult)).toBe(true)
758+
if (!isChatWorkflowRunResult(runResult)) {
759+
throw new Error('Expected a chat workflow run result')
760+
}
761+
expect(runResult.uploadedAttachments).toEqual([
762+
expect.objectContaining({
763+
name: 'diagram.png',
764+
url: '/api/files/serve/execution%2Fdiagram.png',
765+
size: file.size,
766+
type: 'image/png',
767+
key: 'execution/diagram.png',
768+
}),
769+
])
770+
expect(workflowInput.files[0].file).toBe(file)
771+
expect(mockExecute).toHaveBeenCalledWith(
772+
expect.objectContaining({
773+
input: expect.objectContaining({
774+
input: 'Describe this diagram',
775+
conversationId: 'conversation-1',
776+
files: [
777+
expect.objectContaining({
778+
name: 'diagram.png',
779+
url: '/api/files/serve/execution%2Fdiagram.png',
780+
}),
781+
],
782+
}),
783+
})
784+
)
785+
786+
unmount()
787+
})
779788

780789
it('uses only projected live thinking without changing normal settle behavior', async () => {
781790
mockExecute.mockImplementationOnce(async (options) => {

0 commit comments

Comments
 (0)