diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java index 1dc869b1593..dcacca47613 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/api/OpenAiChatModelMutateTests.java @@ -131,4 +131,63 @@ void mutateAndCloneAreEquivalent() { assertThat(mutated).isNotSameAs(cloned); } + @Test + void testApiMutateWithComplexHeaders() { + LinkedMultiValueMap complexHeaders = new LinkedMultiValueMap<>(); + complexHeaders.add("Authorization", "Bearer custom-token"); + complexHeaders.add("X-Custom-Header", "value1"); + complexHeaders.add("X-Custom-Header", "value2"); + complexHeaders.add("User-Agent", "Custom-Client/1.0"); + + OpenAiApi mutatedApi = this.baseApi.mutate().headers(complexHeaders).build(); + + assertThat(mutatedApi.getHeaders()).containsKey("Authorization"); + assertThat(mutatedApi.getHeaders()).containsKey("X-Custom-Header"); + assertThat(mutatedApi.getHeaders()).containsKey("User-Agent"); + assertThat(mutatedApi.getHeaders().get("X-Custom-Header")).hasSize(2); + } + + @Test + void testMutateWithEmptyOptions() { + OpenAiChatOptions emptyOptions = OpenAiChatOptions.builder().build(); + + OpenAiChatModel mutated = this.baseModel.mutate().defaultOptions(emptyOptions).build(); + + assertThat(mutated.getDefaultOptions()).isNotNull(); + assertThat(mutated.getDefaultOptions()).isNotSameAs(this.baseModel.getDefaultOptions()); + } + + @Test + void testApiMutateWithEmptyHeaders() { + LinkedMultiValueMap emptyHeaders = new LinkedMultiValueMap<>(); + + OpenAiApi mutatedApi = this.baseApi.mutate().headers(emptyHeaders).build(); + + assertThat(mutatedApi.getHeaders()).isEmpty(); + } + + @Test + void testCloneAndMutateIndependence() { + // Test that clone and mutate produce independent instances + OpenAiChatModel cloned = this.baseModel.clone(); + OpenAiChatModel mutated = this.baseModel.mutate().build(); + + // Modify cloned instance (if options are mutable) + // This test verifies that operations on one don't affect the other + assertThat(cloned).isNotSameAs(mutated); + assertThat(cloned).isNotSameAs(this.baseModel); + assertThat(mutated).isNotSameAs(this.baseModel); + } + + @Test + void testMutateBuilderValidation() { + // Test that mutate builder validates inputs appropriately + assertThat(this.baseModel.mutate()).isNotNull(); + + // Test building without any changes + OpenAiChatModel unchanged = this.baseModel.mutate().build(); + assertThat(unchanged).isNotNull(); + assertThat(unchanged).isNotSameAs(this.baseModel); + } + } diff --git a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java index 97664d2018a..ca99c5b5d45 100644 --- a/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java +++ b/models/spring-ai-openai/src/test/java/org/springframework/ai/openai/metadata/OpenAiUsageTests.java @@ -208,4 +208,22 @@ void whenPromptCacheMissTokensIsPresent() { assertThat(nativeUsage.promptTokensDetails().cachedTokens()).isEqualTo(15); } + @Test + void whenAllTokenCountsAreZero() { + OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(0, 0, 0); + DefaultUsage usage = getDefaultUsage(openAiUsage); + assertThat(usage.getPromptTokens()).isEqualTo(0); + assertThat(usage.getCompletionTokens()).isEqualTo(0); + assertThat(usage.getTotalTokens()).isEqualTo(0); + } + + @Test + void whenAllTokenCountsAreNull() { + OpenAiApi.Usage openAiUsage = new OpenAiApi.Usage(null, null, null); + DefaultUsage usage = getDefaultUsage(openAiUsage); + assertThat(usage.getPromptTokens()).isEqualTo(0); + assertThat(usage.getCompletionTokens()).isEqualTo(0); + assertThat(usage.getTotalTokens()).isEqualTo(0); + } + }