Skip to content

Commit e0f6e54

Browse files
authored
test: refactor tests to use request recording instead of sdk mocks (#1346)
1 parent 3e1bf05 commit e0f6e54

File tree

6 files changed

+240
-247
lines changed

6 files changed

+240
-247
lines changed

renderer/src/features/mcp-servers/components/__tests__/card-mcp-server.test.tsx

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { expect, it, beforeEach } from 'vitest'
33
import { renderRoute } from '@/common/test/render-route'
44
import { createTestRouter } from '@/common/test/create-test-router'
55
import userEvent from '@testing-library/user-event'
6-
import { server } from '@/common/mocks/node'
6+
import { server, recordRequests } from '@/common/mocks/node'
77
import { http, HttpResponse } from 'msw'
88
import {
99
createRootRoute,
@@ -16,8 +16,6 @@ import { CardMcpServer } from '../card-mcp-server/index'
1616
import { mswEndpoint } from '@/common/mocks/customHandlers'
1717
import { EditServerDialogProvider } from '../../contexts/edit-server-dialog-provider'
1818

19-
let capturedCreateWorkloadPayload: unknown = null
20-
2119
function createCardMcpServerTestRouter() {
2220
const rootRoute = createRootRoute({
2321
component: Outlet,
@@ -55,18 +53,6 @@ const router = createCardMcpServerTestRouter() as unknown as ReturnType<
5553

5654
beforeEach(() => {
5755
router.navigate({ to: '/group/$groupName', params: { groupName: 'default' } })
58-
59-
// Reset captured payload
60-
capturedCreateWorkloadPayload = null
61-
62-
// Override the existing MSW handler to capture the payload
63-
server.use(
64-
http.post(mswEndpoint('/api/v1beta/workloads'), async ({ request }) => {
65-
const payload = await request.json()
66-
capturedCreateWorkloadPayload = payload
67-
return HttpResponse.json({ name: 'test-server-copied' })
68-
})
69-
)
7056
})
7157

7258
it('navigates to logs page when logs menu item is clicked', async () => {
@@ -89,6 +75,7 @@ it('navigates to logs page when logs menu item is clicked', async () => {
8975
})
9076

9177
it('shows "Copy server to a group" menu item and handles the complete workflow', async () => {
78+
const rec = recordRequests()
9279
renderRoute(router)
9380

9481
await waitFor(() => {
@@ -131,33 +118,35 @@ it('shows "Copy server to a group" menu item and handles the complete workflow',
131118
})
132119

133120
await waitFor(() => {
134-
expect(capturedCreateWorkloadPayload).toBeTruthy()
121+
const createCall = rec.recordedRequests.find(
122+
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/workloads'
123+
)
124+
expect(createCall).toBeDefined()
125+
expect(createCall?.payload).toMatchInlineSnapshot(`
126+
{
127+
"cmd_arguments": [],
128+
"env_vars": {},
129+
"group": "default",
130+
"host": "127.0.0.1",
131+
"image": "ghcr.io/postgres/postgres-mcp-server:latest",
132+
"name": "postgres-db-default",
133+
"network_isolation": false,
134+
"secrets": [],
135+
"target_port": 28135,
136+
"transport": "stdio",
137+
"volumes": [],
138+
}
139+
`)
135140
})
136-
expect(capturedCreateWorkloadPayload).toMatchInlineSnapshot(`
137-
{
138-
"cmd_arguments": [],
139-
"env_vars": {},
140-
"group": "default",
141-
"host": "127.0.0.1",
142-
"image": "ghcr.io/postgres/postgres-mcp-server:latest",
143-
"name": "postgres-db-default",
144-
"network_isolation": false,
145-
"secrets": [],
146-
"target_port": 28135,
147-
"transport": "stdio",
148-
"volumes": [],
149-
}
150-
`)
151141
})
152142

153143
it('shows validation error and re-prompts when API returns 409 conflict', async () => {
144+
const rec = recordRequests()
154145
let attemptCount = 0
155-
const capturedNames: string[] = []
156146

157147
server.use(
158148
http.post(mswEndpoint('/api/v1beta/workloads'), async ({ request }) => {
159149
const payload = (await request.json()) as { name: string }
160-
capturedNames.push(payload.name)
161150
attemptCount++
162151

163152
// First two attempts return 409 conflict (plain text, like real API)
@@ -255,8 +244,12 @@ it('shows validation error and re-prompts when API returns 409 conflict', async
255244
await waitFor(() => {
256245
expect(screen.queryByText('Copy server to a group')).not.toBeInTheDocument()
257246
})
258-
expect(attemptCount).toBe(3)
259-
expect(capturedNames).toEqual([
247+
248+
const createCalls = rec.recordedRequests.filter(
249+
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/workloads'
250+
)
251+
expect(createCalls).toHaveLength(3)
252+
expect(createCalls.map((c) => (c.payload as { name: string }).name)).toEqual([
260253
'postgres-db-my-group',
261254
'postgres-db-attempt2',
262255
'postgres-db-final',

renderer/src/features/mcp-servers/components/remote-mcp/__tests__/dialog-form-remote-mcp.test.tsx

Lines changed: 70 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { it, expect, vi, describe, beforeEach } from 'vitest'
33
import { DialogFormRemoteMcp } from '../dialog-form-remote-mcp'
44
import userEvent from '@testing-library/user-event'
55
import { Dialog } from '@/common/components/ui/dialog'
6-
import { server as mswServer } from '@/common/mocks/node'
6+
import { server as mswServer, recordRequests } from '@/common/mocks/node'
77
import { http, HttpResponse } from 'msw'
88
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
99
import { mswEndpoint } from '@/common/mocks/customHandlers'
@@ -166,15 +166,14 @@ describe('DialogFormRemoteMcp', () => {
166166
const user = userEvent.setup({ delay: null })
167167
const mockCheckServerStatus = vi.fn()
168168
const mockOnOpenChange = vi.fn()
169-
let recordedRequest: Request | null = null
169+
const rec = recordRequests()
170170

171171
mockUseCheckServerStatus.mockReturnValue({
172172
checkServerStatus: mockCheckServerStatus,
173173
})
174174

175175
mswServer.use(
176-
http.post(mswEndpoint('/api/v1beta/workloads'), async ({ request }) => {
177-
recordedRequest = request.clone()
176+
http.post(mswEndpoint('/api/v1beta/workloads'), () => {
178177
return HttpResponse.json(
179178
{ name: 'test-remote-server', status: 'running' },
180179
{ status: 201 }
@@ -216,31 +215,31 @@ describe('DialogFormRemoteMcp', () => {
216215
await user.click(screen.getByRole('button', { name: 'Install server' }))
217216

218217
await waitFor(() => {
219-
expect(recordedRequest).not.toBeNull()
218+
const workloadCall = rec.recordedRequests.find(
219+
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/workloads'
220+
)
221+
expect(workloadCall).toBeDefined()
222+
expect(workloadCall?.payload).toEqual(
223+
expect.objectContaining({
224+
url: 'https://api.example.com/mcp',
225+
name: 'test-remote-server',
226+
oauth_config: expect.objectContaining({
227+
authorize_url: '',
228+
callback_port: 8888,
229+
client_id: '',
230+
issuer: '',
231+
oauth_params: {},
232+
scopes: [],
233+
skip_browser: false,
234+
token_url: '',
235+
use_pkce: true,
236+
}),
237+
transport: 'streamable-http',
238+
group: 'default',
239+
})
240+
)
220241
})
221242

222-
expect(recordedRequest).not.toBeNull()
223-
const requestBody = await recordedRequest!.json()
224-
expect(requestBody).toEqual(
225-
expect.objectContaining({
226-
url: 'https://api.example.com/mcp',
227-
name: 'test-remote-server',
228-
oauth_config: expect.objectContaining({
229-
authorize_url: '',
230-
callback_port: 8888,
231-
client_id: '',
232-
issuer: '',
233-
oauth_params: {},
234-
scopes: [],
235-
skip_browser: false,
236-
token_url: '',
237-
use_pkce: true,
238-
}),
239-
transport: 'streamable-http',
240-
group: 'default',
241-
})
242-
)
243-
244243
await waitFor(() => {
245244
expect(mockCheckServerStatus).toHaveBeenCalled()
246245
expect(mockOnOpenChange).toHaveBeenCalled()
@@ -254,11 +253,10 @@ describe('DialogFormRemoteMcp', () => {
254253
'submits Issuer URL when auth type is %s',
255254
async (authOptionLabel, expectedAuthType) => {
256255
const user = userEvent.setup({ delay: null })
257-
let recordedRequest: Request | null = null
256+
const rec = recordRequests()
258257

259258
mswServer.use(
260-
http.post(mswEndpoint('/api/v1beta/workloads'), async ({ request }) => {
261-
recordedRequest = request.clone()
259+
http.post(mswEndpoint('/api/v1beta/workloads'), () => {
262260
return HttpResponse.json(
263261
{ name: 'issuer-enabled-server', status: 'running' },
264262
{ status: 201 }
@@ -325,32 +323,31 @@ describe('DialogFormRemoteMcp', () => {
325323
await user.click(screen.getByRole('button', { name: 'Install server' }))
326324

327325
await waitFor(() => {
328-
expect(recordedRequest).not.toBeNull()
329-
})
330-
331-
expect(recordedRequest).not.toBeNull()
332-
const requestBody = await recordedRequest!.json()
333-
expect(requestBody).toEqual(
334-
expect.objectContaining({
335-
name: 'issuer-enabled-server',
336-
url: 'https://api.example.com/mcp',
337-
transport: 'streamable-http',
338-
group: 'default',
339-
oauth_config: expect.objectContaining({
340-
issuer: issuerValue,
341-
callback_port: 7777,
342-
scopes: [],
343-
}),
344-
})
345-
)
346-
347-
// Verify client_id is set for OIDC
348-
if (expectedAuthType === 'oidc') {
349-
expect(requestBody.oauth_config).toHaveProperty(
350-
'client_id',
351-
'oidc-client-id'
326+
const workloadCall = rec.recordedRequests.find(
327+
(r) => r.method === 'POST' && r.pathname === '/api/v1beta/workloads'
352328
)
353-
}
329+
expect(workloadCall).toBeDefined()
330+
expect(workloadCall?.payload).toEqual(
331+
expect.objectContaining({
332+
name: 'issuer-enabled-server',
333+
url: 'https://api.example.com/mcp',
334+
transport: 'streamable-http',
335+
group: 'default',
336+
oauth_config: expect.objectContaining({
337+
issuer: issuerValue,
338+
callback_port: 7777,
339+
scopes: [],
340+
}),
341+
})
342+
)
343+
344+
if (expectedAuthType === 'oidc') {
345+
expect(
346+
(workloadCall?.payload as { oauth_config: { client_id: string } })
347+
.oauth_config
348+
).toHaveProperty('client_id', 'oidc-client-id')
349+
}
350+
})
354351
}
355352
)
356353

@@ -383,7 +380,7 @@ describe('DialogFormRemoteMcp', () => {
383380
const user = userEvent.setup({ delay: null })
384381
const mockCheckServerStatus = vi.fn()
385382
const mockOnOpenChange = vi.fn()
386-
let recordedRequest: Request | null = null
383+
const rec = recordRequests()
387384

388385
mockUseCheckServerStatus.mockReturnValue({
389386
checkServerStatus: mockCheckServerStatus,
@@ -409,16 +406,12 @@ describe('DialogFormRemoteMcp', () => {
409406
group: 'default',
410407
})
411408
}),
412-
http.patch(
413-
mswEndpoint('/api/v1beta/workloads/:name'),
414-
async ({ request }) => {
415-
recordedRequest = request.clone()
416-
return HttpResponse.json({
417-
name: 'existing-server',
418-
status: 'running',
419-
})
420-
}
421-
)
409+
http.patch(mswEndpoint('/api/v1beta/workloads/:name'), () => {
410+
return HttpResponse.json({
411+
name: 'existing-server',
412+
status: 'running',
413+
})
414+
})
422415
)
423416

424417
renderWithProviders(
@@ -449,19 +442,19 @@ describe('DialogFormRemoteMcp', () => {
449442
await user.click(screen.getByRole('button', { name: /update server/i }))
450443

451444
await waitFor(() => {
452-
expect(recordedRequest).not.toBeNull()
445+
const patchCall = rec.recordedRequests.find(
446+
(r) => r.method === 'PATCH' && r.pathname.includes('/workloads/')
447+
)
448+
expect(patchCall).toBeDefined()
449+
expect(patchCall?.payload).toEqual(
450+
expect.objectContaining({
451+
name: 'existing-server',
452+
url: 'https://new-api.example.com',
453+
transport: 'streamable-http',
454+
})
455+
)
453456
})
454457

455-
expect(recordedRequest).not.toBeNull()
456-
const requestBody = await recordedRequest!.json()
457-
expect(requestBody).toEqual(
458-
expect.objectContaining({
459-
name: 'existing-server',
460-
url: 'https://new-api.example.com',
461-
transport: 'streamable-http',
462-
})
463-
)
464-
465458
await waitFor(() => {
466459
expect(mockCheckServerStatus).toHaveBeenCalled()
467460
expect(mockOnOpenChange).toHaveBeenCalled()

renderer/src/features/mcp-servers/hooks/__tests__/use-mutation-restart-server.test.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
useMutationRestartServerAtStartup,
77
useMutationRestartServer,
88
} from '../use-mutation-restart-server'
9-
import { server } from '@/common/mocks/node'
9+
import { server, recordRequests } from '@/common/mocks/node'
1010
import { http, HttpResponse } from 'msw'
1111
import { toast } from 'sonner'
1212
import { mswEndpoint } from '@/common/mocks/customHandlers'
@@ -61,9 +61,9 @@ beforeEach(() => {
6161

6262
describe('useMutationRestartServerAtStartup', () => {
6363
it('successfully restarts servers from shutdown list', async () => {
64+
const rec = recordRequests()
6465
const { Wrapper, queryClient } = createQueryClientWrapper()
6566

66-
// Use MSW to simulate servers becoming 'running' after restart for polling
6767
server.use(
6868
http.get(
6969
mswEndpoint('/api/v1beta/workloads/:name/status'),
@@ -74,7 +74,6 @@ describe('useMutationRestartServerAtStartup', () => {
7474
status: 'running',
7575
})
7676
}
77-
// Fall back to default behavior for other servers
7877
return HttpResponse.json({
7978
status: 'stopped',
8079
})
@@ -96,7 +95,12 @@ describe('useMutationRestartServerAtStartup', () => {
9695
expect(result.current.isSuccess).toBe(true)
9796
})
9897

99-
// After successful restart, shutdown history should be cleared
98+
const restartCall = rec.recordedRequests.find(
99+
(r) =>
100+
r.method === 'POST' && r.pathname === '/api/v1beta/workloads/restart'
101+
)
102+
expect(restartCall?.payload).toEqual({ names: ['postgres-db', 'github'] })
103+
100104
expect(
101105
window.electronAPI.shutdownStore.clearShutdownHistory
102106
).toHaveBeenCalled()
@@ -182,6 +186,7 @@ describe('useMutationRestartServerAtStartup', () => {
182186

183187
describe('useMutationRestartServer', () => {
184188
it('successfully restarts a single server', async () => {
189+
const rec = recordRequests()
185190
const { Wrapper } = createQueryClientWrapper()
186191
const serverName = 'vscode-server'
187192

@@ -195,6 +200,13 @@ describe('useMutationRestartServer', () => {
195200
await waitFor(() => {
196201
expect(result.current.isSuccess).toBe(true)
197202
})
203+
204+
const restartCall = rec.recordedRequests.find(
205+
(r) =>
206+
r.method === 'POST' &&
207+
r.pathname === `/api/v1beta/workloads/${serverName}/restart`
208+
)
209+
expect(restartCall).toBeDefined()
198210
})
199211

200212
it('handles API error for single server restart', async () => {

0 commit comments

Comments
 (0)