From 4e70246e2150a7478311b6e99535ca80d63cc2a2 Mon Sep 17 00:00:00 2001 From: roost-io <8110509+mgdevstack@users.noreply.github.com> Date: Thu, 15 Jan 2026 08:54:16 +0000 Subject: [PATCH] Unit test generated by RoostGPT Using AI Model gpt-5-nano --- core/pom.xml | 21 +- .../adk/models/BuilderApiClientTest.java | 84 +++++ .../google/adk/models/BuilderApiKeyTest.java | 78 +++++ .../google/adk/models/BuilderBuildTest.java | 157 +++++++++ .../adk/models/BuilderModelNameTest.java | 119 +++++++ .../models/BuilderVertexCredentialsTest.java | 298 ++++++++++++++++ ...GeminiProcessRawResponsesTest.java.invalid | 133 ++++++++ pom.xml | 317 +++++++++++++++++- 8 files changed, 1183 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..6f2df606e --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderApiClientTest.java @@ -0,0 +1,84 @@ +/* + * 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.assertSame; +import static org.mockito.Mockito.mock; + +import com.google.genai.Client; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** Test suite for Gemini.Builder.apiClient(Client apiClient) */ +public class BuilderApiClientTest { + + @Test + @Tag("valid") + public void testApiClientSetsExplicitClient() { + Gemini.Builder builder = Gemini.builder(); + Client mockClient = mock(Client.class); + Gemini.Builder resultBuilder = builder.apiClient(mockClient); + // Ensure fluent API returns the same builder instance (enables chaining) + assertSame(builder, resultBuilder); + // Ensure chaining remains functional by calling another method on the same + // builder + Gemini.Builder chained = resultBuilder.modelName("test-model"); // TODO: adjust + // modelName as + // needed + assertSame(builder, chained); + } + + @Test + @Tag("boundary") + public void testApiClientAcceptsNullClient() { + Gemini.Builder builder = Gemini.builder(); + Gemini.Builder resultBuilder = builder.apiClient(null); + // Even with null input, the fluent API should return the same builder instance + assertSame(builder, resultBuilder); + // Chaining should continue to work with a null client + Gemini.Builder chained = resultBuilder.modelName("test-model-null"); // TODO: + // adjust + // modelName + // as + // needed + assertSame(builder, chained); + } +} 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..0fdef9b77 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderApiKeyTest.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=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.assertSame; + +import com.google.adk.models.Gemini.Builder; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** Tests for the nested Builder.apiKey(...) method on Gemini. */ +public class BuilderApiKeyTest { + + @Test + @Tag("valid") + public void testSettingApiKeyStoresValue() { + // Arrange + Builder builder = Gemini.builder(); + // Act + Builder returned = builder.apiKey("TEST_API_KEY"); // TODO: replace with a real + // key if needed + // Assert + assertSame( + builder, + returned, + "apiKey(...) should return the same Builder instance to support fluent chaining."); + } + + @Test + @Tag("boundary") + public void testSettingApiKeyWithNullKeepsChaining() { + // Arrange + Builder builder = Gemini.builder(); + // Act + Builder returned = builder.apiKey(null); + // Assert + assertSame( + builder, + returned, + "apiKey(null) should return the same Builder instance to support fluent chaining even for null input."); + } +} 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..909fb9500 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderBuildTest.java @@ -0,0 +1,157 @@ +/* + * 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.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class BuilderBuildTest { + + @Test + @Tag("valid") + @DisplayName("buildWithApiClient: Build with explicit apiClient returns Gemini using that client") + public void testBuildWithApiClient() throws Exception { + // Arrange + Gemini.Builder builder = Gemini.builder(); + String modelName = "gemini-2.0"; // TODO: adjust if needed for different test + // environments + Client apiClientMock = mock(Client.class); + // Act + Gemini gemini = builder.modelName(modelName).apiClient(apiClientMock).build(); + // Assert + assertNotNull(gemini, "Gemini instance should not be null"); + // Use reflection to verify internal fields + Field modelField = Gemini.class.getDeclaredField("modelName"); + modelField.setAccessible(true); + Object actualModelName = modelField.get(gemini); + assertEquals(modelName, actualModelName, "modelName should be set to the provided value"); + Field apiClientField = Gemini.class.getDeclaredField("apiClient"); + apiClientField.setAccessible(true); + Object actualApiClient = apiClientField.get(gemini); + assertEquals(apiClientMock, actualApiClient, "apiClient should be the same as provided"); + } + + @Test + @Tag("valid") + @DisplayName("buildWithApiKey: Build with explicit apiKey") + public void testBuildWithApiKey() throws Exception { + // Arrange + Gemini.Builder builder = Gemini.builder(); + String modelName = "gemini-2.0"; // TODO: adjust if needed + String apiKey = "test-api-key"; + // Act + Gemini gemini = builder.modelName(modelName).apiKey(apiKey).build(); + // Assert + Field modelField = Gemini.class.getDeclaredField("modelName"); + modelField.setAccessible(true); + Object actualModelName = modelField.get(gemini); + assertEquals(modelName, actualModelName, "modelName should be set to the provided value"); + Field apiKeyField = Gemini.class.getDeclaredField("apiKey"); + apiKeyField.setAccessible(true); + Object actualApiKey = apiKeyField.get(gemini); + assertEquals(apiKey, actualApiKey, "apiKey should be the same as provided"); + } + + @Test + @Tag("valid") + @DisplayName("buildWithVertexCredentials: Build with vertexCredentials") + public void testBuildWithVertexCredentials() throws Exception { + // Arrange + Gemini.Builder builder = Gemini.builder(); + String modelName = "gemini-2.0"; // TODO: adjust if needed + VertexCredentials vertexCredentials = VertexCredentials.builder().build(); + // Act + Gemini gemini = builder.modelName(modelName).vertexCredentials(vertexCredentials).build(); + // Assert + Field modelField = Gemini.class.getDeclaredField("modelName"); + modelField.setAccessible(true); + Object actualModelName = modelField.get(gemini); + assertEquals(modelName, actualModelName, "modelName should be set to the provided value"); + Field vcField = Gemini.class.getDeclaredField("vertexCredentials"); + vcField.setAccessible(true); + Object actualVertexCreds = vcField.get(gemini); + assertEquals( + vertexCredentials, actualVertexCreds, "vertexCredentials should be the same as provided"); + } + + @Test + @Tag("boundary") + @DisplayName("buildWithNoCredentials: Build with no credentials uses default Client") + public void testBuildWithNoCredentials() throws Exception { + // Arrange + Gemini.Builder builder = Gemini.builder(); + String modelName = "gemini-2.0"; // TODO: adjust if needed + // Act + Gemini gemini = builder.modelName(modelName).build(); + // Assert + Field modelField = Gemini.class.getDeclaredField("modelName"); + modelField.setAccessible(true); + Object actualModelName = modelField.get(gemini); + assertEquals(modelName, actualModelName, "modelName should be set to the provided value"); + Field apiClientField = Gemini.class.getDeclaredField("apiClient"); + apiClientField.setAccessible(true); + Object actualApiClient = apiClientField.get(gemini); + assertNotNull( + actualApiClient, + "apiClient should be initialized by default when no credentials are provided"); + assertTrue(actualApiClient instanceof Client, "apiClient should be an instance of Client"); + } + + @Test + @Tag("invalid") + @DisplayName("buildWithNullModelNameThrows: Null modelName should throw NullPointerException") + public void testBuildWithNullModelNameThrows() { + // Arrange + Gemini.Builder builder = Gemini.builder(); + // Act & Assert + assertThrows( + NullPointerException.class, + () -> builder.build(), + "Expected NPE when modelName is not set"); + } +} 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..7a8ddf5d2 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderModelNameTest.java @@ -0,0 +1,119 @@ +/* + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.google.genai.Client; +import java.lang.reflect.Constructor; +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; +import org.mockito.Mockito; + +/** + * Unit tests for the nested Builder class's modelName(...) method in Gemini. Tests focus on fluent + * chaining and basic behavior using reflection to access the nested builder. + */ +public class BuilderModelNameTest { + + @Test + @Tag("valid") + public void testModelNameSetsValue() { + try { + // Access the nested static Builder class: + // com.google.adk.models.Gemini$Builder + Class builderClass = Class.forName("com.google.adk.models.Gemini$Builder"); + // Instantiate the Builder via its private constructor + Constructor ctor = builderClass.getDeclaredConstructor(); + ctor.setAccessible(true); + Object builder = ctor.newInstance(); + // Invoke modelName(String) and verify fluent chaining returns the same + // instance + Method modelNameMethod = builderClass.getMethod("modelName", String.class); + Object returnedAfterModelName = modelNameMethod.invoke(builder, "gemini-2.0-flash"); + assertSame(builder, returnedAfterModelName); + // Provide a mock Client to test further fluent chaining with apiClient(...) + Client mockClient = Mockito.mock(Client.class); + Method apiClientMethod = builderClass.getMethod("apiClient", Client.class); + Object returnedAfterApiClient = apiClientMethod.invoke(builder, mockClient); + assertSame(builder, returnedAfterApiClient); + // Basic sanity: ensure build() can be invoked and does not return null + Method buildMethod = builderClass.getMethod("build"); + Object geminiInstance = buildMethod.invoke(builder); + assertNotNull(geminiInstance); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Test + @Tag("edge") + public void testModelNameNullValueThrowsOnBuild() { + try { + Class builderClass = Class.forName("com.google.adk.models.Gemini$Builder"); + Constructor ctor = builderClass.getDeclaredConstructor(); + ctor.setAccessible(true); + Object builder = ctor.newInstance(); + // Call modelName(null) and then attempt to build, which should throw due to + // null modelName + Method modelNameMethod = builderClass.getMethod("modelName", String.class); + modelNameMethod.invoke(builder, (Object) null); + Method buildMethod = builderClass.getMethod("build"); + // Expect an InvocationTargetException wrapping a NullPointerException due to + // null modelName + InvocationTargetException thrown = + org.junit.jupiter.api.Assertions.assertThrows( + InvocationTargetException.class, + () -> { + buildMethod.invoke(builder); + }); + // Inspect the cause to ensure it's the expected NPE + Throwable cause = thrown.getTargetException(); + assertTrue(cause instanceof NullPointerException); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} 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..f2b1b308f --- /dev/null +++ b/core/src/test/java/com/google/adk/models/BuilderVertexCredentialsTest.java @@ -0,0 +1,298 @@ +/* + * 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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.mock; + +import com.google.genai.Client; +import java.lang.reflect.Field; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +public class BuilderVertexCredentialsTest { + + // Helper to inspect which credential source was used inside Gemini + private Object getUsedCredentialSource(Gemini gem) { + Class cls = gem.getClass(); + for (String fieldName : new String[] {"apiClient", "apiKey", "vertexCredentials"}) { + try { + Field f = cls.getDeclaredField(fieldName); + f.setAccessible(true); + Object value = f.get(gem); + if (value != null) { + return value; + } + } catch (NoSuchFieldException | IllegalAccessException e) { + // Ignore and try next + } + } + return null; + } + + @Test + @Tag("valid") + @DisplayName("vertexCredentialsWithNonNullSetsFieldAndAllowsChaining") + public void vertexCredentialsWithNonNullSetsFieldAndAllowsChaining() throws Exception { + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + Gemini.Builder returned = builder.vertexCredentials(vc); + // Fluent API contract: same instance returned for chaining + assertSame(builder, returned); + // Prepare to build and verify that VertexCredentials path is used + builder.modelName("gemini-model"); + Gemini gem = builder.build(); + Object used = getUsedCredentialSource(gem); + assertSame(vc, used, "Expected VertexCredentials to be the credential source used."); + } + + @Test + @Tag("boundary") + @DisplayName("vertexCredentialsWithNullDoesNotThrowAndIgnoresInput") + public void vertexCredentialsWithNullDoesNotThrowAndIgnoresInput() { + Gemini.Builder builder = Gemini.builder(); + Gemini.Builder returned = builder.vertexCredentials(null); + // Fluent API contract: same instance returned for chaining even with null + assertSame(builder, returned); + } + + @Test + @Tag("integration") + @DisplayName("vertexCredentialsIgnoredWhenApiClientPresent") + public void vertexCredentialsIgnoredWhenApiClientPresent() throws Exception { + Client mockApiClient = mock(Client.class); + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + builder.apiClient(mockApiClient); + builder.vertexCredentials(vc); + builder.modelName("gemini-model"); + Gemini gem = builder.build(); + Object used = getUsedCredentialSource(gem); + assertSame( + mockApiClient, used, "Expected API client to take precedence over VertexCredentials."); + } + + @Test + @Tag("integration") + @DisplayName("vertexCredentialsIgnoredWhenApiKeyPresent") + public void vertexCredentialsIgnoredWhenApiKeyPresent() throws Exception { + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + builder.apiKey("sample-key"); + builder.vertexCredentials(vc); + builder.modelName("gemini-model"); + Gemini gem = builder.build(); + // When apiKey is present, it should be the credential source used + Object used = getUsedCredentialSource(gem); + assertNotNull(used, "Expected some credential source to be used."); + assert (used instanceof String) : "Expected apiKey to be a String."; + assert "sample-key".equals(used) : "Expected apiKey value to be used."; + } + + @Test + @Tag("valid") + @DisplayName("vertexCredentialsLastValueWinsWithMultipleCalls") + public void vertexCredentialsLastValueWinsWithMultipleCalls() throws Exception { + VertexCredentials vc1 = VertexCredentials.builder().build(); + VertexCredentials vc2 = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + builder.vertexCredentials(vc1); + builder.vertexCredentials(vc2); + builder.modelName("gemini-model"); + Gemini gem = builder.build(); + Object used = getUsedCredentialSource(gem); + assertSame( + vc2, + used, + "The last provided VertexCredentials should be used when no higher-priority credentials are present."); + } + + @Test + @Tag("invalid") + @DisplayName("vertexCredentialsWithoutModelNameThrowsNullPointerException") + public void vertexCredentialsWithoutModelNameThrowsNullPointerException() { + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + builder.vertexCredentials(vc); + NullPointerException ex = assertThrows(NullPointerException.class, () -> builder.build()); + // Ensure that the NPE message indicates the required modelName + String msg = ex.getMessage(); + assertNotNull(msg); + assert msg.contains("modelName") : "Expected message to mention modelName."; + } + + @Test + @Tag("valid") + @DisplayName("vertexCredentialsUsedWhenOnlyVertexCredentialsAndModelNameSet") + public void vertexCredentialsUsedWhenOnlyVertexCredentialsAndModelNameSet() throws Exception { + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + builder.modelName("gemini-model"); + builder.vertexCredentials(vc); + Gemini gem = builder.build(); + Object used = getUsedCredentialSource(gem); + assertSame( + vc, + used, + "Expected VertexCredentials path to be used when only VertexCredentials and modelName are set."); + } + + @Test + @Tag("valid") + @DisplayName("vertexCredentialsOverridesPreviousConsiderationsWhenNoOtherCredentialsArePresent") + public void vertexCredentialsOverridesPreviousConsiderationsWhenNoOtherCredentialsArePresent() + throws Exception { + VertexCredentials vc = VertexCredentials.builder().build(); + Gemini.Builder builder = Gemini.builder(); + builder.modelName("gemini-model"); + // No apiClient/apiKey set; VertexCredentials should be used + builder.vertexCredentials(vc); + Gemini gem = builder.build(); + Object used = getUsedCredentialSource(gem); + assertSame( + vc, + used, + "Expected VertexCredentials to be used when no other credentials are configured."); + } +} 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..be8e9c037 --- /dev/null +++ b/core/src/test/java/com/google/adk/models/GeminiProcessRawResponsesTest.java.invalid @@ -0,0 +1,133 @@ +//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 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.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; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Tag; +import org.mockito.MockedStatic; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import org.junit.jupiter.api.*; + +/** + * Test suite for Gemini.processRawResponses + */ +public class GeminiProcessRawResponsesTest extends BaseLlm { + private static final Logger logger = LoggerFactory.getLogger(GeminiProcessRawResponsesTest.class); + @BeforeEach + public void setup() { + // No special setup required beyond what tests perform. + } + // Test Scenario 1: Single non-thought text chunk from raw response emits a partial text LlmResponse + @Test + @Tag("valid") + public void singleTextChunkEmitsPartialText() { + // Mock the static helper to return a non-thought text chunk + try (MockedStatic mockedUtil = mockStatic(GeminiUtil.class)) { + // Part.fromText returns a List, so take the first element if present + List textParts = Part.fromText("Hello world"); + Optional optionalTextPart = textParts.isEmpty() ? Optional.empty() : Optional.of(textParts.get(0)); + // When getPart0FromLlmResponse is called, return a non-thought text chunk + mockedUtil.when(() -> GeminiUtil.getPart0FromLlmResponse(any())).thenReturn(optionalTextPart); + // Build a minimal GenerateContentResponse with no STOP indicator + GenerateContentResponse rawResp = GenerateContentResponse.builder() + .candidates(new ArrayList<>()) // TODO: customize if needed for different scenarios + .build(); + Flowable flow = Flowable.just(rawResp); + Flowable results = Gemini.processRawResponses(flow); + List emitted = results.toList().blockingGet(); + // We expect exactly one partial text emission + assertEquals(1, emitted.size()); + LlmResponse llm = emitted.get(0); + // Verify partial flag is set + assertTrue(llm.partial().isPresent() && llm.partial().get()); + // Verify content contains the expected text + assertTrue(llm.content().isPresent(), "Content should be present for emitted response"); + Content content = llm.content().get(); + // Content likely holds a list of parts; ensure there is a part with "Hello world" + List parts = content.parts(); + assertNotNull(parts, "Content parts should not be null"); + assertTrue(parts.size() >= 1, "There should be at least one part in content"); + boolean foundHelloWorld = parts.stream() + .anyMatch(p -> p.text().isPresent() && "Hello world".equals(p.text().get())); + assertTrue(foundHelloWorld, "A part with text 'Hello world' should be present in emitted content"); + } + } + // Test Scenario 2: Empty input stream results in no emissions + @Test + @Tag("boundary") + public void emptyInputProducesNoEmissions() { + Flowable emptyFlow = Flowable.empty(); + Flowable results = Gemini.processRawResponses(emptyFlow); + List emitted = results.toList().blockingGet(); + assertNotNull(emitted); + assertEquals(0, emitted.size(), "Emissions should be empty for empty input stream"); + } + // Implement abstract methods from BaseLlm to satisfy compilation for tests + @Override + public Flowable generateContent(LlmRequest llmRequest, boolean stream) { + return Flowable.empty(); + } + @Override + public BaseLlmConnection connect(LlmRequest llmRequest) { + return null; + } +} \ 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