Skip to content

Commit 10d2f32

Browse files
committed
test: enhance resource reading tests
- Implement comprehensive resource content validation for text/plain and application/octet-stream MIME types - Validate resource count expectations (10 resources) - Add elicitation capability testing with ElicitRequest/ElicitResult support - Clean up unused imports (ObjectMapper, ImageFromDockerfile) Signed-off-by: Christian Tzolov <christian.tzolov@broadcom.com>
1 parent b539b98 commit 10d2f32

File tree

5 files changed

+218
-41
lines changed

5 files changed

+218
-41
lines changed

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/client/WebClientStreamableHttpAsyncClientTests.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package io.modelcontextprotocol.client;
22

3-
import com.fasterxml.jackson.databind.ObjectMapper;
43
import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport;
54
import io.modelcontextprotocol.spec.McpClientTransport;
65
import org.junit.jupiter.api.Timeout;
7-
import org.springframework.web.reactive.function.client.WebClient;
86
import org.testcontainers.containers.GenericContainer;
97
import org.testcontainers.containers.wait.strategy.Wait;
10-
import org.testcontainers.images.builder.ImageFromDockerfile;
8+
9+
import org.springframework.web.reactive.function.client.WebClient;
1110

1211
@Timeout(15)
1312
public class WebClientStreamableHttpAsyncClientTests extends AbstractMcpAsyncClientTests {

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

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities;
2020
import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest;
2121
import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult;
22+
import io.modelcontextprotocol.spec.McpSchema.ElicitRequest;
23+
import io.modelcontextprotocol.spec.McpSchema.ElicitResult;
2224
import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest;
2325
import io.modelcontextprotocol.spec.McpSchema.Prompt;
2426
import io.modelcontextprotocol.spec.McpSchema.Resource;
@@ -38,6 +40,7 @@
3840
import static org.assertj.core.api.Assertions.assertThat;
3941
import static org.assertj.core.api.Assertions.assertThatCode;
4042
import static org.assertj.core.api.Assertions.assertThatThrownBy;
43+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
4144

4245
/**
4346
* Test suite for the {@link McpAsyncClient} that can be used with different
@@ -339,18 +342,36 @@ void testRemoveNonExistentRoot() {
339342
}
340343

341344
@Test
342-
@Disabled
343345
void testReadResource() {
344-
withClient(createMcpTransport(), mcpAsyncClient -> {
345-
StepVerifier.create(mcpAsyncClient.listResources()).consumeNextWith(resources -> {
346-
if (!resources.resources().isEmpty()) {
347-
Resource firstResource = resources.resources().get(0);
348-
StepVerifier.create(mcpAsyncClient.readResource(firstResource)).consumeNextWith(result -> {
349-
assertThat(result).isNotNull();
350-
assertThat(result.contents()).isNotNull();
351-
}).verifyComplete();
352-
}
353-
}).verifyComplete();
346+
withClient(createMcpTransport(), client -> {
347+
Flux<McpSchema.ReadResourceResult> resources = client.initialize()
348+
.then(client.listResources(null))
349+
.flatMapMany(r -> Flux.fromIterable(r.resources()))
350+
.flatMap(r -> client.readResource(r));
351+
352+
StepVerifier.create(resources).consumeNextWith(resourceResult -> {
353+
assertThat(resourceResult.contents()).allSatisfy(content -> {
354+
if (content.mimeType().equals("text/plain")) {
355+
McpSchema.TextResourceContents text = assertInstanceOf(McpSchema.TextResourceContents.class,
356+
content);
357+
assertThat(text.mimeType()).isEqualTo("text/plain");
358+
assertThat(text.uri()).isNotEmpty();
359+
assertThat(text.text()).isNotEmpty();
360+
}
361+
else if (content.mimeType().equals("application/octet-stream")) {
362+
McpSchema.BlobResourceContents blob = assertInstanceOf(McpSchema.BlobResourceContents.class,
363+
content);
364+
assertThat(blob.mimeType()).isEqualTo("application/octet-stream");
365+
assertThat(blob.uri()).isNotEmpty();
366+
assertThat(blob.blob()).isNotEmpty();
367+
}
368+
else {
369+
throw new IllegalArgumentException("Unexpected content type: " + content.mimeType());
370+
}
371+
});
372+
})
373+
.expectNextCount(9) // Expect 9 more elements
374+
.verifyComplete();
354375
});
355376
}
356377

@@ -424,6 +445,20 @@ void testInitializeWithSamplingCapability() {
424445
});
425446
}
426447

448+
@Test
449+
void testInitializeWithElicitationCapability() {
450+
ClientCapabilities capabilities = ClientCapabilities.builder().elicitation().build();
451+
ElicitResult elicitResult = ElicitResult.builder()
452+
.message(ElicitResult.Action.ACCEPT)
453+
.content(Map.of("foo", "bar"))
454+
.build();
455+
withClient(createMcpTransport(),
456+
builder -> builder.capabilities(capabilities).elicitation(request -> Mono.just(elicitResult)),
457+
client -> {
458+
StepVerifier.create(client.initialize()).expectNextMatches(Objects::nonNull).verifyComplete();
459+
});
460+
}
461+
427462
@Test
428463
void testInitializeWithAllCapabilities() {
429464
var capabilities = ClientCapabilities.builder()
@@ -435,7 +470,11 @@ void testInitializeWithAllCapabilities() {
435470
Function<CreateMessageRequest, Mono<CreateMessageResult>> samplingHandler = request -> Mono
436471
.just(CreateMessageResult.builder().message("test").model("test-model").build());
437472

438-
withClient(createMcpTransport(), builder -> builder.capabilities(capabilities).sampling(samplingHandler),
473+
Function<ElicitRequest, Mono<ElicitResult>> elicitationHandler = request -> Mono
474+
.just(ElicitResult.builder().message(ElicitResult.Action.ACCEPT).content(Map.of("foo", "bar")).build());
475+
476+
withClient(createMcpTransport(),
477+
builder -> builder.capabilities(capabilities).sampling(samplingHandler).elicitation(elicitationHandler),
439478
client ->
440479

441480
StepVerifier.create(client.initialize()).assertNext(result -> {

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

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import java.util.List;
99
import java.util.Map;
1010
import java.util.concurrent.atomic.AtomicBoolean;
11+
import java.util.concurrent.atomic.AtomicInteger;
1112
import java.util.concurrent.atomic.AtomicReference;
1213
import java.util.function.Consumer;
1314
import java.util.function.Function;
1415

1516
import io.modelcontextprotocol.spec.McpClientTransport;
1617
import io.modelcontextprotocol.spec.McpSchema;
18+
import io.modelcontextprotocol.spec.McpSchema.BlobResourceContents;
1719
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
1820
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
1921
import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities;
@@ -22,22 +24,25 @@
2224
import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;
2325
import io.modelcontextprotocol.spec.McpSchema.ReadResourceResult;
2426
import io.modelcontextprotocol.spec.McpSchema.Resource;
27+
import io.modelcontextprotocol.spec.McpSchema.ResourceContents;
2528
import io.modelcontextprotocol.spec.McpSchema.Root;
2629
import io.modelcontextprotocol.spec.McpSchema.SubscribeRequest;
2730
import io.modelcontextprotocol.spec.McpSchema.TextContent;
31+
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
2832
import io.modelcontextprotocol.spec.McpSchema.Tool;
2933
import io.modelcontextprotocol.spec.McpSchema.UnsubscribeRequest;
3034
import org.junit.jupiter.api.AfterEach;
3135
import org.junit.jupiter.api.BeforeEach;
3236
import org.junit.jupiter.api.Test;
37+
import org.slf4j.Logger;
38+
import org.slf4j.LoggerFactory;
3339
import reactor.core.publisher.Mono;
34-
import reactor.core.scheduler.Scheduler;
35-
import reactor.core.scheduler.Schedulers;
3640
import reactor.test.StepVerifier;
3741

3842
import static org.assertj.core.api.Assertions.assertThat;
3943
import static org.assertj.core.api.Assertions.assertThatCode;
4044
import static org.assertj.core.api.Assertions.assertThatThrownBy;
45+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
4146

4247
/**
4348
* Unit tests for MCP Client Session functionality.
@@ -47,6 +52,8 @@
4752
*/
4853
public abstract class AbstractMcpSyncClientTests {
4954

55+
private static final Logger logger = LoggerFactory.getLogger(AbstractMcpSyncClientTests.class);
56+
5057
private static final String TEST_MESSAGE = "Hello MCP Spring AI!";
5158

5259
abstract protected McpClientTransport createMcpTransport();
@@ -330,18 +337,72 @@ void testReadResourceWithoutInitialization() {
330337

331338
@Test
332339
void testReadResource() {
340+
AtomicInteger readResourceCount = new AtomicInteger(0);
333341
withClient(createMcpTransport(), mcpSyncClient -> {
334342
mcpSyncClient.initialize();
335343
ListResourcesResult resources = mcpSyncClient.listResources(null);
336344

337-
if (!resources.resources().isEmpty()) {
338-
Resource firstResource = resources.resources().get(0);
339-
ReadResourceResult result = mcpSyncClient.readResource(firstResource);
345+
assertThat(resources).isNotNull();
346+
assertThat(resources.resources()).isNotNull();
347+
348+
if (resources.resources().isEmpty()) {
349+
throw new IllegalStateException("No resources available for testing readResource functionality");
350+
}
351+
352+
// Test reading each resource individually for better error isolation
353+
for (Resource resource : resources.resources()) {
354+
ReadResourceResult result = mcpSyncClient.readResource(resource);
340355

341356
assertThat(result).isNotNull();
342-
assertThat(result.contents()).isNotNull();
357+
assertThat(result.contents()).isNotNull().isNotEmpty();
358+
359+
readResourceCount.incrementAndGet();
360+
361+
// Validate each content item
362+
for (ResourceContents content : result.contents()) {
363+
assertThat(content).isNotNull();
364+
assertThat(content.uri()).isNotNull().isNotEmpty();
365+
assertThat(content.mimeType()).isNotNull().isNotEmpty();
366+
367+
// Validate content based on its type with more comprehensive
368+
// checks
369+
switch (content.mimeType()) {
370+
case "text/plain" -> {
371+
TextResourceContents textContent = assertInstanceOf(TextResourceContents.class, content);
372+
assertThat(textContent.text()).isNotNull().isNotEmpty();
373+
// Verify URI consistency
374+
assertThat(textContent.uri()).isEqualTo(resource.uri());
375+
}
376+
case "application/octet-stream" -> {
377+
BlobResourceContents blobContent = assertInstanceOf(BlobResourceContents.class, content);
378+
assertThat(blobContent.blob()).isNotNull().isNotEmpty();
379+
// Verify URI consistency
380+
assertThat(blobContent.uri()).isEqualTo(resource.uri());
381+
// Validate base64 encoding format
382+
assertThat(blobContent.blob()).matches("^[A-Za-z0-9+/]*={0,2}$");
383+
}
384+
default -> {
385+
// More flexible handling of additional MIME types
386+
// Log the unexpected type for debugging but don't fail
387+
// the test
388+
logger.warn("Warning: Encountered unexpected MIME type: {} for resource: {}",
389+
content.mimeType(), resource.uri());
390+
391+
// Still validate basic properties
392+
if (content instanceof TextResourceContents textContent) {
393+
assertThat(textContent.text()).isNotNull();
394+
}
395+
else if (content instanceof BlobResourceContents blobContent) {
396+
assertThat(blobContent.blob()).isNotNull();
397+
}
398+
}
399+
}
400+
}
343401
}
344402
});
403+
404+
// Assert that we read exactly 10 resources
405+
assertThat(readResourceCount.get()).isEqualTo(10);
345406
}
346407

347408
@Test

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

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import static org.assertj.core.api.Assertions.assertThat;
4141
import static org.assertj.core.api.Assertions.assertThatCode;
4242
import static org.assertj.core.api.Assertions.assertThatThrownBy;
43+
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
4344

4445
/**
4546
* Test suite for the {@link McpAsyncClient} that can be used with different
@@ -342,18 +343,34 @@ void testRemoveNonExistentRoot() {
342343
}
343344

344345
@Test
345-
@Disabled
346346
void testReadResource() {
347-
withClient(createMcpTransport(), mcpAsyncClient -> {
348-
StepVerifier.create(mcpAsyncClient.listResources()).consumeNextWith(resources -> {
349-
if (!resources.resources().isEmpty()) {
350-
Resource firstResource = resources.resources().get(0);
351-
StepVerifier.create(mcpAsyncClient.readResource(firstResource)).consumeNextWith(result -> {
352-
assertThat(result).isNotNull();
353-
assertThat(result.contents()).isNotNull();
354-
}).verifyComplete();
355-
}
356-
}).verifyComplete();
347+
withClient(createMcpTransport(), client -> {
348+
Flux<McpSchema.ReadResourceResult> resources = client.initialize()
349+
.then(client.listResources(null))
350+
.flatMapMany(r -> Flux.fromIterable(r.resources()))
351+
.flatMap(r -> client.readResource(r));
352+
353+
StepVerifier.create(resources).consumeNextWith(resourceResult -> {
354+
assertThat(resourceResult.contents()).allSatisfy(content -> {
355+
if (content.mimeType().equals("text/plain")) {
356+
McpSchema.TextResourceContents text = assertInstanceOf(McpSchema.TextResourceContents.class,
357+
content);
358+
assertThat(text.mimeType()).isEqualTo("text/plain");
359+
assertThat(text.uri()).isNotEmpty();
360+
assertThat(text.text()).isNotEmpty();
361+
}
362+
else if (content.mimeType().equals("application/octet-stream")) {
363+
McpSchema.BlobResourceContents blob = assertInstanceOf(McpSchema.BlobResourceContents.class,
364+
content);
365+
assertThat(blob.mimeType()).isEqualTo("application/octet-stream");
366+
assertThat(blob.uri()).isNotEmpty();
367+
assertThat(blob.blob()).isNotEmpty();
368+
}
369+
else {
370+
throw new IllegalArgumentException("Unexpected content type: " + content.mimeType());
371+
}
372+
});
373+
}).expectNextCount(9).verifyComplete();
357374
});
358375
}
359376

0 commit comments

Comments
 (0)