From 2b1e40d1ea13b4ee78b9315bbcee0200ddd3a350 Mon Sep 17 00:00:00 2001 From: roost-io <8110509+mgdevstack@users.noreply.github.com> Date: Tue, 13 Jan 2026 14:28:41 +0530 Subject: [PATCH] Unit test generated by RoostGPT Using AI Model gpt-5-nano --- core/pom.xml | 21 +- .../adk/models/BuilderApiClientTest.java | 76 ++++ .../google/adk/models/BuilderApiKeyTest.java | 85 +++++ .../google/adk/models/BuilderBuildTest.java | 125 +++++++ .../adk/models/BuilderModelNameTest.java | 78 ++++ .../models/BuilderVertexCredentialsTest.java | 338 ++++++++++++++++++ ...GeminiProcessRawResponsesTest.java.invalid | 151 ++++++++ pom.xml | 317 +++++++++++++++- 8 files changed, 1167 insertions(+), 24 deletions(-) create mode 100644 core/src/test/java/com/google/adk/models/BuilderApiClientTest.java create mode 100644 core/src/test/java/com/google/adk/models/BuilderApiKeyTest.java create mode 100644 core/src/test/java/com/google/adk/models/BuilderBuildTest.java create mode 100644 core/src/test/java/com/google/adk/models/BuilderModelNameTest.java create mode 100644 core/src/test/java/com/google/adk/models/BuilderVertexCredentialsTest.java create mode 100644 core/src/test/java/com/google/adk/models/GeminiProcessRawResponsesTest.java.invalid diff --git a/core/pom.xml b/core/pom.xml index fe65715f3..7d2032c7e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -1,4 +1,4 @@ - + 4.0.0 - com.google.adk google-adk-parent - 0.4.1-SNAPSHOT + 0.4.1-SNAPSHOT + - google-adk Agent Development Kit Agent Development Kit: an open-source, code-first toolkit designed to simplify building, evaluating, and deploying advanced AI agents anywhere. - - - com.anthropic @@ -201,6 +197,15 @@ maven-compiler-plugin + + io.spring.javaformat + spring-javaformat-maven-plugin + 0.0.40 + + + + + - + \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/models/BuilderApiClientTest.java b/core/src/test/java/com/google/adk/models/BuilderApiClientTest.java new file mode 100644 index 000000000..31846de8d --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderApiClientTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Open AI and AI Model gpt-5-nano + +ROOST_METHOD_HASH=apiClient_c66ebe1d58 +ROOST_METHOD_SIG_HASH=apiClient_d59e3a5ed5 + +Scenario 1: apiClient_setsExplicitClient + +Details: + TestName: apiClient_setsExplicitClient + Description: Verify that calling apiClient with a non-null Client assigns the provided client to the builder and returns the same builder instance to support chaining. + +Execution: + Arrange: Create a Gemini.Builder via Gemini.builder(); prepare a mock/stub Client instance named mockClient. + Act: Builder resultBuilder = builder.apiClient(mockClient); + Assert: Assert that resultBuilder is the same object instance as the original builder (reference equality); optionally verify that the builder can continue chaining by calling another method and ensuring no exception is thrown. + Validation: Ensures that the explicit client is stored in the builder and that the fluent API contract (returning this) is preserved, enabling method chaining. + + +*/ + +// ********RoostGPT******** + +package com.google.adk.models; + +import static org.junit.jupiter.api.Assertions.*; + +import com.google.genai.Client; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class BuilderApiClientTest { + + @Test + @Tag("valid") + @DisplayName("apiClient_setsExplicitClient") + public void testApiClient_setsExplicitClient() { + Gemini.Builder builder = Gemini.builder(); + Client mockClient = Mockito.mock(Client.class); + Gemini.Builder resultBuilder = builder.apiClient(mockClient); + assertSame(builder, resultBuilder); + // Continue chaining to ensure fluent API contract + Gemini.Builder chained = resultBuilder.modelName("test-model"); + Gemini gemini = chained.build(); + assertNotNull(gemini); + } + + @Test + @Tag("valid") + @DisplayName("apiClient_acceptsNullAndReturnsThisForChaining") + public void testApiClient_acceptsNullAndReturnsThisForChaining() { + Gemini.Builder builder = Gemini.builder(); + Gemini.Builder result = builder.apiClient(null); + assertSame(builder, result); + } + +} diff --git a/core/src/test/java/com/google/adk/models/BuilderApiKeyTest.java b/core/src/test/java/com/google/adk/models/BuilderApiKeyTest.java new file mode 100644 index 000000000..4c51a94a9 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderApiKeyTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Open AI and AI Model gpt-5-nano + +ROOST_METHOD_HASH=apiKey_6f7fe65f1f +ROOST_METHOD_SIG_HASH=apiKey_f645a7d0cc + +Scenario 1: Setting non-null apiKey stores the value and enables fluent chaining on the Builder +Details: + TestName: settingApiKeyStoresValue + Description: Verify that calling apiKey with a non-null string assigns that value to the builder's apiKey field and returns the same Builder instance to support method chaining. +Execution: + Arrange: Instantiate a new Gemini.Builder via Gemini.builder(). + Act: Call builder.apiKey("my-api-key") and capture the returned reference. + Assert: The returned reference is the same instance as the original builder (identity check) to confirm fluent chaining is preserved. +Validation: + The assertion confirms fluent API usage for the Builder.apiKey method, ensuring subsequent builder methods can be chained without losing the current state. + +*/ + +// ********RoostGPT******** + +package com.google.adk.models; + +import static org.junit.jupiter.api.Assertions.*; + +import java.lang.reflect.Field; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +public class BuilderApiKeyTest { + + @Test + @Tag("valid") + public void settingApiKeyStoresValue() throws Exception { + // Arrange + Gemini.Builder builder = Gemini.builder(); + String testApiKey = java.util.UUID.randomUUID().toString(); + // Act + Gemini.Builder returned = builder.apiKey(testApiKey); // TODO: Replace with a + // known key if using + // integration tests + // Assert fluent chaining + assertSame(builder, returned, "apiKey should return the same builder instance for fluent chaining."); + // Assert that the value is stored in the builder (private field access via + // reflection) + Field field = Gemini.Builder.class.getDeclaredField("apiKey"); + field.setAccessible(true); + String value = (String) field.get(builder); + assertEquals(testApiKey, value, "The apiKey value stored in the builder should match the input value."); + } + + @Test + @Tag("valid") + public void settingApiKeyWithNullValue() throws Exception { + // Arrange + Gemini.Builder builder = Gemini.builder(); + // Act + Gemini.Builder returned = builder.apiKey(null); + // Assert fluent chaining + assertSame(builder, returned, "apiKey(null) should return the same builder instance for fluent chaining."); + // Assert that the value stored is null + Field field = Gemini.Builder.class.getDeclaredField("apiKey"); + field.setAccessible(true); + String value = (String) field.get(builder); + assertNull(value, "The apiKey value should be null when null is passed."); + } + +} diff --git a/core/src/test/java/com/google/adk/models/BuilderBuildTest.java b/core/src/test/java/com/google/adk/models/BuilderBuildTest.java new file mode 100644 index 000000000..2edfa62b9 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderBuildTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Open AI and AI Model gpt-5-nano + +ROOST_METHOD_HASH=build_a5a48bce54 +ROOST_METHOD_SIG_HASH=build_6248639c03 + +Scenario 1: Build with explicit apiClient returns Gemini using that client +Details: + TestName: buildWithApiClient + Description: When apiClient is provided to the Builder along with a non-null modelName, the build() method should instantiate Gemini using that exact apiClient, ignoring apiKey and vertexCredentials. +Execution: + Arrange: Create a Builder, set modelName to "gemini-2.0", create a mock Client instance named apiClientMock, and call builder.apiClient(apiClientMock). + Act: Call builder.build(). + Assert: The returned object is an instance of Gemini, and via reflection verify that the internal apiClient reference equals apiClientMock; verify the modelName used is "gemini-2.0". +Validation: + Clarify that the test confirms explicit apiClient takes precedence over other credential options and that the Gemini is wired to use the provided client. + +*/ + +// ********RoostGPT******** + +package com.google.adk.models; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import com.google.genai.Client; +import java.lang.reflect.Field; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.*; +import static com.google.common.base.StandardSystemProperty.JAVA_VERSION; +import com.google.adk.Version; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.ResponseStream; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.FinishReason; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LiveConnectConfig; +import com.google.genai.types.Part; +import io.reactivex.rxjava3.core.Flowable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BuilderBuildTest { + + @Test + @Tag("valid") + public void buildWithApiClient() throws Exception { + Client apiClientMock = mock(Client.class); + Gemini gemini = Gemini.builder().modelName("gemini-2.0").apiClient(apiClientMock).build(); + assertEquals("gemini-2.0", gemini.model()); + Field f = Gemini.class.getDeclaredField("apiClient"); + f.setAccessible(true); + Object value = f.get(gemini); + assertSame(apiClientMock, value); + } + + @Test + @Tag("valid") + public void buildWithApiKey() throws Exception { + String apiKey = "test-key"; + Gemini gemini = Gemini.builder().modelName("gemini-2.0").apiKey(apiKey).build(); + assertEquals("gemini-2.0", gemini.model()); + Field f = Gemini.class.getDeclaredField("apiKey"); + f.setAccessible(true); + Object value = f.get(gemini); + assertEquals(apiKey, (String) value); + } + + @Test + @Tag("valid") + public void buildWithVertexCredentials() throws Exception { + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini gemini = Gemini.builder().modelName("gemini-2.0").vertexCredentials(vc).build(); + assertEquals("gemini-2.0", gemini.model()); + Field f = Gemini.class.getDeclaredField("vertexCredentials"); + f.setAccessible(true); + Object value = f.get(gemini); + assertSame(vc, value); + } + + @Test + @Tag("integration") + public void buildWithDefaultClient() throws Exception { + Gemini gemini = Gemini.builder().modelName("gemini-2.0").build(); + assertEquals("gemini-2.0", gemini.model()); + Field f = Gemini.class.getDeclaredField("apiClient"); + f.setAccessible(true); + Object value = f.get(gemini); + assertNotNull(value); + assertTrue(value instanceof Client); + } + + @Test + @Tag("invalid") + public void buildWithNullModelNameThrows() { + assertThrows(NullPointerException.class, () -> Gemini.builder().modelName(null).build()); + } + +} \ No newline at end of file diff --git a/core/src/test/java/com/google/adk/models/BuilderModelNameTest.java b/core/src/test/java/com/google/adk/models/BuilderModelNameTest.java new file mode 100644 index 000000000..d257aff80 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderModelNameTest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Open AI and AI Model gpt-5-nano + +ROOST_METHOD_HASH=modelName_423c161ca3 +ROOST_METHOD_SIG_HASH=modelName_2d01dc01ba + +Scenario 1: Setting a non-null modelName stores the value and enables fluent chaining + +Details: + TestName: modelNameSetsValue + Description: Verifies that calling modelName with a valid, non-null string assigns the value to the builder and returns the same builder instance to support fluent chaining of subsequent configuration methods. +Execution: + Arrange: Create a new Gemini.Builder instance. + Act: Call modelName("gemini-2.0-flash") on the builder. + Assert: Confirm that the returned object is the same builder instance, enabling fluent chaining to subsequent builder methods. +Validation: + The assertion should verify that no exception is thrown and that the builder is ready to accept further configuration calls, indicating successful value assignment for modelName and support for fluent chaining. + + +*/ + +// ********RoostGPT******** + +package com.google.adk.models; + +import static org.junit.jupiter.api.Assertions.assertSame; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +// Additional imports to satisfy the repository's dependency expectations +/** Unit tests for the nested Builder.modelName(String) method inside Gemini. */ +public class BuilderModelNameTest { + + @Test + @Tag("valid") + public void testModelNameSetsValue() { + // Arrange + Gemini.Builder builder = Gemini.builder(); + // Act + Gemini.Builder result = builder.modelName("gemini-2.0-flash"); + // Assert + assertSame(builder, result); // fluent chaining should return the same builder + // instance + // TODO: If needed, add additional validations to ensure internal state is updated + // accordingly. + } + + @Test + @Tag("boundary") + public void testModelNameNullValueKeepsFluentInterface() { + // Arrange + Gemini.Builder builder = Gemini.builder(); + // Act + Gemini.Builder result = builder.modelName(null); + // Assert + assertSame(builder, result); // fluent chaining should be preserved even with null + // input + } + +} diff --git a/core/src/test/java/com/google/adk/models/BuilderVertexCredentialsTest.java b/core/src/test/java/com/google/adk/models/BuilderVertexCredentialsTest.java new file mode 100644 index 000000000..604af8e80 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderVertexCredentialsTest.java @@ -0,0 +1,338 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Open AI and AI Model gpt-5-nano + +ROOST_METHOD_HASH=vertexCredentials_05534933e8 +ROOST_METHOD_SIG_HASH=vertexCredentials_ad6dd8135d + +Scenario 1: vertexCredentialsWithNonNullSetsFieldAndAllowsChaining + +Details: + TestName: vertexCredentialsWithNonNullSetsFieldAndAllowsChaining + Description: Verifies that calling vertexCredentials with a non-null VertexCredentials on the Builder stores the value in the builder and returns the same Builder instance to enable fluent chaining. It also implies that when the builder later constructs a Gemini, the VertexCredentials path will be used if no higher-precedence option is set. + +Execution: + Arrange: Create a non-null VertexCredentials instance (vc). Obtain a Gemini.Builder via Gemini.builder(). + Act: Call builder.vertexCredentials(vc) and capture the returned value. + Assert: The returned value should be the exact same Builder instance (identity equality). + +Validation: + The test confirms the fluent API contract of vertexCredentials, ensuring that the method mutates the builder state correctly and supports chaining. This aligns with the implementation which returns this after assignment. If subsequent build() is invoked with modelName set and no higher-precedence fields, the Gemini should be constructed using the provided VertexCredentials. + +Scenario 2: vertexCredentialsWithNullDoesNotThrowAndIgnoresInput + +Details: + TestName: vertexCredentialsWithNullDoesNotThrowAndIgnoresInput + Description: Ensures that passing null to vertexCredentials does not throw and simply leaves the internal vertexCredentials field as null, allowing the builder to continue with other configuration paths. + +Execution: + Arrange: Create a VertexCredentials reference vc = null. Obtain a Gemini.Builder via Gemini.builder() and set a modelName later. + Act: Call builder.vertexCredentials(vc) and capture the returned value. + Assert: The returned value should be the same Builder instance. + +Validation: + This confirms that a null input does not cause an exception and that subsequent build() will follow the next-available precedence rule (likely using a default client path if no other credentials are provided). + +Scenario 3: vertexCredentialsIgnoredWhenApiClientPresent + +Details: + TestName: vertexCredentialsIgnoredWhenApiClientPresent + Description: Validates that if an explicit apiClient is already set on the Builder, invoking vertexCredentials does not override the intended path; the build() should use the explicit apiClient path. + +Execution: + Arrange: Create a mock Client instance (mockApiClient). Obtain a Gemini.Builder; set apiClient(mockApiClient); create a VertexCredentials vc and call vertexCredentials(vc) afterward. + Act: Build the Gemini via builder.build(). + Assert: The resulting Gemini should be constructed using the explicit apiClient path rather than VertexCredentials. + +Validation: + The test ensures the precedence rule in the build() method: apiClient takes priority over vertexCredentials when both are present. + +Scenario 4: vertexCredentialsIgnoredWhenApiKeyPresent + +Details: + TestName: vertexCredentialsIgnoredWhenApiKeyPresent + Description: Confirms that when an apiKey is set on the Builder (and apiClient is not set), but vertexCredentials is also provided, the build() should prefer the apiKey path over VertexCredentials. + +Execution: + Arrange: Obtain a Gemini.Builder; set apiKey("sample-key"); call vertexCredentials with a non-null VertexCredentials vc. + Act: Build the Gemini via builder.build(). + Assert: The resulting Gemini should be constructed using the apiKey path (Gemini(modelName, apiKey)) rather than VertexCredentials. + +Validation: + Verifies the precedence order in the build logic: apiKey overrides vertexCredentials when apiClient is not present. + +Scenario 5: vertexCredentialsLastValueWinsWithMultipleCalls + +Details: + TestName: vertexCredentialsLastValueWinsWithMultipleCalls + Description: Checks that successive calls to vertexCredentials overwrite the previously stored value, and the final value is the one used when building the Gemini. + +Execution: + Arrange: Create vc1 and vc2 as VertexCredentials instances. Obtain a Gemini.Builder. + Act: Call builder.vertexCredentials(vc1); then builder.vertexCredentials(vc2); then build(). + Assert: If a path using VertexCredentials is chosen (no apiClient/apiKey set), the Gemini should reflect vc2 as the last provided credentials (in behavior terms). + +Validation: + Ensures that the builder state reflects the most recent vertexCredentials call, consistent with typical fluent API semantics. + +Scenario 6: vertexCredentialsWithoutModelNameThrowsNullPointerException + +Details: + TestName: vertexCredentialsWithoutModelNameThrowsNullPointerException + Description: Validates that calling build() without setting a modelName results in a NullPointerException, confirming the required modelName precondition even when vertexCredentials has been supplied. + +Execution: + Arrange: Obtain a Gemini.Builder; do not call modelName; call vertexCredentials with a non-null VertexCredentials vc. + Act: Call build() and observe the exception. + Assert: A NullPointerException is thrown with the message indicating modelName must be set. + +Validation: + Verifies enforcement of the precondition in build() for modelName, ensuring that a valid model name is always required for Gemini construction. + +Scenario 7: vertexCredentialsUsedWhenOnlyVertexCredentialsAndModelNameSet + +Details: + TestName: vertexCredentialsUsedWhenOnlyVertexCredentialsAndModelNameSet + Description: Ensures that when only a VertexCredentials instance and a modelName are provided (no apiClient/apiKey), the build() path uses VertexCredentials to create the Gemini instance. + +Execution: + Arrange: Create vc, obtain a Builder, set modelName("gemini-model"), call vertexCredentials(vc). + Act: Build the Gemini. + Assert: The Gemini is constructed via the VertexCredentials path (Gemini(modelName, vertexCredentials)). + +Validation: + Confirms the intended path is taken when VertexCredentials is the only credential source provided besides the required modelName. + +Scenario 8: vertexCredentialsOverridesPreviousConsiderationsWhenNoOtherCredentialsArePresent + +Details: + TestName: vertexCredentialsOverridesPreviousConsiderationsWhenNoOtherCredentialsArePresent + Description: Verifies that after previously configuring apiClient or apiKey, calling vertexCredentials does not retroactively change the chosen path unless those higher-priority fields are cleared; specifically, in the absence of apiClient/apiKey, vertexCredentials should be used. + +Execution: + Arrange: Start with a builder that has modelName set and apiClient/apiKey are null. Call vertexCredentials(vc) with a non-null VertexCredentials. + Act: Build the Gemini. + Assert: The VertexCredentials path is used in Gemini construction. + +Validation: + Ensures correct behavior when transitioning from no credentials to VertexCredentials as the sole credential source. + + +*/ + +// ********RoostGPT******** + +package com.google.adk.models; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import com.google.genai.Client; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** + * Tests for Gemini.Builder.vertexCredentials(VertexCredentials) Generated class + * BuilderVertexCredentialsTest: BuilderVertexCredentialsTest + */ +class BuilderVertexCredentialsTest { + + private static final Class BUILDER_CLASS; + + static { + try { + BUILDER_CLASS = Class.forName("com.google.adk.models.Gemini$Builder"); + } + catch (ClassNotFoundException e) { + throw new RuntimeException("Unable to locate Gemini.Builder class", e); + } + } + + private Object newBuilder() { + // Gemini.builder() is expected to provide a Builder instance + try { + return Class.forName("com.google.adk.models.Gemini").getMethod("builder").invoke(null); + } + catch (Exception e) { + throw new RuntimeException("Failed to create Gemini Builder", e); + } + } + + @Test + @Tag("valid") + public void vertexCredentialsWithNonNullSetsFieldAndAllowsChaining() throws Exception { + VertexCredentials vc = VertexCredentials.builder().build(); + Object builder = newBuilder(); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + Object returned = vertexCredentialsMethod.invoke(builder, vc); + assertSame(builder, returned, "vertexCredentials should return the same Builder instance"); + Field fVertexCredentials = BUILDER_CLASS.getDeclaredField("vertexCredentials"); + fVertexCredentials.setAccessible(true); + Object value = fVertexCredentials.get(builder); + assertEquals(vc, value, "vertexCredentials field should store the provided value"); + } + + @Test + @Tag("valid") + public void vertexCredentialsWithNullDoesNotThrowAndIgnoresInput() throws Exception { + Object builder = newBuilder(); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + Object returned = vertexCredentialsMethod.invoke(builder, (Object) null); + assertSame(builder, returned, "vertexCredentials should return the same Builder instance on null input"); + Field fVertexCredentials = BUILDER_CLASS.getDeclaredField("vertexCredentials"); + fVertexCredentials.setAccessible(true); + Object value = fVertexCredentials.get(builder); + assertNull(value, "vertexCredentials field should remain null when null is provided"); + } + + @Test + @Tag("integration") + public void vertexCredentialsIgnoredWhenApiClientPresent() throws Exception { + Object builder = newBuilder(); + Client mockApiClient = mock(Client.class); + Method apiClientMethod = BUILDER_CLASS.getMethod("apiClient", Client.class); + apiClientMethod.invoke(builder, mockApiClient); + VertexCredentials vc = VertexCredentials.builder().build(); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + vertexCredentialsMethod.invoke(builder, vc); + Field fApiClient = BUILDER_CLASS.getDeclaredField("apiClient"); + fApiClient.setAccessible(true); + Object value = fApiClient.get(builder); + assertEquals(mockApiClient, value, "apiClient should be the explicit provided client"); + // Also ensure VertexCredentials was set (to reflect last write), but build() will + // still prefer + // apiClient + Field fVertexCredentials = BUILDER_CLASS.getDeclaredField("vertexCredentials"); + fVertexCredentials.setAccessible(true); + Object vcValue = fVertexCredentials.get(builder); + assertEquals(vc, vcValue, + "vertexCredentials should be stored but ignored by build path when apiClient is present"); + } + + @Test + @Tag("integration") + public void vertexCredentialsIgnoredWhenApiKeyPresent() throws Exception { + Object builder = newBuilder(); + Method apiKeyMethod = BUILDER_CLASS.getMethod("apiKey", String.class); + apiKeyMethod.invoke(builder, "sample-key"); + VertexCredentials vc = VertexCredentials.builder().build(); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + vertexCredentialsMethod.invoke(builder, vc); + Field fApiKey = BUILDER_CLASS.getDeclaredField("apiKey"); + fApiKey.setAccessible(true); + Object keyValue = fApiKey.get(builder); + assertEquals("sample-key", keyValue, "apiKey should store the provided key"); + Field fApiClient = BUILDER_CLASS.getDeclaredField("apiClient"); + fApiClient.setAccessible(true); + Object apiClientValue = fApiClient.get(builder); + assertNull(apiClientValue, "apiClient should remain null when only apiKey is provided"); + } + + @Test + @Tag("boundary") + public void vertexCredentialsLastValueWinsWithMultipleCalls() throws Exception { + Object builder = newBuilder(); + VertexCredentials vc1 = VertexCredentials.builder().build(); + VertexCredentials vc2 = VertexCredentials.builder().build(); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + vertexCredentialsMethod.invoke(builder, vc1); + vertexCredentialsMethod.invoke(builder, vc2); + Field fVertexCredentials = BUILDER_CLASS.getDeclaredField("vertexCredentials"); + fVertexCredentials.setAccessible(true); + Object value = fVertexCredentials.get(builder); + assertEquals(vc2, value, "The last provided VertexCredentials should be stored in the Builder"); + } + + @Test + @Tag("invalid") + public void vertexCredentialsWithoutModelNameThrowsNullPointerException() throws Exception { + Object builder = newBuilder(); + VertexCredentials vc = VertexCredentials.builder().build(); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + vertexCredentialsMethod.invoke(builder, vc); + Method buildMethod = BUILDER_CLASS.getMethod("build"); + InvocationTargetException invocationTargetException = assertThrows(InvocationTargetException.class, () -> { + buildMethod.invoke(builder); + }); + Throwable cause = invocationTargetException.getCause(); + assertNotNull(cause, "Expected a cause for the InvocationTargetException"); + assertTrue(cause instanceof NullPointerException, "Expected NullPointerException when modelName is not set"); + String msg = cause.getMessage(); + assertTrue(msg == null || msg.contains("modelName"), "Error message should indicate missing modelName"); + } + + @Test + @Tag("valid") + public void vertexCredentialsUsedWhenOnlyVertexCredentialsAndModelNameSet() throws Exception { + Object builder = newBuilder(); + VertexCredentials vc = VertexCredentials.builder().build(); + Method modelNameMethod = BUILDER_CLASS.getMethod("modelName", String.class); + modelNameMethod.invoke(builder, "gemini-model"); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + vertexCredentialsMethod.invoke(builder, vc); + Field fVertexCredentials = BUILDER_CLASS.getDeclaredField("vertexCredentials"); + fVertexCredentials.setAccessible(true); + Object value = fVertexCredentials.get(builder); + assertEquals(vc, value, + "VertexCredentials should be used when only VertexCredentials and modelName are provided"); + } + + @Test + @Tag("boundary") + public void vertexCredentialsOverridesPreviousConsiderationsWhenNoOtherCredentialsArePresent() throws Exception { + Object builder = newBuilder(); + VertexCredentials vc = VertexCredentials.builder().build(); + Method modelNameMethod = BUILDER_CLASS.getMethod("modelName", String.class); + modelNameMethod.invoke(builder, "gemini-model"); + Method vertexCredentialsMethod = BUILDER_CLASS.getMethod("vertexCredentials", VertexCredentials.class); + vertexCredentialsMethod.invoke(builder, vc); + Field fApiClient = null; + Field fApiKey = null; + try { + fApiClient = BUILDER_CLASS.getDeclaredField("apiClient"); + fApiClient.setAccessible(true); + } + catch (NoSuchFieldException e) { + // ignore if absent in some implementations + } + try { + fApiKey = BUILDER_CLASS.getDeclaredField("apiKey"); + fApiKey.setAccessible(true); + } + catch (NoSuchFieldException e) { + // ignore if absent in some implementations + } + if (fApiClient != null) { + Object apiClientValue = fApiClient.get(builder); + assertNull(apiClientValue, "apiClient should be null when no explicit client is provided"); + } + if (fApiKey != null) { + Object apiKeyValue = fApiKey.get(builder); + assertNull(apiKeyValue, "apiKey should be null when no explicit key is provided"); + } + Field fVertexCredentials = BUILDER_CLASS.getDeclaredField("vertexCredentials"); + fVertexCredentials.setAccessible(true); + Object value = fVertexCredentials.get(builder); + assertEquals(vc, value, "VertexCredentials should be the active credential source when others are absent"); + } + +} diff --git a/core/src/test/java/com/google/adk/models/GeminiProcessRawResponsesTest.java.invalid b/core/src/test/java/com/google/adk/models/GeminiProcessRawResponsesTest.java.invalid new file mode 100644 index 000000000..0694e88c3 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/GeminiProcessRawResponsesTest.java.invalid @@ -0,0 +1,151 @@ +//This test file is marked invalid as it contains compilation errors. Change the extension to of this file to .java, to manually edit its contents +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// ********RoostGPT******** +/* +Test generated by RoostGPT for test unit-java-adk using AI Type Open AI and AI Model gpt-5-nano + +ROOST_METHOD_HASH=processRawResponses_490580de43 +ROOST_METHOD_SIG_HASH=processRawResponses_b550986252 + +Scenario 1: Single non-thought text chunk from raw response emits a partial text LlmResponse + +Details: + TestName: singleTextChunkEmitsPartialText + Description: This scenario verifies that when a single GenerateContentResponse in the stream contains a non-blank text chunk in the first part (not a thought), the method emits a partial LlmResponse containing only that text, and does not emit a final aggregated response since there is no STOP finishReason in the final input. +Execution: + Arrange: Construct a Flowable containing one raw response whose Part text is "Hello world" and thought() is not present or false. Ensure GeminiUtil.shouldEmitAccumulatedText would allow emitting partial text for this response. + Act: Call Gemini.processRawResponses with the constructed Flowable. + Assert: Assert that the produced LlmResponse sequence contains exactly one element whose content.parts includes "Hello world" (via Part.fromText) and whose partial flag is true. There should be no additional final aggregated responses since no STOP finish reason is observed. +Validation: The assertion confirms that a single non-thought text fragment is emitted as a partial update, validating the non-accumulated path for a simple one-shot text chunk. + +*/ + +// ********RoostGPT******** + +package com.google.adk.models; +import static com.google.common.base.StandardSystemProperty.JAVA_VERSION; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.adk.models.BaseLlm; +import com.google.adk.models.Gemini; +import com.google.adk.models.GeminiUtil; +import com.google.common.collect.ImmutableList; +import com.google.genai.types.Candidate; +import com.google.genai.types.Content; +import com.google.genai.types.GenerateContentResponse; +import com.google.genai.types.FinishReason; +import com.google.genai.types.Part; +import io.reactivex.rxjava3.core.Flowable; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.junit.jupiter.api.*; +import com.google.adk.Version; +import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.google.genai.Client; +import com.google.genai.ResponseStream; +import com.google.genai.types.GenerateContentConfig; +import com.google.genai.types.HttpOptions; +import com.google.genai.types.LiveConnectConfig; +import java.util.ArrayList; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Test suite for Gemini.processRawResponses + */ +public class GeminiProcessRawResponsesTest extends BaseLlm { + private static final String TEST_TEXT_CHUNK = "SAMPLE_TEXT"; + // Minimal implementations to satisfy inheritance + public GeminiProcessRawResponsesTest() { + super("GeminiProcessRawResponsesTestModel"); + } + @Override + public io.reactivex.rxjava3.core.Flowable generateContent( + com.google.adk.models.LlmRequest llmRequest, boolean stream) { + return Flowable.empty(); + } + @Override + public com.google.adk.models.BaseLlmConnection connect(com.google.adk.models.LlmRequest llmRequest) { + return null; + } + @Test + @Tag("integration") + public void test_singleTextChunkEmitsPartialText() { + GenerateContentResponse rawResponse = + GenerateContentResponse.builder() + .candidates(new Candidate[] { + Candidate.builder() + .finishReason(Optional.empty()) + .build() + }) + .build(); + try (MockedStatic mockedUtil = Mockito.mockStatic(GeminiUtil.class)) { + // Mock to force a single text chunk with non-thought text + mockedUtil + .when(() -> GeminiUtil.getPart0FromLlmResponse(Mockito.any())) + .thenReturn(Optional.of(Part.fromText(TEST_TEXT_CHUNK))); + Flowable stream = Flowable.just(rawResponse); + List results = + Gemini.processRawResponses(stream).toList().blockingGet(); + // Expect exactly one partial update for the single text chunk + assertEquals(1, results.size(), "Expected exactly one emitted LlmResponse"); + com.google.adk.models.LlmResponse resp = results.get(0); + // Partial flag should be true + assertTrue(resp.partial().orElse(false), "Expected partial to be true for the text chunk"); + // Content should contain the test text chunk + Optional contentOpt = resp.content(); + assertTrue(contentOpt.isPresent(), "Expected content to be present"); + Content content = contentOpt.get(); + List parts = content.parts(); + boolean found = parts.stream().anyMatch(p -> p.text().orElse("").equals(TEST_TEXT_CHUNK)); + assertTrue(found, "Expected content parts to contain the test text chunk"); + } + } + @Test + @Tag("integration") + public void test_noTextChunkEmitsRawResponseWithoutPartial() { + GenerateContentResponse rawResponse = + GenerateContentResponse.builder() + .candidates(new Candidate[] { + Candidate.builder() + .finishReason(Optional.empty()) + .build() + }) + .build(); + try (MockedStatic mockedUtil = Mockito.mockStatic(GeminiUtil.class)) { + // Mock to simulate no text chunk in the first part + mockedUtil + .when(() -> GeminiUtil.getPart0FromLlmResponse(Mockito.any())) + .thenReturn(Optional.empty()); + Flowable stream = Flowable.just(rawResponse); + List results = + Gemini.processRawResponses(stream).toList().blockingGet(); + // Expect a single emission (the raw response) without partial flag + assertEquals(1, results.size(), "Expected exactly one emitted LlmResponse"); + com.google.adk.models.LlmResponse resp = results.get(0); + assertFalse(resp.partial().orElse(false), "Expected partial to be false when there is no text chunk"); + } + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 6009c7316..cd4d2122b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,4 +1,4 @@ - + - + 4.0.0 - com.google.adk google-adk-parent - 0.4.1-SNAPSHOT + 0.4.1-SNAPSHOT + pom - Google Agent Development Kit Maven Parent POM https://github.com/google/adk-java Google Agent Development Kit (ADK) for Java - core dev @@ -39,12 +35,10 @@ a2a a2a/webservice - 17 ${java.version} UTF-8 - 1.11.0 3.4.1 1.49.0 @@ -73,7 +67,6 @@ 3.9.0 5.4.3 - @@ -112,7 +105,6 @@ pom import - com.anthropic @@ -274,9 +266,28 @@ assertj-core ${assertj.version} + + org.junit.jupiter + junit-jupiter + 5.5.2 + test + + + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + + + + io.spring.javaformat + spring-javaformat-formatter + 0.0.40 + + - @@ -324,8 +335,7 @@ plain - + **/*Test.java @@ -469,6 +479,36 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + + org.apache.maven.plugins + maven-surefire-report-plugin + 3.2.5 + + testReport + + + + + org.apache.maven.plugins + maven-site-plugin + 2.1 + + testReport + + + + + io.spring.javaformat + spring-javaformat-maven-plugin + 0.0.40 + + @@ -528,7 +568,6 @@ - The Apache License, Version 2.0 @@ -558,4 +597,250 @@ https://central.sonatype.com/repository/maven-snapshots/ + + + org.junit.jupiter + junit-jupiter + 5.5.2 + test + + + + org.mockito + mockito-junit-jupiter + 2.23.4 + test + + + + io.spring.javaformat + spring-javaformat-formatter + 0.0.40 + + + + com.anthropic + anthropic-java + 1.4.0 + test + + + + com.anthropic + anthropic-java-vertex + 1.4.0 + test + + + + com.github.docker-java + docker-java + 3.3.6 + test + + + + com.github.docker-java + docker-java-transport-httpclient5 + 3.3.6 + test + + + + io.modelcontextprotocol.sdk + mcp + 0.14.0 + test + + + + com.google.genai + google-genai + 1.28.0 + test + + + + com.squareup.okhttp3 + okhttp + 4.12.0 + test + + + + com.google.auto.value + auto-value-annotations + 1.11.0 + test + + + + com.google.errorprone + error_prone_annotations + 2.38.0 + test + + + + com.fasterxml.jackson.core + jackson-core + 2.19.0 + test + + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + test + + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + test + + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.19.0 + test + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.19.0 + test + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.19.0 + test + + + + com.google.protobuf + protobuf-java + 4.32.0 + test + + + + org.java-websocket + Java-WebSocket + 1.6.0 + test + + + + org.slf4j + slf4j-simple + 2.0.17 + test + + + + com.google.truth + truth + 1.4.4 + test + + + + org.mockito + mockito-core + 5.20.0 + test + + + + net.bytebuddy + byte-buddy + 1.18.2 + test + + + + org.jspecify + jspecify + 1.0.0 + test + + + + io.reactivex.rxjava3 + rxjava + 3.1.5 + test + + + + io.projectreactor + reactor-core + 3.7.0 + test + + + + com.github.tomakehurst + wiremock-jre8 + 2.35.1 + test + + + + guru.nidi + graphviz-java + 0.18.1 + test + + + + org.eclipse.jdt + ecj + 3.41.0 + test + + + + org.apache.maven + maven-plugin-api + 3.9.0 + test + + + + org.apache.maven + maven-core + 3.9.0 + test + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.9.0 + test + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.4.3 + test + + + + org.assertj + assertj-core + 3.27.3 + test + + + \ No newline at end of file