|
19 | 19 | import reactor.test.StepVerifier; |
20 | 20 |
|
21 | 21 | import static org.assertj.core.api.Assertions.assertThat; |
22 | | -import static org.assertj.core.api.Assertions.assertThatThrownBy; |
23 | 22 |
|
24 | 23 | /** |
25 | 24 | * Test suite for {@link McpClientSession} that verifies its JSON-RPC message handling, |
@@ -57,17 +56,6 @@ void tearDown() { |
57 | 56 | } |
58 | 57 | } |
59 | 58 |
|
60 | | - @Test |
61 | | - void testConstructorWithInvalidArguments() { |
62 | | - assertThatThrownBy(() -> new McpClientSession(null, transport, Map.of(), Map.of())) |
63 | | - .isInstanceOf(IllegalArgumentException.class) |
64 | | - .hasMessageContaining("The requestTimeout can not be null"); |
65 | | - |
66 | | - assertThatThrownBy(() -> new McpClientSession(TIMEOUT, null, Map.of(), Map.of())) |
67 | | - .isInstanceOf(IllegalArgumentException.class) |
68 | | - .hasMessageContaining("transport can not be null"); |
69 | | - } |
70 | | - |
71 | 59 | TypeRef<String> responseType = new TypeRef<>() { |
72 | 60 | }; |
73 | 61 |
|
@@ -190,6 +178,155 @@ void testUnknownMethodHandling() { |
190 | 178 | assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.METHOD_NOT_FOUND); |
191 | 179 | } |
192 | 180 |
|
| 181 | + @Test |
| 182 | + void testRequestHandlerThrowsMcpErrorWithJsonRpcError() { |
| 183 | + // Setup: Create a request handler that throws McpError with custom error code and |
| 184 | + // data |
| 185 | + String testMethod = "test.customError"; |
| 186 | + Map<String, Object> errorData = Map.of("customField", "customValue"); |
| 187 | + McpClientSession.RequestHandler<?> failingHandler = params -> Mono |
| 188 | + .error(McpError.builder(123).message("Custom error message").data(errorData).build()); |
| 189 | + |
| 190 | + transport = new MockMcpClientTransport(); |
| 191 | + session = new McpClientSession(TIMEOUT, transport, Map.of(testMethod, failingHandler), Map.of()); |
| 192 | + |
| 193 | + // Simulate incoming request that will trigger the error |
| 194 | + McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod, |
| 195 | + "test-id", null); |
| 196 | + transport.simulateIncomingMessage(request); |
| 197 | + |
| 198 | + // Verify: The response should contain the custom error from McpError |
| 199 | + McpSchema.JSONRPCMessage sentMessage = transport.getLastSentMessage(); |
| 200 | + assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); |
| 201 | + McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; |
| 202 | + assertThat(response.error()).isNotNull(); |
| 203 | + assertThat(response.error().code()).isEqualTo(123); |
| 204 | + assertThat(response.error().message()).isEqualTo("Custom error message"); |
| 205 | + assertThat(response.error().data()).isEqualTo(errorData); |
| 206 | + } |
| 207 | + |
| 208 | + @Test |
| 209 | + void testRequestHandlerThrowsGenericException() { |
| 210 | + // Setup: Create a request handler that throws a generic RuntimeException |
| 211 | + String testMethod = "test.genericError"; |
| 212 | + RuntimeException exception = new RuntimeException("Something went wrong"); |
| 213 | + McpClientSession.RequestHandler<?> failingHandler = params -> Mono.error(exception); |
| 214 | + |
| 215 | + transport = new MockMcpClientTransport(); |
| 216 | + session = new McpClientSession(TIMEOUT, transport, Map.of(testMethod, failingHandler), Map.of()); |
| 217 | + |
| 218 | + // Simulate incoming request that will trigger the error |
| 219 | + McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod, |
| 220 | + "test-id", null); |
| 221 | + transport.simulateIncomingMessage(request); |
| 222 | + |
| 223 | + // Verify: The response should contain INTERNAL_ERROR with aggregated exception |
| 224 | + // messages in data field |
| 225 | + McpSchema.JSONRPCMessage sentMessage = transport.getLastSentMessage(); |
| 226 | + assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); |
| 227 | + McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; |
| 228 | + assertThat(response.error()).isNotNull(); |
| 229 | + assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.INTERNAL_ERROR); |
| 230 | + assertThat(response.error().message()).isEqualTo("Something went wrong"); |
| 231 | + // Verify data field contains aggregated exception messages |
| 232 | + assertThat(response.error().data()).isNotNull(); |
| 233 | + assertThat(response.error().data().toString()).contains("RuntimeException"); |
| 234 | + assertThat(response.error().data().toString()).contains("Something went wrong"); |
| 235 | + } |
| 236 | + |
| 237 | + @Test |
| 238 | + void testRequestHandlerThrowsExceptionWithCause() { |
| 239 | + // Setup: Create a request handler that throws an exception with a cause chain |
| 240 | + String testMethod = "test.chainedError"; |
| 241 | + RuntimeException rootCause = new IllegalArgumentException("Root cause message"); |
| 242 | + RuntimeException middleCause = new IllegalStateException("Middle cause message", rootCause); |
| 243 | + RuntimeException topException = new RuntimeException("Top level message", middleCause); |
| 244 | + McpClientSession.RequestHandler<?> failingHandler = params -> Mono.error(topException); |
| 245 | + |
| 246 | + transport = new MockMcpClientTransport(); |
| 247 | + session = new McpClientSession(TIMEOUT, transport, Map.of(testMethod, failingHandler), Map.of()); |
| 248 | + |
| 249 | + // Simulate incoming request that will trigger the error |
| 250 | + McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod, |
| 251 | + "test-id", null); |
| 252 | + transport.simulateIncomingMessage(request); |
| 253 | + |
| 254 | + // Verify: The response should contain INTERNAL_ERROR with full exception chain |
| 255 | + // in data field |
| 256 | + McpSchema.JSONRPCMessage sentMessage = transport.getLastSentMessage(); |
| 257 | + assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); |
| 258 | + McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; |
| 259 | + assertThat(response.error()).isNotNull(); |
| 260 | + assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.INTERNAL_ERROR); |
| 261 | + assertThat(response.error().message()).isEqualTo("Top level message"); |
| 262 | + // Verify data field contains the full exception chain |
| 263 | + String dataString = response.error().data().toString(); |
| 264 | + assertThat(dataString).contains("RuntimeException"); |
| 265 | + assertThat(dataString).contains("Top level message"); |
| 266 | + assertThat(dataString).contains("IllegalStateException"); |
| 267 | + assertThat(dataString).contains("Middle cause message"); |
| 268 | + assertThat(dataString).contains("IllegalArgumentException"); |
| 269 | + assertThat(dataString).contains("Root cause message"); |
| 270 | + } |
| 271 | + |
| 272 | + @Test |
| 273 | + void testRequestHandlerThrowsMcpErrorWithoutJsonRpcError() { |
| 274 | + // Setup: Create a request handler that throws deprecated McpError without |
| 275 | + // JSONRPCError |
| 276 | + String testMethod = "test.deprecatedError"; |
| 277 | + @SuppressWarnings("deprecation") |
| 278 | + McpError deprecatedError = new McpError("Deprecated error format"); |
| 279 | + McpClientSession.RequestHandler<?> failingHandler = params -> Mono.error(deprecatedError); |
| 280 | + |
| 281 | + transport = new MockMcpClientTransport(); |
| 282 | + session = new McpClientSession(TIMEOUT, transport, Map.of(testMethod, failingHandler), Map.of()); |
| 283 | + |
| 284 | + // Simulate incoming request that will trigger the error |
| 285 | + McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod, |
| 286 | + "test-id", null); |
| 287 | + transport.simulateIncomingMessage(request); |
| 288 | + |
| 289 | + // Verify: The response should create a new INTERNAL_ERROR with aggregated |
| 290 | + // messages |
| 291 | + McpSchema.JSONRPCMessage sentMessage = transport.getLastSentMessage(); |
| 292 | + assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); |
| 293 | + McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; |
| 294 | + assertThat(response.error()).isNotNull(); |
| 295 | + assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.INTERNAL_ERROR); |
| 296 | + assertThat(response.error().message()).isEqualTo("Deprecated error format"); |
| 297 | + // Verify data field contains aggregated exception messages |
| 298 | + assertThat(response.error().data()).isNotNull(); |
| 299 | + assertThat(response.error().data().toString()).contains("McpError"); |
| 300 | + assertThat(response.error().data().toString()).contains("Deprecated error format"); |
| 301 | + } |
| 302 | + |
| 303 | + @Test |
| 304 | + void testRequestHandlerThrowsResourceNotFoundError() { |
| 305 | + // Setup: Create a request handler that throws RESOURCE_NOT_FOUND error |
| 306 | + String testMethod = "test.resourceError"; |
| 307 | + String resourceUri = "file:///missing/resource.txt"; |
| 308 | + McpClientSession.RequestHandler<?> failingHandler = params -> Mono |
| 309 | + .error(McpError.RESOURCE_NOT_FOUND.apply(resourceUri)); |
| 310 | + |
| 311 | + transport = new MockMcpClientTransport(); |
| 312 | + session = new McpClientSession(TIMEOUT, transport, Map.of(testMethod, failingHandler), Map.of()); |
| 313 | + |
| 314 | + // Simulate incoming request that will trigger the error |
| 315 | + McpSchema.JSONRPCRequest request = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, testMethod, |
| 316 | + "test-id", null); |
| 317 | + transport.simulateIncomingMessage(request); |
| 318 | + |
| 319 | + // Verify: The response should preserve the RESOURCE_NOT_FOUND error code and |
| 320 | + // data |
| 321 | + McpSchema.JSONRPCMessage sentMessage = transport.getLastSentMessage(); |
| 322 | + assertThat(sentMessage).isInstanceOf(McpSchema.JSONRPCResponse.class); |
| 323 | + McpSchema.JSONRPCResponse response = (McpSchema.JSONRPCResponse) sentMessage; |
| 324 | + assertThat(response.error()).isNotNull(); |
| 325 | + assertThat(response.error().code()).isEqualTo(McpSchema.ErrorCodes.RESOURCE_NOT_FOUND); |
| 326 | + assertThat(response.error().message()).isEqualTo("Resource not found"); |
| 327 | + assertThat(response.error().data()).isEqualTo(Map.of("uri", resourceUri)); |
| 328 | + } |
| 329 | + |
193 | 330 | @Test |
194 | 331 | void testGracefulShutdown() { |
195 | 332 | StepVerifier.create(session.closeGracefully()).verifyComplete(); |
|
0 commit comments