mcp-rubber-duck 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.releaserc.json +4 -0
- package/CHANGELOG.md +7 -0
- package/package.json +1 -1
- package/tests/approval.test.ts +440 -0
- package/tests/cache.test.ts +240 -0
- package/tests/config.test.ts +468 -0
- package/tests/consensus.test.ts +10 -0
- package/tests/conversation.test.ts +86 -0
- package/tests/duck-debate.test.ts +105 -1
- package/tests/duck-iterate.test.ts +30 -0
- package/tests/duck-judge.test.ts +93 -0
- package/tests/duck-vote.test.ts +46 -0
- package/tests/health.test.ts +129 -0
- package/tests/providers.test.ts +591 -0
- package/tests/safe-logger.test.ts +314 -0
- package/tests/tools/approve-mcp-request.test.ts +239 -0
- package/tests/tools/ask-duck.test.ts +159 -0
- package/tests/tools/chat-duck.test.ts +191 -0
- package/tests/tools/compare-ducks.test.ts +190 -0
- package/tests/tools/duck-council.test.ts +219 -0
- package/tests/tools/get-pending-approvals.test.ts +195 -0
- package/tests/tools/list-ducks.test.ts +144 -0
- package/tests/tools/list-models.test.ts +163 -0
- package/tests/tools/mcp-status.test.ts +330 -0
|
@@ -87,12 +87,39 @@ describe('duckDebateTool', () => {
|
|
|
87
87
|
).rejects.toThrow('Rounds must be between 1 and 10');
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
it('should throw error when less than 2 providers', async () => {
|
|
90
|
+
it('should throw error when less than 2 providers specified', async () => {
|
|
91
91
|
await expect(
|
|
92
92
|
duckDebateTool(mockProviderManager, { prompt: 'Test', format: 'oxford', providers: ['openai'] })
|
|
93
93
|
).rejects.toThrow('At least 2 providers are required');
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
+
it('should throw error when only 1 provider available total', async () => {
|
|
97
|
+
// Create manager with only 1 provider
|
|
98
|
+
const singleProviderConfig = {
|
|
99
|
+
getConfig: jest.fn().mockReturnValue({
|
|
100
|
+
providers: {
|
|
101
|
+
openai: {
|
|
102
|
+
api_key: 'key1',
|
|
103
|
+
base_url: 'https://api.openai.com/v1',
|
|
104
|
+
default_model: 'gpt-4',
|
|
105
|
+
nickname: 'GPT-4',
|
|
106
|
+
models: ['gpt-4'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
default_provider: 'openai',
|
|
110
|
+
cache_ttl: 300,
|
|
111
|
+
enable_failover: false,
|
|
112
|
+
default_temperature: 0.7,
|
|
113
|
+
}),
|
|
114
|
+
} as any;
|
|
115
|
+
|
|
116
|
+
const singleProviderManager = new ProviderManager(singleProviderConfig);
|
|
117
|
+
|
|
118
|
+
await expect(
|
|
119
|
+
duckDebateTool(singleProviderManager, { prompt: 'Test', format: 'oxford' })
|
|
120
|
+
).rejects.toThrow('At least 2 providers are required for a debate');
|
|
121
|
+
});
|
|
122
|
+
|
|
96
123
|
it('should throw error when provider does not exist', async () => {
|
|
97
124
|
await expect(
|
|
98
125
|
duckDebateTool(mockProviderManager, { prompt: 'Test', format: 'oxford', providers: ['openai', 'nonexistent'] })
|
|
@@ -283,4 +310,81 @@ describe('duckDebateTool', () => {
|
|
|
283
310
|
const text = result.content[0].text;
|
|
284
311
|
expect(text).toContain('3 rounds completed');
|
|
285
312
|
});
|
|
313
|
+
|
|
314
|
+
it('should perform multi-round socratic debate', async () => {
|
|
315
|
+
// Round 1: 2 participants
|
|
316
|
+
mockCreate
|
|
317
|
+
.mockResolvedValueOnce({
|
|
318
|
+
choices: [{ message: { content: 'Question round 1' }, finish_reason: 'stop' }],
|
|
319
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
320
|
+
model: 'gpt-4',
|
|
321
|
+
})
|
|
322
|
+
.mockResolvedValueOnce({
|
|
323
|
+
choices: [{ message: { content: 'Response round 1' }, finish_reason: 'stop' }],
|
|
324
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
325
|
+
model: 'gemini-pro',
|
|
326
|
+
})
|
|
327
|
+
// Round 2 - should use "Build on previous responses" prompt
|
|
328
|
+
.mockResolvedValueOnce({
|
|
329
|
+
choices: [{ message: { content: 'Question round 2' }, finish_reason: 'stop' }],
|
|
330
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
331
|
+
model: 'gpt-4',
|
|
332
|
+
})
|
|
333
|
+
.mockResolvedValueOnce({
|
|
334
|
+
choices: [{ message: { content: 'Response round 2' }, finish_reason: 'stop' }],
|
|
335
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
336
|
+
model: 'gemini-pro',
|
|
337
|
+
})
|
|
338
|
+
// Synthesis
|
|
339
|
+
.mockResolvedValueOnce({
|
|
340
|
+
choices: [{ message: { content: 'Socratic synthesis' }, finish_reason: 'stop' }],
|
|
341
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
342
|
+
model: 'gpt-4',
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
const result = await duckDebateTool(mockProviderManager, {
|
|
346
|
+
prompt: 'What is truth?',
|
|
347
|
+
format: 'socratic',
|
|
348
|
+
rounds: 2,
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const text = result.content[0].text;
|
|
352
|
+
expect(text).toContain('Socratic Debate');
|
|
353
|
+
expect(text).toContain('ROUND 1');
|
|
354
|
+
expect(text).toContain('ROUND 2');
|
|
355
|
+
expect(text).toContain('2 rounds completed');
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it('should truncate long arguments in display', async () => {
|
|
359
|
+
// Create arguments longer than 800 characters
|
|
360
|
+
const longArgument = 'A'.repeat(900);
|
|
361
|
+
|
|
362
|
+
mockCreate
|
|
363
|
+
.mockResolvedValueOnce({
|
|
364
|
+
choices: [{ message: { content: longArgument }, finish_reason: 'stop' }],
|
|
365
|
+
usage: { prompt_tokens: 10, completion_tokens: 100, total_tokens: 110 },
|
|
366
|
+
model: 'gpt-4',
|
|
367
|
+
})
|
|
368
|
+
.mockResolvedValueOnce({
|
|
369
|
+
choices: [{ message: { content: 'Short con argument' }, finish_reason: 'stop' }],
|
|
370
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
371
|
+
model: 'gemini-pro',
|
|
372
|
+
})
|
|
373
|
+
.mockResolvedValueOnce({
|
|
374
|
+
choices: [{ message: { content: 'Synthesis' }, finish_reason: 'stop' }],
|
|
375
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
376
|
+
model: 'gpt-4',
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const result = await duckDebateTool(mockProviderManager, {
|
|
380
|
+
prompt: 'Test',
|
|
381
|
+
format: 'oxford',
|
|
382
|
+
rounds: 1,
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const text = result.content[0].text;
|
|
386
|
+
expect(text).toContain('[truncated]');
|
|
387
|
+
// Should not contain the full 900 A's
|
|
388
|
+
expect(text).not.toContain('A'.repeat(900));
|
|
389
|
+
});
|
|
286
390
|
});
|
|
@@ -246,4 +246,34 @@ describe('duckIterateTool', () => {
|
|
|
246
246
|
expect(mockCreate).toHaveBeenCalledTimes(1);
|
|
247
247
|
expect(result.content[0].text).toContain('1 rounds completed');
|
|
248
248
|
});
|
|
249
|
+
|
|
250
|
+
it('should truncate long responses in iteration history', async () => {
|
|
251
|
+
// Create a response longer than 500 characters
|
|
252
|
+
const longResponse = 'A'.repeat(600);
|
|
253
|
+
|
|
254
|
+
mockCreate
|
|
255
|
+
.mockResolvedValueOnce({
|
|
256
|
+
choices: [{ message: { content: longResponse }, finish_reason: 'stop' }],
|
|
257
|
+
usage: { prompt_tokens: 10, completion_tokens: 100, total_tokens: 110 },
|
|
258
|
+
model: 'gpt-4',
|
|
259
|
+
})
|
|
260
|
+
.mockResolvedValueOnce({
|
|
261
|
+
choices: [{ message: { content: 'Short refined response' }, finish_reason: 'stop' }],
|
|
262
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
263
|
+
model: 'gemini-pro',
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const result = await duckIterateTool(mockProviderManager, {
|
|
267
|
+
prompt: 'Test',
|
|
268
|
+
providers: ['openai', 'gemini'],
|
|
269
|
+
mode: 'refine',
|
|
270
|
+
iterations: 2,
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const text = result.content[0].text;
|
|
274
|
+
// Should contain truncated indicator for the long response in history
|
|
275
|
+
expect(text).toContain('[truncated]');
|
|
276
|
+
// Final response section should have the short refined response
|
|
277
|
+
expect(text).toContain('Short refined response');
|
|
278
|
+
});
|
|
249
279
|
});
|
package/tests/duck-judge.test.ts
CHANGED
|
@@ -293,4 +293,97 @@ describe('duckJudgeTool', () => {
|
|
|
293
293
|
expect(text).toContain('gemini');
|
|
294
294
|
expect(text).toContain('Not evaluated');
|
|
295
295
|
});
|
|
296
|
+
|
|
297
|
+
it('should throw error when no judge provider available', async () => {
|
|
298
|
+
// Mock getProviderNames to return empty array
|
|
299
|
+
const originalGetProviderNames = mockProviderManager.getProviderNames;
|
|
300
|
+
mockProviderManager.getProviderNames = jest.fn().mockReturnValue([]);
|
|
301
|
+
|
|
302
|
+
await expect(
|
|
303
|
+
duckJudgeTool(mockProviderManager, {
|
|
304
|
+
responses: mockResponses,
|
|
305
|
+
})
|
|
306
|
+
).rejects.toThrow('No judge provider available');
|
|
307
|
+
|
|
308
|
+
// Restore original
|
|
309
|
+
mockProviderManager.getProviderNames = originalGetProviderNames;
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should handle malformed JSON that throws parse error', async () => {
|
|
313
|
+
// JSON that looks valid but has syntax errors
|
|
314
|
+
mockCreate.mockResolvedValueOnce({
|
|
315
|
+
choices: [{
|
|
316
|
+
message: { content: '{"rankings": [{"provider": "openai", score: invalid}]}' },
|
|
317
|
+
finish_reason: 'stop',
|
|
318
|
+
}],
|
|
319
|
+
usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
|
|
320
|
+
model: 'gpt-4',
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
const result = await duckJudgeTool(mockProviderManager, {
|
|
324
|
+
responses: mockResponses,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const text = result.content[0].text;
|
|
328
|
+
// Should use fallback evaluation
|
|
329
|
+
expect(text).toContain('Judge Evaluation');
|
|
330
|
+
expect(text).toContain('Unable to parse');
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('should match provider by nickname in rankings', async () => {
|
|
334
|
+
// Use nickname instead of provider name
|
|
335
|
+
const judgeResponse = JSON.stringify({
|
|
336
|
+
rankings: [
|
|
337
|
+
{ provider: 'GPT-4', score: 85, justification: 'Good response' },
|
|
338
|
+
{ provider: 'Gemini', score: 75, justification: 'Okay response' },
|
|
339
|
+
],
|
|
340
|
+
summary: 'Matched by nickname.',
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
mockCreate.mockResolvedValueOnce({
|
|
344
|
+
choices: [{
|
|
345
|
+
message: { content: judgeResponse },
|
|
346
|
+
finish_reason: 'stop',
|
|
347
|
+
}],
|
|
348
|
+
usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
|
|
349
|
+
model: 'gpt-4',
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
const result = await duckJudgeTool(mockProviderManager, {
|
|
353
|
+
responses: mockResponses,
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
const text = result.content[0].text;
|
|
357
|
+
expect(text).toContain('85/100');
|
|
358
|
+
expect(text).toContain('75/100');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should match provider by contained name in rankings', async () => {
|
|
362
|
+
// Use partial name that contains the provider name
|
|
363
|
+
const judgeResponse = JSON.stringify({
|
|
364
|
+
rankings: [
|
|
365
|
+
{ provider: 'the openai model', score: 90, justification: 'Excellent' },
|
|
366
|
+
{ provider: 'google gemini response', score: 80, justification: 'Good' },
|
|
367
|
+
],
|
|
368
|
+
summary: 'Matched by contained name.',
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
mockCreate.mockResolvedValueOnce({
|
|
372
|
+
choices: [{
|
|
373
|
+
message: { content: judgeResponse },
|
|
374
|
+
finish_reason: 'stop',
|
|
375
|
+
}],
|
|
376
|
+
usage: { prompt_tokens: 100, completion_tokens: 50, total_tokens: 150 },
|
|
377
|
+
model: 'gpt-4',
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const result = await duckJudgeTool(mockProviderManager, {
|
|
381
|
+
responses: mockResponses,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
const text = result.content[0].text;
|
|
385
|
+
// Should successfully match the providers
|
|
386
|
+
expect(text).toContain('90/100');
|
|
387
|
+
expect(text).toContain('80/100');
|
|
388
|
+
});
|
|
296
389
|
});
|
package/tests/duck-vote.test.ts
CHANGED
|
@@ -247,4 +247,50 @@ describe('duckVoteTool', () => {
|
|
|
247
247
|
|
|
248
248
|
expect(result.content[0].text).toContain('Option A');
|
|
249
249
|
});
|
|
250
|
+
|
|
251
|
+
it('should throw error when no voters available', async () => {
|
|
252
|
+
// Mock getProviderNames to return empty array
|
|
253
|
+
const originalGetProviderNames = mockProviderManager.getProviderNames;
|
|
254
|
+
mockProviderManager.getProviderNames = jest.fn().mockReturnValue([]);
|
|
255
|
+
|
|
256
|
+
await expect(
|
|
257
|
+
duckVoteTool(mockProviderManager, {
|
|
258
|
+
question: 'Test?',
|
|
259
|
+
options: ['Option A', 'Option B'],
|
|
260
|
+
})
|
|
261
|
+
).rejects.toThrow('No voters available');
|
|
262
|
+
|
|
263
|
+
// Restore original
|
|
264
|
+
mockProviderManager.getProviderNames = originalGetProviderNames;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('should handle case when no valid votes result in no winner', async () => {
|
|
268
|
+
// Both responses don't mention any valid option
|
|
269
|
+
mockCreate
|
|
270
|
+
.mockResolvedValueOnce({
|
|
271
|
+
choices: [{
|
|
272
|
+
message: { content: 'I cannot decide between these options.' },
|
|
273
|
+
finish_reason: 'stop',
|
|
274
|
+
}],
|
|
275
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
276
|
+
model: 'gpt-4',
|
|
277
|
+
})
|
|
278
|
+
.mockResolvedValueOnce({
|
|
279
|
+
choices: [{
|
|
280
|
+
message: { content: 'These options are not comparable.' },
|
|
281
|
+
finish_reason: 'stop',
|
|
282
|
+
}],
|
|
283
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
284
|
+
model: 'gemini-pro',
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const result = await duckVoteTool(mockProviderManager, {
|
|
288
|
+
question: 'Test?',
|
|
289
|
+
options: ['Option A', 'Option B'],
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
const text = result.content[0].text;
|
|
293
|
+
expect(text).toContain('No valid votes');
|
|
294
|
+
expect(text).toContain('0/2 valid votes');
|
|
295
|
+
});
|
|
250
296
|
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
import { HealthMonitor } from '../src/services/health.js';
|
|
3
|
+
import { ProviderManager } from '../src/providers/manager.js';
|
|
4
|
+
import { ProviderHealth } from '../src/config/types.js';
|
|
5
|
+
|
|
6
|
+
// Mock dependencies
|
|
7
|
+
jest.mock('../src/utils/logger');
|
|
8
|
+
jest.mock('../src/providers/manager.js');
|
|
9
|
+
|
|
10
|
+
describe('HealthMonitor', () => {
|
|
11
|
+
let healthMonitor: HealthMonitor;
|
|
12
|
+
let mockProviderManager: jest.Mocked<ProviderManager>;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
// Create a mock ProviderManager
|
|
16
|
+
mockProviderManager = {
|
|
17
|
+
checkHealth: jest.fn(),
|
|
18
|
+
} as unknown as jest.Mocked<ProviderManager>;
|
|
19
|
+
|
|
20
|
+
healthMonitor = new HealthMonitor(mockProviderManager);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('performHealthChecks', () => {
|
|
24
|
+
it('should call providerManager.checkHealth and return results', async () => {
|
|
25
|
+
const healthResults: ProviderHealth[] = [
|
|
26
|
+
{
|
|
27
|
+
provider: 'openai',
|
|
28
|
+
healthy: true,
|
|
29
|
+
latency: 150,
|
|
30
|
+
lastCheck: new Date(),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
provider: 'groq',
|
|
34
|
+
healthy: true,
|
|
35
|
+
latency: 80,
|
|
36
|
+
lastCheck: new Date(),
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
mockProviderManager.checkHealth.mockResolvedValue(healthResults);
|
|
41
|
+
|
|
42
|
+
const results = await healthMonitor.performHealthChecks();
|
|
43
|
+
|
|
44
|
+
expect(mockProviderManager.checkHealth).toHaveBeenCalledTimes(1);
|
|
45
|
+
expect(results).toEqual(healthResults);
|
|
46
|
+
expect(results).toHaveLength(2);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should handle unhealthy providers with errors', async () => {
|
|
50
|
+
const healthResults: ProviderHealth[] = [
|
|
51
|
+
{
|
|
52
|
+
provider: 'openai',
|
|
53
|
+
healthy: true,
|
|
54
|
+
latency: 150,
|
|
55
|
+
lastCheck: new Date(),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
provider: 'groq',
|
|
59
|
+
healthy: false,
|
|
60
|
+
lastCheck: new Date(),
|
|
61
|
+
error: 'Connection refused',
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
mockProviderManager.checkHealth.mockResolvedValue(healthResults);
|
|
66
|
+
|
|
67
|
+
const results = await healthMonitor.performHealthChecks();
|
|
68
|
+
|
|
69
|
+
expect(results).toHaveLength(2);
|
|
70
|
+
expect(results[0].healthy).toBe(true);
|
|
71
|
+
expect(results[1].healthy).toBe(false);
|
|
72
|
+
expect(results[1].error).toBe('Connection refused');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle all unhealthy providers', async () => {
|
|
76
|
+
const healthResults: ProviderHealth[] = [
|
|
77
|
+
{
|
|
78
|
+
provider: 'openai',
|
|
79
|
+
healthy: false,
|
|
80
|
+
lastCheck: new Date(),
|
|
81
|
+
error: 'API key invalid',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
provider: 'groq',
|
|
85
|
+
healthy: false,
|
|
86
|
+
lastCheck: new Date(),
|
|
87
|
+
error: 'Timeout',
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
mockProviderManager.checkHealth.mockResolvedValue(healthResults);
|
|
92
|
+
|
|
93
|
+
const results = await healthMonitor.performHealthChecks();
|
|
94
|
+
|
|
95
|
+
expect(results.every((r) => !r.healthy)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should handle empty provider list', async () => {
|
|
99
|
+
mockProviderManager.checkHealth.mockResolvedValue([]);
|
|
100
|
+
|
|
101
|
+
const results = await healthMonitor.performHealthChecks();
|
|
102
|
+
|
|
103
|
+
expect(results).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle providers without latency info', async () => {
|
|
107
|
+
const healthResults: ProviderHealth[] = [
|
|
108
|
+
{
|
|
109
|
+
provider: 'openai',
|
|
110
|
+
healthy: false,
|
|
111
|
+
lastCheck: new Date(),
|
|
112
|
+
error: 'Failed before timing',
|
|
113
|
+
},
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
mockProviderManager.checkHealth.mockResolvedValue(healthResults);
|
|
117
|
+
|
|
118
|
+
const results = await healthMonitor.performHealthChecks();
|
|
119
|
+
|
|
120
|
+
expect(results[0].latency).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should propagate errors from checkHealth', async () => {
|
|
124
|
+
mockProviderManager.checkHealth.mockRejectedValue(new Error('Network error'));
|
|
125
|
+
|
|
126
|
+
await expect(healthMonitor.performHealthChecks()).rejects.toThrow('Network error');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|