2121import java .util .Set ;
2222
2323import org .junit .jupiter .api .Test ;
24+ import org .junit .jupiter .api .BeforeEach ;
2425
2526import org .springframework .ai .mcp .client .common .autoconfigure .aot .McpClientAutoConfigurationRuntimeHints ;
2627import org .springframework .ai .mcp .client .common .autoconfigure .properties .McpStdioClientProperties ;
2728import org .springframework .aot .hint .RuntimeHints ;
2829import org .springframework .aot .hint .TypeReference ;
30+ import org .springframework .aot .hint .MemberCategory ;
2931import org .springframework .core .io .Resource ;
3032import org .springframework .core .io .support .PathMatchingResourcePatternResolver ;
3133
3739 */
3840public 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