Skip to content

Commit 4afa1e7

Browse files
committed
Test cancellable SDK runs
1 parent 977ffc6 commit 4afa1e7

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import * as mainPromptModule from '@codebuff/agent-runtime/main-prompt'
2+
import { afterEach, describe, expect, it, mock, spyOn } from 'bun:test'
3+
4+
import { CodebuffClient } from '../client'
5+
import { createRunController } from '../run-controller'
6+
import * as databaseModule from '../impl/database'
7+
8+
function mockDatabase() {
9+
spyOn(databaseModule, 'getUserInfoFromApiKey').mockResolvedValue({
10+
id: 'user-123',
11+
email: 'test@example.com',
12+
discord_id: null,
13+
stripe_customer_id: null,
14+
banned: false,
15+
created_at: new Date('2024-01-01T00:00:00Z'),
16+
})
17+
spyOn(databaseModule, 'fetchAgentFromDatabase').mockResolvedValue(null)
18+
spyOn(databaseModule, 'startAgentRun').mockResolvedValue('run-1')
19+
spyOn(databaseModule, 'finishAgentRun').mockResolvedValue(undefined)
20+
spyOn(databaseModule, 'addAgentStep').mockResolvedValue('step-1')
21+
}
22+
23+
describe('CodebuffClient runCancellable', () => {
24+
afterEach(() => {
25+
mock.restore()
26+
})
27+
28+
it('aborts the active run signal when cancel is called', async () => {
29+
mockDatabase()
30+
31+
let runtimeSignal: AbortSignal | undefined
32+
let markRuntimeStarted: () => void = () => {}
33+
const runtimeStarted = new Promise<void>((resolve) => {
34+
markRuntimeStarted = resolve
35+
})
36+
spyOn(mainPromptModule, 'callMainPrompt').mockImplementation(
37+
async (params: Parameters<typeof mainPromptModule.callMainPrompt>[0]) => {
38+
runtimeSignal = params.signal
39+
markRuntimeStarted()
40+
return await new Promise<never>((_, reject) => {
41+
params.signal.addEventListener(
42+
'abort',
43+
() => reject(params.signal.reason),
44+
{ once: true },
45+
)
46+
})
47+
},
48+
)
49+
50+
const client = new CodebuffClient({ apiKey: 'test-key' })
51+
const activeRun = client.runCancellable({
52+
agent: 'base2',
53+
prompt: 'set up preview',
54+
})
55+
56+
await runtimeStarted
57+
activeRun.cancel('Stopped from UI')
58+
const result = await activeRun.result
59+
60+
expect(activeRun.controller.cancelled).toBe(true)
61+
expect(activeRun.signal.aborted).toBe(true)
62+
expect(runtimeSignal?.aborted).toBe(true)
63+
expect(result.output.type).toBe('error')
64+
if (result.output.type === 'error') {
65+
expect(result.output.message).toBe('Stopped from UI')
66+
}
67+
})
68+
69+
it('combines caller-provided signals with the run controller', async () => {
70+
mockDatabase()
71+
72+
let runtimeSignal: AbortSignal | undefined
73+
let markRuntimeStarted: () => void = () => {}
74+
const runtimeStarted = new Promise<void>((resolve) => {
75+
markRuntimeStarted = resolve
76+
})
77+
spyOn(mainPromptModule, 'callMainPrompt').mockImplementation(
78+
async (params: Parameters<typeof mainPromptModule.callMainPrompt>[0]) => {
79+
runtimeSignal = params.signal
80+
markRuntimeStarted()
81+
return await new Promise<never>((_, reject) => {
82+
params.signal.addEventListener(
83+
'abort',
84+
() => reject(params.signal.reason),
85+
{ once: true },
86+
)
87+
})
88+
},
89+
)
90+
91+
const externalController = new AbortController()
92+
const runController = createRunController('cloud-run-1')
93+
const client = new CodebuffClient({ apiKey: 'test-key' })
94+
const activeRun = client.runCancellable(
95+
{
96+
agent: 'base2',
97+
prompt: 'build mobile preview',
98+
signal: externalController.signal,
99+
},
100+
runController,
101+
)
102+
103+
await runtimeStarted
104+
externalController.abort(new Error('Request disconnected'))
105+
const result = await activeRun.result
106+
107+
expect(activeRun.id).toBe('cloud-run-1')
108+
expect(runController.cancelled).toBe(false)
109+
expect(activeRun.signal.aborted).toBe(true)
110+
expect(runtimeSignal?.aborted).toBe(true)
111+
expect(result.output.type).toBe('error')
112+
if (result.output.type === 'error') {
113+
expect(result.output.message).toBe('Request disconnected')
114+
}
115+
})
116+
})

0 commit comments

Comments
 (0)