Skip to content

Commit 3e0d8c5

Browse files
committed
Merge branch 'main' into refactor-server-session
2 parents 8704863 + 9c2b836 commit 3e0d8c5

File tree

4 files changed

+46
-107
lines changed

4 files changed

+46
-107
lines changed

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 27 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ void tearDown() {
109109
onClose();
110110
}
111111

112+
<T> void verifyInitializationTimeout(Function<McpAsyncClient, Mono<T>> operation, String action) {
113+
withClient(createMcpTransport(), mcpAsyncClient -> {
114+
StepVerifier.withVirtualTime(() -> operation.apply(mcpAsyncClient))
115+
.expectSubscription()
116+
.thenAwait(getInitializationTimeout())
117+
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
118+
.hasMessage("Client must be initialized before " + action))
119+
.verify();
120+
});
121+
}
122+
112123
@Test
113124
void testConstructorWithInvalidArguments() {
114125
assertThatThrownBy(() -> McpClient.async(null).build()).isInstanceOf(IllegalArgumentException.class)
@@ -121,14 +132,7 @@ void testConstructorWithInvalidArguments() {
121132

122133
@Test
123134
void testListToolsWithoutInitialization() {
124-
withClient(createMcpTransport(), mcpAsyncClient -> {
125-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.listTools(null))
126-
.expectSubscription()
127-
.thenAwait(getInitializationTimeout())
128-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
129-
.hasMessage("Client must be initialized before listing tools"))
130-
.verify();
131-
});
135+
verifyInitializationTimeout(client -> client.listTools(null), "listing tools");
132136
}
133137

134138
@Test
@@ -148,14 +152,7 @@ void testListTools() {
148152

149153
@Test
150154
void testPingWithoutInitialization() {
151-
withClient(createMcpTransport(), mcpAsyncClient -> {
152-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.ping())
153-
.expectSubscription()
154-
.thenAwait(getInitializationTimeout())
155-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
156-
.hasMessage("Client must be initialized before pinging the " + "server"))
157-
.verify();
158-
});
155+
verifyInitializationTimeout(client -> client.ping(), "pinging the server");
159156
}
160157

161158
@Test
@@ -169,16 +166,8 @@ void testPing() {
169166

170167
@Test
171168
void testCallToolWithoutInitialization() {
172-
withClient(createMcpTransport(), mcpAsyncClient -> {
173-
CallToolRequest callToolRequest = new CallToolRequest("echo", Map.of("message", ECHO_TEST_MESSAGE));
174-
175-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.callTool(callToolRequest))
176-
.expectSubscription()
177-
.thenAwait(getInitializationTimeout())
178-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
179-
.hasMessage("Client must be initialized before calling tools"))
180-
.verify();
181-
});
169+
CallToolRequest callToolRequest = new CallToolRequest("echo", Map.of("message", ECHO_TEST_MESSAGE));
170+
verifyInitializationTimeout(client -> client.callTool(callToolRequest), "calling tools");
182171
}
183172

184173
@Test
@@ -212,14 +201,7 @@ void testCallToolWithInvalidTool() {
212201

213202
@Test
214203
void testListResourcesWithoutInitialization() {
215-
withClient(createMcpTransport(), mcpAsyncClient -> {
216-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.listResources(null))
217-
.expectSubscription()
218-
.thenAwait(getInitializationTimeout())
219-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
220-
.hasMessage("Client must be initialized before listing resources"))
221-
.verify();
222-
});
204+
verifyInitializationTimeout(client -> client.listResources(null), "listing resources");
223205
}
224206

225207
@Test
@@ -250,14 +232,7 @@ void testMcpAsyncClientState() {
250232

251233
@Test
252234
void testListPromptsWithoutInitialization() {
253-
withClient(createMcpTransport(), mcpAsyncClient -> {
254-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.listPrompts(null))
255-
.expectSubscription()
256-
.thenAwait(getInitializationTimeout())
257-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
258-
.hasMessage("Client must be initialized before listing prompts"))
259-
.verify();
260-
});
235+
verifyInitializationTimeout(client -> client.listPrompts(null), "listing " + "prompts");
261236
}
262237

263238
@Test
@@ -281,16 +256,8 @@ void testListPrompts() {
281256

282257
@Test
283258
void testGetPromptWithoutInitialization() {
284-
withClient(createMcpTransport(), mcpAsyncClient -> {
285-
GetPromptRequest request = new GetPromptRequest("simple_prompt", Map.of());
286-
287-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.getPrompt(request))
288-
.expectSubscription()
289-
.thenAwait(getInitializationTimeout())
290-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
291-
.hasMessage("Client must be initialized before getting prompts"))
292-
.verify();
293-
});
259+
GetPromptRequest request = new GetPromptRequest("simple_prompt", Map.of());
260+
verifyInitializationTimeout(client -> client.getPrompt(request), "getting " + "prompts");
294261
}
295262

296263
@Test
@@ -311,14 +278,8 @@ void testGetPrompt() {
311278

312279
@Test
313280
void testRootsListChangedWithoutInitialization() {
314-
withClient(createMcpTransport(), mcpAsyncClient -> {
315-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.rootsListChangedNotification())
316-
.expectSubscription()
317-
.thenAwait(getInitializationTimeout())
318-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
319-
.hasMessage("Client must be initialized before sending roots list changed notification"))
320-
.verify();
321-
});
281+
verifyInitializationTimeout(client -> client.rootsListChangedNotification(),
282+
"sending roots list changed notification");
322283
}
323284

324285
@Test
@@ -392,14 +353,7 @@ void testReadResource() {
392353

393354
@Test
394355
void testListResourceTemplatesWithoutInitialization() {
395-
withClient(createMcpTransport(), mcpAsyncClient -> {
396-
StepVerifier.withVirtualTime(() -> mcpAsyncClient.listResourceTemplates())
397-
.expectSubscription()
398-
.thenAwait(getInitializationTimeout())
399-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
400-
.hasMessage("Client must be initialized before listing resource templates"))
401-
.verify();
402-
});
356+
verifyInitializationTimeout(client -> client.listResourceTemplates(), "listing resource templates");
403357
}
404358

405359
@Test
@@ -446,18 +400,9 @@ void testNotificationHandlers() {
446400
resources -> Mono.fromRunnable(() -> resourcesNotificationReceived.set(true)))
447401
.promptsChangeConsumer(prompts -> Mono.fromRunnable(() -> promptsNotificationReceived.set(true))),
448402
mcpAsyncClient -> {
449-
450-
var transport = createMcpTransport();
451-
var client = McpClient.async(transport)
452-
.requestTimeout(getRequestTimeout())
453-
.toolsChangeConsumer(tools -> Mono.fromRunnable(() -> toolsNotificationReceived.set(true)))
454-
.resourcesChangeConsumer(
455-
resources -> Mono.fromRunnable(() -> resourcesNotificationReceived.set(true)))
456-
.promptsChangeConsumer(
457-
prompts -> Mono.fromRunnable(() -> promptsNotificationReceived.set(true)))
458-
.build();
459-
460-
StepVerifier.create(client.initialize()).expectNextMatches(Objects::nonNull).verifyComplete();
403+
StepVerifier.create(mcpAsyncClient.initialize())
404+
.expectNextMatches(Objects::nonNull)
405+
.verifyComplete();
461406
});
462407
}
463408

@@ -501,14 +446,8 @@ void testInitializeWithAllCapabilities() {
501446

502447
@Test
503448
void testLoggingLevelsWithoutInitialization() {
504-
withClient(createMcpTransport(),
505-
mcpAsyncClient -> StepVerifier
506-
.withVirtualTime(() -> mcpAsyncClient.setLoggingLevel(McpSchema.LoggingLevel.DEBUG))
507-
.expectSubscription()
508-
.thenAwait(getInitializationTimeout())
509-
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
510-
.hasMessage("Client must be initialized before setting logging level"))
511-
.verify());
449+
verifyInitializationTimeout(client -> client.setLoggingLevel(McpSchema.LoggingLevel.DEBUG),
450+
"setting logging level");
512451
}
513452

514453
@Test

mcp-test/src/main/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,15 +119,20 @@ <T> void verifyNotificationTimesOut(Consumer<McpSyncClient> operation, String ac
119119
}, action);
120120
}
121121

122-
<T> void verifyCallTimesOut(Function<McpSyncClient, T> operation, String action) {
122+
<T> void verifyCallTimesOut(Function<McpSyncClient, T> blockingOperation, String action) {
123123
withClient(createMcpTransport(), mcpSyncClient -> {
124124
// This scheduler is not replaced by virtual time scheduler
125125
Scheduler customScheduler = Schedulers.newBoundedElastic(1, 1, "actualBoundedElastic");
126126

127-
StepVerifier.withVirtualTime(() -> Mono.fromSupplier(() -> operation.apply(mcpSyncClient))
128-
// offload the blocking call to the real scheduler
127+
StepVerifier.withVirtualTime(() -> Mono.fromSupplier(() -> blockingOperation.apply(mcpSyncClient))
128+
// Offload the blocking call to the real scheduler
129129
.subscribeOn(customScheduler))
130130
.expectSubscription()
131+
// This works without actually waiting but executes all the
132+
// tasks pending execution on the VirtualTimeScheduler.
133+
// It is possible to execute the blocking code from the operation
134+
// because it is blocked on a dedicated Scheduler and the main
135+
// flow is not blocked and uses the VirtualTimeScheduler.
131136
.thenAwait(getInitializationTimeout())
132137
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
133138
.hasMessage("Client must be initialized before " + action))

mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpAsyncClientTests.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import java.util.concurrent.atomic.AtomicReference;
1212
import java.util.function.Consumer;
1313
import java.util.function.Function;
14-
import java.util.function.Supplier;
1514

1615
import io.modelcontextprotocol.spec.ClientMcpTransport;
1716
import io.modelcontextprotocol.spec.McpError;
@@ -402,18 +401,9 @@ void testNotificationHandlers() {
402401
resources -> Mono.fromRunnable(() -> resourcesNotificationReceived.set(true)))
403402
.promptsChangeConsumer(prompts -> Mono.fromRunnable(() -> promptsNotificationReceived.set(true))),
404403
mcpAsyncClient -> {
405-
406-
var transport = createMcpTransport();
407-
var client = McpClient.async(transport)
408-
.requestTimeout(getRequestTimeout())
409-
.toolsChangeConsumer(tools -> Mono.fromRunnable(() -> toolsNotificationReceived.set(true)))
410-
.resourcesChangeConsumer(
411-
resources -> Mono.fromRunnable(() -> resourcesNotificationReceived.set(true)))
412-
.promptsChangeConsumer(
413-
prompts -> Mono.fromRunnable(() -> promptsNotificationReceived.set(true)))
414-
.build();
415-
416-
StepVerifier.create(client.initialize()).expectNextMatches(Objects::nonNull).verifyComplete();
404+
StepVerifier.create(mcpAsyncClient.initialize())
405+
.expectNextMatches(Objects::nonNull)
406+
.verifyComplete();
417407
});
418408
}
419409

mcp/src/test/java/io/modelcontextprotocol/client/AbstractMcpSyncClientTests.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,15 +120,20 @@ <T> void verifyNotificationTimesOut(Consumer<McpSyncClient> operation, String ac
120120
}, action);
121121
}
122122

123-
<T> void verifyCallTimesOut(Function<McpSyncClient, T> operation, String action) {
123+
<T> void verifyCallTimesOut(Function<McpSyncClient, T> blockingOperation, String action) {
124124
withClient(createMcpTransport(), mcpSyncClient -> {
125125
// This scheduler is not replaced by virtual time scheduler
126126
Scheduler customScheduler = Schedulers.newBoundedElastic(1, 1, "actualBoundedElastic");
127127

128-
StepVerifier.withVirtualTime(() -> Mono.fromSupplier(() -> operation.apply(mcpSyncClient))
129-
// offload the blocking call to the real scheduler
128+
StepVerifier.withVirtualTime(() -> Mono.fromSupplier(() -> blockingOperation.apply(mcpSyncClient))
129+
// Offload the blocking call to the real scheduler
130130
.subscribeOn(customScheduler))
131131
.expectSubscription()
132+
// This works without actually waiting but executes all the
133+
// tasks pending execution on the VirtualTimeScheduler.
134+
// It is possible to execute the blocking code from the operation
135+
// because it is blocked on a dedicated Scheduler and the main
136+
// flow is not blocked and uses the VirtualTimeScheduler.
132137
.thenAwait(getInitializationTimeout())
133138
.consumeErrorWith(e -> assertThat(e).isInstanceOf(McpError.class)
134139
.hasMessage("Client must be initialized before " + action))

0 commit comments

Comments
 (0)