Skip to content

Commit 406cf08

Browse files
committed
test: Enhance Anthropic test coverage with comprehensive validation and edge cases
Comprehensive test coverage improvements for Anthropic tests Co-authored-by: Oleksandr Klymenko <[email protected]> Signed-off-by: Oleksandr Klymenko <[email protected]>
1 parent b1326d2 commit 406cf08

File tree

2 files changed

+235
-0
lines changed

2 files changed

+235
-0
lines changed

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/AnthropicChatOptionsTests.java

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,4 +318,157 @@ void testCopyPreservesAllFields() {
318318
assertThat(copied.getToolContext()).isNotSameAs(original.getToolContext());
319319
}
320320

321+
@Test
322+
void testBoundaryValues() {
323+
AnthropicChatOptions options = AnthropicChatOptions.builder()
324+
.maxTokens(Integer.MAX_VALUE)
325+
.temperature(1.0)
326+
.topP(1.0)
327+
.topK(Integer.MAX_VALUE)
328+
.build();
329+
330+
assertThat(options.getMaxTokens()).isEqualTo(Integer.MAX_VALUE);
331+
assertThat(options.getTemperature()).isEqualTo(1.0);
332+
assertThat(options.getTopP()).isEqualTo(1.0);
333+
assertThat(options.getTopK()).isEqualTo(Integer.MAX_VALUE);
334+
}
335+
336+
@Test
337+
void testToolContextWithVariousValueTypes() {
338+
Map<String, Object> mixedMap = Map.of("string", "value", "number", 42, "boolean", true, "null_value", "null",
339+
"nested_list", List.of("a", "b", "c"), "nested_map", Map.of("inner", "value"));
340+
341+
AnthropicChatOptions options = AnthropicChatOptions.builder().toolContext(mixedMap).build();
342+
343+
assertThat(options.getToolContext()).containsAllEntriesOf(mixedMap);
344+
assertThat(options.getToolContext().get("string")).isEqualTo("value");
345+
assertThat(options.getToolContext().get("number")).isEqualTo(42);
346+
assertThat(options.getToolContext().get("boolean")).isEqualTo(true);
347+
}
348+
349+
@Test
350+
void testCopyWithMutableCollections() {
351+
List<String> mutableStops = new java.util.ArrayList<>(List.of("stop1", "stop2"));
352+
Map<String, Object> mutableContext = new java.util.HashMap<>(Map.of("key", "value"));
353+
354+
AnthropicChatOptions original = AnthropicChatOptions.builder()
355+
.stopSequences(mutableStops)
356+
.toolContext(mutableContext)
357+
.build();
358+
359+
AnthropicChatOptions copied = original.copy();
360+
361+
// Modify original collections
362+
mutableStops.add("stop3");
363+
mutableContext.put("new_key", "new_value");
364+
365+
// Copied instance should not be affected
366+
assertThat(copied.getStopSequences()).hasSize(2);
367+
assertThat(copied.getToolContext()).hasSize(1);
368+
assertThat(copied.getStopSequences()).doesNotContain("stop3");
369+
assertThat(copied.getToolContext()).doesNotContainKey("new_key");
370+
}
371+
372+
@Test
373+
void testEqualsWithNullFields() {
374+
AnthropicChatOptions options1 = new AnthropicChatOptions();
375+
AnthropicChatOptions options2 = new AnthropicChatOptions();
376+
377+
assertThat(options1).isEqualTo(options2);
378+
assertThat(options1.hashCode()).isEqualTo(options2.hashCode());
379+
}
380+
381+
@Test
382+
void testEqualsWithMixedNullAndNonNullFields() {
383+
AnthropicChatOptions options1 = AnthropicChatOptions.builder()
384+
.model("test")
385+
.maxTokens(null)
386+
.temperature(0.5)
387+
.build();
388+
389+
AnthropicChatOptions options2 = AnthropicChatOptions.builder()
390+
.model("test")
391+
.maxTokens(null)
392+
.temperature(0.5)
393+
.build();
394+
395+
AnthropicChatOptions options3 = AnthropicChatOptions.builder()
396+
.model("test")
397+
.maxTokens(100)
398+
.temperature(0.5)
399+
.build();
400+
401+
assertThat(options1).isEqualTo(options2);
402+
assertThat(options1).isNotEqualTo(options3);
403+
}
404+
405+
@Test
406+
void testCopyDoesNotShareMetadataReference() {
407+
Metadata originalMetadata = new Metadata("user_123");
408+
AnthropicChatOptions original = AnthropicChatOptions.builder().metadata(originalMetadata).build();
409+
410+
AnthropicChatOptions copied = original.copy();
411+
412+
// Metadata should be the same value but potentially different reference
413+
assertThat(copied.getMetadata()).isEqualTo(original.getMetadata());
414+
415+
// Verify changing original doesn't affect copy
416+
original.setMetadata(new Metadata("different_user"));
417+
assertThat(copied.getMetadata()).isEqualTo(originalMetadata);
418+
}
419+
420+
@Test
421+
void testEqualsWithSelf() {
422+
AnthropicChatOptions options = AnthropicChatOptions.builder().model("test").build();
423+
424+
assertThat(options).isEqualTo(options);
425+
assertThat(options.hashCode()).isEqualTo(options.hashCode());
426+
}
427+
428+
@Test
429+
void testEqualsWithNull() {
430+
AnthropicChatOptions options = AnthropicChatOptions.builder().model("test").build();
431+
432+
assertThat(options).isNotEqualTo(null);
433+
}
434+
435+
@Test
436+
void testEqualsWithDifferentClass() {
437+
AnthropicChatOptions options = AnthropicChatOptions.builder().model("test").build();
438+
439+
assertThat(options).isNotEqualTo("not an AnthropicChatOptions");
440+
assertThat(options).isNotEqualTo(1);
441+
}
442+
443+
@Test
444+
void testBuilderPartialConfiguration() {
445+
// Test builder with only some fields set
446+
AnthropicChatOptions onlyModel = AnthropicChatOptions.builder().model("model-only").build();
447+
448+
AnthropicChatOptions onlyTokens = AnthropicChatOptions.builder().maxTokens(10).build();
449+
450+
AnthropicChatOptions onlyTemperature = AnthropicChatOptions.builder().temperature(0.8).build();
451+
452+
assertThat(onlyModel.getModel()).isEqualTo("model-only");
453+
assertThat(onlyModel.getMaxTokens()).isNull();
454+
455+
assertThat(onlyTokens.getModel()).isNull();
456+
assertThat(onlyTokens.getMaxTokens()).isEqualTo(10);
457+
458+
assertThat(onlyTemperature.getModel()).isNull();
459+
assertThat(onlyTemperature.getTemperature()).isEqualTo(0.8);
460+
}
461+
462+
@Test
463+
void testSetterOverwriteBehavior() {
464+
AnthropicChatOptions options = AnthropicChatOptions.builder().model("initial-model").maxTokens(100).build();
465+
466+
// Overwrite with setters
467+
options.setModel("updated-model");
468+
options.setMaxTokens(10);
469+
470+
assertThat(options.getModel()).isEqualTo("updated-model");
471+
assertThat(options.getMaxTokens()).isEqualTo(10);
472+
}
473+
321474
}

models/spring-ai-anthropic/src/test/java/org/springframework/ai/anthropic/aot/AnthropicRuntimeHintsTests.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,86 @@ void verifyEnumTypesAreRegistered() {
145145
assertThat(registeredTypes.contains(TypeReference.of(AnthropicApi.EventType.class))).isTrue();
146146
}
147147

148+
@Test
149+
void verifyNestedClassesAreRegistered() {
150+
anthropicRuntimeHints.registerHints(runtimeHints, null);
151+
152+
Set<TypeReference> registeredTypes = new HashSet<>();
153+
runtimeHints.reflection().typeHints().forEach(typeHint -> registeredTypes.add(typeHint.getType()));
154+
155+
// Verify nested classes within AnthropicApi are registered
156+
assertThat(registeredTypes.contains(TypeReference.of(AnthropicApi.ChatCompletionRequest.class))).isTrue();
157+
assertThat(registeredTypes.contains(TypeReference.of(AnthropicApi.AnthropicMessage.class))).isTrue();
158+
assertThat(registeredTypes.contains(TypeReference.of(AnthropicApi.ContentBlock.class))).isTrue();
159+
}
160+
161+
@Test
162+
void verifyNoProxyHintsAreRegistered() {
163+
anthropicRuntimeHints.registerHints(runtimeHints, null);
164+
165+
// This implementation should only register reflection hints, not proxy hints
166+
long proxyHintCount = runtimeHints.proxies().jdkProxyHints().count();
167+
assertThat(proxyHintCount).isEqualTo(0);
168+
}
169+
170+
@Test
171+
void verifyNoSerializationHintsAreRegistered() {
172+
anthropicRuntimeHints.registerHints(runtimeHints, null);
173+
174+
// This implementation should only register reflection hints, not serialization
175+
// hints
176+
long serializationHintCount = runtimeHints.serialization().javaSerializationHints().count();
177+
assertThat(serializationHintCount).isEqualTo(0);
178+
}
179+
180+
@Test
181+
void verifyJsonAnnotatedClassesContainExpectedTypes() {
182+
Set<TypeReference> jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage("org.springframework.ai.anthropic");
183+
184+
// Verify that key API classes are found
185+
boolean containsApiClass = jsonAnnotatedClasses.stream()
186+
.anyMatch(typeRef -> typeRef.getName().contains("AnthropicApi")
187+
|| typeRef.getName().contains("ChatCompletion") || typeRef.getName().contains("AnthropicMessage"));
188+
189+
assertThat(containsApiClass).isTrue();
190+
}
191+
192+
@Test
193+
void verifyConsistencyAcrossInstances() {
194+
RuntimeHints hints1 = new RuntimeHints();
195+
RuntimeHints hints2 = new RuntimeHints();
196+
197+
AnthropicRuntimeHints anthropicHints1 = new AnthropicRuntimeHints();
198+
AnthropicRuntimeHints anthropicHints2 = new AnthropicRuntimeHints();
199+
200+
anthropicHints1.registerHints(hints1, null);
201+
anthropicHints2.registerHints(hints2, null);
202+
203+
// Different instances should register the same hints
204+
Set<TypeReference> types1 = new HashSet<>();
205+
Set<TypeReference> types2 = new HashSet<>();
206+
207+
hints1.reflection().typeHints().forEach(hint -> types1.add(hint.getType()));
208+
hints2.reflection().typeHints().forEach(hint -> types2.add(hint.getType()));
209+
210+
assertThat(types1).isEqualTo(types2);
211+
}
212+
213+
@Test
214+
void verifyPackageSpecificity() {
215+
Set<TypeReference> jsonAnnotatedClasses = findJsonAnnotatedClassesInPackage("org.springframework.ai.anthropic");
216+
217+
// All found classes should be from the anthropic package specifically
218+
for (TypeReference classRef : jsonAnnotatedClasses) {
219+
assertThat(classRef.getName()).startsWith("org.springframework.ai.anthropic");
220+
}
221+
222+
// Should not include classes from other AI packages
223+
for (TypeReference classRef : jsonAnnotatedClasses) {
224+
assertThat(classRef.getName()).doesNotContain("vertexai");
225+
assertThat(classRef.getName()).doesNotContain("openai");
226+
assertThat(classRef.getName()).doesNotContain("ollama");
227+
}
228+
}
229+
148230
}

0 commit comments

Comments
 (0)