Skip to content

Commit 9877a54

Browse files
Copilotpethers
andcommitted
fix: address PR review comments
Comment #2805106822: Restore real fetch in integration tests - Add native fetch restoration in beforeAll (Node.js 18+) - Restore mock in afterAll for proper test isolation - Log fetch restoration status Comment #2805106890: Use proper skip mechanism - Replace all `if (!serverAvailable) return;` with `it.skipIf(!serverAvailable)` - Tests now properly marked as skipped, not passed - Prevents false positives in CI Comment #2805106922: Fix test:all script - Change from `test:integration:skip` to `test:integration` - Enables true auto-skip behavior (run when server available) - Aligns with "auto-skip" description Comment #2805106956: Fix timer leak in mcp-client.js - Move clearTimeout to finally block - Ensures timeout cleared on all code paths (success, error, rejection) - Prevents event loop blocking Comment #2805106972: Improve prefix fallback logic - Remove retryCount === 0 condition - Add recursion guard (check toolName.startsWith) - Works on transient errors, not just first attempt Comment #2805106992: Remove unused variable - Remove `const response =` from politician-dashboard test - Variable was assigned but never used Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
1 parent e581b20 commit 9877a54

File tree

5 files changed

+55
-81
lines changed

5 files changed

+55
-81
lines changed

package-lock.json

Lines changed: 7 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"test:coverage": "vitest run --coverage",
1515
"test:integration": "vitest run tests/integration --reporter=verbose",
1616
"test:integration:skip": "SKIP_INTEGRATION_TESTS=true vitest run tests/integration",
17-
"test:all": "npm run test && npm run test:integration:skip",
17+
"test:all": "npm run test && npm run test:integration",
1818
"cypress:open": "cypress open",
1919
"cypress:run": "cypress run",
2020
"e2e": "npm run build && start-server-and-test preview http://localhost:4173 cypress:run",

scripts/mcp-client.js

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ export class MCPClient {
7373
this.requestCount++;
7474
}
7575

76+
const controller = new AbortController();
77+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
78+
7679
try {
77-
const controller = new AbortController();
78-
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
79-
8080
// MCP uses JSON-RPC 2.0 protocol
8181
// Call the tool using tools/call method
8282
// Try with server prefix first (riksdag-regering--tool_name)
@@ -102,8 +102,6 @@ export class MCPClient {
102102
signal: controller.signal
103103
});
104104

105-
clearTimeout(timeoutId);
106-
107105
if (!response.ok) {
108106
// Provide more detailed error information
109107
let errorBody = '';
@@ -122,10 +120,11 @@ export class MCPClient {
122120
const errorMsg = jsonRpcResponse.error.message || JSON.stringify(jsonRpcResponse.error);
123121

124122
// If tool not found and we used prefix, try without prefix
125-
if (errorMsg.includes('not found') && toolName.includes('--') && retryCount === 0) {
123+
// Guard against infinite recursion by checking if tool already doesn't have prefix
124+
if (errorMsg.includes('not found') && toolName.startsWith('riksdag-regering--')) {
126125
console.warn(`⚠️ Tool not found with prefix, retrying without prefix...`);
127126
// Recursive call without prefix
128-
return this.request(tool.replace(/^.*--/, ''), params, retryCount);
127+
return this.request(tool.replace(/^riksdag-regering--/, ''), params, retryCount);
129128
}
130129

131130
throw new Error(`MCP tool error: ${errorMsg}`);
@@ -152,6 +151,9 @@ export class MCPClient {
152151
this.errorCount++;
153152

154153
throw new Error(`MCP request failed: ${error.message}`);
154+
} finally {
155+
// Always clear timeout to prevent timer leak
156+
clearTimeout(timeoutId);
155157
}
156158
}
157159

tests/integration/mcp-client.integration.test.js

Lines changed: 37 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,24 @@ const MCP_SERVER_URL = process.env.MCP_SERVER_URL || 'https://riksdag-regering-a
2525
describeIntegration('MCP Client Integration Tests', () => {
2626
let client;
2727
let serverAvailable = false;
28+
let originalFetch;
2829

2930
beforeAll(async () => {
3031
console.log('\n🔍 Testing MCP server availability...');
3132
console.log(`📡 Server URL: ${MCP_SERVER_URL}`);
3233

34+
// Restore real fetch for integration tests (setup.js globally mocks it)
35+
// Use native fetch (available in Node.js 18+)
36+
originalFetch = global.fetch;
37+
38+
// Try to restore real fetch - Node.js 18+ has native fetch
39+
if (typeof globalThis.fetch === 'function' && globalThis.fetch !== global.fetch) {
40+
global.fetch = globalThis.fetch;
41+
console.log('✓ Restored native fetch');
42+
} else {
43+
console.warn('⚠️ Could not restore real fetch, using mock (tests may not hit actual server)');
44+
}
45+
3346
client = new MCPClient({ baseURL: MCP_SERVER_URL, timeout: 30000 });
3447

3548
// Test server availability before running tests
@@ -49,6 +62,11 @@ describeIntegration('MCP Client Integration Tests', () => {
4962
}, TEST_TIMEOUT);
5063

5164
afterAll(() => {
65+
// Restore original fetch mock from setup.js
66+
if (originalFetch) {
67+
global.fetch = originalFetch;
68+
}
69+
5270
if (serverAvailable) {
5371
const stats = client.getStats();
5472
console.log('\n📊 MCP Client Statistics:');
@@ -59,18 +77,13 @@ describeIntegration('MCP Client Integration Tests', () => {
5977
});
6078

6179
describe('Server Availability', () => {
62-
it('should connect to MCP server', () => {
63-
if (!serverAvailable) {
64-
console.log('⚠️ Skipping: MCP server not available');
65-
return;
66-
}
80+
it.skipIf(!serverAvailable)('should connect to MCP server', () => {
6781
expect(serverAvailable).toBe(true);
6882
});
6983
});
7084

7185
describe('fetchCalendarEvents', () => {
72-
it('should fetch calendar events for next 7 days', async () => {
73-
if (!serverAvailable) return;
86+
it.skipIf(!serverAvailable)('should fetch calendar events for next 7 days', async () => {
7487

7588
const today = new Date();
7689
const from = today.toISOString().split('T')[0];
@@ -93,9 +106,7 @@ describeIntegration('MCP Client Integration Tests', () => {
93106
}
94107
}, TEST_TIMEOUT);
95108

96-
it('should filter calendar events by organ', async () => {
97-
if (!serverAvailable) return;
98-
109+
it.skipIf(!serverAvailable)('should filter calendar events by organ', async () => {
99110
const today = new Date();
100111
const from = today.toISOString().split('T')[0];
101112
const endDate = new Date(today.getTime() + 30 * 24 * 60 * 60 * 1000);
@@ -109,9 +120,7 @@ describeIntegration('MCP Client Integration Tests', () => {
109120
});
110121

111122
describe('fetchCommitteeReports', () => {
112-
it('should fetch latest committee reports', async () => {
113-
if (!serverAvailable) return;
114-
123+
it.skipIf(!serverAvailable)('should fetch latest committee reports', async () => {
115124
const reports = await client.fetchCommitteeReports(5);
116125

117126
expect(reports).toBeDefined();
@@ -126,9 +135,7 @@ describeIntegration('MCP Client Integration Tests', () => {
126135
}
127136
}, TEST_TIMEOUT);
128137

129-
it('should fetch committee reports for specific riksmöte', async () => {
130-
if (!serverAvailable) return;
131-
138+
it.skipIf(!serverAvailable)('should fetch committee reports for specific riksmöte', async () => {
132139
const currentYear = new Date().getFullYear();
133140
const rm = `${currentYear - 1}/${String(currentYear).substring(2)}`;
134141

@@ -140,9 +147,7 @@ describeIntegration('MCP Client Integration Tests', () => {
140147
});
141148

142149
describe('fetchPropositions', () => {
143-
it('should fetch latest government propositions', async () => {
144-
if (!serverAvailable) return;
145-
150+
it.skipIf(!serverAvailable)('should fetch latest government propositions', async () => {
146151
const propositions = await client.fetchPropositions(5);
147152

148153
expect(propositions).toBeDefined();
@@ -159,9 +164,7 @@ describeIntegration('MCP Client Integration Tests', () => {
159164
});
160165

161166
describe('fetchMotions', () => {
162-
it('should fetch latest motions', async () => {
163-
if (!serverAvailable) return;
164-
167+
it.skipIf(!serverAvailable)('should fetch latest motions', async () => {
165168
const motions = await client.fetchMotions(5);
166169

167170
expect(motions).toBeDefined();
@@ -178,9 +181,7 @@ describeIntegration('MCP Client Integration Tests', () => {
178181
});
179182

180183
describe('searchDocuments', () => {
181-
it('should search riksdag documents', async () => {
182-
if (!serverAvailable) return;
183-
184+
it.skipIf(!serverAvailable)('should search riksdag documents', async () => {
184185
const documents = await client.searchDocuments({
185186
sok: 'budget',
186187
limit: 5
@@ -198,9 +199,7 @@ describeIntegration('MCP Client Integration Tests', () => {
198199
}
199200
}, TEST_TIMEOUT);
200201

201-
it('should search documents by type', async () => {
202-
if (!serverAvailable) return;
203-
202+
it.skipIf(!serverAvailable)('should search documents by type', async () => {
204203
const documents = await client.searchDocuments({
205204
doktyp: 'mot',
206205
limit: 3
@@ -212,9 +211,7 @@ describeIntegration('MCP Client Integration Tests', () => {
212211
});
213212

214213
describe('searchSpeeches', () => {
215-
it('should search parliamentary speeches', async () => {
216-
if (!serverAvailable) return;
217-
214+
it.skipIf(!serverAvailable)('should search parliamentary speeches', async () => {
218215
const speeches = await client.searchSpeeches({
219216
sok: 'miljö',
220217
limit: 5
@@ -234,9 +231,7 @@ describeIntegration('MCP Client Integration Tests', () => {
234231
});
235232

236233
describe('fetchMPs', () => {
237-
it('should fetch all MPs', async () => {
238-
if (!serverAvailable) return;
239-
234+
it.skipIf(!serverAvailable)('should fetch all MPs', async () => {
240235
const mps = await client.fetchMPs({});
241236

242237
expect(mps).toBeDefined();
@@ -251,9 +246,7 @@ describeIntegration('MCP Client Integration Tests', () => {
251246
}
252247
}, TEST_TIMEOUT);
253248

254-
it('should filter MPs by party', async () => {
255-
if (!serverAvailable) return;
256-
249+
it.skipIf(!serverAvailable)('should filter MPs by party', async () => {
257250
const mps = await client.fetchMPs({ parti: 'S' });
258251

259252
expect(Array.isArray(mps)).toBe(true);
@@ -268,9 +261,7 @@ describeIntegration('MCP Client Integration Tests', () => {
268261
});
269262

270263
describe('fetchVotingRecords', () => {
271-
it('should fetch voting records', async () => {
272-
if (!serverAvailable) return;
273-
264+
it.skipIf(!serverAvailable)('should fetch voting records', async () => {
274265
const currentYear = new Date().getFullYear();
275266
const rm = `${currentYear - 1}/${String(currentYear).substring(2)}`;
276267

@@ -293,9 +284,7 @@ describeIntegration('MCP Client Integration Tests', () => {
293284
});
294285

295286
describe('fetchGovernmentDocuments', () => {
296-
it('should fetch government documents', async () => {
297-
if (!serverAvailable) return;
298-
287+
it.skipIf(!serverAvailable)('should fetch government documents', async () => {
299288
const docs = await client.fetchGovernmentDocuments({
300289
type: 'pressmeddelanden',
301290
limit: 5
@@ -315,9 +304,7 @@ describeIntegration('MCP Client Integration Tests', () => {
315304
});
316305

317306
describe('Error Handling', () => {
318-
it('should handle invalid date formats gracefully', async () => {
319-
if (!serverAvailable) return;
320-
307+
it.skipIf(!serverAvailable)('should handle invalid date formats gracefully', async () => {
321308
try {
322309
await client.fetchCalendarEvents('invalid-date', '2026-12-31');
323310
// If it doesn't throw, that's okay - server might handle it
@@ -328,9 +315,7 @@ describeIntegration('MCP Client Integration Tests', () => {
328315
}
329316
}, TEST_TIMEOUT);
330317

331-
it('should handle network timeouts', async () => {
332-
if (!serverAvailable) return;
333-
318+
it.skipIf(!serverAvailable)('should handle network timeouts', async () => {
334319
const slowClient = new MCPClient({
335320
baseURL: MCP_SERVER_URL,
336321
timeout: 100 // Very short timeout
@@ -347,9 +332,7 @@ describeIntegration('MCP Client Integration Tests', () => {
347332
});
348333

349334
describe('Response Data Quality', () => {
350-
it('should return consistent data structures', async () => {
351-
if (!serverAvailable) return;
352-
335+
it.skipIf(!serverAvailable)('should return consistent data structures', async () => {
353336
const today = new Date();
354337
const from = today.toISOString().split('T')[0];
355338
const tom = new Date(today.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
@@ -373,9 +356,7 @@ describeIntegration('MCP Client Integration Tests', () => {
373356
});
374357

375358
describe('Performance', () => {
376-
it('should complete requests within timeout', async () => {
377-
if (!serverAvailable) return;
378-
359+
it.skipIf(!serverAvailable)('should complete requests within timeout', async () => {
379360
const startTime = Date.now();
380361

381362
await client.fetchPropositions(5);
@@ -386,9 +367,7 @@ describeIntegration('MCP Client Integration Tests', () => {
386367
console.log(` ✓ Request completed in ${duration}ms`);
387368
}, TEST_TIMEOUT);
388369

389-
it('should handle concurrent requests', async () => {
390-
if (!serverAvailable) return;
391-
370+
it.skipIf(!serverAvailable)('should handle concurrent requests', async () => {
392371
const startTime = Date.now();
393372

394373
const results = await Promise.all([

tests/politician-dashboard.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ describe('Politician Career & Productivity Analytics Dashboard', () => {
455455
global.fetch = mockFetch;
456456

457457
try {
458-
const response = await global.fetch('fake-url.csv');
458+
await global.fetch('fake-url.csv');
459459
expect.unreachable();
460460
} catch (error) {
461461
expect(error.message).toBe('Network error');

0 commit comments

Comments
 (0)