Skip to content

Commit eaca392

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents 4027b90 + 59f2b3b commit eaca392

File tree

72 files changed

+5748
-67
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+5748
-67
lines changed

advisors/spring-ai-advisors-vector-store/src/test/java/org/springframework/ai/chat/client/advisor/vectorstore/VectorStoreChatMemoryAdvisorTests.java

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.mockito.Mockito;
2121

22+
import org.springframework.ai.chat.prompt.PromptTemplate;
2223
import org.springframework.ai.vectorstore.VectorStore;
24+
import reactor.core.scheduler.Scheduler;
2325

26+
import static org.assertj.core.api.Assertions.assertThat;
2427
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2528

2629
/**
@@ -91,4 +94,116 @@ void whenDefaultTopKIsNegativeThenThrow() {
9194
.hasMessageContaining("topK must be greater than 0");
9295
}
9396

97+
@Test
98+
void whenBuilderWithValidVectorStoreThenSuccess() {
99+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
100+
101+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore).build();
102+
103+
assertThat(advisor).isNotNull();
104+
}
105+
106+
@Test
107+
void whenBuilderWithAllValidParametersThenSuccess() {
108+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
109+
Scheduler scheduler = Mockito.mock(Scheduler.class);
110+
PromptTemplate systemPromptTemplate = Mockito.mock(PromptTemplate.class);
111+
112+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
113+
.conversationId("test-conversation")
114+
.scheduler(scheduler)
115+
.systemPromptTemplate(systemPromptTemplate)
116+
.defaultTopK(5)
117+
.build();
118+
119+
assertThat(advisor).isNotNull();
120+
}
121+
122+
@Test
123+
void whenDefaultConversationIdIsBlankThenThrow() {
124+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
125+
126+
assertThatThrownBy(() -> VectorStoreChatMemoryAdvisor.builder(vectorStore).conversationId(" ").build())
127+
.isInstanceOf(IllegalArgumentException.class)
128+
.hasMessageContaining("defaultConversationId cannot be null or empty");
129+
}
130+
131+
@Test
132+
void whenBuilderWithValidConversationIdThenSuccess() {
133+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
134+
135+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
136+
.conversationId("valid-id")
137+
.build();
138+
139+
assertThat(advisor).isNotNull();
140+
}
141+
142+
@Test
143+
void whenBuilderWithValidTopKThenSuccess() {
144+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
145+
146+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
147+
.defaultTopK(10)
148+
.build();
149+
150+
assertThat(advisor).isNotNull();
151+
}
152+
153+
@Test
154+
void whenBuilderWithMinimumTopKThenSuccess() {
155+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
156+
157+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore).defaultTopK(1).build();
158+
159+
assertThat(advisor).isNotNull();
160+
}
161+
162+
@Test
163+
void whenBuilderWithLargeTopKThenSuccess() {
164+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
165+
166+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
167+
.defaultTopK(1000)
168+
.build();
169+
170+
assertThat(advisor).isNotNull();
171+
}
172+
173+
@Test
174+
void whenBuilderCalledMultipleTimesWithSameVectorStoreThenSuccess() {
175+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
176+
177+
VectorStoreChatMemoryAdvisor advisor1 = VectorStoreChatMemoryAdvisor.builder(vectorStore).build();
178+
VectorStoreChatMemoryAdvisor advisor2 = VectorStoreChatMemoryAdvisor.builder(vectorStore).build();
179+
180+
assertThat(advisor1).isNotNull();
181+
assertThat(advisor2).isNotNull();
182+
assertThat(advisor1).isNotSameAs(advisor2);
183+
}
184+
185+
@Test
186+
void whenBuilderWithCustomSchedulerThenSuccess() {
187+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
188+
Scheduler customScheduler = Mockito.mock(Scheduler.class);
189+
190+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
191+
.scheduler(customScheduler)
192+
.build();
193+
194+
assertThat(advisor).isNotNull();
195+
}
196+
197+
@Test
198+
void whenBuilderWithCustomSystemPromptTemplateThenSuccess() {
199+
VectorStore vectorStore = Mockito.mock(VectorStore.class);
200+
PromptTemplate customTemplate = Mockito.mock(PromptTemplate.class);
201+
202+
VectorStoreChatMemoryAdvisor advisor = VectorStoreChatMemoryAdvisor.builder(vectorStore)
203+
.systemPromptTemplate(customTemplate)
204+
.build();
205+
206+
assertThat(advisor).isNotNull();
207+
}
208+
94209
}

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
}

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/StreamableHttpHttpClientTransportAutoConfiguration.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import com.fasterxml.jackson.databind.ObjectMapper;
3636

3737
import io.modelcontextprotocol.client.McpSyncClient;
38-
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
3938
import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport;
4039
import io.modelcontextprotocol.spec.McpSchema;
4140

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerAutoConfiguration.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,9 @@ public McpSyncServer mcpSyncServer(McpServerTransportProvider transportProvider,
248248
}
249249

250250
rootsChangeConsumers.ifAvailable(consumer -> {
251-
serverBuilder.rootsChangeHandler((exchange, roots) -> consumer.accept((McpSyncServerExchange) exchange,
252-
(List<McpSchema.Root>) roots));
251+
BiConsumer<McpSyncServerExchange, List<McpSchema.Root>> syncConsumer = (exchange, roots) -> consumer
252+
.accept(exchange, roots);
253+
serverBuilder.rootsChangeHandler(syncConsumer);
253254
logger.info("Registered roots change consumer");
254255
});
255256

0 commit comments

Comments
 (0)