21
21
import java .util .Set ;
22
22
23
23
import org .junit .jupiter .api .Test ;
24
+ import org .junit .jupiter .api .BeforeEach ;
24
25
25
26
import org .springframework .ai .mcp .client .common .autoconfigure .aot .McpClientAutoConfigurationRuntimeHints ;
26
27
import org .springframework .ai .mcp .client .common .autoconfigure .properties .McpStdioClientProperties ;
27
28
import org .springframework .aot .hint .RuntimeHints ;
28
29
import org .springframework .aot .hint .TypeReference ;
30
+ import org .springframework .aot .hint .MemberCategory ;
29
31
import org .springframework .core .io .Resource ;
30
32
import org .springframework .core .io .support .PathMatchingResourcePatternResolver ;
31
33
37
39
*/
38
40
public class McpClientAutoConfigurationRuntimeHintsTests {
39
41
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
+
40
56
@ Test
41
57
void registerHints () throws IOException {
42
58
43
- RuntimeHints runtimeHints = new RuntimeHints ();
44
-
45
- McpClientAutoConfigurationRuntimeHints mcpRuntimeHints = new McpClientAutoConfigurationRuntimeHints ();
46
59
mcpRuntimeHints .registerHints (runtimeHints , null );
47
60
48
61
boolean hasJsonPattern = runtimeHints .resources ()
49
62
.resourcePatternHints ()
50
63
.anyMatch (resourceHints -> resourceHints .getIncludes ()
51
64
.stream ()
52
- .anyMatch (pattern -> "**.json" .equals (pattern .getPattern ())));
65
+ .anyMatch (pattern -> JSON_PATTERN .equals (pattern .getPattern ())));
53
66
54
67
assertThat (hasJsonPattern ).as ("The **.json resource pattern should be registered" ).isTrue ();
55
68
@@ -80,8 +93,7 @@ else if (path.endsWith("/nested/nested-config.json")) {
80
93
81
94
assertThat (foundSubfolderJson ).as ("nested-config.json should exist in the nested subfolder" ).isTrue ();
82
95
83
- Set <TypeReference > jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage (
84
- "org.springframework.ai.mcp.client.autoconfigure" );
96
+ Set <TypeReference > jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage (MCP_CLIENT_PACKAGE );
85
97
86
98
Set <TypeReference > registeredTypes = new HashSet <>();
87
99
runtimeHints .reflection ().typeHints ().forEach (typeHint -> registeredTypes .add (typeHint .getType ()));
@@ -97,4 +109,105 @@ else if (path.endsWith("/nested/nested-config.json")) {
97
109
.isTrue ();
98
110
}
99
111
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
+
100
213
}
0 commit comments