diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java index 45557f23a6d..e4f0aa812c5 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolCallingChatOptionsTests.java @@ -235,4 +235,38 @@ void deprecatedMethodsShouldWorkCorrectly() { assertThat(options.getInternalToolExecutionEnabled()).isTrue(); } + @Test + void defaultConstructorShouldInitializeWithEmptyCollections() { + DefaultToolCallingChatOptions options = new DefaultToolCallingChatOptions(); + + assertThat(options.getToolCallbacks()).isEmpty(); + assertThat(options.getToolNames()).isEmpty(); + assertThat(options.getToolContext()).isEmpty(); + assertThat(options.getInternalToolExecutionEnabled()).isNull(); + } + + @Test + void builderShouldHandleEmptyCollections() { + ToolCallingChatOptions options = DefaultToolCallingChatOptions.builder() + .toolCallbacks(List.of()) + .toolNames(Set.of()) + .toolContext(Map.of()) + .build(); + + assertThat(options.getToolCallbacks()).isEmpty(); + assertThat(options.getToolNames()).isEmpty(); + assertThat(options.getToolContext()).isEmpty(); + } + + @Test + void setInternalToolExecutionEnabledShouldAcceptNullValue() { + DefaultToolCallingChatOptions options = new DefaultToolCallingChatOptions(); + options.setInternalToolExecutionEnabled(true); + assertThat(options.getInternalToolExecutionEnabled()).isTrue(); + + // Should be able to set back to null + options.setInternalToolExecutionEnabled(null); + assertThat(options.getInternalToolExecutionEnabled()).isNull(); + } + } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionEligibilityPredicateTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionEligibilityPredicateTests.java index 8b92a3fad79..b8e32f1ade0 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionEligibilityPredicateTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionEligibilityPredicateTests.java @@ -134,4 +134,69 @@ void whenEmptyGenerationsList() { assertThat(result).isFalse(); } + @Test + void whenMultipleGenerationsWithMixedToolCalls() { + // Create a ToolCallingChatOptions with internal tool execution enabled + ToolCallingChatOptions options = ToolCallingChatOptions.builder().internalToolExecutionEnabled(true).build(); + + // Create multiple generations - some with tool calls, some without + AssistantMessage.ToolCall toolCall = new AssistantMessage.ToolCall("id1", "function", "testTool", "{}"); + AssistantMessage messageWithToolCall = new AssistantMessage("test1", Map.of(), List.of(toolCall)); + AssistantMessage messageWithoutToolCall = new AssistantMessage("test2"); + + ChatResponse chatResponse = new ChatResponse( + List.of(new Generation(messageWithToolCall), new Generation(messageWithoutToolCall))); + + // Test the predicate - should return true if any generation has tool calls + boolean result = this.predicate.test(options, chatResponse); + assertThat(result).isTrue(); + } + + @Test + void whenMultipleGenerationsWithoutToolCalls() { + // Create a ToolCallingChatOptions with internal tool execution enabled + ToolCallingChatOptions options = ToolCallingChatOptions.builder().internalToolExecutionEnabled(true).build(); + + // Create multiple generations without tool calls + AssistantMessage message1 = new AssistantMessage("test1"); + AssistantMessage message2 = new AssistantMessage("test2"); + + ChatResponse chatResponse = new ChatResponse(List.of(new Generation(message1), new Generation(message2))); + + // Test the predicate + boolean result = this.predicate.test(options, chatResponse); + assertThat(result).isFalse(); + } + + @Test + void whenAssistantMessageHasEmptyToolCallsList() { + // Create a ToolCallingChatOptions with internal tool execution enabled + ToolCallingChatOptions options = ToolCallingChatOptions.builder().internalToolExecutionEnabled(true).build(); + + // Create a ChatResponse with AssistantMessage having empty tool calls list + AssistantMessage assistantMessage = new AssistantMessage("test", Map.of(), List.of()); + ChatResponse chatResponse = new ChatResponse(List.of(new Generation(assistantMessage))); + + // Test the predicate + boolean result = this.predicate.test(options, chatResponse); + assertThat(result).isFalse(); + } + + @Test + void whenMultipleToolCallsPresent() { + // Create a ToolCallingChatOptions with internal tool execution enabled + ToolCallingChatOptions options = ToolCallingChatOptions.builder().internalToolExecutionEnabled(true).build(); + + // Create a ChatResponse with multiple tool calls + AssistantMessage.ToolCall toolCall1 = new AssistantMessage.ToolCall("id1", "function", "testTool1", "{}"); + AssistantMessage.ToolCall toolCall2 = new AssistantMessage.ToolCall("id2", "function", "testTool2", + "{\"param\": \"value\"}"); + AssistantMessage assistantMessage = new AssistantMessage("test", Map.of(), List.of(toolCall1, toolCall2)); + ChatResponse chatResponse = new ChatResponse(List.of(new Generation(assistantMessage))); + + // Test the predicate + boolean result = this.predicate.test(options, chatResponse); + assertThat(result).isTrue(); + } + } diff --git a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionResultTests.java b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionResultTests.java index 786a7593202..c6c06c1e75e 100644 --- a/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionResultTests.java +++ b/spring-ai-model/src/test/java/org/springframework/ai/model/tool/DefaultToolExecutionResultTests.java @@ -59,4 +59,135 @@ void builder() { assertThat(result.returnDirect()).isTrue(); } + @Test + void whenBuilderWithMinimalRequiredFields() { + var conversationHistory = new ArrayList(); + var result = DefaultToolExecutionResult.builder().conversationHistory(conversationHistory).build(); + + assertThat(result.conversationHistory()).isEqualTo(conversationHistory); + assertThat(result.returnDirect()).isFalse(); // Default value should be false + } + + @Test + void whenBuilderWithReturnDirectFalse() { + var conversationHistory = new ArrayList(); + var result = DefaultToolExecutionResult.builder() + .conversationHistory(conversationHistory) + .returnDirect(false) + .build(); + + assertThat(result.conversationHistory()).isEqualTo(conversationHistory); + assertThat(result.returnDirect()).isFalse(); + } + + @Test + void whenConversationHistoryIsEmpty() { + var conversationHistory = new ArrayList(); + var result = DefaultToolExecutionResult.builder() + .conversationHistory(conversationHistory) + .returnDirect(true) + .build(); + + assertThat(result.conversationHistory()).isEmpty(); + assertThat(result.returnDirect()).isTrue(); + } + + @Test + void whenConversationHistoryHasMultipleMessages() { + var conversationHistory = new ArrayList(); + var message1 = new org.springframework.ai.chat.messages.UserMessage("Hello"); + var message2 = new org.springframework.ai.chat.messages.AssistantMessage("Hi there!"); + conversationHistory.add(message1); + conversationHistory.add(message2); + + var result = DefaultToolExecutionResult.builder() + .conversationHistory(conversationHistory) + .returnDirect(false) + .build(); + + assertThat(result.conversationHistory()).hasSize(2); + assertThat(result.conversationHistory()).containsExactly(message1, message2); + assertThat(result.returnDirect()).isFalse(); + } + + @Test + void whenConversationHistoryHasNullElementsInMiddle() { + var history = new ArrayList(); + history.add(new org.springframework.ai.chat.messages.UserMessage("First message")); + history.add(null); + history.add(new org.springframework.ai.chat.messages.AssistantMessage("Last message")); + + assertThatThrownBy(() -> DefaultToolExecutionResult.builder().conversationHistory(history).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("conversationHistory cannot contain null elements"); + } + + @Test + void whenConversationHistoryHasMultipleNullElements() { + var history = new ArrayList(); + history.add(null); + history.add(null); + history.add(new org.springframework.ai.chat.messages.UserMessage("Valid message")); + + assertThatThrownBy(() -> DefaultToolExecutionResult.builder().conversationHistory(history).build()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("conversationHistory cannot contain null elements"); + } + + @Test + void whenBuilderIsReused() { + var conversationHistory1 = new ArrayList(); + conversationHistory1.add(new org.springframework.ai.chat.messages.UserMessage("Message 1")); + + var conversationHistory2 = new ArrayList(); + conversationHistory2.add(new org.springframework.ai.chat.messages.UserMessage("Message 2")); + + var builder = DefaultToolExecutionResult.builder(); + + var result1 = builder.conversationHistory(conversationHistory1).returnDirect(true).build(); + + var result2 = builder.conversationHistory(conversationHistory2).returnDirect(false).build(); + + assertThat(result1.conversationHistory()).isEqualTo(conversationHistory1); + assertThat(result1.returnDirect()).isTrue(); + assertThat(result2.conversationHistory()).isEqualTo(conversationHistory2); + assertThat(result2.returnDirect()).isFalse(); + } + + @Test + void whenConversationHistoryIsModifiedAfterBuilding() { + var conversationHistory = new ArrayList(); + var originalMessage = new org.springframework.ai.chat.messages.UserMessage("Original"); + conversationHistory.add(originalMessage); + + var result = DefaultToolExecutionResult.builder().conversationHistory(conversationHistory).build(); + + // Modify the original list after building + conversationHistory.add(new org.springframework.ai.chat.messages.AssistantMessage("Added later")); + + // The result should reflect the modification if the same list reference is used + // This tests whether the builder stores a reference or creates a copy + assertThat(result.conversationHistory()).hasSize(2); + assertThat(result.conversationHistory().get(0)).isEqualTo(originalMessage); + } + + @Test + void whenEqualsAndHashCodeAreConsistent() { + var conversationHistory = new ArrayList(); + conversationHistory.add(new org.springframework.ai.chat.messages.UserMessage("Test message")); + + var result1 = DefaultToolExecutionResult.builder() + .conversationHistory(conversationHistory) + .returnDirect(true) + .build(); + + var result2 = DefaultToolExecutionResult.builder() + .conversationHistory(conversationHistory) + .returnDirect(true) + .build(); + + assertThat(result1).isEqualTo(result2); + assertThat(result1.hashCode()).isEqualTo(result2.hashCode()); + } + }