Skip to content

Commit 24845ca

Browse files
authored
test: enhance test coverage for ChatClient response handling and builder functionality (#4216)
Signed-off-by: Oleksandr Klymenko <[email protected]>
1 parent e09482c commit 24845ca

File tree

3 files changed

+289
-1
lines changed

3 files changed

+289
-1
lines changed

spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/ChatClientResponseEntityTests.java

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.springframework.core.ParameterizedTypeReference;
3939

4040
import static org.assertj.core.api.Assertions.assertThat;
41+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
4142
import static org.mockito.BDDMockito.given;
4243

4344
/**
@@ -136,8 +137,104 @@ public void customSoCResponseEntityTest() {
136137
assertThat(userMessage.getText()).contains("Tell me about Max");
137138
}
138139

139-
record MyBean(String name, int age) {
140+
@Test
141+
public void whenEmptyResponseContentThenHandleGracefully() {
142+
var chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage(""))));
143+
given(this.chatModel.call(this.promptCaptor.capture())).willReturn(chatResponse);
144+
145+
assertThatThrownBy(() -> ChatClient.builder(this.chatModel)
146+
.build()
147+
.prompt()
148+
.user("test")
149+
.call()
150+
.responseEntity(MyBean.class)).isInstanceOf(RuntimeException.class);
151+
}
152+
153+
@Test
154+
public void whenInvalidJsonResponseThenThrows() {
155+
var chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("invalid json content"))));
156+
given(this.chatModel.call(this.promptCaptor.capture())).willReturn(chatResponse);
157+
158+
assertThatThrownBy(() -> ChatClient.builder(this.chatModel)
159+
.build()
160+
.prompt()
161+
.user("test")
162+
.call()
163+
.responseEntity(MyBean.class)).isInstanceOf(RuntimeException.class);
164+
}
165+
166+
@Test
167+
public void whenParameterizedTypeWithMapThenParseCorrectly() {
168+
var chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("""
169+
{
170+
"key1": "value1",
171+
"key2": "value2",
172+
"key3": "value3"
173+
}
174+
"""))));
175+
176+
given(this.chatModel.call(this.promptCaptor.capture())).willReturn(chatResponse);
140177

178+
ResponseEntity<ChatResponse, Map<String, String>> responseEntity = ChatClient.builder(this.chatModel)
179+
.build()
180+
.prompt()
181+
.user("test")
182+
.call()
183+
.responseEntity(new ParameterizedTypeReference<Map<String, String>>() {
184+
});
185+
186+
assertThat(responseEntity.getEntity()).containsEntry("key1", "value1");
187+
assertThat(responseEntity.getEntity()).containsEntry("key2", "value2");
188+
assertThat(responseEntity.getEntity()).containsEntry("key3", "value3");
189+
}
190+
191+
@Test
192+
public void whenEmptyArrayResponseThenReturnEmptyList() {
193+
var chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("[]"))));
194+
given(this.chatModel.call(this.promptCaptor.capture())).willReturn(chatResponse);
195+
196+
ResponseEntity<ChatResponse, List<MyBean>> responseEntity = ChatClient.builder(this.chatModel)
197+
.build()
198+
.prompt()
199+
.user("test")
200+
.call()
201+
.responseEntity(new ParameterizedTypeReference<List<MyBean>>() {
202+
});
203+
204+
assertThat(responseEntity.getEntity()).isEmpty();
205+
}
206+
207+
@Test
208+
public void whenBooleanPrimitiveResponseThenParseCorrectly() {
209+
var chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("true"))));
210+
given(this.chatModel.call(this.promptCaptor.capture())).willReturn(chatResponse);
211+
212+
ResponseEntity<ChatResponse, Boolean> responseEntity = ChatClient.builder(this.chatModel)
213+
.build()
214+
.prompt()
215+
.user("Is this true?")
216+
.call()
217+
.responseEntity(Boolean.class);
218+
219+
assertThat(responseEntity.getEntity()).isTrue();
220+
}
221+
222+
@Test
223+
public void whenIntegerResponseThenParseCorrectly() {
224+
var chatResponse = new ChatResponse(List.of(new Generation(new AssistantMessage("1"))));
225+
given(this.chatModel.call(this.promptCaptor.capture())).willReturn(chatResponse);
226+
227+
ResponseEntity<ChatResponse, Integer> responseEntity = ChatClient.builder(this.chatModel)
228+
.build()
229+
.prompt()
230+
.user("What is the answer?")
231+
.call()
232+
.responseEntity(Integer.class);
233+
234+
assertThat(responseEntity.getEntity()).isEqualTo(1);
235+
}
236+
237+
record MyBean(String name, int age) {
141238
}
142239

143240
}

spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/ChatClientResponseTests.java

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@
2020
import java.util.Map;
2121

2222
import org.junit.jupiter.api.Test;
23+
import org.springframework.ai.chat.model.ChatResponse;
24+
import org.springframework.ai.chat.model.Generation;
2325

2426
import static org.assertj.core.api.Assertions.assertThat;
2527
import static org.assertj.core.api.Assertions.assertThatThrownBy;
28+
import static org.mockito.Mockito.mock;
2629

2730
/**
2831
* Unit tests for {@link ChatClientResponse}.
@@ -82,4 +85,102 @@ void whenMutateThenImmutableContext() {
8285
assertThat(response.context()).containsEntry("key", "value");
8386
}
8487

88+
@Test
89+
void whenValidChatResponseThenCreateSuccessfully() {
90+
ChatResponse chatResponse = mock(ChatResponse.class);
91+
Map<String, Object> context = Map.of("key", "value");
92+
93+
ChatClientResponse response = new ChatClientResponse(chatResponse, context);
94+
95+
assertThat(response.chatResponse()).isEqualTo(chatResponse);
96+
assertThat(response.context()).containsExactlyInAnyOrderEntriesOf(context);
97+
}
98+
99+
@Test
100+
void whenBuilderWithValidDataThenCreateSuccessfully() {
101+
ChatResponse chatResponse = mock(ChatResponse.class);
102+
Map<String, Object> context = Map.of("key1", "value1", "key2", 42);
103+
104+
ChatClientResponse response = ChatClientResponse.builder().chatResponse(chatResponse).context(context).build();
105+
106+
assertThat(response.chatResponse()).isEqualTo(chatResponse);
107+
assertThat(response.context()).containsExactlyInAnyOrderEntriesOf(context);
108+
}
109+
110+
@Test
111+
void whenEmptyContextThenCreateSuccessfully() {
112+
ChatResponse chatResponse = mock(ChatResponse.class);
113+
Map<String, Object> emptyContext = Map.of();
114+
115+
ChatClientResponse response = new ChatClientResponse(chatResponse, emptyContext);
116+
117+
assertThat(response.chatResponse()).isEqualTo(chatResponse);
118+
assertThat(response.context()).isEmpty();
119+
}
120+
121+
@Test
122+
void whenContextWithNullValuesThenCreateSuccessfully() {
123+
ChatResponse chatResponse = mock(ChatResponse.class);
124+
Map<String, Object> context = new HashMap<>();
125+
context.put("key1", "value1");
126+
context.put("key2", null);
127+
128+
ChatClientResponse response = new ChatClientResponse(chatResponse, context);
129+
130+
assertThat(response.context()).containsEntry("key1", "value1");
131+
assertThat(response.context()).containsEntry("key2", null);
132+
}
133+
134+
@Test
135+
void whenCopyWithNullChatResponseThenPreserveNull() {
136+
Map<String, Object> context = Map.of("key", "value");
137+
ChatClientResponse response = new ChatClientResponse(null, context);
138+
139+
ChatClientResponse copy = response.copy();
140+
141+
assertThat(copy.chatResponse()).isNull();
142+
assertThat(copy.context()).containsExactlyInAnyOrderEntriesOf(context);
143+
}
144+
145+
@Test
146+
void whenMutateWithNewChatResponseThenUpdate() {
147+
ChatResponse originalResponse = mock(ChatResponse.class);
148+
ChatResponse newResponse = mock(ChatResponse.class);
149+
Map<String, Object> context = Map.of("key", "value");
150+
151+
ChatClientResponse response = new ChatClientResponse(originalResponse, context);
152+
ChatClientResponse mutated = response.mutate().chatResponse(newResponse).build();
153+
154+
assertThat(response.chatResponse()).isEqualTo(originalResponse);
155+
assertThat(mutated.chatResponse()).isEqualTo(newResponse);
156+
assertThat(mutated.context()).containsExactlyInAnyOrderEntriesOf(context);
157+
}
158+
159+
@Test
160+
void whenBuilderWithoutChatResponseThenCreateWithNull() {
161+
Map<String, Object> context = Map.of("key", "value");
162+
163+
ChatClientResponse response = ChatClientResponse.builder().context(context).build();
164+
165+
assertThat(response.chatResponse()).isNull();
166+
}
167+
168+
@Test
169+
void whenComplexObjectsInContextThenPreserveCorrectly() {
170+
ChatResponse chatResponse = mock(ChatResponse.class);
171+
Generation generation = mock(Generation.class);
172+
Map<String, Object> nestedMap = Map.of("nested", "value");
173+
174+
Map<String, Object> context = Map.of("string", "value", "number", 1, "boolean", true, "generation", generation,
175+
"map", nestedMap);
176+
177+
ChatClientResponse response = new ChatClientResponse(chatResponse, context);
178+
179+
assertThat(response.context()).containsEntry("string", "value");
180+
assertThat(response.context()).containsEntry("number", 1);
181+
assertThat(response.context()).containsEntry("boolean", true);
182+
assertThat(response.context()).containsEntry("generation", generation);
183+
assertThat(response.context()).containsEntry("map", nestedMap);
184+
}
185+
85186
}

spring-ai-client-chat/src/test/java/org/springframework/ai/chat/client/DefaultChatClientBuilderTests.java

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,94 @@ void whenOverridingUserPromptThenLatestValueIsUsed() {
159159
assertThat(defaultRequest.getUserText()).isEqualTo("second user prompt");
160160
}
161161

162+
@Test
163+
void whenDefaultUserStringSetThenAppliedToRequest() {
164+
var chatModel = mock(ChatModel.class);
165+
var builder = new DefaultChatClientBuilder(chatModel);
166+
167+
builder.defaultUser("test user prompt");
168+
169+
var defaultRequest = (DefaultChatClient.DefaultChatClientRequestSpec) ReflectionTestUtils.getField(builder,
170+
"defaultRequest");
171+
assertThat(defaultRequest.getUserText()).isEqualTo("test user prompt");
172+
}
173+
174+
@Test
175+
void whenDefaultSystemStringSetThenAppliedToRequest() {
176+
var chatModel = mock(ChatModel.class);
177+
var builder = new DefaultChatClientBuilder(chatModel);
178+
179+
builder.defaultSystem("test system prompt");
180+
181+
var defaultRequest = (DefaultChatClient.DefaultChatClientRequestSpec) ReflectionTestUtils.getField(builder,
182+
"defaultRequest");
183+
assertThat(defaultRequest.getSystemText()).isEqualTo("test system prompt");
184+
}
185+
186+
@Test
187+
void whenBuilderMethodChainingThenAllSettingsApplied() {
188+
var chatModel = mock(ChatModel.class);
189+
190+
var builder = new DefaultChatClientBuilder(chatModel).defaultSystem("system prompt").defaultUser("user prompt");
191+
192+
var defaultRequest = (DefaultChatClient.DefaultChatClientRequestSpec) ReflectionTestUtils.getField(builder,
193+
"defaultRequest");
194+
195+
assertThat(defaultRequest.getSystemText()).isEqualTo("system prompt");
196+
assertThat(defaultRequest.getUserText()).isEqualTo("user prompt");
197+
}
198+
199+
@Test
200+
void whenCloneWithAllSettingsThenAllAreCopied() {
201+
var chatModel = mock(ChatModel.class);
202+
203+
var originalBuilder = new DefaultChatClientBuilder(chatModel).defaultSystem("system prompt")
204+
.defaultUser("user prompt");
205+
206+
var clonedBuilder = (DefaultChatClientBuilder) originalBuilder.clone();
207+
var clonedRequest = (DefaultChatClient.DefaultChatClientRequestSpec) ReflectionTestUtils.getField(clonedBuilder,
208+
"defaultRequest");
209+
210+
assertThat(clonedRequest.getSystemText()).isEqualTo("system prompt");
211+
assertThat(clonedRequest.getUserText()).isEqualTo("user prompt");
212+
}
213+
214+
@Test
215+
void whenBuilderUsedMultipleTimesThenProducesDifferentInstances() {
216+
var chatModel = mock(ChatModel.class);
217+
var builder = new DefaultChatClientBuilder(chatModel);
218+
219+
var client1 = builder.build();
220+
var client2 = builder.build();
221+
222+
assertThat(client1).isNotSameAs(client2);
223+
assertThat(client1).isInstanceOf(DefaultChatClient.class);
224+
assertThat(client2).isInstanceOf(DefaultChatClient.class);
225+
}
226+
227+
@Test
228+
void whenDefaultUserWithTemplateVariablesThenProcessed() {
229+
var chatModel = mock(ChatModel.class);
230+
var builder = new DefaultChatClientBuilder(chatModel);
231+
232+
builder.defaultUser("Hello {name}, welcome to {service}!");
233+
234+
var defaultRequest = (DefaultChatClient.DefaultChatClientRequestSpec) ReflectionTestUtils.getField(builder,
235+
"defaultRequest");
236+
assertThat(defaultRequest.getUserText()).isEqualTo("Hello {name}, welcome to {service}!");
237+
}
238+
239+
@Test
240+
void whenMultipleSystemSettingsThenLastOneWins() {
241+
var chatModel = mock(ChatModel.class);
242+
var builder = new DefaultChatClientBuilder(chatModel);
243+
244+
builder.defaultSystem("first system message");
245+
builder.defaultSystem("final system message");
246+
247+
var defaultRequest = (DefaultChatClient.DefaultChatClientRequestSpec) ReflectionTestUtils.getField(builder,
248+
"defaultRequest");
249+
assertThat(defaultRequest.getSystemText()).isEqualTo("final system message");
250+
}
251+
162252
}

0 commit comments

Comments
 (0)