Skip to content

Commit 0ba1546

Browse files
alxkmsobychacko
authored andcommitted
test: Enhanced test coverage for McpClientAutoConfigurationRuntimeHintsTests
* Test infrastructure: Added @beforeeach setup and extracted constants for better maintainability * Idempotency testing: Verifies multiple registration calls don't create duplicates for both reflection and resource hints * Member category validation: Ensures all MemberCategory values are registered for MCP client types * Resource pattern verification: Tests for JSON resource pattern registration and persistence * Nested class validation: Specific tests for McpStdioClientProperties.Parameters registration * ClassLoader scenarios: Tests with null ClassLoader for typical Spring Boot usage Signed-off-by: Alex Klimenko <[email protected]>
1 parent 1256eaf commit 0ba1546

File tree

1 file changed

+119
-6
lines changed

1 file changed

+119
-6
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/test/java/org/springframework/ai/mcp/client/common/autoconfigure/McpClientAutoConfigurationRuntimeHintsTests.java

Lines changed: 119 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@
2121
import java.util.Set;
2222

2323
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.api.BeforeEach;
2425

2526
import org.springframework.ai.mcp.client.common.autoconfigure.aot.McpClientAutoConfigurationRuntimeHints;
2627
import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpStdioClientProperties;
2728
import org.springframework.aot.hint.RuntimeHints;
2829
import org.springframework.aot.hint.TypeReference;
30+
import org.springframework.aot.hint.MemberCategory;
2931
import org.springframework.core.io.Resource;
3032
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
3133

@@ -37,19 +39,30 @@
3739
*/
3840
public class McpClientAutoConfigurationRuntimeHintsTests {
3941

42+
private static final String MCP_CLIENT_PACKAGE = "org.springframework.ai.mcp.client.autoconfigure";
43+
44+
private static final String JSON_PATTERN = "**.json";
45+
46+
private RuntimeHints runtimeHints;
47+
48+
private McpClientAutoConfigurationRuntimeHints mcpRuntimeHints;
49+
50+
@BeforeEach
51+
void setUp() {
52+
runtimeHints = new RuntimeHints();
53+
mcpRuntimeHints = new McpClientAutoConfigurationRuntimeHints();
54+
}
55+
4056
@Test
4157
void registerHints() throws IOException {
4258

43-
RuntimeHints runtimeHints = new RuntimeHints();
44-
45-
McpClientAutoConfigurationRuntimeHints mcpRuntimeHints = new McpClientAutoConfigurationRuntimeHints();
4659
mcpRuntimeHints.registerHints(runtimeHints, null);
4760

4861
boolean hasJsonPattern = runtimeHints.resources()
4962
.resourcePatternHints()
5063
.anyMatch(resourceHints -> resourceHints.getIncludes()
5164
.stream()
52-
.anyMatch(pattern -> "**.json".equals(pattern.getPattern())));
65+
.anyMatch(pattern -> JSON_PATTERN.equals(pattern.getPattern())));
5366

5467
assertThat(hasJsonPattern).as("The **.json resource pattern should be registered").isTrue();
5568

@@ -80,8 +93,7 @@ else if (path.endsWith("/nested/nested-config.json")) {
8093

8194
assertThat(foundSubfolderJson).as("nested-config.json should exist in the nested subfolder").isTrue();
8295

83-
Set<TypeReference> jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage(
84-
"org.springframework.ai.mcp.client.autoconfigure");
96+
Set<TypeReference> jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage(MCP_CLIENT_PACKAGE);
8597

8698
Set<TypeReference> registeredTypes = new HashSet<>();
8799
runtimeHints.reflection().typeHints().forEach(typeHint -> registeredTypes.add(typeHint.getType()));
@@ -97,4 +109,105 @@ else if (path.endsWith("/nested/nested-config.json")) {
97109
.isTrue();
98110
}
99111

112+
@Test
113+
void registerHintsWithNullClassLoader() {
114+
// Test that registering hints with null ClassLoader works correctly
115+
mcpRuntimeHints.registerHints(runtimeHints, null);
116+
117+
boolean hasJsonPattern = runtimeHints.resources()
118+
.resourcePatternHints()
119+
.anyMatch(resourceHints -> resourceHints.getIncludes()
120+
.stream()
121+
.anyMatch(pattern -> JSON_PATTERN.equals(pattern.getPattern())));
122+
123+
assertThat(hasJsonPattern).as("The **.json resource pattern should be registered with null ClassLoader")
124+
.isTrue();
125+
}
126+
127+
@Test
128+
void allMemberCategoriesAreRegistered() {
129+
mcpRuntimeHints.registerHints(runtimeHints, null);
130+
131+
Set<TypeReference> jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage(MCP_CLIENT_PACKAGE);
132+
133+
// Verify that all MemberCategory values are registered for each type
134+
runtimeHints.reflection().typeHints().forEach(typeHint -> {
135+
if (jsonAnnotatedClasses.contains(typeHint.getType())) {
136+
Set<MemberCategory> expectedCategories = Set.of(MemberCategory.values());
137+
Set<MemberCategory> actualCategories = typeHint.getMemberCategories();
138+
assertThat(actualCategories.containsAll(expectedCategories)).isTrue();
139+
}
140+
});
141+
}
142+
143+
@Test
144+
void verifySpecificMcpClientClasses() {
145+
mcpRuntimeHints.registerHints(runtimeHints, null);
146+
147+
Set<TypeReference> registeredTypes = new HashSet<>();
148+
runtimeHints.reflection().typeHints().forEach(typeHint -> registeredTypes.add(typeHint.getType()));
149+
150+
// Verify specific MCP client classes are registered
151+
assertThat(registeredTypes.contains(TypeReference.of(McpStdioClientProperties.Parameters.class)))
152+
.as("McpStdioClientProperties.Parameters class should be registered")
153+
.isTrue();
154+
}
155+
156+
@Test
157+
void multipleRegistrationCallsAreIdempotent() {
158+
// Register hints multiple times and verify no duplicates
159+
mcpRuntimeHints.registerHints(runtimeHints, null);
160+
int firstRegistrationCount = (int) runtimeHints.reflection().typeHints().count();
161+
162+
mcpRuntimeHints.registerHints(runtimeHints, null);
163+
int secondRegistrationCount = (int) runtimeHints.reflection().typeHints().count();
164+
165+
assertThat(firstRegistrationCount).isEqualTo(secondRegistrationCount);
166+
167+
// Verify resource pattern registration is also idempotent
168+
boolean hasJsonPattern = runtimeHints.resources()
169+
.resourcePatternHints()
170+
.anyMatch(resourceHints -> resourceHints.getIncludes()
171+
.stream()
172+
.anyMatch(pattern -> JSON_PATTERN.equals(pattern.getPattern())));
173+
174+
assertThat(hasJsonPattern).as("JSON pattern should still be registered after multiple calls").isTrue();
175+
}
176+
177+
@Test
178+
void verifyJsonResourcePatternIsRegistered() {
179+
mcpRuntimeHints.registerHints(runtimeHints, null);
180+
181+
// Verify the specific JSON resource pattern is registered
182+
boolean hasJsonPattern = runtimeHints.resources()
183+
.resourcePatternHints()
184+
.anyMatch(resourceHints -> resourceHints.getIncludes()
185+
.stream()
186+
.anyMatch(pattern -> JSON_PATTERN.equals(pattern.getPattern())));
187+
188+
assertThat(hasJsonPattern).as("The **.json resource pattern should be registered").isTrue();
189+
}
190+
191+
@Test
192+
void verifyNestedClassesAreRegistered() {
193+
mcpRuntimeHints.registerHints(runtimeHints, null);
194+
195+
Set<TypeReference> registeredTypes = new HashSet<>();
196+
runtimeHints.reflection().typeHints().forEach(typeHint -> registeredTypes.add(typeHint.getType()));
197+
198+
// Verify nested classes are properly registered
199+
assertThat(registeredTypes.contains(TypeReference.of(McpStdioClientProperties.Parameters.class)))
200+
.as("Nested Parameters class should be registered")
201+
.isTrue();
202+
}
203+
204+
@Test
205+
void verifyResourcePatternHintsArePresentAfterRegistration() {
206+
mcpRuntimeHints.registerHints(runtimeHints, null);
207+
208+
// Verify that resource pattern hints are present
209+
long patternCount = runtimeHints.resources().resourcePatternHints().count();
210+
assertThat(patternCount).isGreaterThan(0);
211+
}
212+
100213
}

0 commit comments

Comments
 (0)