diff --git a/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolRuntimeHintsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolRuntimeHintsTests.java index 30683356203..622a5c678b9 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolRuntimeHintsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/aot/ToolRuntimeHintsTests.java @@ -47,4 +47,36 @@ void registerHintsWithNullClassLoader() { assertThatCode(() -> toolRuntimeHints.registerHints(runtimeHints, null)).doesNotThrowAnyException(); } + @Test + void registerHintsWithCustomClassLoader() { + RuntimeHints runtimeHints = new RuntimeHints(); + ToolRuntimeHints toolRuntimeHints = new ToolRuntimeHints(); + ClassLoader customClassLoader = Thread.currentThread().getContextClassLoader(); + + toolRuntimeHints.registerHints(runtimeHints, customClassLoader); + + assertThat(runtimeHints).matches(reflection().onType(DefaultToolCallResultConverter.class)); + } + + @Test + void registerHintsMultipleTimes() { + RuntimeHints runtimeHints = new RuntimeHints(); + ToolRuntimeHints toolRuntimeHints = new ToolRuntimeHints(); + + toolRuntimeHints.registerHints(runtimeHints, null); + toolRuntimeHints.registerHints(runtimeHints, null); + + assertThat(runtimeHints).matches(reflection().onType(DefaultToolCallResultConverter.class)); + } + + @Test + void toolRuntimeHintsInstanceCreation() { + assertThatCode(() -> new ToolRuntimeHints()).doesNotThrowAnyException(); + + ToolRuntimeHints hints1 = new ToolRuntimeHints(); + ToolRuntimeHints hints2 = new ToolRuntimeHints(); + + assertThat(hints1).isNotSameAs(hints2); + } + } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/metadata/UsageTests.java b/spring-ai-model/src/test/java/org/springframework/ai/metadata/UsageTests.java index 72b1c6cc169..029e2e9b0c5 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/metadata/UsageTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/metadata/UsageTests.java @@ -87,4 +87,28 @@ void totalTokensEqualsPromptTokensPlusGenerationTokens() { verifyUsage(usage); } + @Test + void totalTokensHandlesZeroPromptTokens() { + Usage usage = mockUsage(0, 1); + + assertThat(usage.getTotalTokens()).isEqualTo(1); + verifyUsage(usage); + } + + @Test + void totalTokensHandlesZeroCompletionTokens() { + Usage usage = mockUsage(1, 0); + + assertThat(usage.getTotalTokens()).isEqualTo(1); + verifyUsage(usage); + } + + @Test + void totalTokensHandlesBothZeroTokens() { + Usage usage = mockUsage(0, 0); + + assertThat(usage.getTotalTokens()).isZero(); + verifyUsage(usage); + } + } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java b/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java index a708671d405..690909f06df 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/transformer/KeywordMetadataEnricherTest.java @@ -165,4 +165,106 @@ private String getDefaultTemplatePromptText(int keywordCount, String documentCon return prompt.getContents(); } + @Test + void testApplyWithEmptyDocumentsList() { + List emptyDocuments = List.of(); + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 3); + + keywordMetadataEnricher.apply(emptyDocuments); + + verify(chatModel, never()).call(any(Prompt.class)); + } + + @Test + void testApplyWithSingleDocument() { + List documents = List.of(new Document("single content")); + given(chatModel.call(any(Prompt.class))).willReturn(new ChatResponse( + List.of(new Generation(new AssistantMessage("single, keyword, test, document, content"))))); + + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 5); + keywordMetadataEnricher.apply(documents); + + verify(chatModel, times(1)).call(promptCaptor.capture()); + assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY, + "single, keyword, test, document, content"); + } + + @Test + void testApplyWithDocumentContainingExistingMetadata() { + Document document = new Document("content with existing metadata"); + document.getMetadata().put("existing_key", "existing_value"); + List documents = List.of(document); + given(chatModel.call(any(Prompt.class))) + .willReturn(new ChatResponse(List.of(new Generation(new AssistantMessage("new, keywords"))))); + + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 2); + keywordMetadataEnricher.apply(documents); + + assertThat(documents.get(0).getMetadata()).containsEntry("existing_key", "existing_value"); + assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY, "new, keywords"); + } + + @Test + void testApplyWithEmptyStringResponse() { + List documents = List.of(new Document("content")); + given(chatModel.call(any(Prompt.class))) + .willReturn(new ChatResponse(List.of(new Generation(new AssistantMessage(""))))); + + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 3); + keywordMetadataEnricher.apply(documents); + + assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY, ""); + } + + @Test + void testApplyWithWhitespaceOnlyResponse() { + List documents = List.of(new Document("content")); + given(chatModel.call(any(Prompt.class))) + .willReturn(new ChatResponse(List.of(new Generation(new AssistantMessage(" \n\t "))))); + + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 3); + keywordMetadataEnricher.apply(documents); + + assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY, " \n\t "); + } + + @Test + void testApplyOverwritesExistingKeywords() { + Document document = new Document("content"); + document.getMetadata().put(EXCERPT_KEYWORDS_METADATA_KEY, "old, keywords"); + List documents = List.of(document); + given(chatModel.call(any(Prompt.class))) + .willReturn(new ChatResponse(List.of(new Generation(new AssistantMessage("new, keywords"))))); + + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 2); + keywordMetadataEnricher.apply(documents); + + assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY, "new, keywords"); + } + + @Test + void testBuilderWithBothKeywordCountAndTemplate() { + PromptTemplate customTemplate = new PromptTemplate(CUSTOM_TEMPLATE); + + KeywordMetadataEnricher enricher = builder(chatModel).keywordCount(5).keywordsTemplate(customTemplate).build(); + + assertThat(enricher.getKeywordsTemplate()).isEqualTo(customTemplate); + } + + @Test + void testApplyWithSpecialCharactersInContent() { + List documents = List.of(new Document("Content with special chars: @#$%^&*()")); + given(chatModel.call(any(Prompt.class))).willReturn( + new ChatResponse(List.of(new Generation(new AssistantMessage("special, characters, content"))))); + + KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel, 3); + keywordMetadataEnricher.apply(documents); + + verify(chatModel, times(1)).call(promptCaptor.capture()); + assertThat(promptCaptor.getValue().getUserMessage().getText()) + .contains("Content with special chars: @#$%^&*()"); + assertThat(documents.get(0).getMetadata()).containsEntry(EXCERPT_KEYWORDS_METADATA_KEY, + "special, characters, content"); + } + }