Skip to content

Commit eaada5e

Browse files
committed
Bug fix random
1 parent dceda7b commit eaada5e

File tree

2 files changed

+318
-13
lines changed

2 files changed

+318
-13
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
5+
import { loggerMock } from '@sim/testing'
6+
import { beforeEach, describe, expect, it, vi } from 'vitest'
7+
8+
vi.mock('@sim/logger', () => loggerMock)
9+
10+
const { runStreamLoop, BillingLimitError, CopilotBackendError } = vi.hoisted(() => {
11+
class MockBillingLimitError extends Error {
12+
constructor(public readonly userId: string) {
13+
super('Usage limit reached')
14+
this.name = 'BillingLimitError'
15+
}
16+
}
17+
18+
class MockCopilotBackendError extends Error {
19+
status?: number
20+
body?: string
21+
22+
constructor(message: string, options?: { status?: number; body?: string }) {
23+
super(message)
24+
this.name = 'CopilotBackendError'
25+
this.status = options?.status
26+
this.body = options?.body
27+
}
28+
}
29+
30+
return {
31+
runStreamLoop: vi.fn(),
32+
BillingLimitError: MockBillingLimitError,
33+
CopilotBackendError: MockCopilotBackendError,
34+
}
35+
})
36+
37+
const { createRunSegment, updateRunStatus } = vi.hoisted(() => ({
38+
createRunSegment: vi.fn(),
39+
updateRunStatus: vi.fn(),
40+
}))
41+
42+
const { prepareExecutionContext, getEffectiveDecryptedEnv, handleBillingLimitResponse } = vi.hoisted(
43+
() => ({
44+
prepareExecutionContext: vi.fn(),
45+
getEffectiveDecryptedEnv: vi.fn(),
46+
handleBillingLimitResponse: vi.fn(),
47+
})
48+
)
49+
50+
const { executeToolAndReport } = vi.hoisted(() => ({
51+
executeToolAndReport: vi.fn(),
52+
}))
53+
54+
vi.mock('@/lib/copilot/request/go/stream', () => ({
55+
runStreamLoop,
56+
BillingLimitError,
57+
CopilotBackendError,
58+
}))
59+
60+
vi.mock('@/lib/copilot/async-runs/repository', () => ({
61+
createRunSegment,
62+
updateRunStatus,
63+
}))
64+
65+
vi.mock('@/lib/copilot/tools/handlers/context', () => ({
66+
prepareExecutionContext,
67+
}))
68+
69+
vi.mock('@/lib/environment/utils', () => ({
70+
getEffectiveDecryptedEnv,
71+
}))
72+
73+
vi.mock('@/lib/copilot/request/tools/billing', () => ({
74+
handleBillingLimitResponse,
75+
}))
76+
77+
vi.mock('@/lib/copilot/request/tools/executor', () => ({
78+
executeToolAndReport,
79+
}))
80+
81+
import { MothershipStreamV1ToolOutcome } from '@/lib/copilot/generated/mothership-stream-v1'
82+
import type { ExecutionContext } from '@/lib/copilot/request/types'
83+
import { runCopilotLifecycle } from './run'
84+
85+
describe('runCopilotLifecycle', () => {
86+
beforeEach(() => {
87+
vi.resetAllMocks()
88+
createRunSegment.mockResolvedValue(null)
89+
updateRunStatus.mockResolvedValue(null)
90+
})
91+
92+
it('recovers missing checkpointed tool state from settled error completions', async () => {
93+
const execContext: ExecutionContext = {
94+
userId: 'user-1',
95+
workflowId: 'workflow-1',
96+
abortSignal: undefined,
97+
}
98+
99+
runStreamLoop.mockImplementationOnce(async (_url, _init, context) => {
100+
context.toolCalls.set('tool-1', {
101+
id: 'tool-1',
102+
name: 'read',
103+
status: 'executing',
104+
startTime: Date.now(),
105+
})
106+
context.pendingToolPromises.set(
107+
'tool-1',
108+
Promise.resolve({
109+
status: MothershipStreamV1ToolOutcome.error,
110+
message: 'boom',
111+
data: { error: 'boom' },
112+
})
113+
)
114+
context.awaitingAsyncContinuation = {
115+
checkpointId: 'cp-1',
116+
executionId: 'exec-1',
117+
runId: 'run-1',
118+
pendingToolCallIds: ['tool-1'],
119+
}
120+
context.streamComplete = true
121+
})
122+
123+
runStreamLoop.mockImplementationOnce(async (url, init, context) => {
124+
expect(url).toContain('/api/tools/resume')
125+
const body = JSON.parse(String(init.body))
126+
expect(body).toEqual(
127+
expect.objectContaining({
128+
checkpointId: 'cp-1',
129+
results: [
130+
{
131+
callId: 'tool-1',
132+
name: 'read',
133+
data: { error: 'boom' },
134+
success: false,
135+
},
136+
],
137+
})
138+
)
139+
context.streamComplete = true
140+
})
141+
142+
const result = await runCopilotLifecycle(
143+
{
144+
message: 'hello',
145+
messageId: 'msg-1',
146+
},
147+
{
148+
userId: 'user-1',
149+
executionId: 'exec-1',
150+
runId: 'run-1',
151+
goRoute: '/api/copilot',
152+
executionContext: execContext,
153+
}
154+
)
155+
156+
expect(runStreamLoop).toHaveBeenCalledTimes(2)
157+
expect(result.toolCalls).toEqual(
158+
expect.arrayContaining([
159+
expect.objectContaining({
160+
id: 'tool-1',
161+
status: MothershipStreamV1ToolOutcome.error,
162+
error: 'boom',
163+
result: { error: 'boom' },
164+
}),
165+
])
166+
)
167+
})
168+
})

0 commit comments

Comments
 (0)