|
12 | 12 | import org.apache.catalina.LifecycleException; |
13 | 13 | import org.apache.catalina.LifecycleState; |
14 | 14 | import org.apache.catalina.startup.Tomcat; |
| 15 | +import org.slf4j.Logger; |
| 16 | +import org.slf4j.LoggerFactory; |
15 | 17 |
|
16 | 18 | import static org.assertj.core.api.Assertions.assertThat; |
17 | 19 | import org.junit.jupiter.api.AfterEach; |
|
44 | 46 | */ |
45 | 47 | class McpCompletionTests { |
46 | 48 |
|
| 49 | + private static final Logger logger = LoggerFactory.getLogger(McpCompletionTests.class); |
| 50 | + |
47 | 51 | private HttpServletSseServerTransportProvider mcpServerTransportProvider; |
48 | 52 |
|
49 | 53 | private static final int PORT = TomcatTestUtil.findAvailablePort(); |
@@ -255,6 +259,10 @@ else if ("products_db".equals(db)) { |
255 | 259 | mcpServer.close(); |
256 | 260 | } |
257 | 261 |
|
| 262 | + // Flaky test: Under CI load, the SSE connection may close mid-test (EOF on chunked |
| 263 | + // transfer), causing the server to remove the session. Subsequent requests then |
| 264 | + // fail with "Session not found". Retrying with a fresh client establishes a new |
| 265 | + // SSE connection and session. |
258 | 266 | @Test |
259 | 267 | void testCompletionErrorOnMissingContext() { |
260 | 268 | BiFunction<McpSyncServerExchange, CompleteRequest, CompleteResult> completionHandler = (exchange, request) -> { |
@@ -297,28 +305,41 @@ void testCompletionErrorOnMissingContext() { |
297 | 305 | new ResourceReference(ResourceReference.TYPE, "db://{database}/{table}"), completionHandler)) |
298 | 306 | .build(); |
299 | 307 |
|
300 | | - try (var mcpClient = clientBuilder.clientInfo(new McpSchema.Implementation("Sample" + "client", "0.0.0")) |
301 | | - .build();) { |
302 | | - InitializeResult initResult = mcpClient.initialize(); |
303 | | - assertThat(initResult).isNotNull(); |
304 | | - |
305 | | - // Try to complete table without database context - should raise error |
306 | | - CompleteRequest requestWithoutContext = new CompleteRequest( |
307 | | - new ResourceReference(ResourceReference.TYPE, "db://{database}/{table}"), |
308 | | - new CompleteRequest.CompleteArgument("table", "")); |
309 | | - |
310 | | - assertThatExceptionOfType(McpError.class) |
311 | | - .isThrownBy(() -> mcpClient.completeCompletion(requestWithoutContext)) |
312 | | - .withMessageContaining("Please select a database first"); |
313 | | - |
314 | | - // Now complete with proper context - should work normally |
315 | | - CompleteRequest requestWithContext = new CompleteRequest( |
316 | | - new ResourceReference(ResourceReference.TYPE, "db://{database}/{table}"), |
317 | | - new CompleteRequest.CompleteArgument("table", ""), |
318 | | - new CompleteRequest.CompleteContext(Map.of("database", "test_db"))); |
319 | | - |
320 | | - CompleteResult resultWithContext = mcpClient.completeCompletion(requestWithContext); |
321 | | - assertThat(resultWithContext.completion().values()).containsExactly("users", "orders", "products"); |
| 308 | + int maxAttempts = 3; |
| 309 | + RuntimeException lastError = null; |
| 310 | + for (int attempt = 1; attempt <= maxAttempts; attempt++) { |
| 311 | + try (var mcpClient = clientBuilder.clientInfo(new McpSchema.Implementation("Sample" + "client", "0.0.0")) |
| 312 | + .build()) { |
| 313 | + InitializeResult initResult = mcpClient.initialize(); |
| 314 | + assertThat(initResult).isNotNull(); |
| 315 | + |
| 316 | + // Try to complete table without database context - should raise error |
| 317 | + CompleteRequest requestWithoutContext = new CompleteRequest( |
| 318 | + new ResourceReference(ResourceReference.TYPE, "db://{database}/{table}"), |
| 319 | + new CompleteRequest.CompleteArgument("table", "")); |
| 320 | + |
| 321 | + assertThatExceptionOfType(McpError.class) |
| 322 | + .isThrownBy(() -> mcpClient.completeCompletion(requestWithoutContext)) |
| 323 | + .withMessageContaining("Please select a database first"); |
| 324 | + |
| 325 | + // Now complete with proper context - should work normally |
| 326 | + CompleteRequest requestWithContext = new CompleteRequest( |
| 327 | + new ResourceReference(ResourceReference.TYPE, "db://{database}/{table}"), |
| 328 | + new CompleteRequest.CompleteArgument("table", ""), |
| 329 | + new CompleteRequest.CompleteContext(Map.of("database", "test_db"))); |
| 330 | + |
| 331 | + CompleteResult resultWithContext = mcpClient.completeCompletion(requestWithContext); |
| 332 | + assertThat(resultWithContext.completion().values()).containsExactly("users", "orders", "products"); |
| 333 | + lastError = null; |
| 334 | + break; // Success |
| 335 | + } |
| 336 | + catch (RuntimeException e) { |
| 337 | + lastError = e; |
| 338 | + logger.warn("Attempt {} failed, retrying with fresh client", attempt, e); |
| 339 | + } |
| 340 | + } |
| 341 | + if (lastError != null) { |
| 342 | + throw lastError; |
322 | 343 | } |
323 | 344 |
|
324 | 345 | mcpServer.close(); |
|
0 commit comments