Skip to content

Commit cdf0571

Browse files
authored
test: centralize HTTP mock handlers in web tests (#3087)
* test: centralize HTTP mock handlers in web tests * fix conflicts
1 parent 3223552 commit cdf0571

16 files changed

+1685
-1591
lines changed

web/src/components/common/tests/ConnectionMonitor.test.tsx

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
import { render, screen, waitFor } from '@testing-library/react';
22
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3-
import { http, HttpResponse } from 'msw';
43
import { setupServer } from 'msw/node';
4+
import { mockHandlers, createHandler } from '../../../test/mockHandlers';
55
import ConnectionMonitor from '../ConnectionMonitor';
66

77
const server = setupServer(
8-
http.get('*/api/health', () => {
9-
return new HttpResponse(JSON.stringify({ status: 'ok' }), {
10-
status: 200,
11-
headers: { 'Content-Type': 'application/json' },
12-
});
13-
})
8+
mockHandlers.health.check(true)
149
);
1510

1611
describe('ConnectionMonitor', () => {
@@ -33,9 +28,7 @@ describe('ConnectionMonitor', () => {
3328

3429
it('should show modal when health check fails', async () => {
3530
server.use(
36-
http.get('*/api/health', () => {
37-
return HttpResponse.error();
38-
})
31+
mockHandlers.health.check(false)
3932
);
4033

4134
render(<ConnectionMonitor />);
@@ -46,22 +39,10 @@ describe('ConnectionMonitor', () => {
4639
}, 6000);
4740

4841
it('should handle automatic retry', async () => {
49-
let retryCount = 0;
50-
42+
const callCounter = { callCount: 0 };
43+
5144
server.use(
52-
http.get('*/api/health', () => {
53-
retryCount++;
54-
55-
// Fail first time, succeed on second automatic retry
56-
if (retryCount === 1) {
57-
return HttpResponse.error();
58-
}
59-
60-
return new HttpResponse(JSON.stringify({ status: 'ok' }), {
61-
status: 200,
62-
headers: { 'Content-Type': 'application/json' },
63-
});
64-
})
45+
createHandler.healthRetrySuccess(callCounter)
6546
);
6647

6748
render(<ConnectionMonitor />);
@@ -85,9 +66,7 @@ describe('ConnectionMonitor', () => {
8566

8667
it('should show retry countdown timer', async () => {
8768
server.use(
88-
http.get('*/api/health', () => {
89-
return HttpResponse.error();
90-
})
69+
mockHandlers.health.check(false)
9170
);
9271

9372
render(<ConnectionMonitor />);

web/src/components/wizard/installation/tests/AppInstallationPhase.test.tsx

Lines changed: 50 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,15 @@
11
import { describe, it, expect, vi, beforeAll, beforeEach, afterEach, afterAll } from 'vitest';
22
import { screen, waitFor } from '@testing-library/react';
3-
import { http, HttpResponse } from 'msw';
43
import { setupServer } from 'msw/node';
54
import { renderWithProviders } from '../../../../test/setup.tsx';
65
import AppInstallationPhase from '../phases/AppInstallationPhase.tsx';
76
import { withNextButtonOnly } from './TestWrapper.tsx';
7+
import { mockHandlers, createHandler, type Target, type Mode } from '../../../../test/mockHandlers.ts';
88

99
const TestAppInstallationPhase = withNextButtonOnly(AppInstallationPhase);
1010

11-
const createServer = (target: 'linux' | 'kubernetes', mode: 'install' | 'upgrade' = 'install') => setupServer(
12-
// Mock app installation/upgrade status endpoint
13-
http.get(`*/api/${target}/${mode}/app/status`, () => {
14-
return HttpResponse.json({
15-
status: {
16-
state: 'Running',
17-
description: mode === 'upgrade' ? 'Upgrading application components...' : 'Installing application components...'
18-
}
19-
});
20-
})
11+
const createServer = (target: Target, mode: Mode = 'install') => setupServer(
12+
mockHandlers.app.getStatus('Running', target, mode)
2113
);
2214

2315
describe.each([
@@ -70,14 +62,7 @@ describe.each([
7062

7163
it('shows loading state during installation', async () => {
7264
server.use(
73-
http.get(`*/api/${target}/${mode}/app/status`, () => {
74-
return HttpResponse.json({
75-
status: {
76-
state: 'Running',
77-
description: 'Installing application components...'
78-
}
79-
});
80-
})
65+
mockHandlers.app.getStatus('Running', target, mode)
8166
);
8267

8368
renderWithProviders(
@@ -110,14 +95,7 @@ describe.each([
11095

11196
it('shows success state when installation completes successfully', async () => {
11297
server.use(
113-
http.get(`*/api/${target}/${mode}/app/status`, () => {
114-
return HttpResponse.json({
115-
status: {
116-
state: 'Succeeded',
117-
description: 'Application installed successfully'
118-
}
119-
});
120-
})
98+
mockHandlers.app.getStatus('Succeeded', target, mode)
12199
);
122100

123101
renderWithProviders(
@@ -146,7 +124,7 @@ describe.each([
146124
expect(nextButton).not.toBeDisabled();
147125
});
148126

149-
// Should call onStateChange with "Succeeded"
127+
// Should call onStateChange with "Succeeded"
150128
await waitFor(() => {
151129
const calls = mockOnStateChange.mock.calls.map(args => args[0]);
152130
expect(calls).toEqual(['Running', 'Succeeded']);
@@ -156,14 +134,7 @@ describe.each([
156134

157135
it('shows error state when installation fails', async () => {
158136
server.use(
159-
http.get(`*/api/${target}/${mode}/app/status`, () => {
160-
return HttpResponse.json({
161-
status: {
162-
state: 'Failed',
163-
description: 'Installation failed due to insufficient resources'
164-
}
165-
});
166-
})
137+
mockHandlers.app.getStatus('Failed', target, mode)
167138
);
168139

169140
renderWithProviders(
@@ -203,15 +174,11 @@ describe.each([
203174

204175
it('handles API error gracefully', async () => {
205176
server.use(
206-
http.get(`*/api/${target}/${mode}/app/status`, () => {
207-
return HttpResponse.json(
208-
{
209-
statusCode: 500,
210-
message: 'Internal server error'
211-
},
212-
{ status: 500 }
213-
);
214-
})
177+
mockHandlers.app.getStatus(
178+
{ error: { statusCode: 500, message: 'Internal server error' } },
179+
target,
180+
mode
181+
)
215182
);
216183

217184
renderWithProviders(
@@ -243,9 +210,7 @@ describe.each([
243210

244211
it('handles network error gracefully', async () => {
245212
server.use(
246-
http.get(`*/api/${target}/${mode}/app/status`, () => {
247-
return HttpResponse.error();
248-
})
213+
mockHandlers.app.getStatus({ networkError: true }, target, mode)
249214
);
250215

251216
renderWithProviders(
@@ -277,9 +242,7 @@ describe.each([
277242

278243
it('shows default loading state when no status is available', async () => {
279244
server.use(
280-
http.get(`*/api/${target}/${mode}/app/status`, () => {
281-
return HttpResponse.json({});
282-
})
245+
mockHandlers.app.getStatus({ empty: true }, target, mode)
283246
);
284247

285248
renderWithProviders(
@@ -310,17 +273,13 @@ describe.each([
310273
});
311274

312275
it('stops polling when installation succeeds', async () => {
313-
let callCount = 0;
276+
const callCounter = { callCount: 0 };
314277
server.use(
315-
http.get(`*/api/${target}/${mode}/app/status`, () => {
316-
callCount++;
317-
return HttpResponse.json({
318-
status: {
319-
state: 'Succeeded',
320-
description: 'Application installed successfully'
321-
}
322-
});
323-
})
278+
createHandler.withCallCounter(
279+
`*/api/${target}/${mode}/app/status`,
280+
{ status: { state: 'Succeeded' } },
281+
callCounter
282+
)
324283
);
325284

326285
renderWithProviders(
@@ -343,27 +302,23 @@ describe.each([
343302
expect(screen.getByTestId('app-installation-success')).toBeInTheDocument();
344303
});
345304

346-
const initialCallCount = callCount;
347-
305+
const initialCallCount = callCounter.callCount;
306+
348307
// Wait a bit more and ensure no additional calls are made
349308
await new Promise(resolve => setTimeout(resolve, 3000));
350-
309+
351310
// Should not make additional API calls after success
352-
expect(callCount).toBeLessThanOrEqual(initialCallCount + 1); // Allow for one potential additional call due to timing
311+
expect(callCounter.callCount).toBeLessThanOrEqual(initialCallCount + 1); // Allow for one potential additional call due to timing
353312
});
354313

355314
it('stops polling when installation fails', async () => {
356-
let callCount = 0;
315+
const callCounter = { callCount: 0 };
357316
server.use(
358-
http.get(`*/api/${target}/${mode}/app/status`, () => {
359-
callCount++;
360-
return HttpResponse.json({
361-
status: {
362-
state: 'Failed',
363-
description: 'Installation failed'
364-
}
365-
});
366-
})
317+
createHandler.withCallCounter(
318+
`*/api/${target}/${mode}/app/status`,
319+
{ status: { state: 'Failed' } },
320+
callCounter
321+
)
367322
);
368323

369324
renderWithProviders(
@@ -386,27 +341,24 @@ describe.each([
386341
expect(screen.getByTestId('app-installation-error')).toBeInTheDocument();
387342
});
388343

389-
const initialCallCount = callCount;
390-
344+
const initialCallCount = callCounter.callCount;
345+
391346
// Wait a bit more and ensure no additional calls are made
392347
await new Promise(resolve => setTimeout(resolve, 3000));
393-
348+
394349
// Should not make additional API calls after failure
395-
expect(callCount).toBeLessThanOrEqual(initialCallCount + 1); // Allow for one potential additional call due to timing
350+
expect(callCounter.callCount).toBeLessThanOrEqual(initialCallCount + 1); // Allow for one potential additional call due to timing
396351
});
397352

398353
it('displays custom status description when available', async () => {
399354
const customDescription = 'Configuring application settings and finalizing setup...';
400-
355+
401356
server.use(
402-
http.get(`*/api/${target}/${mode}/app/status`, () => {
403-
return HttpResponse.json({
404-
status: {
405-
state: 'Running',
406-
description: customDescription
407-
}
408-
});
409-
})
357+
mockHandlers.app.getStatus(
358+
{ state: 'Running', description: customDescription },
359+
target,
360+
mode
361+
)
410362
);
411363

412364
renderWithProviders(
@@ -432,13 +384,7 @@ describe.each([
432384

433385
it('displays fallback message when no status description is available', async () => {
434386
server.use(
435-
http.get(`*/api/${target}/${mode}/app/status`, () => {
436-
return HttpResponse.json({
437-
status: {
438-
state: 'Running'
439-
}
440-
});
441-
})
387+
mockHandlers.app.getStatus({ state: 'Running' }, target, mode)
442388
);
443389

444390
renderWithProviders(
@@ -494,21 +440,13 @@ describe.each([
494440
it('handles API error responses gracefully when starting installation', async () => {
495441
// Mock app status endpoint to return Pending state to trigger mutation
496442
server.use(
497-
http.get(`*/api/${target}/${mode}/app/status`, () => {
498-
return HttpResponse.json({
499-
status: { state: 'Pending', description: 'Waiting to start...' }
500-
});
501-
}),
443+
mockHandlers.app.getStatus('Pending', target, mode),
502444
// Mock app install/upgrade endpoint to return API error
503-
http.post(`*/api/${target}/${mode}/app/${mode}`, () => {
504-
return HttpResponse.json(
505-
{
506-
statusCode: 400,
507-
message: 'Application preflight checks failed. Cannot proceed with installation.'
508-
},
509-
{ status: 400 }
510-
);
511-
})
445+
mockHandlers.app.start(
446+
{ error: { statusCode: 400, message: 'Application preflight checks failed. Cannot proceed with installation.' } },
447+
target,
448+
mode
449+
)
512450
);
513451

514452
renderWithProviders(
@@ -535,15 +473,9 @@ describe.each([
535473
it('handles network failure during installation start', async () => {
536474
// Mock app status endpoint to return Pending state to trigger mutation
537475
server.use(
538-
http.get(`*/api/${target}/${mode}/app/status`, () => {
539-
return HttpResponse.json({
540-
status: { state: 'Pending', description: 'Waiting to start...' }
541-
});
542-
}),
476+
mockHandlers.app.getStatus('Pending', target, mode),
543477
// Mock app install/upgrade endpoint to return network error
544-
http.post(`*/api/${target}/${mode}/app/${mode}`, () => {
545-
return HttpResponse.error();
546-
})
478+
mockHandlers.app.start({ networkError: true }, target, mode)
547479
);
548480

549481
renderWithProviders(
@@ -567,4 +499,4 @@ describe.each([
567499
expect(mockOnNext).not.toHaveBeenCalled();
568500
});
569501
});
570-
});
502+
});

0 commit comments

Comments
 (0)