Skip to content

Commit 18ea77a

Browse files
committed
feat(web): add mock-db and dependencies tests
1 parent 4794d5a commit 18ea77a

File tree

2 files changed

+500
-0
lines changed

2 files changed

+500
-0
lines changed
Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
import { beforeEach, describe, expect, mock, test } from 'bun:test'
2+
3+
import { getDependencies } from '../_get'
4+
5+
import {
6+
createMockDbSelect,
7+
createMockLogger,
8+
mockDbSchema,
9+
} from '@/testing/mock-db'
10+
11+
// Mock the db module
12+
const mockDbSelect = mock(() => ({}))
13+
14+
mock.module('@codebuff/internal/db', () => ({
15+
default: {
16+
select: mockDbSelect,
17+
},
18+
}))
19+
20+
mock.module('@codebuff/internal/db/schema', () => mockDbSchema)
21+
22+
describe('/api/agents/[publisherId]/[agentId]/[version]/dependencies GET endpoint', () => {
23+
let mockLogger: ReturnType<typeof createMockLogger>
24+
25+
const createMockParams = (overrides: Partial<{ publisherId: string; agentId: string; version: string }> = {}) => {
26+
return Promise.resolve({
27+
publisherId: 'test-publisher',
28+
agentId: 'test-agent',
29+
version: '1.0.0',
30+
...overrides,
31+
})
32+
}
33+
34+
beforeEach(() => {
35+
mockLogger = createMockLogger()
36+
37+
// Reset to default empty mock
38+
mockDbSelect.mockImplementation(createMockDbSelect({ publishers: [], rootAgent: null }))
39+
})
40+
41+
describe('Parameter validation', () => {
42+
test('returns 400 when publisherId is missing', async () => {
43+
const response = await getDependencies({
44+
params: createMockParams({ publisherId: '' }),
45+
logger: mockLogger,
46+
})
47+
48+
expect(response.status).toBe(400)
49+
const body = await response.json()
50+
expect(body).toEqual({ error: 'Missing required parameters' })
51+
})
52+
53+
test('returns 400 when agentId is missing', async () => {
54+
const response = await getDependencies({
55+
params: createMockParams({ agentId: '' }),
56+
logger: mockLogger,
57+
})
58+
59+
expect(response.status).toBe(400)
60+
const body = await response.json()
61+
expect(body).toEqual({ error: 'Missing required parameters' })
62+
})
63+
64+
test('returns 400 when version is missing', async () => {
65+
const response = await getDependencies({
66+
params: createMockParams({ version: '' }),
67+
logger: mockLogger,
68+
})
69+
70+
expect(response.status).toBe(400)
71+
const body = await response.json()
72+
expect(body).toEqual({ error: 'Missing required parameters' })
73+
})
74+
})
75+
76+
describe('Publisher not found', () => {
77+
test('returns 404 when publisher does not exist', async () => {
78+
mockDbSelect.mockImplementation(createMockDbSelect({
79+
publishers: [], // No publishers
80+
rootAgent: null,
81+
}))
82+
83+
const response = await getDependencies({
84+
params: createMockParams(),
85+
logger: mockLogger,
86+
})
87+
88+
expect(response.status).toBe(404)
89+
const body = await response.json()
90+
expect(body).toEqual({ error: 'Publisher not found' })
91+
})
92+
})
93+
94+
describe('Agent not found', () => {
95+
test('returns 404 when agent does not exist', async () => {
96+
mockDbSelect.mockImplementation(createMockDbSelect({
97+
publishers: [{ id: 'test-publisher' }],
98+
rootAgent: null, // No agent
99+
}))
100+
101+
const response = await getDependencies({
102+
params: createMockParams(),
103+
logger: mockLogger,
104+
})
105+
106+
expect(response.status).toBe(404)
107+
const body = await response.json()
108+
expect(body).toEqual({ error: 'Agent not found' })
109+
})
110+
})
111+
112+
describe('Agent with no subagents', () => {
113+
test('returns tree with single node when agent has no spawnableAgents', async () => {
114+
mockDbSelect.mockImplementation(createMockDbSelect({
115+
publishers: [{ id: 'test-publisher' }],
116+
rootAgent: {
117+
id: 'test-agent',
118+
version: '1.0.0',
119+
publisher_id: 'test-publisher',
120+
data: { displayName: 'Test Agent', spawnableAgents: [] },
121+
},
122+
}))
123+
124+
const response = await getDependencies({
125+
params: createMockParams(),
126+
logger: mockLogger,
127+
})
128+
129+
expect(response.status).toBe(200)
130+
const body = await response.json()
131+
expect(body.root.fullId).toBe('test-publisher/test-agent@1.0.0')
132+
expect(body.root.displayName).toBe('Test Agent')
133+
expect(body.root.children).toEqual([])
134+
expect(body.totalAgents).toBe(1)
135+
expect(body.maxDepth).toBe(0)
136+
expect(body.hasCycles).toBe(false)
137+
})
138+
139+
test('returns tree with single node when spawnableAgents is not an array', async () => {
140+
mockDbSelect.mockImplementation(createMockDbSelect({
141+
publishers: [{ id: 'test-publisher' }],
142+
rootAgent: {
143+
id: 'test-agent',
144+
version: '1.0.0',
145+
publisher_id: 'test-publisher',
146+
data: { displayName: 'Test Agent', spawnableAgents: 'not-an-array' },
147+
},
148+
}))
149+
150+
const response = await getDependencies({
151+
params: createMockParams(),
152+
logger: mockLogger,
153+
})
154+
155+
expect(response.status).toBe(200)
156+
const body = await response.json()
157+
expect(body.root.children).toEqual([])
158+
expect(body.totalAgents).toBe(1)
159+
})
160+
})
161+
162+
describe('Agent data parsing', () => {
163+
test('handles agent data as JSON string', async () => {
164+
mockDbSelect.mockImplementation(createMockDbSelect({
165+
publishers: [{ id: 'test-publisher' }],
166+
rootAgent: {
167+
id: 'test-agent',
168+
version: '1.0.0',
169+
publisher_id: 'test-publisher',
170+
data: JSON.stringify({ displayName: 'Parsed Agent', spawnableAgents: [] }),
171+
},
172+
}))
173+
174+
const response = await getDependencies({
175+
params: createMockParams(),
176+
logger: mockLogger,
177+
})
178+
179+
expect(response.status).toBe(200)
180+
const body = await response.json()
181+
expect(body.root.displayName).toBe('Parsed Agent')
182+
})
183+
184+
test('uses agentId as displayName when displayName is not provided', async () => {
185+
mockDbSelect.mockImplementation(createMockDbSelect({
186+
publishers: [{ id: 'test-publisher' }],
187+
rootAgent: {
188+
id: 'test-agent',
189+
version: '1.0.0',
190+
publisher_id: 'test-publisher',
191+
data: { spawnableAgents: [] },
192+
},
193+
}))
194+
195+
const response = await getDependencies({
196+
params: createMockParams(),
197+
logger: mockLogger,
198+
})
199+
200+
expect(response.status).toBe(200)
201+
const body = await response.json()
202+
expect(body.root.displayName).toBe('test-agent')
203+
})
204+
205+
test('uses name as displayName when displayName is not provided but name is', async () => {
206+
mockDbSelect.mockImplementation(createMockDbSelect({
207+
publishers: [{ id: 'test-publisher' }],
208+
rootAgent: {
209+
id: 'test-agent',
210+
version: '1.0.0',
211+
publisher_id: 'test-publisher',
212+
data: { name: 'Agent Name', spawnableAgents: [] },
213+
},
214+
}))
215+
216+
const response = await getDependencies({
217+
params: createMockParams(),
218+
logger: mockLogger,
219+
})
220+
221+
expect(response.status).toBe(200)
222+
const body = await response.json()
223+
expect(body.root.displayName).toBe('Agent Name')
224+
})
225+
})
226+
227+
describe('Internal server error', () => {
228+
test('returns 500 when database throws an error', async () => {
229+
mockDbSelect.mockImplementation(() => {
230+
throw new Error('Database connection failed')
231+
})
232+
233+
const response = await getDependencies({
234+
params: createMockParams(),
235+
logger: mockLogger,
236+
})
237+
238+
expect(response.status).toBe(500)
239+
const body = await response.json()
240+
expect(body).toEqual({ error: 'Internal server error' })
241+
expect(mockLogger.error).toHaveBeenCalled()
242+
})
243+
244+
test('returns 500 when params promise rejects', async () => {
245+
const response = await getDependencies({
246+
params: Promise.reject(new Error('Params error')),
247+
logger: mockLogger,
248+
})
249+
250+
expect(response.status).toBe(500)
251+
const body = await response.json()
252+
expect(body).toEqual({ error: 'Internal server error' })
253+
expect(mockLogger.error).toHaveBeenCalled()
254+
})
255+
})
256+
257+
describe('Agent with subagents', () => {
258+
test('returns tree with children when agent has spawnableAgents', async () => {
259+
mockDbSelect.mockImplementation(createMockDbSelect({
260+
publishers: [{ id: 'test-publisher' }],
261+
rootAgent: {
262+
id: 'test-agent',
263+
version: '1.0.0',
264+
publisher_id: 'test-publisher',
265+
data: {
266+
displayName: 'Root Agent',
267+
spawnableAgents: ['test-publisher/child-agent@1.0.0'],
268+
},
269+
},
270+
childAgents: [{
271+
id: 'child-agent',
272+
version: '1.0.0',
273+
publisher_id: 'test-publisher',
274+
data: { displayName: 'Child Agent', spawnableAgents: [] },
275+
}],
276+
}))
277+
278+
const response = await getDependencies({
279+
params: createMockParams(),
280+
logger: mockLogger,
281+
})
282+
283+
expect(response.status).toBe(200)
284+
const body = await response.json()
285+
expect(body.root.displayName).toBe('Root Agent')
286+
expect(body.root.children).toHaveLength(1)
287+
expect(body.root.children[0].displayName).toBe('Child Agent')
288+
expect(body.totalAgents).toBe(2)
289+
expect(body.maxDepth).toBe(1)
290+
})
291+
292+
test('handles unavailable child agents gracefully', async () => {
293+
mockDbSelect.mockImplementation(createMockDbSelect({
294+
publishers: [{ id: 'test-publisher' }],
295+
rootAgent: {
296+
id: 'test-agent',
297+
version: '1.0.0',
298+
publisher_id: 'test-publisher',
299+
data: {
300+
displayName: 'Root Agent',
301+
spawnableAgents: ['test-publisher/missing-agent@1.0.0'],
302+
},
303+
},
304+
childAgents: [], // No child agents found
305+
}))
306+
307+
const response = await getDependencies({
308+
params: createMockParams(),
309+
logger: mockLogger,
310+
})
311+
312+
expect(response.status).toBe(200)
313+
const body = await response.json()
314+
expect(body.root.children).toHaveLength(1)
315+
expect(body.root.children[0].isAvailable).toBe(false)
316+
expect(body.root.children[0].displayName).toBe('missing-agent')
317+
})
318+
})
319+
320+
describe('spawnerPrompt handling', () => {
321+
test('includes spawnerPrompt in response when present', async () => {
322+
mockDbSelect.mockImplementation(createMockDbSelect({
323+
publishers: [{ id: 'test-publisher' }],
324+
rootAgent: {
325+
id: 'test-agent',
326+
version: '1.0.0',
327+
publisher_id: 'test-publisher',
328+
data: {
329+
displayName: 'Test Agent',
330+
spawnerPrompt: 'Use this agent to help with testing',
331+
spawnableAgents: [],
332+
},
333+
},
334+
}))
335+
336+
const response = await getDependencies({
337+
params: createMockParams(),
338+
logger: mockLogger,
339+
})
340+
341+
expect(response.status).toBe(200)
342+
const body = await response.json()
343+
expect(body.root.spawnerPrompt).toBe('Use this agent to help with testing')
344+
})
345+
346+
test('sets spawnerPrompt to null when not present', async () => {
347+
mockDbSelect.mockImplementation(createMockDbSelect({
348+
publishers: [{ id: 'test-publisher' }],
349+
rootAgent: {
350+
id: 'test-agent',
351+
version: '1.0.0',
352+
publisher_id: 'test-publisher',
353+
data: { displayName: 'Test Agent', spawnableAgents: [] },
354+
},
355+
}))
356+
357+
const response = await getDependencies({
358+
params: createMockParams(),
359+
logger: mockLogger,
360+
})
361+
362+
expect(response.status).toBe(200)
363+
const body = await response.json()
364+
expect(body.root.spawnerPrompt).toBeNull()
365+
})
366+
})
367+
})

0 commit comments

Comments
 (0)