From 17b6de91b5224e9e78663317130daafe3a2dd385 Mon Sep 17 00:00:00 2001 From: Mark Daoust Date: Wed, 6 May 2026 09:17:35 -0700 Subject: [PATCH] chore: Add stainless tests PiperOrigin-RevId: 911376794 --- pom.xml | 42 +- .../client/okhttp/OkHttpClientTest.kt | 60 + .../interactions/core/ClientOptionsTest.kt | 73 + .../interactions/core/ObjectMappersTest.kt | 133 + .../interactions/core/PhantomReachableTest.kt | 43 + .../genai/interactions/core/UtilsTest.kt | 49 + .../genai/interactions/core/ValuesTest.kt | 160 + .../core/handlers/SseHandlerTest.kt | 150 + .../core/handlers/StreamHandlerTest.kt | 110 + .../core/http/AsyncStreamResponseTest.kt | 284 ++ .../interactions/core/http/HeadersTest.kt | 258 ++ .../core/http/HttpRequestBodiesTest.kt | 745 +++++ .../interactions/core/http/HttpRequestTest.kt | 126 + .../interactions/core/http/QueryParamsTest.kt | 196 ++ .../core/http/RetryingHttpClientTest.kt | 472 +++ .../models/interactions/AllowedToolsTest.kt | 52 + .../models/interactions/AnnotationTest.kt | 188 ++ .../models/interactions/ArgumentsDeltaTest.kt | 46 + .../models/interactions/AudioContentTest.kt | 66 + .../interactions/AudioResponseFormatTest.kt | 63 + .../CodeExecutionCallArgumentsTest.kt | 58 + .../interactions/CodeExecutionCallStepTest.kt | 76 + .../CodeExecutionResultStepTest.kt | 63 + .../models/interactions/ContentTest.kt | 249 ++ .../CreateAgentInteractionParamsTest.kt | 368 +++ .../CreateModelInteractionParamsTest.kt | 436 +++ .../DeepResearchAgentConfigTest.kt | 62 + .../interactions/DocumentContentTest.kt | 60 + .../interactions/DynamicAgentConfigTest.kt | 46 + .../models/interactions/ErrorEventTest.kt | 58 + .../models/interactions/FileCitationTest.kt | 89 + .../interactions/FileSearchCallStepTest.kt | 51 + .../interactions/FileSearchResultStepTest.kt | 57 + .../interactions/FunctionCallStepTest.kt | 77 + .../interactions/FunctionResultStepTest.kt | 68 + .../models/interactions/FunctionTest.kt | 61 + .../interactions/GenerationConfigTest.kt | 119 + .../GoogleMapsCallArgumentsTest.kt | 49 + .../interactions/GoogleMapsCallStepTest.kt | 61 + .../interactions/GoogleMapsResultStepTest.kt | 115 + .../interactions/GoogleMapsResultTest.kt | 98 + .../GoogleSearchCallArgumentsTest.kt | 51 + .../interactions/GoogleSearchCallStepTest.kt | 65 + .../GoogleSearchResultDeltaTest.kt | 67 + .../GoogleSearchResultStepTest.kt | 76 + .../interactions/GoogleSearchResultTest.kt | 50 + .../models/interactions/ImageConfigTest.kt | 57 + .../models/interactions/ImageContentTest.kt | 63 + .../interactions/ImageResponseFormatTest.kt | 63 + .../InteractionCancelParamsTest.kt | 40 + .../InteractionCompletedEventTest.kt | 490 +++ .../InteractionCreateParamsTest.kt | 469 +++ .../InteractionCreatedEventTest.kt | 490 +++ .../InteractionDeleteParamsTest.kt | 40 + .../InteractionDeleteResponseTest.kt | 46 + .../interactions/InteractionGetParamsTest.kt | 76 + .../interactions/InteractionSseEventTest.kt | 921 ++++++ .../InteractionStatusUpdateTest.kt | 61 + .../models/interactions/InteractionTest.kt | 490 +++ .../interactions/McpServerToolCallStepTest.kt | 80 + .../McpServerToolResultStepTest.kt | 70 + .../interactions/ModelOutputStepTest.kt | 94 + .../models/interactions/PlaceCitationTest.kt | 89 + .../models/interactions/SpeechConfigTest.kt | 52 + .../models/interactions/StepDeltaTest.kt | 51 + .../models/interactions/StepStartTest.kt | 111 + .../models/interactions/StepStopTest.kt | 49 + .../models/interactions/StepTest.kt | 1019 ++++++ .../models/interactions/TextContentTest.kt | 82 + .../interactions/TextResponseFormatTest.kt | 72 + .../models/interactions/ThoughtStepTest.kt | 97 + .../interactions/ToolChoiceConfigTest.kt | 59 + .../models/interactions/ToolTest.kt | 389 +++ .../models/interactions/UrlCitationTest.kt | 53 + .../UrlContextCallArgumentsTest.kt | 49 + .../interactions/UrlContextCallStepTest.kt | 61 + .../interactions/UrlContextResultDeltaTest.kt | 76 + .../interactions/UrlContextResultStepTest.kt | 79 + .../interactions/UrlContextResultTest.kt | 51 + .../models/interactions/UsageTest.kt | 162 + .../models/interactions/UserInputStepTest.kt | 94 + .../models/interactions/VideoContentTest.kt | 63 + .../interactions/VideoResponseFormatTest.kt | 64 + .../services/ErrorHandlingTest.kt | 2844 +++++++++++++++++ .../services/ServiceParamsTest.kt | 214 ++ .../async/InteractionServiceAsyncTest.kt | 408 +++ .../blocking/InteractionServiceTest.kt | 400 +++ 87 files changed, 16183 insertions(+), 1 deletion(-) create mode 100644 src/test/java/com/google/genai/interactions/client/okhttp/OkHttpClientTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/ClientOptionsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/ObjectMappersTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/PhantomReachableTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/UtilsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/ValuesTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/handlers/SseHandlerTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/handlers/StreamHandlerTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/http/AsyncStreamResponseTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/http/HeadersTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/http/HttpRequestBodiesTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/http/HttpRequestTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/http/QueryParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/core/http/RetryingHttpClientTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/AllowedToolsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/AnnotationTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ArgumentsDeltaTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/AudioContentTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/AudioResponseFormatTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallArgumentsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ContentTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/CreateAgentInteractionParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/CreateModelInteractionParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/DeepResearchAgentConfigTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/DocumentContentTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/DynamicAgentConfigTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ErrorEventTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/FileCitationTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/FileSearchCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/FileSearchResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/FunctionCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/FunctionResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/FunctionTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GenerationConfigTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallArgumentsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallArgumentsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultDeltaTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ImageConfigTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ImageContentTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ImageResponseFormatTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionCancelParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionCompletedEventTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionCreateParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionCreatedEventTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteResponseTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionGetParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionSseEventTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionStatusUpdateTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/InteractionTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/McpServerToolCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/McpServerToolResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ModelOutputStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/PlaceCitationTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/SpeechConfigTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/StepDeltaTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/StepStartTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/StepStopTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/StepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/TextContentTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/TextResponseFormatTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ThoughtStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ToolChoiceConfigTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/ToolTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UrlCitationTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallArgumentsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultDeltaTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UsageTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/UserInputStepTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/VideoContentTest.kt create mode 100644 src/test/java/com/google/genai/interactions/models/interactions/VideoResponseFormatTest.kt create mode 100644 src/test/java/com/google/genai/interactions/services/ErrorHandlingTest.kt create mode 100644 src/test/java/com/google/genai/interactions/services/ServiceParamsTest.kt create mode 100644 src/test/java/com/google/genai/interactions/services/async/InteractionServiceAsyncTest.kt create mode 100644 src/test/java/com/google/genai/interactions/services/blocking/InteractionServiceTest.kt diff --git a/pom.xml b/pom.xml index a064798276e..a9c9f2eea05 100644 --- a/pom.xml +++ b/pom.xml @@ -179,6 +179,12 @@ kotlin-reflect ${kotlin.version} + + org.jetbrains.kotlin + kotlin-test-junit5 + ${kotlin.version} + test + com.fasterxml.jackson.module @@ -190,6 +196,37 @@ error_prone_annotations 2.36.0 + + + org.assertj + assertj-core + 3.25.3 + test + + + org.wiremock + wiremock + 3.5.4 + test + + + org.junit-pioneer + junit-pioneer + 2.2.0 + test + + + org.mockito.kotlin + mockito-kotlin + 5.4.0 + test + + + org.mockito + mockito-junit-jupiter + 5.12.0 + test + @@ -244,6 +281,9 @@ plain + + **/ProGuardCompatibilityTest.kt + @@ -345,7 +385,7 @@ - 1.8 + 11 -Xjvm-default=all diff --git a/src/test/java/com/google/genai/interactions/client/okhttp/OkHttpClientTest.kt b/src/test/java/com/google/genai/interactions/client/okhttp/OkHttpClientTest.kt new file mode 100644 index 00000000000..805600666ce --- /dev/null +++ b/src/test/java/com/google/genai/interactions/client/okhttp/OkHttpClientTest.kt @@ -0,0 +1,60 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.client.okhttp + +import com.google.genai.interactions.core.http.HttpMethod +import com.google.genai.interactions.core.http.HttpRequest +import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo +import com.github.tomakehurst.wiremock.junit5.WireMockTest +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.ResourceLock + +@WireMockTest +@ResourceLock("https://github.com/wiremock/wiremock/issues/169") +internal class OkHttpClientTest { + + private lateinit var baseUrl: String + private lateinit var httpClient: OkHttpClient + + @BeforeEach + fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { + baseUrl = wmRuntimeInfo.httpBaseUrl + httpClient = OkHttpClient.builder().build() + } + + @Test + fun executeAsync_whenFutureCancelled_cancelsUnderlyingCall() { + stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) + val responseFuture = + httpClient.executeAsync( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build() + ) + val call = httpClient.okHttpClient.dispatcher.runningCalls().single() + + responseFuture.cancel(false) + + // Should have cancelled the underlying call + assertThat(call.isCanceled()).isTrue() + } +} diff --git a/src/test/java/com/google/genai/interactions/core/ClientOptionsTest.kt b/src/test/java/com/google/genai/interactions/core/ClientOptionsTest.kt new file mode 100644 index 00000000000..98c3762bfcb --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/ClientOptionsTest.kt @@ -0,0 +1,73 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.core + +import com.google.genai.interactions.core.http.HttpClient +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify + +@ExtendWith(MockitoExtension::class) +internal class ClientOptionsTest { + + private val httpClient = mock() + + @Test + fun putHeader_canOverwriteDefaultHeader() { + val clientOptions = + ClientOptions.builder() + .httpClient(httpClient) + .putHeader("User-Agent", "My User Agent") + .apiKey("My API Key") + .build() + + assertThat(clientOptions.headers.values("User-Agent")).containsExactly("My User Agent") + } + + @Test + fun toBuilder_apiKeyCanBeUpdated() { + var clientOptions = + ClientOptions.builder().httpClient(httpClient).apiKey("My API Key").build() + + clientOptions = clientOptions.toBuilder().apiKey("another My API Key").build() + + assertThat(clientOptions.headers.values("x-goog-api-key")) + .containsExactly("another My API Key") + } + + @Test + fun toBuilder_whenOriginalClientOptionsGarbageCollected_doesNotCloseOriginalClient() { + var clientOptions = + ClientOptions.builder().httpClient(httpClient).apiKey("My API Key").build() + verify(httpClient, never()).close() + + // Overwrite the `clientOptions` variable so that the original `ClientOptions` is GC'd. + clientOptions = clientOptions.toBuilder().build() + System.gc() + Thread.sleep(100) + + verify(httpClient, never()).close() + // This exists so that `clientOptions` is still reachable. + assertThat(clientOptions).isEqualTo(clientOptions) + } +} diff --git a/src/test/java/com/google/genai/interactions/core/ObjectMappersTest.kt b/src/test/java/com/google/genai/interactions/core/ObjectMappersTest.kt new file mode 100644 index 00000000000..af2d8bd5160 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/ObjectMappersTest.kt @@ -0,0 +1,133 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.exc.MismatchedInputException +import com.fasterxml.jackson.module.kotlin.readValue +import java.time.LocalDate +import java.time.LocalTime +import java.time.OffsetDateTime +import java.time.ZoneOffset +import kotlin.reflect.KClass +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource +import org.junitpioneer.jupiter.cartesian.CartesianTest + +internal class ObjectMappersTest { + + internal class ClassWithBooleanFieldPrefixedWithIs(private val isActive: JsonField) { + + @JsonProperty("is_active") @ExcludeMissing fun _isActive() = isActive + } + + @Test + fun write_whenFieldPrefixedWithIs_keepsPrefix() { + val value = ClassWithBooleanFieldPrefixedWithIs(JsonField.of(true)) + + val json = jsonMapper().writeValueAsString(value) + + assertThat(json).isEqualTo("{\"is_active\":true}") + } + + internal class Class(@get:JsonProperty("field") @JsonProperty("field") val field: String) + + enum class ShapeTestCase(val value: Any, val kClass: KClass<*>) { + STRING("Hello World!", String::class), + BOOLEAN(true, Boolean::class), + FLOAT(3.14F, Float::class), + DOUBLE(3.14, Double::class), + INTEGER(42, Int::class), + LONG(42L, Long::class), + MAP(mapOf("property" to "value"), Map::class), + CLASS(Class("Hello World!"), Class::class), + LIST(listOf(1, 2, 3), List::class); + + companion object { + val VALID_CONVERSIONS = + listOf( + FLOAT to DOUBLE, + DOUBLE to FLOAT, + INTEGER to FLOAT, + INTEGER to DOUBLE, + INTEGER to LONG, + LONG to FLOAT, + LONG to DOUBLE, + LONG to INTEGER, + CLASS to MAP, + ) + } + } + + @CartesianTest + fun read(@CartesianTest.Enum shape1: ShapeTestCase, @CartesianTest.Enum shape2: ShapeTestCase) { + val jsonMapper = jsonMapper() + val json = jsonMapper.writeValueAsString(shape1.value) + + val e = catchThrowable { jsonMapper.readValue(json, shape2.kClass.java) } + + if (shape1 == shape2 || shape1 to shape2 in ShapeTestCase.VALID_CONVERSIONS) { + assertThat(e).isNull() + } else { + assertThat(e).isInstanceOf(MismatchedInputException::class.java) + } + } + + enum class LenientOffsetDateTimeTestCase( + val string: String, + val expectedOffsetDateTime: OffsetDateTime, + ) { + DATE( + "1998-04-21", + expectedOffsetDateTime = + OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(0, 0), ZoneOffset.UTC), + ), + DATE_TIME( + "1998-04-21T04:00:00", + expectedOffsetDateTime = + OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(4, 0), ZoneOffset.UTC), + ), + ZONED_DATE_TIME_1( + "1998-04-21T04:00:00+03:00", + expectedOffsetDateTime = + OffsetDateTime.of( + LocalDate.of(1998, 4, 21), + LocalTime.of(4, 0), + ZoneOffset.ofHours(3), + ), + ), + ZONED_DATE_TIME_2( + "1998-04-21T04:00:00Z", + expectedOffsetDateTime = + OffsetDateTime.of(LocalDate.of(1998, 4, 21), LocalTime.of(4, 0), ZoneOffset.UTC), + ), + } + + @ParameterizedTest + @EnumSource + fun readOffsetDateTime_lenient(testCase: LenientOffsetDateTimeTestCase) { + val jsonMapper = jsonMapper() + val json = jsonMapper.writeValueAsString(testCase.string) + + val offsetDateTime = jsonMapper().readValue(json) + + assertThat(offsetDateTime).isEqualTo(testCase.expectedOffsetDateTime) + } +} diff --git a/src/test/java/com/google/genai/interactions/core/PhantomReachableTest.kt b/src/test/java/com/google/genai/interactions/core/PhantomReachableTest.kt new file mode 100644 index 00000000000..74a1c4092f8 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/PhantomReachableTest.kt @@ -0,0 +1,43 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class PhantomReachableTest { + + @Test + fun closeWhenPhantomReachable_whenObservedIsGarbageCollected_closesCloseable() { + var closed = false + val closeable = AutoCloseable { closed = true } + + closeWhenPhantomReachable( + // Pass an inline object for the object to observe so that it becomes immediately + // unreachable. + Any(), + closeable, + ) + + assertThat(closed).isFalse() + + System.gc() + Thread.sleep(100) + + assertThat(closed).isTrue() + } +} diff --git a/src/test/java/com/google/genai/interactions/core/UtilsTest.kt b/src/test/java/com/google/genai/interactions/core/UtilsTest.kt new file mode 100644 index 00000000000..a6befa08016 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/UtilsTest.kt @@ -0,0 +1,49 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UtilsTest { + @Test + fun contentDeepEquals() { + assertThat(42 contentEquals 42).isTrue() + assertThat(42 contentEquals "Hello World!").isFalse() + assertThat(byteArrayOf(1, 2, 3) contentEquals byteArrayOf(1, 2, 3)).isTrue() + assertThat(byteArrayOf(1, 2, 3) contentEquals byteArrayOf(1, 2, 4)).isFalse() + assertThat( + arrayOf(byteArrayOf(1, 2), byteArrayOf(3)) contentEquals + arrayOf(byteArrayOf(1, 2), byteArrayOf(3)) + ) + .isTrue() + assertThat( + arrayOf(byteArrayOf(1, 2), byteArrayOf(3)) contentEquals + arrayOf(byteArrayOf(1), byteArrayOf(2, 3)) + ) + .isFalse() + } + + @Test + fun contentToString() { + assertThat((42).contentToString()).isEqualTo("42") + assertThat("Hello World!".contentToString()).isEqualTo("Hello World!") + assertThat(byteArrayOf(1, 2, 3).contentToString()).isEqualTo("[1, 2, 3]") + assertThat(arrayOf(byteArrayOf(1, 2), byteArrayOf(3)).contentToString()) + .isEqualTo("[[1, 2], [3]]") + } +} diff --git a/src/test/java/com/google/genai/interactions/core/ValuesTest.kt b/src/test/java/com/google/genai/interactions/core/ValuesTest.kt new file mode 100644 index 00000000000..2673093e491 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/ValuesTest.kt @@ -0,0 +1,160 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core + +import java.util.Optional +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class ValuesTest { + companion object { + private val NON_JSON = Any() + } + + enum class TestCase( + val value: JsonField<*>, + val expectedIsMissing: Boolean = false, + val expectedIsNull: Boolean = false, + val expectedAsKnown: Optional<*> = Optional.empty(), + val expectedAsBoolean: Optional = Optional.empty(), + val expectedAsNumber: Optional = Optional.empty(), + val expectedAsString: Optional = Optional.empty(), + val expectedAsArray: Optional> = Optional.empty(), + val expectedAsObject: Optional> = Optional.empty(), + ) { + MISSING(JsonMissing.of(), expectedIsMissing = true), + NULL(JsonNull.of(), expectedIsNull = true), + KNOWN(KnownValue.of(NON_JSON), expectedAsKnown = Optional.of(NON_JSON)), + KNOWN_BOOLEAN( + KnownValue.of(true), + expectedAsKnown = Optional.of(true), + expectedAsBoolean = Optional.of(true), + ), + BOOLEAN(JsonBoolean.of(true), expectedAsBoolean = Optional.of(true)), + KNOWN_NUMBER( + KnownValue.of(42), + expectedAsKnown = Optional.of(42), + expectedAsNumber = Optional.of(42), + ), + NUMBER(JsonNumber.of(42), expectedAsNumber = Optional.of(42)), + KNOWN_STRING( + KnownValue.of("hello"), + expectedAsKnown = Optional.of("hello"), + expectedAsString = Optional.of("hello"), + ), + STRING(JsonString.of("hello"), expectedAsString = Optional.of("hello")), + KNOWN_ARRAY_NOT_ALL_JSON( + KnownValue.of(listOf("a", "b", NON_JSON)), + expectedAsKnown = Optional.of(listOf("a", "b", NON_JSON)), + ), + KNOWN_ARRAY( + KnownValue.of(listOf("a", "b", "c")), + expectedAsKnown = Optional.of(listOf("a", "b", "c")), + expectedAsArray = + Optional.of(listOf(JsonString.of("a"), JsonString.of("b"), JsonString.of("c"))), + ), + ARRAY( + JsonArray.of(listOf(JsonString.of("a"), JsonString.of("b"), JsonString.of("c"))), + expectedAsArray = + Optional.of(listOf(JsonString.of("a"), JsonString.of("b"), JsonString.of("c"))), + ), + KNOWN_OBJECT_NOT_ALL_STRING_KEYS( + KnownValue.of(mapOf("a" to "b", 42 to "c")), + expectedAsKnown = Optional.of(mapOf("a" to "b", 42 to "c")), + ), + KNOWN_OBJECT_NOT_ALL_JSON( + KnownValue.of(mapOf("a" to "b", "b" to NON_JSON)), + expectedAsKnown = Optional.of(mapOf("a" to "b", "b" to NON_JSON)), + ), + KNOWN_OBJECT( + KnownValue.of(mapOf("a" to "b", "b" to "c")), + expectedAsKnown = Optional.of(mapOf("a" to "b", "b" to "c")), + expectedAsObject = + Optional.of(mapOf("a" to JsonString.of("b"), "b" to JsonString.of("c"))), + ), + OBJECT( + JsonObject.of(mapOf("a" to JsonString.of("b"), "b" to JsonString.of("c"))), + expectedAsObject = + Optional.of(mapOf("a" to JsonString.of("b"), "b" to JsonString.of("c"))), + ), + } + + @ParameterizedTest + @EnumSource + fun isMissing(testCase: TestCase) { + val isMissing = testCase.value.isMissing() + + assertThat(isMissing).isEqualTo(testCase.expectedIsMissing) + } + + @ParameterizedTest + @EnumSource + fun isNull(testCase: TestCase) { + val isNull = testCase.value.isNull() + + assertThat(isNull).isEqualTo(testCase.expectedIsNull) + } + + @ParameterizedTest + @EnumSource + fun asKnown(testCase: TestCase) { + val known = testCase.value.asKnown() + + assertThat(known).isEqualTo(testCase.expectedAsKnown) + } + + @ParameterizedTest + @EnumSource + fun asBoolean(testCase: TestCase) { + val boolean = testCase.value.asBoolean() + + assertThat(boolean).isEqualTo(testCase.expectedAsBoolean) + } + + @ParameterizedTest + @EnumSource + fun asNumber(testCase: TestCase) { + val number = testCase.value.asNumber() + + assertThat(number).isEqualTo(testCase.expectedAsNumber) + } + + @ParameterizedTest + @EnumSource + fun asString(testCase: TestCase) { + val string = testCase.value.asString() + + assertThat(string).isEqualTo(testCase.expectedAsString) + } + + @ParameterizedTest + @EnumSource + fun asArray(testCase: TestCase) { + val array = testCase.value.asArray() + + assertThat(array).isEqualTo(testCase.expectedAsArray) + } + + @ParameterizedTest + @EnumSource + fun asObject(testCase: TestCase) { + val obj = testCase.value.asObject() + + assertThat(obj).isEqualTo(testCase.expectedAsObject) + } +} diff --git a/src/test/java/com/google/genai/interactions/core/handlers/SseHandlerTest.kt b/src/test/java/com/google/genai/interactions/core/handlers/SseHandlerTest.kt new file mode 100644 index 00000000000..52358107adc --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/handlers/SseHandlerTest.kt @@ -0,0 +1,150 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.core.handlers + +import com.google.genai.interactions.core.http.Headers +import com.google.genai.interactions.core.http.HttpResponse +import com.google.genai.interactions.core.http.SseMessage +import com.google.genai.interactions.core.jsonMapper +import java.io.InputStream +import java.util.stream.Collectors.toList +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class SseHandlerTest { + + enum class TestCase( + internal val body: String, + internal val expectedMessages: List? = null, + internal val expectedException: Exception? = null, + ) { + DATA_MISSING_EVENT( + buildString { + append("data: {\"foo\":true}\n") + append("\n") + }, + listOf(sseMessageBuilder().data("{\"foo\":true}").build()), + ), + MULTIPLE_DATA_MISSING_EVENT( + buildString { + append("data: {\"foo\":true}\n") + append("\n") + append("data: {\"bar\":false}\n") + append("\n") + }, + listOf( + sseMessageBuilder().data("{\"foo\":true}").build(), + sseMessageBuilder().data("{\"bar\":false}").build(), + ), + ), + DATA_JSON_ESCAPED_DOUBLE_NEW_LINE( + buildString { + append("data: {\n") + append("data: \"foo\":\n") + append("data: true}\n") + append("\n\n") + }, + listOf(sseMessageBuilder().data("{\n\"foo\":\ntrue}").build()), + ), + MULTIPLE_DATA_LINES( + buildString { + append("data: {\n") + append("data: \"foo\":\n") + append("data: true}\n") + append("\n\n") + }, + listOf(sseMessageBuilder().data("{\n\"foo\":\ntrue}").build()), + ), + SPECIAL_NEW_LINE_CHARACTER( + buildString { + append("data: {\"content\":\" culpa\"}\n") + append("\n") + append("data: {\"content\":\" \u2028\"}\n") + append("\n") + append("data: {\"content\":\"foo\"}\n") + append("\n") + }, + listOf( + sseMessageBuilder().data("{\"content\":\" culpa\"}").build(), + sseMessageBuilder().data("{\"content\":\" \u2028\"}").build(), + sseMessageBuilder().data("{\"content\":\"foo\"}").build(), + ), + ), + MULTI_BYTE_CHARACTER( + buildString { + append("data: {\"content\":\"\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0438\"}\n") + append("\n") + }, + listOf(sseMessageBuilder().data("{\"content\":\"известни\"}").build()), + ), + } + + @ParameterizedTest + @EnumSource + fun handle(testCase: TestCase) { + val response = httpResponse(testCase.body) + var messages: List? = null + var exception: Exception? = null + + try { + messages = + sseHandler(jsonMapper()).handle(response).use { it.stream().collect(toList()) } + } catch (e: Exception) { + exception = e + } + + if (testCase.expectedMessages != null) { + assertThat(messages).containsExactlyElementsOf(testCase.expectedMessages) + } + if (testCase.expectedException != null) { + assertThat(exception).isInstanceOf(testCase.expectedException.javaClass) + assertThat(exception).hasMessage(testCase.expectedException.message) + } + } + + @Test + fun cannotReuseStream() { + val response = httpResponse("body") + val streamResponse = sseHandler(jsonMapper()).handle(response) + + val throwable = + streamResponse.use { + it.stream().collect(toList()) + catchThrowable { it.stream().collect(toList()) } + } + + assertThat(throwable).isInstanceOf(IllegalStateException::class.java) + } +} + +private fun httpResponse(body: String): HttpResponse = + object : HttpResponse { + override fun statusCode(): Int = 0 + + override fun headers(): Headers = Headers.builder().build() + + override fun body(): InputStream = body.toByteArray().inputStream() + + override fun close() {} + } + +private fun sseMessageBuilder() = SseMessage.builder().jsonMapper(jsonMapper()) diff --git a/src/test/java/com/google/genai/interactions/core/handlers/StreamHandlerTest.kt b/src/test/java/com/google/genai/interactions/core/handlers/StreamHandlerTest.kt new file mode 100644 index 00000000000..af512e43dcb --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/handlers/StreamHandlerTest.kt @@ -0,0 +1,110 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core.handlers + +import com.google.genai.interactions.core.http.Headers +import com.google.genai.interactions.core.http.HttpResponse +import com.google.genai.interactions.errors.GeminiNextGenApiIoException +import java.io.IOException +import java.io.InputStream +import kotlin.streams.asSequence +import kotlin.test.Test +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.assertThrows + +internal class StreamHandlerTest { + + @Test + fun streamHandler_splitsStreamOnNewlines() { + val handler = streamHandler { _, lines -> yieldAll(lines) } + val streamResponse = handler.handle(httpResponse("a\nbb\nccc\ndddd".byteInputStream())) + + val lines = streamResponse.stream().asSequence().toList() + + assertThat(lines).containsExactly("a", "bb", "ccc", "dddd") + } + + @Test + fun streamHandler_whenClosedEarly_stopsYielding() { + val handler = streamHandler { _, lines -> yieldAll(lines) } + val streamResponse = handler.handle(httpResponse("a\nbb\nccc\ndddd".byteInputStream())) + + val lines = + streamResponse + .stream() + .asSequence() + .onEach { + if (it == "bb") { + streamResponse.close() + } + } + .toList() + + assertThat(lines).containsExactly("a", "bb") + } + + @Test + fun streamHandler_whenReaderThrowsIOException_wrapsException() { + val handler = streamHandler { _, lines -> lines.forEach {} } + val streamResponse = handler.handle(httpResponse("a\nb\nc\n".byteInputStream().throwing())) + + val e = assertThrows { streamResponse.stream().forEach {} } + assertThat(e).hasMessage("Stream failed") + assertThat(e).hasCauseInstanceOf(IOException::class.java) + } + + @Test + fun streamHandler_whenBlockThrowsIOException_doesNotWrapException() { + val ioException = IOException("BOOM!") + val handler = + streamHandler { _, lines -> + lines.forEachIndexed { index, _ -> + if (index == 2) { + throw ioException + } + } + } + val streamResponse = handler.handle(httpResponse("a\nb\nc\n".byteInputStream())) + + val e = assertThrows { streamResponse.stream().forEach {} } + assertThat(e).isSameAs(ioException) + } + + private fun httpResponse(body: InputStream): HttpResponse = + object : HttpResponse { + + override fun statusCode(): Int = 0 + + override fun headers(): Headers = Headers.builder().build() + + override fun body(): InputStream = body + + override fun close() {} + } + + private fun InputStream.throwing(): InputStream = + object : InputStream() { + + override fun read(): Int { + val byte = this@throwing.read() + if (byte == -1) { + throw IOException("BOOM!") + } + return byte + } + } +} diff --git a/src/test/java/com/google/genai/interactions/core/http/AsyncStreamResponseTest.kt b/src/test/java/com/google/genai/interactions/core/http/AsyncStreamResponseTest.kt new file mode 100644 index 00000000000..3358ceb6356 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/http/AsyncStreamResponseTest.kt @@ -0,0 +1,284 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core.http + +import java.util.* +import java.util.concurrent.CompletableFuture +import java.util.concurrent.Executor +import java.util.stream.Stream +import kotlin.streams.asStream +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertDoesNotThrow +import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.kotlin.* + +@ExtendWith(MockitoExtension::class) +internal class AsyncStreamResponseTest { + + companion object { + private val ERROR = RuntimeException("ERROR!") + } + + private val streamResponse = + spy> { + doReturn(Stream.of("chunk1", "chunk2", "chunk3")).whenever(it).stream() + } + private val erroringStreamResponse = + spy> { + doReturn( + sequence { + yield("chunk1") + yield("chunk2") + throw ERROR + } + .asStream() + ) + .whenever(it) + .stream() + } + private val executor = + spy { + doAnswer { invocation -> invocation.getArgument(0).run() } + .whenever(it) + .execute(any()) + } + private val handler = mock>() + + @Test + fun subscribe_whenAlreadySubscribed_throws() { + val asyncStreamResponse = CompletableFuture>().toAsync(executor) + asyncStreamResponse.subscribe {} + + val throwable = catchThrowable { asyncStreamResponse.subscribe {} } + + assertThat(throwable).isInstanceOf(IllegalStateException::class.java) + assertThat(throwable).hasMessage("Cannot subscribe more than once") + verify(executor, never()).execute(any()) + } + + @Test + fun subscribe_whenClosed_throws() { + val asyncStreamResponse = CompletableFuture>().toAsync(executor) + asyncStreamResponse.close() + + val throwable = catchThrowable { asyncStreamResponse.subscribe {} } + + assertThat(throwable).isInstanceOf(IllegalStateException::class.java) + assertThat(throwable).hasMessage("Cannot subscribe after the response is closed") + verify(executor, never()).execute(any()) + } + + @Test + fun subscribe_whenFutureCompletesAfterClose_doesNothing() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe(handler) + asyncStreamResponse.close() + + future.complete(streamResponse) + + verify(handler, never()).onNext(any()) + verify(handler, never()).onComplete(any()) + verify(executor, times(1)).execute(any()) + } + + @Test + fun subscribe_whenFutureErrors_callsOnComplete() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe(handler) + + future.completeExceptionally(ERROR) + + verify(handler, never()).onNext(any()) + verify(handler, times(1)).onComplete(Optional.of(ERROR)) + verify(executor, times(1)).execute(any()) + } + + @Test + fun subscribe_whenFutureCompletes_runsHandler() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe(handler) + + future.complete(streamResponse) + + inOrder(handler, streamResponse) { + verify(handler, times(1)).onNext("chunk1") + verify(handler, times(1)).onNext("chunk2") + verify(handler, times(1)).onNext("chunk3") + verify(handler, times(1)).onComplete(Optional.empty()) + verify(streamResponse, times(1)).close() + } + verify(executor, times(1)).execute(any()) + } + + @Test + fun subscribe_whenStreamErrors_callsOnCompleteEarly() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe(handler) + + future.complete(erroringStreamResponse) + + inOrder(handler, erroringStreamResponse) { + verify(handler, times(1)).onNext("chunk1") + verify(handler, times(1)).onNext("chunk2") + verify(handler, times(1)).onComplete(Optional.of(ERROR)) + verify(erroringStreamResponse, times(1)).close() + } + verify(executor, times(1)).execute(any()) + } + + @Test + fun onCompleteFuture_whenStreamResponseFutureNotCompleted_onCompleteFutureNotCompleted() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isNotCompleted + } + + @Test + fun onCompleteFuture_whenStreamResponseFutureErrors_onCompleteFutureCompletedExceptionally() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + future.completeExceptionally(ERROR) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isCompletedExceptionally + } + + @Test + fun onCompleteFuture_whenStreamResponseFutureCompletedButStillStreaming_onCompleteFutureNotCompleted() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + future.complete(streamResponse) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isNotCompleted + } + + @Test + fun onCompleteFuture_whenStreamResponseFutureCompletedAndStreamErrors_onCompleteFutureCompletedExceptionally() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe(handler) + future.complete(erroringStreamResponse) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isCompletedExceptionally + } + + @Test + fun onCompleteFuture_whenStreamResponseFutureCompletedAndStreamCompleted_onCompleteFutureCompleted() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe(handler) + future.complete(streamResponse) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isCompleted + } + + @Test + fun onCompleteFuture_whenHandlerOnCompleteWithoutThrowableThrows_onCompleteFutureCompleted() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe( + object : AsyncStreamResponse.Handler { + override fun onNext(value: String) {} + + override fun onComplete(error: Optional) = throw ERROR + } + ) + future.complete(streamResponse) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isCompleted + } + + @Test + fun onCompleteFuture_whenHandlerOnCompleteWithThrowableThrows_onCompleteFutureCompletedExceptionally() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.subscribe( + object : AsyncStreamResponse.Handler { + override fun onNext(value: String) {} + + override fun onComplete(error: Optional) = throw ERROR + } + ) + future.complete(erroringStreamResponse) + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isCompletedExceptionally + } + + @Test + fun onCompleteFuture_whenClosed_onCompleteFutureCompleted() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.close() + + val onCompletableFuture = asyncStreamResponse.onCompleteFuture() + + assertThat(onCompletableFuture).isCompleted + } + + @Test + fun close_whenNotClosed_closesStreamResponse() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + + asyncStreamResponse.close() + future.complete(streamResponse) + + verify(streamResponse, times(1)).close() + } + + @Test + fun close_whenAlreadyClosed_doesNothing() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.close() + future.complete(streamResponse) + + asyncStreamResponse.close() + + verify(streamResponse, times(1)).close() + } + + @Test + fun close_whenFutureErrors_doesNothing() { + val future = CompletableFuture>() + val asyncStreamResponse = future.toAsync(executor) + asyncStreamResponse.close() + + assertDoesNotThrow { future.completeExceptionally(ERROR) } + } +} diff --git a/src/test/java/com/google/genai/interactions/core/http/HeadersTest.kt b/src/test/java/com/google/genai/interactions/core/http/HeadersTest.kt new file mode 100644 index 00000000000..f92cba23c30 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/http/HeadersTest.kt @@ -0,0 +1,258 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class HeadersTest { + + enum class TestCase( + val headers: Headers, + val expectedMap: Map>, + val expectedSize: Int, + ) { + EMPTY(Headers.builder().build(), expectedMap = mapOf(), expectedSize = 0), + PUT_ONE( + Headers.builder().put("name", "value").build(), + expectedMap = mapOf("name" to listOf("value")), + expectedSize = 1, + ), + PUT_MULTIPLE( + Headers.builder().put("name", listOf("value1", "value2")).build(), + expectedMap = mapOf("name" to listOf("value1", "value2")), + expectedSize = 2, + ), + MULTIPLE_PUT( + Headers.builder().put("name1", "value").put("name2", "value").build(), + expectedMap = mapOf("name1" to listOf("value"), "name2" to listOf("value")), + expectedSize = 2, + ), + MULTIPLE_PUT_SAME_NAME( + Headers.builder().put("name", "value1").put("name", "value2").build(), + expectedMap = mapOf("name" to listOf("value1", "value2")), + expectedSize = 2, + ), + MULTIPLE_PUT_MULTIPLE( + Headers.builder() + .put("name", listOf("value1", "value2")) + .put("name", listOf("value1", "value2")) + .build(), + expectedMap = mapOf("name" to listOf("value1", "value2", "value1", "value2")), + expectedSize = 4, + ), + PUT_CASE_INSENSITIVE( + Headers.builder() + .put("name", "value1") + .put("NAME", "value2") + .put("nAmE", "value3") + .build(), + expectedMap = mapOf("name" to listOf("value1", "value2", "value3")), + expectedSize = 3, + ), + PUT_ALL_MAP( + Headers.builder() + .putAll( + mapOf( + "name1" to listOf("value1", "value2"), + "name2" to listOf("value1", "value2"), + ) + ) + .build(), + expectedMap = + mapOf("name1" to listOf("value1", "value2"), "name2" to listOf("value1", "value2")), + expectedSize = 4, + ), + PUT_ALL_HEADERS( + Headers.builder().putAll(Headers.builder().put("name", "value").build()).build(), + expectedMap = mapOf("name" to listOf("value")), + expectedSize = 1, + ), + PUT_ALL_CASE_INSENSITIVE( + Headers.builder() + .putAll( + mapOf( + "name" to listOf("value1"), + "NAME" to listOf("value2"), + "nAmE" to listOf("value3"), + ) + ) + .build(), + expectedMap = mapOf("name" to listOf("value1", "value2", "value3")), + expectedSize = 3, + ), + REMOVE_ABSENT( + Headers.builder().remove("name").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_PRESENT_ONE( + Headers.builder().put("name", "value").remove("name").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_PRESENT_MULTIPLE( + Headers.builder().put("name", listOf("value1", "value2")).remove("name").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_CASE_INSENSITIVE( + Headers.builder().put("name", listOf("value1", "value2")).remove("NAME").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_ALL( + Headers.builder() + .put("name1", "value") + .put("name3", "value") + .removeAll(setOf("name1", "name2", "name3")) + .build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_ALL_CASE_INSENSITIVE( + Headers.builder() + .put("name1", "value") + .put("name3", "value") + .removeAll(setOf("NAME1", "nAmE3")) + .build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + CLEAR( + Headers.builder().put("name1", "value").put("name2", "value").clear().build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REPLACE_ONE_ABSENT( + Headers.builder().replace("name", "value").build(), + expectedMap = mapOf("name" to listOf("value")), + expectedSize = 1, + ), + REPLACE_ONE_PRESENT_ONE( + Headers.builder().put("name", "value1").replace("name", "value2").build(), + expectedMap = mapOf("name" to listOf("value2")), + expectedSize = 1, + ), + REPLACE_ONE_PRESENT_MULTIPLE( + Headers.builder() + .put("name", listOf("value1", "value2")) + .replace("name", "value3") + .build(), + expectedMap = mapOf("name" to listOf("value3")), + expectedSize = 1, + ), + REPLACE_MULTIPLE_ABSENT( + Headers.builder().replace("name", listOf("value1", "value2")).build(), + expectedMap = mapOf("name" to listOf("value1", "value2")), + expectedSize = 2, + ), + REPLACE_MULTIPLE_PRESENT_ONE( + Headers.builder() + .put("name", "value1") + .replace("name", listOf("value2", "value3")) + .build(), + expectedMap = mapOf("name" to listOf("value2", "value3")), + expectedSize = 2, + ), + REPLACE_MULTIPLE_PRESENT_MULTIPLE( + Headers.builder() + .put("name", listOf("value1", "value2")) + .replace("name", listOf("value3", "value4")) + .build(), + expectedMap = mapOf("name" to listOf("value3", "value4")), + expectedSize = 2, + ), + REPLACE_CASE_INSENSITIVE( + Headers.builder() + .put("name", "value1") + .replace("NAME", listOf("value2", "value3")) + .build(), + expectedMap = mapOf("NAME" to listOf("value2", "value3")), + expectedSize = 2, + ), + REPLACE_ALL_MAP( + Headers.builder() + .put("name1", "value1") + .put("name2", "value1") + .put("name3", "value1") + .replaceAll(mapOf("name1" to listOf("value2"), "name3" to listOf("value2"))) + .build(), + expectedMap = + mapOf( + "name1" to listOf("value2"), + "name2" to listOf("value1"), + "name3" to listOf("value2"), + ), + expectedSize = 3, + ), + REPLACE_ALL_HEADERS( + Headers.builder() + .put("name1", "value1") + .put("name2", "value1") + .put("name3", "value1") + .replaceAll(Headers.builder().put("name1", "value2").put("name3", "value2").build()) + .build(), + expectedMap = + mapOf( + "name1" to listOf("value2"), + "name2" to listOf("value1"), + "name3" to listOf("value2"), + ), + expectedSize = 3, + ), + REPLACE_ALL_CASE_INSENSITIVE( + Headers.builder() + .put("name1", "value1") + .put("name2", "value1") + .replaceAll(mapOf("NAME1" to listOf("value2"), "nAmE2" to listOf("value2"))) + .build(), + expectedMap = mapOf("NAME1" to listOf("value2"), "nAmE2" to listOf("value2")), + expectedSize = 2, + ), + } + + @ParameterizedTest + @EnumSource + fun namesAndValues(testCase: TestCase) { + val map = mutableMapOf>() + val headers = testCase.headers + headers.names().forEach { name -> map[name] = headers.values(name) } + + assertThat(map).isEqualTo(testCase.expectedMap) + } + + @ParameterizedTest + @EnumSource + fun caseInsensitiveNames(testCase: TestCase) { + val headers = testCase.headers + + for (name in headers.names()) { + assertThat(headers.values(name)).isEqualTo(headers.values(name.lowercase())) + assertThat(headers.values(name)).isEqualTo(headers.values(name.uppercase())) + } + } + + @ParameterizedTest + @EnumSource + fun size(testCase: TestCase) { + val size = testCase.headers.size + + assertThat(size).isEqualTo(testCase.expectedSize) + } +} diff --git a/src/test/java/com/google/genai/interactions/core/http/HttpRequestBodiesTest.kt b/src/test/java/com/google/genai/interactions/core/http/HttpRequestBodiesTest.kt new file mode 100644 index 00000000000..f142098ef35 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/http/HttpRequestBodiesTest.kt @@ -0,0 +1,745 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.core.http + +import com.google.genai.interactions.core.MultipartField +import com.google.genai.interactions.core.jsonMapper +import java.io.ByteArrayOutputStream +import java.io.InputStream +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class HttpRequestBodiesTest { + + @Test + fun multipartFormData_serializesFieldWithFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "file" to + MultipartField.builder() + .value("hello") + .filename("hello.txt") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(output.size().toLong()).isEqualTo(body.contentLength()) + val boundary = body.contentType()!!.substringAfter("multipart/form-data; boundary=") + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="file"; filename="hello.txt" + |Content-Type: text/plain + | + |hello + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesFieldWithoutFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field" to + MultipartField.builder() + .value("value") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(output.size().toLong()).isEqualTo(body.contentLength()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="field" + |Content-Type: text/plain + | + |value + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesInputStream() { + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the non-repeatable code + // path. + val inputStream = "stream content".byteInputStream().buffered() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder() + .value(inputStream) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data" + |Content-Type: application/octet-stream + | + |stream content + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesByteArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "binary" to + MultipartField.builder() + .value("abc".toByteArray()) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="binary" + |Content-Type: application/octet-stream + | + |abc + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesBooleanValue() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "flag" to + MultipartField.builder() + .value(true) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="flag" + |Content-Type: text/plain + | + |true + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNumberValue() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "count" to + MultipartField.builder().value(42).contentType("text/plain").build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="count" + |Content-Type: text/plain + | + |42 + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNullValueAsNoParts() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "present" to + MultipartField.builder() + .value("yes") + .contentType("text/plain") + .build(), + "absent" to + MultipartField.builder() + .value(null as String?) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="present" + |Content-Type: text/plain + | + |yes + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "items" to + MultipartField.builder>() + .value(listOf("alpha", "beta", "gamma")) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="items" + |Content-Type: text/plain + | + |alpha,beta,gamma + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesObjectAsNestedParts() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "meta" to + MultipartField.builder>() + .value(mapOf("key1" to "val1", "key2" to "val2")) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="meta[key1]" + |Content-Type: text/plain + | + |val1 + |--$boundary + |Content-Disposition: form-data; name="meta[key2]" + |Content-Type: text/plain + | + |val2 + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesMultipleFields() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "name" to + MultipartField.builder() + .value("Alice") + .contentType("text/plain") + .build(), + "age" to + MultipartField.builder().value(30).contentType("text/plain").build(), + "file" to + MultipartField.builder() + .value("file contents") + .filename("doc.txt") + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="name" + |Content-Type: text/plain + | + |Alice + |--$boundary + |Content-Disposition: form-data; name="age" + |Content-Type: text/plain + | + |30 + |--$boundary + |Content-Disposition: form-data; name="file"; filename="doc.txt" + |Content-Type: text/plain + | + |file contents + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_quotesSpecialCharactersInNameAndFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field\nname" to + MultipartField.builder() + .value("value") + .filename("file\r\"name.txt") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="field%0Aname"; filename="file%0D%22name.txt" + |Content-Type: text/plain + | + |value + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_writeIsRepeatable() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field" to + MultipartField.builder() + .value("repeatable") + .contentType("text/plain") + .build() + ), + ) + + val output1 = ByteArrayOutputStream() + body.writeTo(output1) + val output2 = ByteArrayOutputStream() + body.writeTo(output2) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output1.size().toLong()) + val boundary = boundary(body) + val expected = + """ + |--$boundary + |Content-Disposition: form-data; name="field" + |Content-Type: text/plain + | + |repeatable + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + assertThat(output1.toString("UTF-8")).isEqualTo(expected) + assertThat(output2.toString("UTF-8")).isEqualTo(expected) + } + + @Test + fun multipartFormData_serializesByteArrayInputStream() { + // ByteArrayInputStream is specifically handled as repeatable with known content length. + val inputStream = "byte array stream".byteInputStream() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder() + .value(inputStream) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data" + |Content-Type: application/octet-stream + | + |byte array stream + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesInputStreamWithFilename() { + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the non-repeatable code + // path. + val inputStream = "file data".byteInputStream().buffered() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "upload" to + MultipartField.builder() + .value(inputStream) + .filename("upload.bin") + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="upload"; filename="upload.bin" + |Content-Type: application/octet-stream + | + |file data + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNestedArrayInObject() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder>>() + .value(mapOf("tags" to listOf("a", "b"))) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data[tags]" + |Content-Type: text/plain + | + |a,b + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_contentLengthIsUnknownWhenInputStreamPresent() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "text" to + MultipartField.builder() + .value("hello") + .contentType("text/plain") + .build(), + "stream" to + MultipartField.builder() + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the + // non-repeatable code path. + .value("data".byteInputStream().buffered()) + .contentType("application/octet-stream") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="text" + |Content-Type: text/plain + | + |hello + |--$boundary + |Content-Disposition: form-data; name="stream" + |Content-Type: application/octet-stream + | + |data + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesEmptyArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "required" to + MultipartField.builder() + .value("present") + .contentType("text/plain") + .build(), + "items" to + MultipartField.builder>() + .value(emptyList()) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="required" + |Content-Type: text/plain + | + |present + |--$boundary + |Content-Disposition: form-data; name="items" + |Content-Type: text/plain + | + | + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesEmptyObject() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "required" to + MultipartField.builder() + .value("present") + .contentType("text/plain") + .build(), + "meta" to + MultipartField.builder>() + .value(emptyMap()) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="required" + |Content-Type: text/plain + | + |present + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + private fun boundary(body: HttpRequestBody): String = + body.contentType()!!.substringAfter("multipart/form-data; boundary=") +} diff --git a/src/test/java/com/google/genai/interactions/core/http/HttpRequestTest.kt b/src/test/java/com/google/genai/interactions/core/http/HttpRequestTest.kt new file mode 100644 index 00000000000..7b132179236 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/http/HttpRequestTest.kt @@ -0,0 +1,126 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class HttpRequestTest { + + enum class UrlTestCase(val request: HttpRequest, val expectedUrl: String) { + BASE_URL_ONLY( + HttpRequest.builder().method(HttpMethod.GET).baseUrl("https://api.example.com").build(), + expectedUrl = "https://api.example.com", + ), + BASE_URL_WITH_TRAILING_SLASH( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com/") + .build(), + expectedUrl = "https://api.example.com/", + ), + SINGLE_PATH_SEGMENT( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .build(), + expectedUrl = "https://api.example.com/users", + ), + MULTIPLE_PATH_SEGMENTS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegments("users", "123", "profile") + .build(), + expectedUrl = "https://api.example.com/users/123/profile", + ), + PATH_SEGMENT_WITH_SPECIAL_CHARS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("user name") + .build(), + expectedUrl = "https://api.example.com/user+name", + ), + SINGLE_QUERY_PARAM( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParam("limit", "10") + .build(), + expectedUrl = "https://api.example.com/users?limit=10", + ), + MULTIPLE_QUERY_PARAMS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParam("limit", "10") + .putQueryParam("offset", "20") + .build(), + expectedUrl = "https://api.example.com/users?limit=10&offset=20", + ), + QUERY_PARAM_WITH_SPECIAL_CHARS( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("search") + .putQueryParam("q", "hello world") + .build(), + expectedUrl = "https://api.example.com/search?q=hello+world", + ), + MULTIPLE_VALUES_SAME_PARAM( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com") + .addPathSegment("users") + .putQueryParams("tags", listOf("admin", "user")) + .build(), + expectedUrl = "https://api.example.com/users?tags=admin&tags=user", + ), + BASE_URL_WITH_TRAILING_SLASH_AND_PATH( + HttpRequest.builder() + .method(HttpMethod.GET) + .baseUrl("https://api.example.com/") + .addPathSegment("users") + .build(), + expectedUrl = "https://api.example.com/users", + ), + COMPLEX_URL( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl("https://api.example.com") + .addPathSegments("v1", "users", "123") + .putQueryParams("include", listOf("profile", "settings")) + .putQueryParam("format", "json") + .build(), + expectedUrl = + "https://api.example.com/v1/users/123?include=profile&include=settings&format=json", + ), + } + + @ParameterizedTest + @EnumSource + fun url(testCase: UrlTestCase) { + val actualUrl = testCase.request.url() + + assertThat(actualUrl).isEqualTo(testCase.expectedUrl) + } +} diff --git a/src/test/java/com/google/genai/interactions/core/http/QueryParamsTest.kt b/src/test/java/com/google/genai/interactions/core/http/QueryParamsTest.kt new file mode 100644 index 00000000000..2614f9c6589 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/http/QueryParamsTest.kt @@ -0,0 +1,196 @@ +/* +* 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. +*/ + +package com.google.genai.interactions.core.http + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class QueryParamsTest { + + enum class TestCase( + val queryParams: QueryParams, + val expectedMap: Map>, + val expectedSize: Int, + ) { + EMPTY(QueryParams.builder().build(), expectedMap = mapOf(), expectedSize = 0), + PUT_ONE( + QueryParams.builder().put("key", "value").build(), + expectedMap = mapOf("key" to listOf("value")), + expectedSize = 1, + ), + PUT_MULTIPLE( + QueryParams.builder().put("key", listOf("value1", "value2")).build(), + expectedMap = mapOf("key" to listOf("value1", "value2")), + expectedSize = 2, + ), + MULTIPLE_PUT( + QueryParams.builder().put("key1", "value").put("key2", "value").build(), + expectedMap = mapOf("key1" to listOf("value"), "key2" to listOf("value")), + expectedSize = 2, + ), + MULTIPLE_PUT_SAME_NAME( + QueryParams.builder().put("key", "value1").put("key", "value2").build(), + expectedMap = mapOf("key" to listOf("value1", "value2")), + expectedSize = 2, + ), + MULTIPLE_PUT_MULTIPLE( + QueryParams.builder() + .put("key", listOf("value1", "value2")) + .put("key", listOf("value1", "value2")) + .build(), + expectedMap = mapOf("key" to listOf("value1", "value2", "value1", "value2")), + expectedSize = 4, + ), + PUT_ALL_MAP( + QueryParams.builder() + .putAll( + mapOf( + "key1" to listOf("value1", "value2"), + "key2" to listOf("value1", "value2"), + ) + ) + .build(), + expectedMap = + mapOf("key1" to listOf("value1", "value2"), "key2" to listOf("value1", "value2")), + expectedSize = 4, + ), + PUT_ALL_HEADERS( + QueryParams.builder().putAll(QueryParams.builder().put("key", "value").build()).build(), + expectedMap = mapOf("key" to listOf("value")), + expectedSize = 1, + ), + REMOVE_ABSENT( + QueryParams.builder().remove("key").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_PRESENT_ONE( + QueryParams.builder().put("key", "value").remove("key").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_PRESENT_MULTIPLE( + QueryParams.builder().put("key", listOf("value1", "value2")).remove("key").build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REMOVE_ALL( + QueryParams.builder() + .put("key1", "value") + .put("key3", "value") + .removeAll(setOf("key1", "key2", "key3")) + .build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + CLEAR( + QueryParams.builder().put("key1", "value").put("key2", "value").clear().build(), + expectedMap = mapOf(), + expectedSize = 0, + ), + REPLACE_ONE_ABSENT( + QueryParams.builder().replace("key", "value").build(), + expectedMap = mapOf("key" to listOf("value")), + expectedSize = 1, + ), + REPLACE_ONE_PRESENT_ONE( + QueryParams.builder().put("key", "value1").replace("key", "value2").build(), + expectedMap = mapOf("key" to listOf("value2")), + expectedSize = 1, + ), + REPLACE_ONE_PRESENT_MULTIPLE( + QueryParams.builder() + .put("key", listOf("value1", "value2")) + .replace("key", "value3") + .build(), + expectedMap = mapOf("key" to listOf("value3")), + expectedSize = 1, + ), + REPLACE_MULTIPLE_ABSENT( + QueryParams.builder().replace("key", listOf("value1", "value2")).build(), + expectedMap = mapOf("key" to listOf("value1", "value2")), + expectedSize = 2, + ), + REPLACE_MULTIPLE_PRESENT_ONE( + QueryParams.builder() + .put("key", "value1") + .replace("key", listOf("value2", "value3")) + .build(), + expectedMap = mapOf("key" to listOf("value2", "value3")), + expectedSize = 2, + ), + REPLACE_MULTIPLE_PRESENT_MULTIPLE( + QueryParams.builder() + .put("key", listOf("value1", "value2")) + .replace("key", listOf("value3", "value4")) + .build(), + expectedMap = mapOf("key" to listOf("value3", "value4")), + expectedSize = 2, + ), + REPLACE_ALL_MAP( + QueryParams.builder() + .put("key1", "value1") + .put("key2", "value1") + .put("key3", "value1") + .replaceAll(mapOf("key1" to listOf("value2"), "key3" to listOf("value2"))) + .build(), + expectedMap = + mapOf( + "key1" to listOf("value2"), + "key2" to listOf("value1"), + "key3" to listOf("value2"), + ), + expectedSize = 3, + ), + REPLACE_ALL_HEADERS( + QueryParams.builder() + .put("key1", "value1") + .put("key2", "value1") + .put("key3", "value1") + .replaceAll( + QueryParams.builder().put("key1", "value2").put("key3", "value2").build() + ) + .build(), + expectedMap = + mapOf( + "key1" to listOf("value2"), + "key2" to listOf("value1"), + "key3" to listOf("value2"), + ), + expectedSize = 3, + ), + } + + @ParameterizedTest + @EnumSource + fun keysAndValues(testCase: TestCase) { + val map = mutableMapOf>() + val queryParams = testCase.queryParams + queryParams.keys().forEach { key -> map[key] = queryParams.values(key) } + + assertThat(map).isEqualTo(testCase.expectedMap) + } + + @ParameterizedTest + @EnumSource + fun size(testCase: TestCase) { + val size = testCase.queryParams.size + + assertThat(size).isEqualTo(testCase.expectedSize) + } +} diff --git a/src/test/java/com/google/genai/interactions/core/http/RetryingHttpClientTest.kt b/src/test/java/com/google/genai/interactions/core/http/RetryingHttpClientTest.kt new file mode 100644 index 00000000000..488e890ec06 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/core/http/RetryingHttpClientTest.kt @@ -0,0 +1,472 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.core.http + +import com.google.genai.interactions.client.okhttp.OkHttpClient +import com.google.genai.interactions.core.RequestOptions +import com.google.genai.interactions.core.Sleeper +import com.google.genai.interactions.errors.GeminiNextGenApiRetryableException +import com.github.tomakehurst.wiremock.client.WireMock.matching +import com.github.tomakehurst.wiremock.client.WireMock.ok +import com.github.tomakehurst.wiremock.client.WireMock.post +import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.resetAllScenarios +import com.github.tomakehurst.wiremock.client.WireMock.serviceUnavailable +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.github.tomakehurst.wiremock.client.WireMock.verify +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo +import com.github.tomakehurst.wiremock.junit5.WireMockTest +import com.github.tomakehurst.wiremock.stubbing.Scenario +import java.io.InputStream +import java.time.Clock +import java.time.Duration +import java.time.OffsetDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter +import java.util.concurrent.CompletableFuture +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.parallel.ResourceLock +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource + +@WireMockTest +@ResourceLock("https://github.com/wiremock/wiremock/issues/169") +internal class RetryingHttpClientTest { + + private var openResponseCount = 0 + private lateinit var baseUrl: String + private lateinit var httpClient: HttpClient + + private class RecordingSleeper : Sleeper { + val durations = mutableListOf() + + override fun sleep(duration: Duration) { + durations.add(duration) + } + + override fun sleepAsync(duration: Duration): CompletableFuture { + durations.add(duration) + return CompletableFuture.completedFuture(null) + } + + override fun close() {} + } + + @BeforeEach + fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { + baseUrl = wmRuntimeInfo.httpBaseUrl + val okHttpClient = OkHttpClient.builder().build() + httpClient = + object : HttpClient { + + override fun execute( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse = trackClose(okHttpClient.execute(request, requestOptions)) + + override fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): CompletableFuture = + okHttpClient.executeAsync(request, requestOptions).thenApply { trackClose(it) } + + override fun close() = okHttpClient.close() + + private fun trackClose(response: HttpResponse): HttpResponse { + openResponseCount++ + return object : HttpResponse { + + private var isClosed = false + + override fun statusCode(): Int = response.statusCode() + + override fun headers(): Headers = response.headers() + + override fun body(): InputStream = response.body() + + override fun close() { + response.close() + if (isClosed) { + return + } + openResponseCount-- + isClosed = true + } + } + } + } + resetAllScenarios() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + verify(1, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).isEmpty() + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withIdempotencyHeader(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .withHeader("X-Some-Header", matching("stainless-java-retry-.+")) + .willReturn(ok()) + ) + val sleeper = RecordingSleeper() + val retryingClient = + retryingHttpClientBuilder(sleeper) + .maxRetries(2) + .idempotencyHeader("X-Some-Header") + .build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + verify(1, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).isEmpty() + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterHeader(async: Boolean) { + val retryAfterDate = "Wed, 21 Oct 2015 07:28:00 GMT" + stubFor( + post(urlPathEqualTo("/something")) + // First we fail with a retry after header given as a date + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(serviceUnavailable().withHeader("Retry-After", retryAfterDate)) + .willSetStateTo("RETRY_AFTER_DATE") + ) + stubFor( + post(urlPathEqualTo("/something")) + // Then we fail with a retry after header given as a delay + .inScenario("foo") + .whenScenarioStateIs("RETRY_AFTER_DATE") + .willReturn(serviceUnavailable().withHeader("Retry-After", "1.234")) + .willSetStateTo("RETRY_AFTER_DELAY") + ) + stubFor( + post(urlPathEqualTo("/something")) + // Then we return a success + .inScenario("foo") + .whenScenarioStateIs("RETRY_AFTER_DELAY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + // Fix the clock to 5 seconds before the Retry-After date so the date-based backoff is + // deterministic. + val retryAfterDateTime = + OffsetDateTime.parse(retryAfterDate, DateTimeFormatter.RFC_1123_DATE_TIME) + val clock = Clock.fixed(retryAfterDateTime.minusSeconds(5).toInstant(), ZoneOffset.UTC) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper, clock).maxRetries(2).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + + assertThat(sleeper.durations) + .containsExactly(Duration.ofSeconds(5), Duration.ofMillis(1234)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterMsHeader(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(serviceUnavailable().withHeader("Retry-After-Ms", "10")) + .willSetStateTo("RETRY_AFTER_DELAY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") // then we return a success + .whenScenarioStateIs("RETRY_AFTER_DELAY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + verify(2, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).containsExactly(Duration.ofMillis(10)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryableException(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(ok())) + + var callCount = 0 + val failingHttpClient = + object : HttpClient { + override fun execute( + request: HttpRequest, + requestOptions: RequestOptions, + ): HttpResponse { + callCount++ + if (callCount == 1) { + throw GeminiNextGenApiRetryableException("Simulated retryable failure") + } + return httpClient.execute(request, requestOptions) + } + + override fun executeAsync( + request: HttpRequest, + requestOptions: RequestOptions, + ): CompletableFuture { + callCount++ + if (callCount == 1) { + val future = CompletableFuture() + future.completeExceptionally( + GeminiNextGenApiRetryableException("Simulated retryable failure") + ) + return future + } + return httpClient.executeAsync(request, requestOptions) + } + + override fun close() = httpClient.close() + } + + val sleeper = RecordingSleeper() + val retryingClient = + RetryingHttpClient.builder() + .httpClient(failingHttpClient) + .maxRetries(2) + .sleeper(sleeper) + .build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + verify(1, postRequestedFor(urlPathEqualTo("/something"))) + // Exponential backoff with jitter: 0.5s * jitter where jitter is in [0.75, 1.0]. + assertThat(sleeper.durations).hasSize(1) + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withExponentialBackoff(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(3).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + // All retries exhausted; the last 503 response is returned. + assertThat(response.statusCode()).isEqualTo(503) + verify(4, postRequestedFor(urlPathEqualTo("/something"))) + // Exponential backoff with jitter: backoff = min(0.5 * 2^(retries-1), 8) * jitter where + // jitter is in [0.75, 1.0]. + assertThat(sleeper.durations).hasSize(3) + // retries=1: 0.5s * [0.75, 1.0] + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + // retries=2: 1s * [0.75, 1.0] + assertThat(sleeper.durations[1]).isBetween(Duration.ofMillis(750), Duration.ofMillis(1000)) + // retries=3: 2s * [0.75, 1.0] + assertThat(sleeper.durations[2]).isBetween(Duration.ofMillis(1500), Duration.ofMillis(2000)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withExponentialBackoffCap(async: Boolean) { + stubFor(post(urlPathEqualTo("/something")).willReturn(serviceUnavailable())) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(6).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(503) + verify(7, postRequestedFor(urlPathEqualTo("/something"))) + assertThat(sleeper.durations).hasSize(6) + // retries=5: backoff hits the 8s cap * [0.75, 1.0] + assertThat(sleeper.durations[4]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000)) + // retries=6: still capped at 8s * [0.75, 1.0] + assertThat(sleeper.durations[5]).isBetween(Duration.ofMillis(6000), Duration.ofMillis(8000)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterMsPriorityOverRetryAfter(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn( + serviceUnavailable() + .withHeader("Retry-After-Ms", "50") + .withHeader("Retry-After", "2") + ) + .willSetStateTo("RETRY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs("RETRY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + // Retry-After-Ms (50ms) takes priority over Retry-After (2s). + assertThat(sleeper.durations).containsExactly(Duration.ofMillis(50)) + assertNoResponseLeaks() + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun execute_withRetryAfterUnparseable(async: Boolean) { + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(serviceUnavailable().withHeader("Retry-After", "not-a-date-or-number")) + .willSetStateTo("RETRY") + ) + stubFor( + post(urlPathEqualTo("/something")) + .inScenario("foo") + .whenScenarioStateIs("RETRY") + .willReturn(ok()) + .willSetStateTo("COMPLETED") + ) + val sleeper = RecordingSleeper() + val retryingClient = retryingHttpClientBuilder(sleeper).maxRetries(1).build() + + val response = + retryingClient.execute( + HttpRequest.builder() + .method(HttpMethod.POST) + .baseUrl(baseUrl) + .addPathSegment("something") + .build(), + async, + ) + + assertThat(response.statusCode()).isEqualTo(200) + // Unparseable Retry-After falls through to exponential backoff. + assertThat(sleeper.durations).hasSize(1) + assertThat(sleeper.durations[0]).isBetween(Duration.ofMillis(375), Duration.ofMillis(500)) + assertNoResponseLeaks() + } + + private fun retryingHttpClientBuilder( + sleeper: RecordingSleeper, + clock: Clock = Clock.systemUTC(), + ) = RetryingHttpClient.builder().httpClient(httpClient).sleeper(sleeper).clock(clock) + + private fun HttpClient.execute(request: HttpRequest, async: Boolean): HttpResponse = + if (async) executeAsync(request).get() else execute(request) + + // When retrying, all failed responses should be closed. Only the final returned response should + // be open. + private fun assertNoResponseLeaks() = assertThat(openResponseCount).isEqualTo(1) +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/AllowedToolsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/AllowedToolsTest.kt new file mode 100644 index 00000000000..cea8019290e --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/AllowedToolsTest.kt @@ -0,0 +1,52 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class AllowedToolsTest { + + @Test + fun create() { + val allowedTools = + AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build() + + assertThat(allowedTools.mode()).contains(ToolChoiceType.AUTO) + assertThat(allowedTools.tools().getOrNull()).containsExactly("string") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val allowedTools = + AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build() + + val roundtrippedAllowedTools = + jsonMapper.readValue( + jsonMapper.writeValueAsString(allowedTools), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAllowedTools).isEqualTo(allowedTools) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/AnnotationTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/AnnotationTest.kt new file mode 100644 index 00000000000..54ee74ba6c6 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/AnnotationTest.kt @@ -0,0 +1,188 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import com.google.genai.interactions.errors.GeminiNextGenApiInvalidDataException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class AnnotationTest { + + @Test + fun ofUrlCitation() { + val urlCitation = + UrlCitation.builder().endIndex(0).startIndex(0).title("title").url("url").build() + + val annotation = Annotation.ofUrlCitation(urlCitation) + + assertThat(annotation.urlCitation()).contains(urlCitation) + assertThat(annotation.fileCitation()).isEmpty + assertThat(annotation.placeCitation()).isEmpty + } + + @Test + fun ofUrlCitationRoundtrip() { + val jsonMapper = jsonMapper() + val annotation = + Annotation.ofUrlCitation( + UrlCitation.builder().endIndex(0).startIndex(0).title("title").url("url").build() + ) + + val roundtrippedAnnotation = + jsonMapper.readValue( + jsonMapper.writeValueAsString(annotation), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAnnotation).isEqualTo(annotation) + } + + @Test + fun ofFileCitation() { + val fileCitation = + FileCitation.builder() + .customMetadata( + FileCitation.CustomMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .documentUri("document_uri") + .endIndex(0) + .fileName("file_name") + .mediaId("media_id") + .pageNumber(0) + .source("source") + .startIndex(0) + .build() + + val annotation = Annotation.ofFileCitation(fileCitation) + + assertThat(annotation.urlCitation()).isEmpty + assertThat(annotation.fileCitation()).contains(fileCitation) + assertThat(annotation.placeCitation()).isEmpty + } + + @Test + fun ofFileCitationRoundtrip() { + val jsonMapper = jsonMapper() + val annotation = + Annotation.ofFileCitation( + FileCitation.builder() + .customMetadata( + FileCitation.CustomMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .documentUri("document_uri") + .endIndex(0) + .fileName("file_name") + .mediaId("media_id") + .pageNumber(0) + .source("source") + .startIndex(0) + .build() + ) + + val roundtrippedAnnotation = + jsonMapper.readValue( + jsonMapper.writeValueAsString(annotation), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAnnotation).isEqualTo(annotation) + } + + @Test + fun ofPlaceCitation() { + val placeCitation = + PlaceCitation.builder() + .endIndex(0) + .name("name") + .placeId("place_id") + .addReviewSnippet( + PlaceCitation.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .startIndex(0) + .url("url") + .build() + + val annotation = Annotation.ofPlaceCitation(placeCitation) + + assertThat(annotation.urlCitation()).isEmpty + assertThat(annotation.fileCitation()).isEmpty + assertThat(annotation.placeCitation()).contains(placeCitation) + } + + @Test + fun ofPlaceCitationRoundtrip() { + val jsonMapper = jsonMapper() + val annotation = + Annotation.ofPlaceCitation( + PlaceCitation.builder() + .endIndex(0) + .name("name") + .placeId("place_id") + .addReviewSnippet( + PlaceCitation.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .startIndex(0) + .url("url") + .build() + ) + + val roundtrippedAnnotation = + jsonMapper.readValue( + jsonMapper.writeValueAsString(annotation), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAnnotation).isEqualTo(annotation) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val annotation = jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = assertThrows { annotation.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ArgumentsDeltaTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ArgumentsDeltaTest.kt new file mode 100644 index 00000000000..20595c1b3eb --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ArgumentsDeltaTest.kt @@ -0,0 +1,46 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ArgumentsDeltaTest { + + @Test + fun create() { + val argumentsDelta = ArgumentsDelta.builder().build() + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val argumentsDelta = ArgumentsDelta.builder().build() + + val roundtrippedArgumentsDelta = + jsonMapper.readValue( + jsonMapper.writeValueAsString(argumentsDelta), + jacksonTypeRef(), + ) + + assertThat(roundtrippedArgumentsDelta).isEqualTo(argumentsDelta) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/AudioContentTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/AudioContentTest.kt new file mode 100644 index 00000000000..e65f72d51df --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/AudioContentTest.kt @@ -0,0 +1,66 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class AudioContentTest { + + @Test + fun create() { + val audioContent = + AudioContent.builder() + .channels(0) + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(AudioContent.MimeType.AUDIO_WAV) + .sampleRate(0) + .uri("uri") + .build() + + assertThat(audioContent.channels()).contains(0) + assertThat(audioContent.data()).contains("U3RhaW5sZXNzIHJvY2tz") + assertThat(audioContent.mimeType()).contains(AudioContent.MimeType.AUDIO_WAV) + assertThat(audioContent.sampleRate()).contains(0) + assertThat(audioContent.uri()).contains("uri") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val audioContent = + AudioContent.builder() + .channels(0) + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(AudioContent.MimeType.AUDIO_WAV) + .sampleRate(0) + .uri("uri") + .build() + + val roundtrippedAudioContent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(audioContent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAudioContent).isEqualTo(audioContent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/AudioResponseFormatTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/AudioResponseFormatTest.kt new file mode 100644 index 00000000000..5d705989b27 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/AudioResponseFormatTest.kt @@ -0,0 +1,63 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class AudioResponseFormatTest { + + @Test + fun create() { + val audioResponseFormat = + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + + assertThat(audioResponseFormat.bitRate()).contains(0) + assertThat(audioResponseFormat.delivery()).contains(AudioResponseFormat.Delivery.INLINE) + assertThat(audioResponseFormat.mimeType()).contains(AudioResponseFormat.MimeType.AUDIO_MP3) + assertThat(audioResponseFormat.sampleRate()).contains(0) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val audioResponseFormat = + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + + val roundtrippedAudioResponseFormat = + jsonMapper.readValue( + jsonMapper.writeValueAsString(audioResponseFormat), + jacksonTypeRef(), + ) + + assertThat(roundtrippedAudioResponseFormat).isEqualTo(audioResponseFormat) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallArgumentsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallArgumentsTest.kt new file mode 100644 index 00000000000..6be1514b8e7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallArgumentsTest.kt @@ -0,0 +1,58 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class CodeExecutionCallArgumentsTest { + + @Test + fun create() { + val codeExecutionCallArguments = + CodeExecutionCallArguments.builder() + .code("code") + .language(CodeExecutionCallArguments.Language.PYTHON) + .build() + + assertThat(codeExecutionCallArguments.code()).contains("code") + assertThat(codeExecutionCallArguments.language()) + .contains(CodeExecutionCallArguments.Language.PYTHON) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val codeExecutionCallArguments = + CodeExecutionCallArguments.builder() + .code("code") + .language(CodeExecutionCallArguments.Language.PYTHON) + .build() + + val roundtrippedCodeExecutionCallArguments = + jsonMapper.readValue( + jsonMapper.writeValueAsString(codeExecutionCallArguments), + jacksonTypeRef(), + ) + + assertThat(roundtrippedCodeExecutionCallArguments).isEqualTo(codeExecutionCallArguments) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallStepTest.kt new file mode 100644 index 00000000000..9bb667e3685 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionCallStepTest.kt @@ -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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class CodeExecutionCallStepTest { + + @Test + fun create() { + val codeExecutionCallStep = + CodeExecutionCallStep.builder() + .id("id") + .arguments( + CodeExecutionCallStep.Arguments.builder() + .code("code") + .language(CodeExecutionCallStep.Arguments.Language.PYTHON) + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(codeExecutionCallStep.id()).isEqualTo("id") + assertThat(codeExecutionCallStep.arguments()) + .isEqualTo( + CodeExecutionCallStep.Arguments.builder() + .code("code") + .language(CodeExecutionCallStep.Arguments.Language.PYTHON) + .build() + ) + assertThat(codeExecutionCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val codeExecutionCallStep = + CodeExecutionCallStep.builder() + .id("id") + .arguments( + CodeExecutionCallStep.Arguments.builder() + .code("code") + .language(CodeExecutionCallStep.Arguments.Language.PYTHON) + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedCodeExecutionCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(codeExecutionCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedCodeExecutionCallStep).isEqualTo(codeExecutionCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionResultStepTest.kt new file mode 100644 index 00000000000..0a41808a99f --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/CodeExecutionResultStepTest.kt @@ -0,0 +1,63 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class CodeExecutionResultStepTest { + + @Test + fun create() { + val codeExecutionResultStep = + CodeExecutionResultStep.builder() + .callId("call_id") + .result("result") + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(codeExecutionResultStep.callId()).isEqualTo("call_id") + assertThat(codeExecutionResultStep.result()).isEqualTo("result") + assertThat(codeExecutionResultStep.isError()).contains(true) + assertThat(codeExecutionResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val codeExecutionResultStep = + CodeExecutionResultStep.builder() + .callId("call_id") + .result("result") + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedCodeExecutionResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(codeExecutionResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedCodeExecutionResultStep).isEqualTo(codeExecutionResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ContentTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ContentTest.kt new file mode 100644 index 00000000000..e6b9b376114 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ContentTest.kt @@ -0,0 +1,249 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import com.google.genai.interactions.errors.GeminiNextGenApiInvalidDataException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class ContentTest { + + @Test + fun ofText() { + val text = + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + + val content = Content.ofText(text) + + assertThat(content.text()).contains(text) + assertThat(content.image()).isEmpty + assertThat(content.audio()).isEmpty + assertThat(content.document()).isEmpty + assertThat(content.video()).isEmpty + } + + @Test + fun ofTextRoundtrip() { + val jsonMapper = jsonMapper() + val content = + Content.ofText( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + + val roundtrippedContent = + jsonMapper.readValue(jsonMapper.writeValueAsString(content), jacksonTypeRef()) + + assertThat(roundtrippedContent).isEqualTo(content) + } + + @Test + fun ofImage() { + val image = + ImageContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(ImageContent.MimeType.IMAGE_PNG) + .resolution(ImageContent.Resolution.LOW) + .uri("uri") + .build() + + val content = Content.ofImage(image) + + assertThat(content.text()).isEmpty + assertThat(content.image()).contains(image) + assertThat(content.audio()).isEmpty + assertThat(content.document()).isEmpty + assertThat(content.video()).isEmpty + } + + @Test + fun ofImageRoundtrip() { + val jsonMapper = jsonMapper() + val content = + Content.ofImage( + ImageContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(ImageContent.MimeType.IMAGE_PNG) + .resolution(ImageContent.Resolution.LOW) + .uri("uri") + .build() + ) + + val roundtrippedContent = + jsonMapper.readValue(jsonMapper.writeValueAsString(content), jacksonTypeRef()) + + assertThat(roundtrippedContent).isEqualTo(content) + } + + @Test + fun ofAudio() { + val audio = + AudioContent.builder() + .channels(0) + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(AudioContent.MimeType.AUDIO_WAV) + .sampleRate(0) + .uri("uri") + .build() + + val content = Content.ofAudio(audio) + + assertThat(content.text()).isEmpty + assertThat(content.image()).isEmpty + assertThat(content.audio()).contains(audio) + assertThat(content.document()).isEmpty + assertThat(content.video()).isEmpty + } + + @Test + fun ofAudioRoundtrip() { + val jsonMapper = jsonMapper() + val content = + Content.ofAudio( + AudioContent.builder() + .channels(0) + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(AudioContent.MimeType.AUDIO_WAV) + .sampleRate(0) + .uri("uri") + .build() + ) + + val roundtrippedContent = + jsonMapper.readValue(jsonMapper.writeValueAsString(content), jacksonTypeRef()) + + assertThat(roundtrippedContent).isEqualTo(content) + } + + @Test + fun ofDocument() { + val document = + DocumentContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(DocumentContent.MimeType.APPLICATION_PDF) + .uri("uri") + .build() + + val content = Content.ofDocument(document) + + assertThat(content.text()).isEmpty + assertThat(content.image()).isEmpty + assertThat(content.audio()).isEmpty + assertThat(content.document()).contains(document) + assertThat(content.video()).isEmpty + } + + @Test + fun ofDocumentRoundtrip() { + val jsonMapper = jsonMapper() + val content = + Content.ofDocument( + DocumentContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(DocumentContent.MimeType.APPLICATION_PDF) + .uri("uri") + .build() + ) + + val roundtrippedContent = + jsonMapper.readValue(jsonMapper.writeValueAsString(content), jacksonTypeRef()) + + assertThat(roundtrippedContent).isEqualTo(content) + } + + @Test + fun ofVideo() { + val video = + VideoContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(VideoContent.MimeType.VIDEO_MP4) + .resolution(VideoContent.Resolution.LOW) + .uri("uri") + .build() + + val content = Content.ofVideo(video) + + assertThat(content.text()).isEmpty + assertThat(content.image()).isEmpty + assertThat(content.audio()).isEmpty + assertThat(content.document()).isEmpty + assertThat(content.video()).contains(video) + } + + @Test + fun ofVideoRoundtrip() { + val jsonMapper = jsonMapper() + val content = + Content.ofVideo( + VideoContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(VideoContent.MimeType.VIDEO_MP4) + .resolution(VideoContent.Resolution.LOW) + .uri("uri") + .build() + ) + + val roundtrippedContent = + jsonMapper.readValue(jsonMapper.writeValueAsString(content), jacksonTypeRef()) + + assertThat(roundtrippedContent).isEqualTo(content) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val content = jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = assertThrows { content.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/CreateAgentInteractionParamsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/CreateAgentInteractionParamsTest.kt new file mode 100644 index 00000000000..ddc507c64f1 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/CreateAgentInteractionParamsTest.kt @@ -0,0 +1,368 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import java.time.OffsetDateTime +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class CreateAgentInteractionParamsTest { + + @Test + fun create() { + val createAgentInteractionParams = + CreateAgentInteractionParams.builder() + .agent(CreateAgentInteractionParams.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .id("id") + .agentConfig(DynamicAgentConfig.builder().build()) + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateAgentInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateAgentInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateAgentInteractionParams.ServiceTier.FLEX) + .status(CreateAgentInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateAgentInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateAgentInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + + assertThat(createAgentInteractionParams.agent()) + .isEqualTo(CreateAgentInteractionParams.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + assertThat(createAgentInteractionParams.input()) + .isEqualTo( + CreateAgentInteractionParams.Input.ofTextContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + ) + assertThat(createAgentInteractionParams.id()).contains("id") + assertThat(createAgentInteractionParams.agentConfig()) + .contains( + CreateAgentInteractionParams.AgentConfig.ofDynamic( + DynamicAgentConfig.builder().build() + ) + ) + assertThat(createAgentInteractionParams.background()).contains(true) + assertThat(createAgentInteractionParams.created()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(createAgentInteractionParams.previousInteractionId()) + .contains("previous_interaction_id") + assertThat(createAgentInteractionParams.responseFormat()) + .contains( + CreateAgentInteractionParams.ResponseFormat.ofList( + listOf( + CreateAgentInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + ) + assertThat(createAgentInteractionParams.responseMimeType()).contains("response_mime_type") + assertThat(createAgentInteractionParams.responseModalities().getOrNull()) + .containsExactly(CreateAgentInteractionParams.ResponseModality.TEXT) + assertThat(createAgentInteractionParams.role()).contains("role") + assertThat(createAgentInteractionParams.serviceTier()) + .contains(CreateAgentInteractionParams.ServiceTier.FLEX) + assertThat(createAgentInteractionParams.status()) + .contains(CreateAgentInteractionParams.Status.IN_PROGRESS) + assertThat(createAgentInteractionParams.store()).contains(true) + assertThat(createAgentInteractionParams.stream()).contains(true) + assertThat(createAgentInteractionParams.systemInstruction()).contains("system_instruction") + assertThat(createAgentInteractionParams.tools().getOrNull()) + .containsExactly( + Tool.ofFunction( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + ) + assertThat(createAgentInteractionParams.updated()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(createAgentInteractionParams.usage()) + .contains( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + assertThat(createAgentInteractionParams.webhookConfig()) + .contains( + CreateAgentInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateAgentInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val createAgentInteractionParams = + CreateAgentInteractionParams.builder() + .agent(CreateAgentInteractionParams.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .id("id") + .agentConfig(DynamicAgentConfig.builder().build()) + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateAgentInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateAgentInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateAgentInteractionParams.ServiceTier.FLEX) + .status(CreateAgentInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateAgentInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateAgentInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + + val roundtrippedCreateAgentInteractionParams = + jsonMapper.readValue( + jsonMapper.writeValueAsString(createAgentInteractionParams), + jacksonTypeRef(), + ) + + assertThat(roundtrippedCreateAgentInteractionParams).isEqualTo(createAgentInteractionParams) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/CreateModelInteractionParamsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/CreateModelInteractionParamsTest.kt new file mode 100644 index 00000000000..3a2f89128f7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/CreateModelInteractionParamsTest.kt @@ -0,0 +1,436 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import java.time.OffsetDateTime +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class CreateModelInteractionParamsTest { + + @Test + fun create() { + val createModelInteractionParams = + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + + assertThat(createModelInteractionParams.input()) + .isEqualTo( + CreateModelInteractionParams.Input.ofTextContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + ) + assertThat(createModelInteractionParams.model()) + .isEqualTo(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + assertThat(createModelInteractionParams.id()).contains("id") + assertThat(createModelInteractionParams.background()).contains(true) + assertThat(createModelInteractionParams.created()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(createModelInteractionParams.generationConfig()) + .contains( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + assertThat(createModelInteractionParams.previousInteractionId()) + .contains("previous_interaction_id") + assertThat(createModelInteractionParams.responseFormat()) + .contains( + CreateModelInteractionParams.ResponseFormat.ofList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + ) + assertThat(createModelInteractionParams.responseMimeType()).contains("response_mime_type") + assertThat(createModelInteractionParams.responseModalities().getOrNull()) + .containsExactly(CreateModelInteractionParams.ResponseModality.TEXT) + assertThat(createModelInteractionParams.role()).contains("role") + assertThat(createModelInteractionParams.serviceTier()) + .contains(CreateModelInteractionParams.ServiceTier.FLEX) + assertThat(createModelInteractionParams.status()) + .contains(CreateModelInteractionParams.Status.IN_PROGRESS) + assertThat(createModelInteractionParams.store()).contains(true) + assertThat(createModelInteractionParams.stream()).contains(true) + assertThat(createModelInteractionParams.systemInstruction()).contains("system_instruction") + assertThat(createModelInteractionParams.tools().getOrNull()) + .containsExactly( + Tool.ofFunction( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + ) + assertThat(createModelInteractionParams.updated()) + .contains(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + assertThat(createModelInteractionParams.usage()) + .contains( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + assertThat(createModelInteractionParams.webhookConfig()) + .contains( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val createModelInteractionParams = + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + + val roundtrippedCreateModelInteractionParams = + jsonMapper.readValue( + jsonMapper.writeValueAsString(createModelInteractionParams), + jacksonTypeRef(), + ) + + assertThat(roundtrippedCreateModelInteractionParams).isEqualTo(createModelInteractionParams) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/DeepResearchAgentConfigTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/DeepResearchAgentConfigTest.kt new file mode 100644 index 00000000000..714cc7dd19a --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/DeepResearchAgentConfigTest.kt @@ -0,0 +1,62 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class DeepResearchAgentConfigTest { + + @Test + fun create() { + val deepResearchAgentConfig = + DeepResearchAgentConfig.builder() + .collaborativePlanning(true) + .thinkingSummaries(DeepResearchAgentConfig.ThinkingSummaries.AUTO) + .visualization(DeepResearchAgentConfig.Visualization.OFF) + .build() + + assertThat(deepResearchAgentConfig.collaborativePlanning()).contains(true) + assertThat(deepResearchAgentConfig.thinkingSummaries()) + .contains(DeepResearchAgentConfig.ThinkingSummaries.AUTO) + assertThat(deepResearchAgentConfig.visualization()) + .contains(DeepResearchAgentConfig.Visualization.OFF) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val deepResearchAgentConfig = + DeepResearchAgentConfig.builder() + .collaborativePlanning(true) + .thinkingSummaries(DeepResearchAgentConfig.ThinkingSummaries.AUTO) + .visualization(DeepResearchAgentConfig.Visualization.OFF) + .build() + + val roundtrippedDeepResearchAgentConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(deepResearchAgentConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedDeepResearchAgentConfig).isEqualTo(deepResearchAgentConfig) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/DocumentContentTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/DocumentContentTest.kt new file mode 100644 index 00000000000..56bc5047784 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/DocumentContentTest.kt @@ -0,0 +1,60 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class DocumentContentTest { + + @Test + fun create() { + val documentContent = + DocumentContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(DocumentContent.MimeType.APPLICATION_PDF) + .uri("uri") + .build() + + assertThat(documentContent.data()).contains("U3RhaW5sZXNzIHJvY2tz") + assertThat(documentContent.mimeType()).contains(DocumentContent.MimeType.APPLICATION_PDF) + assertThat(documentContent.uri()).contains("uri") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val documentContent = + DocumentContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(DocumentContent.MimeType.APPLICATION_PDF) + .uri("uri") + .build() + + val roundtrippedDocumentContent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(documentContent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedDocumentContent).isEqualTo(documentContent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/DynamicAgentConfigTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/DynamicAgentConfigTest.kt new file mode 100644 index 00000000000..50a2a95e5c9 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/DynamicAgentConfigTest.kt @@ -0,0 +1,46 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class DynamicAgentConfigTest { + + @Test + fun create() { + val dynamicAgentConfig = DynamicAgentConfig.builder().build() + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val dynamicAgentConfig = DynamicAgentConfig.builder().build() + + val roundtrippedDynamicAgentConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(dynamicAgentConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedDynamicAgentConfig).isEqualTo(dynamicAgentConfig) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ErrorEventTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ErrorEventTest.kt new file mode 100644 index 00000000000..3e323719920 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ErrorEventTest.kt @@ -0,0 +1,58 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ErrorEventTest { + + @Test + fun create() { + val errorEvent = + ErrorEvent.builder() + .error(ErrorEvent.Error.builder().code("code").message("message").build()) + .eventId("event_id") + .build() + + assertThat(errorEvent.error()) + .contains(ErrorEvent.Error.builder().code("code").message("message").build()) + assertThat(errorEvent.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val errorEvent = + ErrorEvent.builder() + .error(ErrorEvent.Error.builder().code("code").message("message").build()) + .eventId("event_id") + .build() + + val roundtrippedErrorEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(errorEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedErrorEvent).isEqualTo(errorEvent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/FileCitationTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/FileCitationTest.kt new file mode 100644 index 00000000000..56f26a935d8 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/FileCitationTest.kt @@ -0,0 +1,89 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FileCitationTest { + + @Test + fun create() { + val fileCitation = + FileCitation.builder() + .customMetadata( + FileCitation.CustomMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .documentUri("document_uri") + .endIndex(0) + .fileName("file_name") + .mediaId("media_id") + .pageNumber(0) + .source("source") + .startIndex(0) + .build() + + assertThat(fileCitation.customMetadata()) + .contains( + FileCitation.CustomMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + assertThat(fileCitation.documentUri()).contains("document_uri") + assertThat(fileCitation.endIndex()).contains(0) + assertThat(fileCitation.fileName()).contains("file_name") + assertThat(fileCitation.mediaId()).contains("media_id") + assertThat(fileCitation.pageNumber()).contains(0) + assertThat(fileCitation.source()).contains("source") + assertThat(fileCitation.startIndex()).contains(0) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val fileCitation = + FileCitation.builder() + .customMetadata( + FileCitation.CustomMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .documentUri("document_uri") + .endIndex(0) + .fileName("file_name") + .mediaId("media_id") + .pageNumber(0) + .source("source") + .startIndex(0) + .build() + + val roundtrippedFileCitation = + jsonMapper.readValue( + jsonMapper.writeValueAsString(fileCitation), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFileCitation).isEqualTo(fileCitation) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/FileSearchCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/FileSearchCallStepTest.kt new file mode 100644 index 00000000000..5a3a294dfbc --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/FileSearchCallStepTest.kt @@ -0,0 +1,51 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FileSearchCallStepTest { + + @Test + fun create() { + val fileSearchCallStep = + FileSearchCallStep.builder().id("id").signature("U3RhaW5sZXNzIHJvY2tz").build() + + assertThat(fileSearchCallStep.id()).isEqualTo("id") + assertThat(fileSearchCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val fileSearchCallStep = + FileSearchCallStep.builder().id("id").signature("U3RhaW5sZXNzIHJvY2tz").build() + + val roundtrippedFileSearchCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(fileSearchCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFileSearchCallStep).isEqualTo(fileSearchCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/FileSearchResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/FileSearchResultStepTest.kt new file mode 100644 index 00000000000..870c0e75267 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/FileSearchResultStepTest.kt @@ -0,0 +1,57 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FileSearchResultStepTest { + + @Test + fun create() { + val fileSearchResultStep = + FileSearchResultStep.builder() + .callId("call_id") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(fileSearchResultStep.callId()).isEqualTo("call_id") + assertThat(fileSearchResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val fileSearchResultStep = + FileSearchResultStep.builder() + .callId("call_id") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedFileSearchResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(fileSearchResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFileSearchResultStep).isEqualTo(fileSearchResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/FunctionCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/FunctionCallStepTest.kt new file mode 100644 index 00000000000..3b5635d271b --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/FunctionCallStepTest.kt @@ -0,0 +1,77 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FunctionCallStepTest { + + @Test + fun create() { + val functionCallStep = + FunctionCallStep.builder() + .id("id") + .arguments( + FunctionCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(functionCallStep.id()).isEqualTo("id") + assertThat(functionCallStep.arguments()) + .isEqualTo( + FunctionCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + assertThat(functionCallStep.name()).isEqualTo("name") + assertThat(functionCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val functionCallStep = + FunctionCallStep.builder() + .id("id") + .arguments( + FunctionCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedFunctionCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(functionCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFunctionCallStep).isEqualTo(functionCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/FunctionResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/FunctionResultStepTest.kt new file mode 100644 index 00000000000..49b81225480 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/FunctionResultStepTest.kt @@ -0,0 +1,68 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FunctionResultStepTest { + + @Test + fun create() { + val functionResultStep = + FunctionResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .isError(true) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(functionResultStep.callId()).isEqualTo("call_id") + assertThat(functionResultStep.result()) + .isEqualTo(FunctionResultStep.Result.ofJsonValue(JsonValue.from(mapOf()))) + assertThat(functionResultStep.isError()).contains(true) + assertThat(functionResultStep.name()).contains("name") + assertThat(functionResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val functionResultStep = + FunctionResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .isError(true) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedFunctionResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(functionResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFunctionResultStep).isEqualTo(functionResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/FunctionTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/FunctionTest.kt new file mode 100644 index 00000000000..4c228f8b693 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/FunctionTest.kt @@ -0,0 +1,61 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class FunctionTest { + + @Test + fun create() { + val function = + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + + assertThat(function.description()).contains("description") + assertThat(function.name()).contains("name") + assertThat(function._parameters()).isEqualTo(JsonValue.from(mapOf())) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val function = + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + + val roundtrippedFunction = + jsonMapper.readValue( + jsonMapper.writeValueAsString(function), + jacksonTypeRef(), + ) + + assertThat(roundtrippedFunction).isEqualTo(function) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GenerationConfigTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GenerationConfigTest.kt new file mode 100644 index 00000000000..cad80119950 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GenerationConfigTest.kt @@ -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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GenerationConfigTest { + + @Test + fun create() { + val generationConfig = + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + + assertThat(generationConfig.imageConfig()) + .contains( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + assertThat(generationConfig.maxOutputTokens()).contains(0) + assertThat(generationConfig.seed()).contains(0) + assertThat(generationConfig.speechConfig().getOrNull()) + .containsExactly( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + assertThat(generationConfig.stopSequences().getOrNull()).containsExactly("string") + assertThat(generationConfig.temperature()).contains(0.0f) + assertThat(generationConfig.thinkingLevel()).contains(ThinkingLevel.MINIMAL) + assertThat(generationConfig.thinkingSummaries()) + .contains(GenerationConfig.ThinkingSummaries.AUTO) + assertThat(generationConfig.toolChoice()) + .contains(GenerationConfig.ToolChoice.ofType(ToolChoiceType.AUTO)) + assertThat(generationConfig.topP()).contains(0.0f) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val generationConfig = + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + + val roundtrippedGenerationConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(generationConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGenerationConfig).isEqualTo(generationConfig) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallArgumentsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallArgumentsTest.kt new file mode 100644 index 00000000000..55f46bb2fb7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallArgumentsTest.kt @@ -0,0 +1,49 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleMapsCallArgumentsTest { + + @Test + fun create() { + val googleMapsCallArguments = GoogleMapsCallArguments.builder().addQuery("string").build() + + assertThat(googleMapsCallArguments.queries().getOrNull()).containsExactly("string") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleMapsCallArguments = GoogleMapsCallArguments.builder().addQuery("string").build() + + val roundtrippedGoogleMapsCallArguments = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleMapsCallArguments), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleMapsCallArguments).isEqualTo(googleMapsCallArguments) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallStepTest.kt new file mode 100644 index 00000000000..4834dbf7cc7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsCallStepTest.kt @@ -0,0 +1,61 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleMapsCallStepTest { + + @Test + fun create() { + val googleMapsCallStep = + GoogleMapsCallStep.builder() + .id("id") + .arguments(GoogleMapsCallStep.Arguments.builder().addQuery("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(googleMapsCallStep.id()).isEqualTo("id") + assertThat(googleMapsCallStep.arguments()) + .contains(GoogleMapsCallStep.Arguments.builder().addQuery("string").build()) + assertThat(googleMapsCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleMapsCallStep = + GoogleMapsCallStep.builder() + .id("id") + .arguments(GoogleMapsCallStep.Arguments.builder().addQuery("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedGoogleMapsCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleMapsCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleMapsCallStep).isEqualTo(googleMapsCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultStepTest.kt new file mode 100644 index 00000000000..d0d977cf2ec --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultStepTest.kt @@ -0,0 +1,115 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleMapsResultStepTest { + + @Test + fun create() { + val googleMapsResultStep = + GoogleMapsResultStep.builder() + .callId("call_id") + .addResult( + GoogleMapsResultStep.Result.builder() + .addPlace( + GoogleMapsResultStep.Result.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResultStep.Result.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(googleMapsResultStep.callId()).isEqualTo("call_id") + assertThat(googleMapsResultStep.result()) + .containsExactly( + GoogleMapsResultStep.Result.builder() + .addPlace( + GoogleMapsResultStep.Result.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResultStep.Result.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + ) + assertThat(googleMapsResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleMapsResultStep = + GoogleMapsResultStep.builder() + .callId("call_id") + .addResult( + GoogleMapsResultStep.Result.builder() + .addPlace( + GoogleMapsResultStep.Result.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResultStep.Result.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedGoogleMapsResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleMapsResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleMapsResultStep).isEqualTo(googleMapsResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultTest.kt new file mode 100644 index 00000000000..5594e785c43 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleMapsResultTest.kt @@ -0,0 +1,98 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleMapsResultTest { + + @Test + fun create() { + val googleMapsResult = + GoogleMapsResult.builder() + .addPlace( + GoogleMapsResult.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResult.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + + assertThat(googleMapsResult.places().getOrNull()) + .containsExactly( + GoogleMapsResult.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResult.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + assertThat(googleMapsResult.widgetContextToken()).contains("widget_context_token") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleMapsResult = + GoogleMapsResult.builder() + .addPlace( + GoogleMapsResult.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResult.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + + val roundtrippedGoogleMapsResult = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleMapsResult), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleMapsResult).isEqualTo(googleMapsResult) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallArgumentsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallArgumentsTest.kt new file mode 100644 index 00000000000..b7e093615f8 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallArgumentsTest.kt @@ -0,0 +1,51 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleSearchCallArgumentsTest { + + @Test + fun create() { + val googleSearchCallArguments = + GoogleSearchCallArguments.builder().addQuery("string").build() + + assertThat(googleSearchCallArguments.queries().getOrNull()).containsExactly("string") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleSearchCallArguments = + GoogleSearchCallArguments.builder().addQuery("string").build() + + val roundtrippedGoogleSearchCallArguments = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleSearchCallArguments), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleSearchCallArguments).isEqualTo(googleSearchCallArguments) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallStepTest.kt new file mode 100644 index 00000000000..428638ef93a --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchCallStepTest.kt @@ -0,0 +1,65 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleSearchCallStepTest { + + @Test + fun create() { + val googleSearchCallStep = + GoogleSearchCallStep.builder() + .id("id") + .arguments(GoogleSearchCallStep.Arguments.builder().addQuery("string").build()) + .searchType(GoogleSearchCallStep.SearchType.WEB_SEARCH) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(googleSearchCallStep.id()).isEqualTo("id") + assertThat(googleSearchCallStep.arguments()) + .isEqualTo(GoogleSearchCallStep.Arguments.builder().addQuery("string").build()) + assertThat(googleSearchCallStep.searchType()) + .contains(GoogleSearchCallStep.SearchType.WEB_SEARCH) + assertThat(googleSearchCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleSearchCallStep = + GoogleSearchCallStep.builder() + .id("id") + .arguments(GoogleSearchCallStep.Arguments.builder().addQuery("string").build()) + .searchType(GoogleSearchCallStep.SearchType.WEB_SEARCH) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedGoogleSearchCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleSearchCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleSearchCallStep).isEqualTo(googleSearchCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultDeltaTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultDeltaTest.kt new file mode 100644 index 00000000000..1bb4009bb62 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultDeltaTest.kt @@ -0,0 +1,67 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleSearchResultDeltaTest { + + @Test + fun create() { + val googleSearchResultDelta = + GoogleSearchResultDelta.builder() + .addResult( + GoogleSearchResult.builder().searchSuggestions("search_suggestions").build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(googleSearchResultDelta.result()) + .containsExactly( + GoogleSearchResult.builder().searchSuggestions("search_suggestions").build() + ) + assertThat(googleSearchResultDelta.isError()).contains(true) + assertThat(googleSearchResultDelta.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleSearchResultDelta = + GoogleSearchResultDelta.builder() + .addResult( + GoogleSearchResult.builder().searchSuggestions("search_suggestions").build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedGoogleSearchResultDelta = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleSearchResultDelta), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleSearchResultDelta).isEqualTo(googleSearchResultDelta) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultStepTest.kt new file mode 100644 index 00000000000..e9e6b02e6e4 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultStepTest.kt @@ -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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleSearchResultStepTest { + + @Test + fun create() { + val googleSearchResultStep = + GoogleSearchResultStep.builder() + .callId("call_id") + .addResult( + GoogleSearchResultStep.Result.builder() + .searchSuggestions("search_suggestions") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(googleSearchResultStep.callId()).isEqualTo("call_id") + assertThat(googleSearchResultStep.result()) + .containsExactly( + GoogleSearchResultStep.Result.builder() + .searchSuggestions("search_suggestions") + .build() + ) + assertThat(googleSearchResultStep.isError()).contains(true) + assertThat(googleSearchResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleSearchResultStep = + GoogleSearchResultStep.builder() + .callId("call_id") + .addResult( + GoogleSearchResultStep.Result.builder() + .searchSuggestions("search_suggestions") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedGoogleSearchResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleSearchResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleSearchResultStep).isEqualTo(googleSearchResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultTest.kt new file mode 100644 index 00000000000..144f6400920 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/GoogleSearchResultTest.kt @@ -0,0 +1,50 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class GoogleSearchResultTest { + + @Test + fun create() { + val googleSearchResult = + GoogleSearchResult.builder().searchSuggestions("search_suggestions").build() + + assertThat(googleSearchResult.searchSuggestions()).contains("search_suggestions") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val googleSearchResult = + GoogleSearchResult.builder().searchSuggestions("search_suggestions").build() + + val roundtrippedGoogleSearchResult = + jsonMapper.readValue( + jsonMapper.writeValueAsString(googleSearchResult), + jacksonTypeRef(), + ) + + assertThat(roundtrippedGoogleSearchResult).isEqualTo(googleSearchResult) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ImageConfigTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ImageConfigTest.kt new file mode 100644 index 00000000000..4cc75535f27 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ImageConfigTest.kt @@ -0,0 +1,57 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ImageConfigTest { + + @Test + fun create() { + val imageConfig = + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + + assertThat(imageConfig.aspectRatio()).contains(ImageConfig.AspectRatio._1_1) + assertThat(imageConfig.imageSize()).contains(ImageConfig.ImageSize._1_K) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val imageConfig = + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + + val roundtrippedImageConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(imageConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedImageConfig).isEqualTo(imageConfig) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ImageContentTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ImageContentTest.kt new file mode 100644 index 00000000000..3fdff51a661 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ImageContentTest.kt @@ -0,0 +1,63 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ImageContentTest { + + @Test + fun create() { + val imageContent = + ImageContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(ImageContent.MimeType.IMAGE_PNG) + .resolution(ImageContent.Resolution.LOW) + .uri("uri") + .build() + + assertThat(imageContent.data()).contains("U3RhaW5sZXNzIHJvY2tz") + assertThat(imageContent.mimeType()).contains(ImageContent.MimeType.IMAGE_PNG) + assertThat(imageContent.resolution()).contains(ImageContent.Resolution.LOW) + assertThat(imageContent.uri()).contains("uri") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val imageContent = + ImageContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(ImageContent.MimeType.IMAGE_PNG) + .resolution(ImageContent.Resolution.LOW) + .uri("uri") + .build() + + val roundtrippedImageContent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(imageContent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedImageContent).isEqualTo(imageContent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ImageResponseFormatTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ImageResponseFormatTest.kt new file mode 100644 index 00000000000..d218a86a124 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ImageResponseFormatTest.kt @@ -0,0 +1,63 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ImageResponseFormatTest { + + @Test + fun create() { + val imageResponseFormat = + ImageResponseFormat.builder() + .aspectRatio(ImageResponseFormat.AspectRatio._1_1) + .delivery(ImageResponseFormat.Delivery.INLINE) + .imageSize(ImageResponseFormat.ImageSize._512) + .mimeType(ImageResponseFormat.MimeType.IMAGE_JPEG) + .build() + + assertThat(imageResponseFormat.aspectRatio()).contains(ImageResponseFormat.AspectRatio._1_1) + assertThat(imageResponseFormat.delivery()).contains(ImageResponseFormat.Delivery.INLINE) + assertThat(imageResponseFormat.imageSize()).contains(ImageResponseFormat.ImageSize._512) + assertThat(imageResponseFormat.mimeType()).contains(ImageResponseFormat.MimeType.IMAGE_JPEG) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val imageResponseFormat = + ImageResponseFormat.builder() + .aspectRatio(ImageResponseFormat.AspectRatio._1_1) + .delivery(ImageResponseFormat.Delivery.INLINE) + .imageSize(ImageResponseFormat.ImageSize._512) + .mimeType(ImageResponseFormat.MimeType.IMAGE_JPEG) + .build() + + val roundtrippedImageResponseFormat = + jsonMapper.readValue( + jsonMapper.writeValueAsString(imageResponseFormat), + jacksonTypeRef(), + ) + + assertThat(roundtrippedImageResponseFormat).isEqualTo(imageResponseFormat) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionCancelParamsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCancelParamsTest.kt new file mode 100644 index 00000000000..6460676a478 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCancelParamsTest.kt @@ -0,0 +1,40 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionCancelParamsTest { + + @Test + fun create() { + InteractionCancelParams.builder().apiVersion("api_version").id("id").build() + } + + @Test + fun pathParams() { + val params = InteractionCancelParams.builder().id("id").build() + + assertThat(params._pathParam(0)).isEqualTo("") + assertThat(params._pathParam(1)).isEqualTo("id") + // out-of-bound path param + assertThat(params._pathParam(2)).isEqualTo("") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionCompletedEventTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCompletedEventTest.kt new file mode 100644 index 00000000000..da56db723c4 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCompletedEventTest.kt @@ -0,0 +1,490 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import java.time.OffsetDateTime +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionCompletedEventTest { + + @Test + fun create() { + val interactionCompletedEvent = + InteractionCompletedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + assertThat(interactionCompletedEvent.interaction()) + .isEqualTo( + Interaction.builder() + .id("v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg") + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + assertThat(interactionCompletedEvent.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val interactionCompletedEvent = + InteractionCompletedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + val roundtrippedInteractionCompletedEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionCompletedEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionCompletedEvent).isEqualTo(interactionCompletedEvent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionCreateParamsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCreateParamsTest.kt new file mode 100644 index 00000000000..6fd28bf5650 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCreateParamsTest.kt @@ -0,0 +1,469 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.google.genai.interactions.core.JsonValue +import java.time.OffsetDateTime +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionCreateParamsTest { + + @Test + fun create() { + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + } + + @Test + fun pathParams() { + val params = + InteractionCreateParams.builder() + .body( + CreateModelInteractionParams.builder() + .input(TextContent.builder().text("text").build()) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .build() + ) + .build() + + assertThat(params._pathParam(0)).isEqualTo("") + // out-of-bound path param + assertThat(params._pathParam(1)).isEqualTo("") + } + + @Test + fun body() { + val params = + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + + val body = params._body() + + assertThat(body) + .isEqualTo( + InteractionCreateParams.Body.ofCreateModelInteractionParams( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + ) + } + + @Test + fun bodyWithoutOptionalFields() { + val params = + InteractionCreateParams.builder() + .body( + CreateModelInteractionParams.builder() + .input(TextContent.builder().text("text").build()) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .build() + ) + .build() + + val body = params._body() + + assertThat(body) + .isEqualTo( + InteractionCreateParams.Body.ofCreateModelInteractionParams( + CreateModelInteractionParams.builder() + .input(TextContent.builder().text("text").build()) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .build() + ) + ) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionCreatedEventTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCreatedEventTest.kt new file mode 100644 index 00000000000..a9f4369b351 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionCreatedEventTest.kt @@ -0,0 +1,490 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import java.time.OffsetDateTime +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionCreatedEventTest { + + @Test + fun create() { + val interactionCreatedEvent = + InteractionCreatedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + assertThat(interactionCreatedEvent.interaction()) + .isEqualTo( + Interaction.builder() + .id("v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg") + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + assertThat(interactionCreatedEvent.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val interactionCreatedEvent = + InteractionCreatedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + val roundtrippedInteractionCreatedEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionCreatedEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionCreatedEvent).isEqualTo(interactionCreatedEvent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteParamsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteParamsTest.kt new file mode 100644 index 00000000000..268b9ba54c7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteParamsTest.kt @@ -0,0 +1,40 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionDeleteParamsTest { + + @Test + fun create() { + InteractionDeleteParams.builder().apiVersion("api_version").id("id").build() + } + + @Test + fun pathParams() { + val params = InteractionDeleteParams.builder().id("id").build() + + assertThat(params._pathParam(0)).isEqualTo("") + assertThat(params._pathParam(1)).isEqualTo("id") + // out-of-bound path param + assertThat(params._pathParam(2)).isEqualTo("") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteResponseTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteResponseTest.kt new file mode 100644 index 00000000000..ac8c9be3cb7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionDeleteResponseTest.kt @@ -0,0 +1,46 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionDeleteResponseTest { + + @Test + fun create() { + val interactionDeleteResponse = InteractionDeleteResponse.builder().build() + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val interactionDeleteResponse = InteractionDeleteResponse.builder().build() + + val roundtrippedInteractionDeleteResponse = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionDeleteResponse), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionDeleteResponse).isEqualTo(interactionDeleteResponse) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionGetParamsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionGetParamsTest.kt new file mode 100644 index 00000000000..90ce83a55f9 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionGetParamsTest.kt @@ -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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.google.genai.interactions.core.http.QueryParams +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionGetParamsTest { + + @Test + fun create() { + InteractionGetParams.builder() + .apiVersion("api_version") + .id("id") + .includeInput(true) + .lastEventId("last_event_id") + .build() + } + + @Test + fun pathParams() { + val params = InteractionGetParams.builder().id("id").build() + + assertThat(params._pathParam(0)).isEqualTo("") + assertThat(params._pathParam(1)).isEqualTo("id") + // out-of-bound path param + assertThat(params._pathParam(2)).isEqualTo("") + } + + @Test + fun queryParams() { + val params = + InteractionGetParams.builder() + .apiVersion("api_version") + .id("id") + .includeInput(true) + .lastEventId("last_event_id") + .build() + + val queryParams = params._queryParams() + + assertThat(queryParams) + .isEqualTo( + QueryParams.builder() + .put("include_input", "true") + .put("last_event_id", "last_event_id") + .build() + ) + } + + @Test + fun queryParamsWithoutOptionalFields() { + val params = InteractionGetParams.builder().id("id").build() + + val queryParams = params._queryParams() + + assertThat(queryParams).isEqualTo(QueryParams.builder().build()) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionSseEventTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionSseEventTest.kt new file mode 100644 index 00000000000..b4c85c7d618 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionSseEventTest.kt @@ -0,0 +1,921 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import com.google.genai.interactions.errors.GeminiNextGenApiInvalidDataException +import java.time.OffsetDateTime +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class InteractionSseEventTest { + + @Test + fun ofCreated() { + val created = + InteractionCreatedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + val interactionSseEvent = InteractionSseEvent.ofCreated(created) + + assertThat(interactionSseEvent.created()).contains(created) + assertThat(interactionSseEvent.completed()).isEmpty + assertThat(interactionSseEvent.statusUpdate()).isEmpty + assertThat(interactionSseEvent.error()).isEmpty + assertThat(interactionSseEvent.stepStart()).isEmpty + assertThat(interactionSseEvent.stepDelta()).isEmpty + assertThat(interactionSseEvent.stepStop()).isEmpty + } + + @Test + fun ofCreatedRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofCreated( + InteractionCreatedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + ) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + @Test + fun ofCompleted() { + val completed = + InteractionCompletedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + val interactionSseEvent = InteractionSseEvent.ofCompleted(completed) + + assertThat(interactionSseEvent.created()).isEmpty + assertThat(interactionSseEvent.completed()).contains(completed) + assertThat(interactionSseEvent.statusUpdate()).isEmpty + assertThat(interactionSseEvent.error()).isEmpty + assertThat(interactionSseEvent.stepStart()).isEmpty + assertThat(interactionSseEvent.stepDelta()).isEmpty + assertThat(interactionSseEvent.stepStop()).isEmpty + } + + @Test + fun ofCompletedRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofCompleted( + InteractionCompletedEvent.builder() + .interaction( + Interaction.builder() + .id( + "v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg" + ) + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + ) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + @Test + fun ofStatusUpdate() { + val statusUpdate = + InteractionStatusUpdate.builder() + .interactionId("interaction_id") + .status(InteractionStatusUpdate.Status.IN_PROGRESS) + .eventId("event_id") + .build() + + val interactionSseEvent = InteractionSseEvent.ofStatusUpdate(statusUpdate) + + assertThat(interactionSseEvent.created()).isEmpty + assertThat(interactionSseEvent.completed()).isEmpty + assertThat(interactionSseEvent.statusUpdate()).contains(statusUpdate) + assertThat(interactionSseEvent.error()).isEmpty + assertThat(interactionSseEvent.stepStart()).isEmpty + assertThat(interactionSseEvent.stepDelta()).isEmpty + assertThat(interactionSseEvent.stepStop()).isEmpty + } + + @Test + fun ofStatusUpdateRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofStatusUpdate( + InteractionStatusUpdate.builder() + .interactionId("interaction_id") + .status(InteractionStatusUpdate.Status.IN_PROGRESS) + .eventId("event_id") + .build() + ) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + @Test + fun ofError() { + val error = + ErrorEvent.builder() + .error(ErrorEvent.Error.builder().code("code").message("message").build()) + .eventId("event_id") + .build() + + val interactionSseEvent = InteractionSseEvent.ofError(error) + + assertThat(interactionSseEvent.created()).isEmpty + assertThat(interactionSseEvent.completed()).isEmpty + assertThat(interactionSseEvent.statusUpdate()).isEmpty + assertThat(interactionSseEvent.error()).contains(error) + assertThat(interactionSseEvent.stepStart()).isEmpty + assertThat(interactionSseEvent.stepDelta()).isEmpty + assertThat(interactionSseEvent.stepStop()).isEmpty + } + + @Test + fun ofErrorRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofError( + ErrorEvent.builder() + .error(ErrorEvent.Error.builder().code("code").message("message").build()) + .eventId("event_id") + .build() + ) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + @Test + fun ofStepStart() { + val stepStart = + StepStart.builder() + .index(0) + .step( + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + val interactionSseEvent = InteractionSseEvent.ofStepStart(stepStart) + + assertThat(interactionSseEvent.created()).isEmpty + assertThat(interactionSseEvent.completed()).isEmpty + assertThat(interactionSseEvent.statusUpdate()).isEmpty + assertThat(interactionSseEvent.error()).isEmpty + assertThat(interactionSseEvent.stepStart()).contains(stepStart) + assertThat(interactionSseEvent.stepDelta()).isEmpty + assertThat(interactionSseEvent.stepStop()).isEmpty + } + + @Test + fun ofStepStartRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofStepStart( + StepStart.builder() + .index(0) + .step( + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + ) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + @Test + fun ofStepDelta() { + val stepDelta = StepDelta.builder().textDelta("text").index(0).eventId("event_id").build() + + val interactionSseEvent = InteractionSseEvent.ofStepDelta(stepDelta) + + assertThat(interactionSseEvent.created()).isEmpty + assertThat(interactionSseEvent.completed()).isEmpty + assertThat(interactionSseEvent.statusUpdate()).isEmpty + assertThat(interactionSseEvent.error()).isEmpty + assertThat(interactionSseEvent.stepStart()).isEmpty + assertThat(interactionSseEvent.stepDelta()).contains(stepDelta) + assertThat(interactionSseEvent.stepStop()).isEmpty + } + + @Test + fun ofStepDeltaRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofStepDelta( + StepDelta.builder().textDelta("text").index(0).eventId("event_id").build() + ) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + @Test + fun ofStepStop() { + val stepStop = StepStop.builder().index(0).eventId("event_id").build() + + val interactionSseEvent = InteractionSseEvent.ofStepStop(stepStop) + + assertThat(interactionSseEvent.created()).isEmpty + assertThat(interactionSseEvent.completed()).isEmpty + assertThat(interactionSseEvent.statusUpdate()).isEmpty + assertThat(interactionSseEvent.error()).isEmpty + assertThat(interactionSseEvent.stepStart()).isEmpty + assertThat(interactionSseEvent.stepDelta()).isEmpty + assertThat(interactionSseEvent.stepStop()).contains(stepStop) + } + + @Test + fun ofStepStopRoundtrip() { + val jsonMapper = jsonMapper() + val interactionSseEvent = + InteractionSseEvent.ofStepStop(StepStop.builder().index(0).eventId("event_id").build()) + + val roundtrippedInteractionSseEvent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionSseEvent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionSseEvent).isEqualTo(interactionSseEvent) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val interactionSseEvent = + jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = + assertThrows { interactionSseEvent.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionStatusUpdateTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionStatusUpdateTest.kt new file mode 100644 index 00000000000..5bc4d4f0dec --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionStatusUpdateTest.kt @@ -0,0 +1,61 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionStatusUpdateTest { + + @Test + fun create() { + val interactionStatusUpdate = + InteractionStatusUpdate.builder() + .interactionId("interaction_id") + .status(InteractionStatusUpdate.Status.IN_PROGRESS) + .eventId("event_id") + .build() + + assertThat(interactionStatusUpdate.interactionId()).isEqualTo("interaction_id") + assertThat(interactionStatusUpdate.status()) + .isEqualTo(InteractionStatusUpdate.Status.IN_PROGRESS) + assertThat(interactionStatusUpdate.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val interactionStatusUpdate = + InteractionStatusUpdate.builder() + .interactionId("interaction_id") + .status(InteractionStatusUpdate.Status.IN_PROGRESS) + .eventId("event_id") + .build() + + val roundtrippedInteractionStatusUpdate = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interactionStatusUpdate), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteractionStatusUpdate).isEqualTo(interactionStatusUpdate) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/InteractionTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/InteractionTest.kt new file mode 100644 index 00000000000..5e0742cc4f8 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/InteractionTest.kt @@ -0,0 +1,490 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import java.time.OffsetDateTime +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class InteractionTest { + + @Test + fun create() { + val interaction = + Interaction.builder() + .id("v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg") + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + + assertThat(interaction.id()) + .isEqualTo("v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg") + assertThat(interaction.created()).isEqualTo(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + assertThat(interaction.status()).isEqualTo(Interaction.Status.COMPLETED) + assertThat(interaction.steps()) + .containsExactly( + Step.ofModelOutput( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + ) + assertThat(interaction.updated()).isEqualTo(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + assertThat(interaction.agent()) + .contains(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + assertThat(interaction.agentConfig()) + .contains(Interaction.AgentConfig.ofDynamic(DynamicAgentConfig.builder().build())) + assertThat(interaction.generationConfig()) + .contains( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + assertThat(interaction.input()) + .contains( + Interaction.Input.ofTextContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + ) + assertThat(interaction.model()).contains(Model.GEMINI_3_FLASH_PREVIEW) + assertThat(interaction.previousInteractionId()).contains("previous_interaction_id") + assertThat(interaction.responseFormat()) + .contains( + Interaction.ResponseFormat.ofList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + ) + assertThat(interaction.responseMimeType()).contains("response_mime_type") + assertThat(interaction.responseModalities().getOrNull()) + .containsExactly(Interaction.ResponseModality.TEXT) + assertThat(interaction.role()).contains("role") + assertThat(interaction.serviceTier()).contains(Interaction.ServiceTier.FLEX) + assertThat(interaction.systemInstruction()).contains("system_instruction") + assertThat(interaction.tools().getOrNull()) + .containsExactly( + Tool.ofFunction( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + ) + assertThat(interaction.usage()) + .contains( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + assertThat(interaction.webhookConfig()) + .contains( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val interaction = + Interaction.builder() + .id("v1_ChdXS0l4YWZXTk9xbk0xZThQczhEcmlROBIXV0tJeGFmV05PcW5NMWU4UHM4RHJpUTg") + .created(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .status(Interaction.Status.COMPLETED) + .addStep( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text( + "Hello! I'm doing well, functioning as expected. Thank you for asking! How are you doing today?" + ) + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .updated(OffsetDateTime.parse("2025-12-04T15:01:45Z")) + .agent(Interaction.Agent.DEEP_RESEARCH_PRO_PREVIEW_12_2025) + .agentConfig(DynamicAgentConfig.builder().build()) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_3_FLASH_PREVIEW) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + Interaction.ResponseFormat.InnerResponseFormat.ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(Interaction.ResponseModality.TEXT) + .role("role") + .serviceTier(Interaction.ServiceTier.FLEX) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(7) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(7) + .totalOutputTokens(23) + .totalThoughtTokens(49) + .totalTokens(79) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + Interaction.WebhookConfig.builder() + .addUris("string") + .userMetadata( + Interaction.WebhookConfig.UserMetadata.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + + val roundtrippedInteraction = + jsonMapper.readValue( + jsonMapper.writeValueAsString(interaction), + jacksonTypeRef(), + ) + + assertThat(roundtrippedInteraction).isEqualTo(interaction) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/McpServerToolCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/McpServerToolCallStepTest.kt new file mode 100644 index 00000000000..bd04585ee9e --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/McpServerToolCallStepTest.kt @@ -0,0 +1,80 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class McpServerToolCallStepTest { + + @Test + fun create() { + val mcpServerToolCallStep = + McpServerToolCallStep.builder() + .id("id") + .arguments( + McpServerToolCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(mcpServerToolCallStep.id()).isEqualTo("id") + assertThat(mcpServerToolCallStep.arguments()) + .isEqualTo( + McpServerToolCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + assertThat(mcpServerToolCallStep.name()).isEqualTo("name") + assertThat(mcpServerToolCallStep.serverName()).isEqualTo("server_name") + assertThat(mcpServerToolCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val mcpServerToolCallStep = + McpServerToolCallStep.builder() + .id("id") + .arguments( + McpServerToolCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedMcpServerToolCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(mcpServerToolCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedMcpServerToolCallStep).isEqualTo(mcpServerToolCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/McpServerToolResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/McpServerToolResultStepTest.kt new file mode 100644 index 00000000000..5a904f9e966 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/McpServerToolResultStepTest.kt @@ -0,0 +1,70 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class McpServerToolResultStepTest { + + @Test + fun create() { + val mcpServerToolResultStep = + McpServerToolResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(mcpServerToolResultStep.callId()).isEqualTo("call_id") + assertThat(mcpServerToolResultStep.result()) + .isEqualTo( + McpServerToolResultStep.Result.ofJsonValue(JsonValue.from(mapOf())) + ) + assertThat(mcpServerToolResultStep.name()).contains("name") + assertThat(mcpServerToolResultStep.serverName()).contains("server_name") + assertThat(mcpServerToolResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val mcpServerToolResultStep = + McpServerToolResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedMcpServerToolResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(mcpServerToolResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedMcpServerToolResultStep).isEqualTo(mcpServerToolResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ModelOutputStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ModelOutputStepTest.kt new file mode 100644 index 00000000000..2fa89615157 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ModelOutputStepTest.kt @@ -0,0 +1,94 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ModelOutputStepTest { + + @Test + fun create() { + val modelOutputStep = + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + assertThat(modelOutputStep.content().getOrNull()) + .containsExactly( + Content.ofText( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val modelOutputStep = + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + val roundtrippedModelOutputStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(modelOutputStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedModelOutputStep).isEqualTo(modelOutputStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/PlaceCitationTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/PlaceCitationTest.kt new file mode 100644 index 00000000000..3c4f60c05f4 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/PlaceCitationTest.kt @@ -0,0 +1,89 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class PlaceCitationTest { + + @Test + fun create() { + val placeCitation = + PlaceCitation.builder() + .endIndex(0) + .name("name") + .placeId("place_id") + .addReviewSnippet( + PlaceCitation.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .startIndex(0) + .url("url") + .build() + + assertThat(placeCitation.endIndex()).contains(0) + assertThat(placeCitation.name()).contains("name") + assertThat(placeCitation.placeId()).contains("place_id") + assertThat(placeCitation.reviewSnippets().getOrNull()) + .containsExactly( + PlaceCitation.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + assertThat(placeCitation.startIndex()).contains(0) + assertThat(placeCitation.url()).contains("url") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val placeCitation = + PlaceCitation.builder() + .endIndex(0) + .name("name") + .placeId("place_id") + .addReviewSnippet( + PlaceCitation.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .startIndex(0) + .url("url") + .build() + + val roundtrippedPlaceCitation = + jsonMapper.readValue( + jsonMapper.writeValueAsString(placeCitation), + jacksonTypeRef(), + ) + + assertThat(roundtrippedPlaceCitation).isEqualTo(placeCitation) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/SpeechConfigTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/SpeechConfigTest.kt new file mode 100644 index 00000000000..6c2269ed964 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/SpeechConfigTest.kt @@ -0,0 +1,52 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class SpeechConfigTest { + + @Test + fun create() { + val speechConfig = + SpeechConfig.builder().language("language").speaker("speaker").voice("voice").build() + + assertThat(speechConfig.language()).contains("language") + assertThat(speechConfig.speaker()).contains("speaker") + assertThat(speechConfig.voice()).contains("voice") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val speechConfig = + SpeechConfig.builder().language("language").speaker("speaker").voice("voice").build() + + val roundtrippedSpeechConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(speechConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedSpeechConfig).isEqualTo(speechConfig) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/StepDeltaTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/StepDeltaTest.kt new file mode 100644 index 00000000000..2c455ce3868 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/StepDeltaTest.kt @@ -0,0 +1,51 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class StepDeltaTest { + + @Test + fun create() { + val stepDelta = StepDelta.builder().textDelta("text").index(0).eventId("event_id").build() + + assertThat(stepDelta.delta()) + .isEqualTo(StepDelta.Delta.ofText(StepDelta.Delta.Text.builder().text("text").build())) + assertThat(stepDelta.index()).isEqualTo(0) + assertThat(stepDelta.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val stepDelta = StepDelta.builder().textDelta("text").index(0).eventId("event_id").build() + + val roundtrippedStepDelta = + jsonMapper.readValue( + jsonMapper.writeValueAsString(stepDelta), + jacksonTypeRef(), + ) + + assertThat(roundtrippedStepDelta).isEqualTo(stepDelta) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/StepStartTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/StepStartTest.kt new file mode 100644 index 00000000000..6eb1ecb5bdd --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/StepStartTest.kt @@ -0,0 +1,111 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class StepStartTest { + + @Test + fun create() { + val stepStart = + StepStart.builder() + .index(0) + .step( + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + assertThat(stepStart.index()).isEqualTo(0) + assertThat(stepStart.step()) + .isEqualTo( + Step.ofUserInput( + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + ) + assertThat(stepStart.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val stepStart = + StepStart.builder() + .index(0) + .step( + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + .eventId("event_id") + .build() + + val roundtrippedStepStart = + jsonMapper.readValue( + jsonMapper.writeValueAsString(stepStart), + jacksonTypeRef(), + ) + + assertThat(roundtrippedStepStart).isEqualTo(stepStart) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/StepStopTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/StepStopTest.kt new file mode 100644 index 00000000000..97bd6ebbcbf --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/StepStopTest.kt @@ -0,0 +1,49 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class StepStopTest { + + @Test + fun create() { + val stepStop = StepStop.builder().index(0).eventId("event_id").build() + + assertThat(stepStop.index()).isEqualTo(0) + assertThat(stepStop.eventId()).contains("event_id") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val stepStop = StepStop.builder().index(0).eventId("event_id").build() + + val roundtrippedStepStop = + jsonMapper.readValue( + jsonMapper.writeValueAsString(stepStop), + jacksonTypeRef(), + ) + + assertThat(roundtrippedStepStop).isEqualTo(stepStop) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/StepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/StepTest.kt new file mode 100644 index 00000000000..7df1695d43f --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/StepTest.kt @@ -0,0 +1,1019 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import com.google.genai.interactions.errors.GeminiNextGenApiInvalidDataException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class StepTest { + + @Test + fun ofUserInput() { + val userInput = + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + val step = Step.ofUserInput(userInput) + + assertThat(step.userInput()).contains(userInput) + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofUserInputRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofUserInput( + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofModelOutput() { + val modelOutput = + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + val step = Step.ofModelOutput(modelOutput) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).contains(modelOutput) + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofModelOutputRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofModelOutput( + ModelOutputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofThought() { + val thought = + ThoughtStep.builder() + .signature("U3RhaW5sZXNzIHJvY2tz") + .addSummary( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + val step = Step.ofThought(thought) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).contains(thought) + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofThoughtRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofThought( + ThoughtStep.builder() + .signature("U3RhaW5sZXNzIHJvY2tz") + .addSummary( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofFunctionCall() { + val functionCall = + FunctionCallStep.builder() + .id("id") + .arguments( + FunctionCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofFunctionCall(functionCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).contains(functionCall) + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofFunctionCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofFunctionCall( + FunctionCallStep.builder() + .id("id") + .arguments( + FunctionCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofCodeExecutionCall() { + val codeExecutionCall = + CodeExecutionCallStep.builder() + .id("id") + .arguments( + CodeExecutionCallStep.Arguments.builder() + .code("code") + .language(CodeExecutionCallStep.Arguments.Language.PYTHON) + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofCodeExecutionCall(codeExecutionCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).contains(codeExecutionCall) + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofCodeExecutionCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofCodeExecutionCall( + CodeExecutionCallStep.builder() + .id("id") + .arguments( + CodeExecutionCallStep.Arguments.builder() + .code("code") + .language(CodeExecutionCallStep.Arguments.Language.PYTHON) + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofUrlContextCall() { + val urlContextCall = + UrlContextCallStep.builder() + .id("id") + .arguments(UrlContextCallStep.Arguments.builder().addUrl("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofUrlContextCall(urlContextCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).contains(urlContextCall) + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofUrlContextCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofUrlContextCall( + UrlContextCallStep.builder() + .id("id") + .arguments(UrlContextCallStep.Arguments.builder().addUrl("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofMcpServerToolCall() { + val mcpServerToolCall = + McpServerToolCallStep.builder() + .id("id") + .arguments( + McpServerToolCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofMcpServerToolCall(mcpServerToolCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).contains(mcpServerToolCall) + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofMcpServerToolCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofMcpServerToolCall( + McpServerToolCallStep.builder() + .id("id") + .arguments( + McpServerToolCallStep.Arguments.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofGoogleSearchCall() { + val googleSearchCall = + GoogleSearchCallStep.builder() + .id("id") + .arguments(GoogleSearchCallStep.Arguments.builder().addQuery("string").build()) + .searchType(GoogleSearchCallStep.SearchType.WEB_SEARCH) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofGoogleSearchCall(googleSearchCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).contains(googleSearchCall) + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofGoogleSearchCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofGoogleSearchCall( + GoogleSearchCallStep.builder() + .id("id") + .arguments(GoogleSearchCallStep.Arguments.builder().addQuery("string").build()) + .searchType(GoogleSearchCallStep.SearchType.WEB_SEARCH) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofFileSearchCall() { + val fileSearchCall = + FileSearchCallStep.builder().id("id").signature("U3RhaW5sZXNzIHJvY2tz").build() + + val step = Step.ofFileSearchCall(fileSearchCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).contains(fileSearchCall) + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofFileSearchCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofFileSearchCall( + FileSearchCallStep.builder().id("id").signature("U3RhaW5sZXNzIHJvY2tz").build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofGoogleMapsCall() { + val googleMapsCall = + GoogleMapsCallStep.builder() + .id("id") + .arguments(GoogleMapsCallStep.Arguments.builder().addQuery("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofGoogleMapsCall(googleMapsCall) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).contains(googleMapsCall) + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofGoogleMapsCallRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofGoogleMapsCall( + GoogleMapsCallStep.builder() + .id("id") + .arguments(GoogleMapsCallStep.Arguments.builder().addQuery("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofFunctionResult() { + val functionResult = + FunctionResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .isError(true) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofFunctionResult(functionResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).contains(functionResult) + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofFunctionResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofFunctionResult( + FunctionResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .isError(true) + .name("name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofCodeExecutionResult() { + val codeExecutionResult = + CodeExecutionResultStep.builder() + .callId("call_id") + .result("result") + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofCodeExecutionResult(codeExecutionResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).contains(codeExecutionResult) + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofCodeExecutionResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofCodeExecutionResult( + CodeExecutionResultStep.builder() + .callId("call_id") + .result("result") + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofUrlContextResult() { + val urlContextResult = + UrlContextResultStep.builder() + .callId("call_id") + .addResult( + UrlContextResultStep.Result.builder() + .status(UrlContextResultStep.Result.Status.SUCCESS) + .url("url") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofUrlContextResult(urlContextResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).contains(urlContextResult) + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofUrlContextResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofUrlContextResult( + UrlContextResultStep.builder() + .callId("call_id") + .addResult( + UrlContextResultStep.Result.builder() + .status(UrlContextResultStep.Result.Status.SUCCESS) + .url("url") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofGoogleSearchResult() { + val googleSearchResult = + GoogleSearchResultStep.builder() + .callId("call_id") + .addResult( + GoogleSearchResultStep.Result.builder() + .searchSuggestions("search_suggestions") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofGoogleSearchResult(googleSearchResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).contains(googleSearchResult) + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofGoogleSearchResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofGoogleSearchResult( + GoogleSearchResultStep.builder() + .callId("call_id") + .addResult( + GoogleSearchResultStep.Result.builder() + .searchSuggestions("search_suggestions") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofMcpServerToolResult() { + val mcpServerToolResult = + McpServerToolResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofMcpServerToolResult(mcpServerToolResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).contains(mcpServerToolResult) + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofMcpServerToolResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofMcpServerToolResult( + McpServerToolResultStep.builder() + .callId("call_id") + .result(JsonValue.from(mapOf())) + .name("name") + .serverName("server_name") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofFileSearchResult() { + val fileSearchResult = + FileSearchResultStep.builder() + .callId("call_id") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofFileSearchResult(fileSearchResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).contains(fileSearchResult) + assertThat(step.googleMapsResult()).isEmpty + } + + @Test + fun ofFileSearchResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofFileSearchResult( + FileSearchResultStep.builder() + .callId("call_id") + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + @Test + fun ofGoogleMapsResult() { + val googleMapsResult = + GoogleMapsResultStep.builder() + .callId("call_id") + .addResult( + GoogleMapsResultStep.Result.builder() + .addPlace( + GoogleMapsResultStep.Result.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResultStep.Result.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val step = Step.ofGoogleMapsResult(googleMapsResult) + + assertThat(step.userInput()).isEmpty + assertThat(step.modelOutput()).isEmpty + assertThat(step.thought()).isEmpty + assertThat(step.functionCall()).isEmpty + assertThat(step.codeExecutionCall()).isEmpty + assertThat(step.urlContextCall()).isEmpty + assertThat(step.mcpServerToolCall()).isEmpty + assertThat(step.googleSearchCall()).isEmpty + assertThat(step.fileSearchCall()).isEmpty + assertThat(step.googleMapsCall()).isEmpty + assertThat(step.functionResult()).isEmpty + assertThat(step.codeExecutionResult()).isEmpty + assertThat(step.urlContextResult()).isEmpty + assertThat(step.googleSearchResult()).isEmpty + assertThat(step.mcpServerToolResult()).isEmpty + assertThat(step.fileSearchResult()).isEmpty + assertThat(step.googleMapsResult()).contains(googleMapsResult) + } + + @Test + fun ofGoogleMapsResultRoundtrip() { + val jsonMapper = jsonMapper() + val step = + Step.ofGoogleMapsResult( + GoogleMapsResultStep.builder() + .callId("call_id") + .addResult( + GoogleMapsResultStep.Result.builder() + .addPlace( + GoogleMapsResultStep.Result.Place.builder() + .name("name") + .placeId("place_id") + .addReviewSnippet( + GoogleMapsResultStep.Result.Place.ReviewSnippet.builder() + .reviewId("review_id") + .title("title") + .url("url") + .build() + ) + .url("url") + .build() + ) + .widgetContextToken("widget_context_token") + .build() + ) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + ) + + val roundtrippedStep = + jsonMapper.readValue(jsonMapper.writeValueAsString(step), jacksonTypeRef()) + + assertThat(roundtrippedStep).isEqualTo(step) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val step = jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = assertThrows { step.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/TextContentTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/TextContentTest.kt new file mode 100644 index 00000000000..d406a124e88 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/TextContentTest.kt @@ -0,0 +1,82 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class TextContentTest { + + @Test + fun create() { + val textContent = + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + + assertThat(textContent.text()).isEqualTo("text") + assertThat(textContent.annotations().getOrNull()) + .containsExactly( + Annotation.ofUrlCitation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val textContent = + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + + val roundtrippedTextContent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(textContent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedTextContent).isEqualTo(textContent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/TextResponseFormatTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/TextResponseFormatTest.kt new file mode 100644 index 00000000000..d4cbaf56149 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/TextResponseFormatTest.kt @@ -0,0 +1,72 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class TextResponseFormatTest { + + @Test + fun create() { + val textResponseFormat = + TextResponseFormat.builder() + .mimeType(TextResponseFormat.MimeType.APPLICATION_JSON) + .schema( + TextResponseFormat.Schema.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + + assertThat(textResponseFormat.mimeType()) + .contains(TextResponseFormat.MimeType.APPLICATION_JSON) + assertThat(textResponseFormat.schema()) + .contains( + TextResponseFormat.Schema.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val textResponseFormat = + TextResponseFormat.builder() + .mimeType(TextResponseFormat.MimeType.APPLICATION_JSON) + .schema( + TextResponseFormat.Schema.builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + + val roundtrippedTextResponseFormat = + jsonMapper.readValue( + jsonMapper.writeValueAsString(textResponseFormat), + jacksonTypeRef(), + ) + + assertThat(roundtrippedTextResponseFormat).isEqualTo(textResponseFormat) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ThoughtStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ThoughtStepTest.kt new file mode 100644 index 00000000000..84cc97a9962 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ThoughtStepTest.kt @@ -0,0 +1,97 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ThoughtStepTest { + + @Test + fun create() { + val thoughtStep = + ThoughtStep.builder() + .signature("U3RhaW5sZXNzIHJvY2tz") + .addSummary( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + assertThat(thoughtStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + assertThat(thoughtStep.summary().getOrNull()) + .containsExactly( + ThoughtStep.Summary.ofText( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val thoughtStep = + ThoughtStep.builder() + .signature("U3RhaW5sZXNzIHJvY2tz") + .addSummary( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + val roundtrippedThoughtStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(thoughtStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedThoughtStep).isEqualTo(thoughtStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ToolChoiceConfigTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ToolChoiceConfigTest.kt new file mode 100644 index 00000000000..4588972d606 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ToolChoiceConfigTest.kt @@ -0,0 +1,59 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ToolChoiceConfigTest { + + @Test + fun create() { + val toolChoiceConfig = + ToolChoiceConfig.builder() + .allowedTools( + AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build() + ) + .build() + + assertThat(toolChoiceConfig.allowedTools()) + .contains(AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build()) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val toolChoiceConfig = + ToolChoiceConfig.builder() + .allowedTools( + AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build() + ) + .build() + + val roundtrippedToolChoiceConfig = + jsonMapper.readValue( + jsonMapper.writeValueAsString(toolChoiceConfig), + jacksonTypeRef(), + ) + + assertThat(roundtrippedToolChoiceConfig).isEqualTo(toolChoiceConfig) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/ToolTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/ToolTest.kt new file mode 100644 index 00000000000..1384e086016 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/ToolTest.kt @@ -0,0 +1,389 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.jsonMapper +import com.google.genai.interactions.errors.GeminiNextGenApiInvalidDataException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.EnumSource + +internal class ToolTest { + + @Test + fun ofFunction() { + val function = + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + + val tool = Tool.ofFunction(function) + + assertThat(tool.function()).contains(function) + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofFunctionRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofFunction( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofCodeExecution() { + val tool = Tool.ofCodeExecution() + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).contains(JsonValue.from(mapOf("type" to "code_execution"))) + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofCodeExecutionRoundtrip() { + val jsonMapper = jsonMapper() + val tool = Tool.ofCodeExecution() + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofUrlContext() { + val tool = Tool.ofUrlContext() + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).contains(JsonValue.from(mapOf("type" to "url_context"))) + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofUrlContextRoundtrip() { + val jsonMapper = jsonMapper() + val tool = Tool.ofUrlContext() + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofComputerUse() { + val computerUse = + Tool.ComputerUse.builder() + .environment(Tool.ComputerUse.Environment.BROWSER) + .addExcludedPredefinedFunction("string") + .build() + + val tool = Tool.ofComputerUse(computerUse) + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).contains(computerUse) + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofComputerUseRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofComputerUse( + Tool.ComputerUse.builder() + .environment(Tool.ComputerUse.Environment.BROWSER) + .addExcludedPredefinedFunction("string") + .build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofMcpServer() { + val mcpServer = + Tool.McpServer.builder() + .addAllowedTool( + AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build() + ) + .headers( + Tool.McpServer.Headers.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) + .name("name") + .url("url") + .build() + + val tool = Tool.ofMcpServer(mcpServer) + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).contains(mcpServer) + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofMcpServerRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofMcpServer( + Tool.McpServer.builder() + .addAllowedTool( + AllowedTools.builder().mode(ToolChoiceType.AUTO).addTool("string").build() + ) + .headers( + Tool.McpServer.Headers.builder() + .putAdditionalProperty("foo", JsonValue.from("string")) + .build() + ) + .name("name") + .url("url") + .build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofGoogleSearch() { + val googleSearch = + Tool.GoogleSearch.builder() + .addSearchType(Tool.GoogleSearch.SearchType.WEB_SEARCH) + .build() + + val tool = Tool.ofGoogleSearch(googleSearch) + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).contains(googleSearch) + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofGoogleSearchRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofGoogleSearch( + Tool.GoogleSearch.builder() + .addSearchType(Tool.GoogleSearch.SearchType.WEB_SEARCH) + .build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofFileSearch() { + val fileSearch = + Tool.FileSearch.builder() + .addFileSearchStoreName("string") + .metadataFilter("metadata_filter") + .topK(0) + .build() + + val tool = Tool.ofFileSearch(fileSearch) + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).contains(fileSearch) + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofFileSearchRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofFileSearch( + Tool.FileSearch.builder() + .addFileSearchStoreName("string") + .metadataFilter("metadata_filter") + .topK(0) + .build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofGoogleMaps() { + val googleMaps = + Tool.GoogleMaps.builder().enableWidget(true).latitude(0.0).longitude(0.0).build() + + val tool = Tool.ofGoogleMaps(googleMaps) + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).contains(googleMaps) + assertThat(tool.retrieval()).isEmpty + } + + @Test + fun ofGoogleMapsRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofGoogleMaps( + Tool.GoogleMaps.builder().enableWidget(true).latitude(0.0).longitude(0.0).build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + @Test + fun ofRetrieval() { + val retrieval = + Tool.Retrieval.builder() + .addRetrievalType(Tool.Retrieval.RetrievalType.VERTEX_AI_SEARCH) + .vertexAiSearchConfig( + Tool.Retrieval.VertexAiSearchConfig.builder() + .addDatastore("string") + .engine("engine") + .build() + ) + .build() + + val tool = Tool.ofRetrieval(retrieval) + + assertThat(tool.function()).isEmpty + assertThat(tool.codeExecution()).isEmpty + assertThat(tool.urlContext()).isEmpty + assertThat(tool.computerUse()).isEmpty + assertThat(tool.mcpServer()).isEmpty + assertThat(tool.googleSearch()).isEmpty + assertThat(tool.fileSearch()).isEmpty + assertThat(tool.googleMaps()).isEmpty + assertThat(tool.retrieval()).contains(retrieval) + } + + @Test + fun ofRetrievalRoundtrip() { + val jsonMapper = jsonMapper() + val tool = + Tool.ofRetrieval( + Tool.Retrieval.builder() + .addRetrievalType(Tool.Retrieval.RetrievalType.VERTEX_AI_SEARCH) + .vertexAiSearchConfig( + Tool.Retrieval.VertexAiSearchConfig.builder() + .addDatastore("string") + .engine("engine") + .build() + ) + .build() + ) + + val roundtrippedTool = + jsonMapper.readValue(jsonMapper.writeValueAsString(tool), jacksonTypeRef()) + + assertThat(roundtrippedTool).isEqualTo(tool) + } + + enum class IncompatibleJsonShapeTestCase(val value: JsonValue) { + BOOLEAN(JsonValue.from(false)), + STRING(JsonValue.from("invalid")), + INTEGER(JsonValue.from(-1)), + FLOAT(JsonValue.from(3.14)), + ARRAY(JsonValue.from(listOf("invalid", "array"))), + } + + @ParameterizedTest + @EnumSource + fun incompatibleJsonShapeDeserializesToUnknown(testCase: IncompatibleJsonShapeTestCase) { + val tool = jsonMapper().convertValue(testCase.value, jacksonTypeRef()) + + val e = assertThrows { tool.validate() } + assertThat(e).hasMessageStartingWith("Unknown ") + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UrlCitationTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UrlCitationTest.kt new file mode 100644 index 00000000000..f78d6f0a555 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UrlCitationTest.kt @@ -0,0 +1,53 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UrlCitationTest { + + @Test + fun create() { + val urlCitation = + UrlCitation.builder().endIndex(0).startIndex(0).title("title").url("url").build() + + assertThat(urlCitation.endIndex()).contains(0) + assertThat(urlCitation.startIndex()).contains(0) + assertThat(urlCitation.title()).contains("title") + assertThat(urlCitation.url()).contains("url") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val urlCitation = + UrlCitation.builder().endIndex(0).startIndex(0).title("title").url("url").build() + + val roundtrippedUrlCitation = + jsonMapper.readValue( + jsonMapper.writeValueAsString(urlCitation), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUrlCitation).isEqualTo(urlCitation) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallArgumentsTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallArgumentsTest.kt new file mode 100644 index 00000000000..751aa410a36 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallArgumentsTest.kt @@ -0,0 +1,49 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UrlContextCallArgumentsTest { + + @Test + fun create() { + val urlContextCallArguments = UrlContextCallArguments.builder().addUrl("string").build() + + assertThat(urlContextCallArguments.urls().getOrNull()).containsExactly("string") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val urlContextCallArguments = UrlContextCallArguments.builder().addUrl("string").build() + + val roundtrippedUrlContextCallArguments = + jsonMapper.readValue( + jsonMapper.writeValueAsString(urlContextCallArguments), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUrlContextCallArguments).isEqualTo(urlContextCallArguments) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallStepTest.kt new file mode 100644 index 00000000000..48f6fa4483b --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextCallStepTest.kt @@ -0,0 +1,61 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UrlContextCallStepTest { + + @Test + fun create() { + val urlContextCallStep = + UrlContextCallStep.builder() + .id("id") + .arguments(UrlContextCallStep.Arguments.builder().addUrl("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(urlContextCallStep.id()).isEqualTo("id") + assertThat(urlContextCallStep.arguments()) + .isEqualTo(UrlContextCallStep.Arguments.builder().addUrl("string").build()) + assertThat(urlContextCallStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val urlContextCallStep = + UrlContextCallStep.builder() + .id("id") + .arguments(UrlContextCallStep.Arguments.builder().addUrl("string").build()) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedUrlContextCallStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(urlContextCallStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUrlContextCallStep).isEqualTo(urlContextCallStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultDeltaTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultDeltaTest.kt new file mode 100644 index 00000000000..69d48d8282e --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultDeltaTest.kt @@ -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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UrlContextResultDeltaTest { + + @Test + fun create() { + val urlContextResultDelta = + UrlContextResultDelta.builder() + .addResult( + UrlContextResult.builder() + .status(UrlContextResult.Status.SUCCESS) + .url("url") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(urlContextResultDelta.result()) + .containsExactly( + UrlContextResult.builder() + .status(UrlContextResult.Status.SUCCESS) + .url("url") + .build() + ) + assertThat(urlContextResultDelta.isError()).contains(true) + assertThat(urlContextResultDelta.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val urlContextResultDelta = + UrlContextResultDelta.builder() + .addResult( + UrlContextResult.builder() + .status(UrlContextResult.Status.SUCCESS) + .url("url") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedUrlContextResultDelta = + jsonMapper.readValue( + jsonMapper.writeValueAsString(urlContextResultDelta), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUrlContextResultDelta).isEqualTo(urlContextResultDelta) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultStepTest.kt new file mode 100644 index 00000000000..f90f4f3f347 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultStepTest.kt @@ -0,0 +1,79 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UrlContextResultStepTest { + + @Test + fun create() { + val urlContextResultStep = + UrlContextResultStep.builder() + .callId("call_id") + .addResult( + UrlContextResultStep.Result.builder() + .status(UrlContextResultStep.Result.Status.SUCCESS) + .url("url") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + assertThat(urlContextResultStep.callId()).isEqualTo("call_id") + assertThat(urlContextResultStep.result()) + .containsExactly( + UrlContextResultStep.Result.builder() + .status(UrlContextResultStep.Result.Status.SUCCESS) + .url("url") + .build() + ) + assertThat(urlContextResultStep.isError()).contains(true) + assertThat(urlContextResultStep.signature()).contains("U3RhaW5sZXNzIHJvY2tz") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val urlContextResultStep = + UrlContextResultStep.builder() + .callId("call_id") + .addResult( + UrlContextResultStep.Result.builder() + .status(UrlContextResultStep.Result.Status.SUCCESS) + .url("url") + .build() + ) + .isError(true) + .signature("U3RhaW5sZXNzIHJvY2tz") + .build() + + val roundtrippedUrlContextResultStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(urlContextResultStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUrlContextResultStep).isEqualTo(urlContextResultStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultTest.kt new file mode 100644 index 00000000000..1de455ffa44 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UrlContextResultTest.kt @@ -0,0 +1,51 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UrlContextResultTest { + + @Test + fun create() { + val urlContextResult = + UrlContextResult.builder().status(UrlContextResult.Status.SUCCESS).url("url").build() + + assertThat(urlContextResult.status()).contains(UrlContextResult.Status.SUCCESS) + assertThat(urlContextResult.url()).contains("url") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val urlContextResult = + UrlContextResult.builder().status(UrlContextResult.Status.SUCCESS).url("url").build() + + val roundtrippedUrlContextResult = + jsonMapper.readValue( + jsonMapper.writeValueAsString(urlContextResult), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUrlContextResult).isEqualTo(urlContextResult) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UsageTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UsageTest.kt new file mode 100644 index 00000000000..7168f78edd7 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UsageTest.kt @@ -0,0 +1,162 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UsageTest { + + @Test + fun create() { + val usage = + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + + assertThat(usage.cachedTokensByModality().getOrNull()) + .containsExactly( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + assertThat(usage.groundingToolCount().getOrNull()) + .containsExactly( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + assertThat(usage.inputTokensByModality().getOrNull()) + .containsExactly( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + assertThat(usage.outputTokensByModality().getOrNull()) + .containsExactly( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + assertThat(usage.toolUseTokensByModality().getOrNull()) + .containsExactly( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + assertThat(usage.totalCachedTokens()).contains(0) + assertThat(usage.totalInputTokens()).contains(0) + assertThat(usage.totalOutputTokens()).contains(0) + assertThat(usage.totalThoughtTokens()).contains(0) + assertThat(usage.totalTokens()).contains(0) + assertThat(usage.totalToolUseTokens()).contains(0) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val usage = + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + + val roundtrippedUsage = + jsonMapper.readValue(jsonMapper.writeValueAsString(usage), jacksonTypeRef()) + + assertThat(roundtrippedUsage).isEqualTo(usage) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/UserInputStepTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/UserInputStepTest.kt new file mode 100644 index 00000000000..101457a6656 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/UserInputStepTest.kt @@ -0,0 +1,94 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import kotlin.jvm.optionals.getOrNull +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class UserInputStepTest { + + @Test + fun create() { + val userInputStep = + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + assertThat(userInputStep.content().getOrNull()) + .containsExactly( + Content.ofText( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + ) + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val userInputStep = + UserInputStep.builder() + .addContent( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .build() + + val roundtrippedUserInputStep = + jsonMapper.readValue( + jsonMapper.writeValueAsString(userInputStep), + jacksonTypeRef(), + ) + + assertThat(roundtrippedUserInputStep).isEqualTo(userInputStep) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/VideoContentTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/VideoContentTest.kt new file mode 100644 index 00000000000..e2cddf9075a --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/VideoContentTest.kt @@ -0,0 +1,63 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class VideoContentTest { + + @Test + fun create() { + val videoContent = + VideoContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(VideoContent.MimeType.VIDEO_MP4) + .resolution(VideoContent.Resolution.LOW) + .uri("uri") + .build() + + assertThat(videoContent.data()).contains("U3RhaW5sZXNzIHJvY2tz") + assertThat(videoContent.mimeType()).contains(VideoContent.MimeType.VIDEO_MP4) + assertThat(videoContent.resolution()).contains(VideoContent.Resolution.LOW) + assertThat(videoContent.uri()).contains("uri") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val videoContent = + VideoContent.builder() + .data("U3RhaW5sZXNzIHJvY2tz") + .mimeType(VideoContent.MimeType.VIDEO_MP4) + .resolution(VideoContent.Resolution.LOW) + .uri("uri") + .build() + + val roundtrippedVideoContent = + jsonMapper.readValue( + jsonMapper.writeValueAsString(videoContent), + jacksonTypeRef(), + ) + + assertThat(roundtrippedVideoContent).isEqualTo(videoContent) + } +} diff --git a/src/test/java/com/google/genai/interactions/models/interactions/VideoResponseFormatTest.kt b/src/test/java/com/google/genai/interactions/models/interactions/VideoResponseFormatTest.kt new file mode 100644 index 00000000000..cd30c1ee1ab --- /dev/null +++ b/src/test/java/com/google/genai/interactions/models/interactions/VideoResponseFormatTest.kt @@ -0,0 +1,64 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.models.interactions + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.google.genai.interactions.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class VideoResponseFormatTest { + + @Test + fun create() { + val videoResponseFormat = + VideoResponseFormat.builder() + .aspectRatio(VideoResponseFormat.AspectRatio._16_9) + .delivery(VideoResponseFormat.Delivery.INLINE) + .duration("duration") + .gcsUri("gcsUri") + .build() + + assertThat(videoResponseFormat.aspectRatio()) + .contains(VideoResponseFormat.AspectRatio._16_9) + assertThat(videoResponseFormat.delivery()).contains(VideoResponseFormat.Delivery.INLINE) + assertThat(videoResponseFormat.duration()).contains("duration") + assertThat(videoResponseFormat.gcsUri()).contains("gcsUri") + } + + @Test + fun roundtrip() { + val jsonMapper = jsonMapper() + val videoResponseFormat = + VideoResponseFormat.builder() + .aspectRatio(VideoResponseFormat.AspectRatio._16_9) + .delivery(VideoResponseFormat.Delivery.INLINE) + .duration("duration") + .gcsUri("gcsUri") + .build() + + val roundtrippedVideoResponseFormat = + jsonMapper.readValue( + jsonMapper.writeValueAsString(videoResponseFormat), + jacksonTypeRef(), + ) + + assertThat(roundtrippedVideoResponseFormat).isEqualTo(videoResponseFormat) + } +} diff --git a/src/test/java/com/google/genai/interactions/services/ErrorHandlingTest.kt b/src/test/java/com/google/genai/interactions/services/ErrorHandlingTest.kt new file mode 100644 index 00000000000..526fd002a03 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/services/ErrorHandlingTest.kt @@ -0,0 +1,2844 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.services + +import com.google.genai.interactions.client.GeminiNextGenApiClient +import com.google.genai.interactions.client.okhttp.GeminiNextGenApiOkHttpClient +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.core.http.Headers +import com.google.genai.interactions.core.jsonMapper +import com.google.genai.interactions.errors.BadRequestException +import com.google.genai.interactions.errors.GeminiNextGenApiException +import com.google.genai.interactions.errors.InternalServerException +import com.google.genai.interactions.errors.NotFoundException +import com.google.genai.interactions.errors.PermissionDeniedException +import com.google.genai.interactions.errors.RateLimitException +import com.google.genai.interactions.errors.UnauthorizedException +import com.google.genai.interactions.errors.UnexpectedStatusCodeException +import com.google.genai.interactions.errors.UnprocessableEntityException +import com.google.genai.interactions.models.interactions.AudioResponseFormat +import com.google.genai.interactions.models.interactions.CreateModelInteractionParams +import com.google.genai.interactions.models.interactions.Function +import com.google.genai.interactions.models.interactions.GenerationConfig +import com.google.genai.interactions.models.interactions.ImageConfig +import com.google.genai.interactions.models.interactions.InteractionCreateParams +import com.google.genai.interactions.models.interactions.Model +import com.google.genai.interactions.models.interactions.SpeechConfig +import com.google.genai.interactions.models.interactions.TextContent +import com.google.genai.interactions.models.interactions.ThinkingLevel +import com.google.genai.interactions.models.interactions.ToolChoiceType +import com.google.genai.interactions.models.interactions.UrlCitation +import com.google.genai.interactions.models.interactions.Usage +import com.github.tomakehurst.wiremock.client.WireMock.anyUrl +import com.github.tomakehurst.wiremock.client.WireMock.post +import com.github.tomakehurst.wiremock.client.WireMock.status +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo +import com.github.tomakehurst.wiremock.junit5.WireMockTest +import java.time.OffsetDateTime +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.entry +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.api.parallel.ResourceLock + +@WireMockTest +@ResourceLock("https://github.com/wiremock/wiremock/issues/169") +internal class ErrorHandlingTest { + + companion object { + + private val ERROR_JSON: JsonValue = JsonValue.from(mapOf("errorProperty" to "42")) + + private val ERROR_JSON_BYTES: ByteArray = jsonMapper().writeValueAsBytes(ERROR_JSON) + + private const val HEADER_NAME: String = "Error-Header" + + private const val HEADER_VALUE: String = "42" + + private const val NOT_JSON: String = "Not JSON" + } + + private lateinit var client: GeminiNextGenApiClient + + @BeforeEach + fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { + client = + GeminiNextGenApiOkHttpClient.builder() + .baseUrl(wmRuntimeInfo.httpBaseUrl) + .apiKey("My API Key") + .build() + } + + @Test + fun interactionsCreate400() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(400).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(400) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate400WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(400).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(400) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate401() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(401).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(401) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate401WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(401).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(401) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate403() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(403).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(403) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate403WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(403).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(403) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate404() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(404).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(404) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate404WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(404).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(404) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate422() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(422).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(422) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate422WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(422).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(422) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate429() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(429).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(429) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate429WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(429).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(429) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate500() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(500).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(500) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate500WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(500).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(500) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate999() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn( + status(999).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(999) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreate999WithRawResponse() { + val interactionService = client.interactions().withRawResponse() + stubFor( + post(anyUrl()) + .willReturn( + status(999).withHeader(HEADER_NAME, HEADER_VALUE).withBody(ERROR_JSON_BYTES) + ) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e.statusCode()).isEqualTo(999) + assertThat(e.headers().toMap()).contains(entry(HEADER_NAME, listOf(HEADER_VALUE))) + assertThat(e.body()).isEqualTo(ERROR_JSON) + } + + @Test + fun interactionsCreateInvalidJsonBody() { + val interactionService = client.interactions() + stubFor( + post(anyUrl()) + .willReturn(status(200).withHeader(HEADER_NAME, HEADER_VALUE).withBody(NOT_JSON)) + ) + + val e = + assertThrows { + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat + .InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType( + AudioResponseFormat.MimeType.AUDIO_MP3 + ) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality( + CreateModelInteractionParams.ResponseModality.TEXT + ) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality( + Usage.CachedTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality( + Usage.OutputTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality( + Usage.ToolUseTokensByModality.Modality.TEXT + ) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + } + + assertThat(e).hasMessage("Error reading response") + } + + private fun Headers.toMap(): Map> = + mutableMapOf>().also { map -> + names().forEach { map[it] = values(it) } + } +} diff --git a/src/test/java/com/google/genai/interactions/services/ServiceParamsTest.kt b/src/test/java/com/google/genai/interactions/services/ServiceParamsTest.kt new file mode 100644 index 00000000000..0d988bfb0d8 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/services/ServiceParamsTest.kt @@ -0,0 +1,214 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.services + +import com.google.genai.interactions.client.GeminiNextGenApiClient +import com.google.genai.interactions.client.okhttp.GeminiNextGenApiOkHttpClient +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.models.interactions.AudioResponseFormat +import com.google.genai.interactions.models.interactions.CreateModelInteractionParams +import com.google.genai.interactions.models.interactions.Function +import com.google.genai.interactions.models.interactions.GenerationConfig +import com.google.genai.interactions.models.interactions.ImageConfig +import com.google.genai.interactions.models.interactions.InteractionCreateParams +import com.google.genai.interactions.models.interactions.Model +import com.google.genai.interactions.models.interactions.SpeechConfig +import com.google.genai.interactions.models.interactions.TextContent +import com.google.genai.interactions.models.interactions.ThinkingLevel +import com.google.genai.interactions.models.interactions.ToolChoiceType +import com.google.genai.interactions.models.interactions.UrlCitation +import com.google.genai.interactions.models.interactions.Usage +import com.github.tomakehurst.wiremock.client.WireMock.anyUrl +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.client.WireMock.ok +import com.github.tomakehurst.wiremock.client.WireMock.post +import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.verify +import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo +import com.github.tomakehurst.wiremock.junit5.WireMockTest +import java.time.OffsetDateTime +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.parallel.ResourceLock + +@WireMockTest +@ResourceLock("https://github.com/wiremock/wiremock/issues/169") +internal class ServiceParamsTest { + + private lateinit var client: GeminiNextGenApiClient + + @BeforeEach + fun beforeEach(wmRuntimeInfo: WireMockRuntimeInfo) { + client = + GeminiNextGenApiOkHttpClient.builder() + .baseUrl(wmRuntimeInfo.httpBaseUrl) + .apiKey("My API Key") + .build() + } + + @Disabled("Mock server tests are disabled") + @Test + fun create() { + val interactionService = client.interactions() + stubFor(post(anyUrl()).willReturn(ok("{}"))) + + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .putAdditionalHeader("Secret-Header", "42") + .putAdditionalQueryParam("secret_query_param", "42") + .build() + ) + + verify( + postRequestedFor(anyUrl()) + .withHeader("Secret-Header", equalTo("42")) + .withQueryParam("secret_query_param", equalTo("42")) + ) + } +} diff --git a/src/test/java/com/google/genai/interactions/services/async/InteractionServiceAsyncTest.kt b/src/test/java/com/google/genai/interactions/services/async/InteractionServiceAsyncTest.kt new file mode 100644 index 00000000000..095f832ef4d --- /dev/null +++ b/src/test/java/com/google/genai/interactions/services/async/InteractionServiceAsyncTest.kt @@ -0,0 +1,408 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.services.async + +import com.google.genai.interactions.client.okhttp.GeminiNextGenApiOkHttpClientAsync +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.models.interactions.AudioResponseFormat +import com.google.genai.interactions.models.interactions.CreateModelInteractionParams +import com.google.genai.interactions.models.interactions.Function +import com.google.genai.interactions.models.interactions.GenerationConfig +import com.google.genai.interactions.models.interactions.ImageConfig +import com.google.genai.interactions.models.interactions.InteractionCancelParams +import com.google.genai.interactions.models.interactions.InteractionCreateParams +import com.google.genai.interactions.models.interactions.InteractionDeleteParams +import com.google.genai.interactions.models.interactions.InteractionGetParams +import com.google.genai.interactions.models.interactions.Model +import com.google.genai.interactions.models.interactions.SpeechConfig +import com.google.genai.interactions.models.interactions.TextContent +import com.google.genai.interactions.models.interactions.ThinkingLevel +import com.google.genai.interactions.models.interactions.ToolChoiceType +import com.google.genai.interactions.models.interactions.UrlCitation +import com.google.genai.interactions.models.interactions.Usage +import java.time.OffsetDateTime +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +internal class InteractionServiceAsyncTest { + + @Disabled("Mock server tests are disabled") + @Test + fun create() { + val client = GeminiNextGenApiOkHttpClientAsync.builder().apiKey("My API Key").build() + val interactionServiceAsync = client.interactions() + + val interactionFuture = + interactionServiceAsync.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + + val interaction = interactionFuture.get() + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun createStreaming() { + val client = GeminiNextGenApiOkHttpClientAsync.builder().apiKey("My API Key").build() + val interactionServiceAsync = client.interactions() + + val interactionStreamResponse = + interactionServiceAsync.createStreaming( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + + val onCompleteFuture = + interactionStreamResponse + .subscribe { interaction -> interaction.validate() } + .onCompleteFuture() + onCompleteFuture.get() + } + + @Disabled("Mock server tests are disabled") + @Test + fun delete() { + val client = GeminiNextGenApiOkHttpClientAsync.builder().apiKey("My API Key").build() + val interactionServiceAsync = client.interactions() + + val interactionFuture = + interactionServiceAsync.delete( + InteractionDeleteParams.builder().apiVersion("api_version").id("id").build() + ) + + val interaction = interactionFuture.get() + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun cancel() { + val client = GeminiNextGenApiOkHttpClientAsync.builder().apiKey("My API Key").build() + val interactionServiceAsync = client.interactions() + + val interactionFuture = + interactionServiceAsync.cancel( + InteractionCancelParams.builder().apiVersion("api_version").id("id").build() + ) + + val interaction = interactionFuture.get() + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun get() { + val client = GeminiNextGenApiOkHttpClientAsync.builder().apiKey("My API Key").build() + val interactionServiceAsync = client.interactions() + + val interactionFuture = + interactionServiceAsync.get( + InteractionGetParams.builder() + .apiVersion("api_version") + .id("id") + .includeInput(true) + .lastEventId("last_event_id") + .build() + ) + + val interaction = interactionFuture.get() + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun getStreaming() { + val client = GeminiNextGenApiOkHttpClientAsync.builder().apiKey("My API Key").build() + val interactionServiceAsync = client.interactions() + + val interactionStreamResponse = + interactionServiceAsync.getStreaming( + InteractionGetParams.builder() + .apiVersion("api_version") + .id("id") + .includeInput(true) + .lastEventId("last_event_id") + .build() + ) + + val onCompleteFuture = + interactionStreamResponse + .subscribe { interaction -> interaction.validate() } + .onCompleteFuture() + onCompleteFuture.get() + } +} diff --git a/src/test/java/com/google/genai/interactions/services/blocking/InteractionServiceTest.kt b/src/test/java/com/google/genai/interactions/services/blocking/InteractionServiceTest.kt new file mode 100644 index 00000000000..91fb1dad8b6 --- /dev/null +++ b/src/test/java/com/google/genai/interactions/services/blocking/InteractionServiceTest.kt @@ -0,0 +1,400 @@ +/* +* 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. +*/ + +// File generated from our OpenAPI spec by Stainless. + +package com.google.genai.interactions.services.blocking + +import com.google.genai.interactions.client.okhttp.GeminiNextGenApiOkHttpClient +import com.google.genai.interactions.core.JsonValue +import com.google.genai.interactions.models.interactions.AudioResponseFormat +import com.google.genai.interactions.models.interactions.CreateModelInteractionParams +import com.google.genai.interactions.models.interactions.Function +import com.google.genai.interactions.models.interactions.GenerationConfig +import com.google.genai.interactions.models.interactions.ImageConfig +import com.google.genai.interactions.models.interactions.InteractionCancelParams +import com.google.genai.interactions.models.interactions.InteractionCreateParams +import com.google.genai.interactions.models.interactions.InteractionDeleteParams +import com.google.genai.interactions.models.interactions.InteractionGetParams +import com.google.genai.interactions.models.interactions.Model +import com.google.genai.interactions.models.interactions.SpeechConfig +import com.google.genai.interactions.models.interactions.TextContent +import com.google.genai.interactions.models.interactions.ThinkingLevel +import com.google.genai.interactions.models.interactions.ToolChoiceType +import com.google.genai.interactions.models.interactions.UrlCitation +import com.google.genai.interactions.models.interactions.Usage +import java.time.OffsetDateTime +import org.junit.jupiter.api.Disabled +import org.junit.jupiter.api.Test + +internal class InteractionServiceTest { + + @Disabled("Mock server tests are disabled") + @Test + fun create() { + val client = GeminiNextGenApiOkHttpClient.builder().apiKey("My API Key").build() + val interactionService = client.interactions() + + val interaction = + interactionService.create( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun createStreaming() { + val client = GeminiNextGenApiOkHttpClient.builder().apiKey("My API Key").build() + val interactionService = client.interactions() + + val interactionStreamResponse = + interactionService.createStreaming( + InteractionCreateParams.builder() + .apiVersion("api_version") + .body( + CreateModelInteractionParams.builder() + .input( + TextContent.builder() + .text("text") + .addAnnotation( + UrlCitation.builder() + .endIndex(0) + .startIndex(0) + .title("title") + .url("url") + .build() + ) + .build() + ) + .model(Model.GEMINI_2_5_COMPUTER_USE_PREVIEW_10_2025) + .id("id") + .background(true) + .created(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .generationConfig( + GenerationConfig.builder() + .imageConfig( + ImageConfig.builder() + .aspectRatio(ImageConfig.AspectRatio._1_1) + .imageSize(ImageConfig.ImageSize._1_K) + .build() + ) + .maxOutputTokens(0) + .seed(0) + .addSpeechConfig( + SpeechConfig.builder() + .language("language") + .speaker("speaker") + .voice("voice") + .build() + ) + .addStopSequence("string") + .temperature(0.0f) + .thinkingLevel(ThinkingLevel.MINIMAL) + .thinkingSummaries(GenerationConfig.ThinkingSummaries.AUTO) + .toolChoice(ToolChoiceType.AUTO) + .topP(0.0f) + .build() + ) + .previousInteractionId("previous_interaction_id") + .responseFormatOfList( + listOf( + CreateModelInteractionParams.ResponseFormat.InnerResponseFormat + .ofAudio( + AudioResponseFormat.builder() + .bitRate(0) + .delivery(AudioResponseFormat.Delivery.INLINE) + .mimeType(AudioResponseFormat.MimeType.AUDIO_MP3) + .sampleRate(0) + .build() + ) + ) + ) + .responseMimeType("response_mime_type") + .addResponseModality(CreateModelInteractionParams.ResponseModality.TEXT) + .role("role") + .serviceTier(CreateModelInteractionParams.ServiceTier.FLEX) + .status(CreateModelInteractionParams.Status.IN_PROGRESS) + .store(true) + .stream(true) + .systemInstruction("system_instruction") + .addTool( + Function.builder() + .description("description") + .name("name") + .parameters(JsonValue.from(mapOf())) + .build() + ) + .updated(OffsetDateTime.parse("2019-12-27T18:11:19.117Z")) + .usage( + Usage.builder() + .addCachedTokensByModality( + Usage.CachedTokensByModality.builder() + .modality(Usage.CachedTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addGroundingToolCount( + Usage.GroundingToolCount.builder() + .count(0) + .type(Usage.GroundingToolCount.Type.GOOGLE_SEARCH) + .build() + ) + .addInputTokensByModality( + Usage.InputTokensByModality.builder() + .modality(Usage.InputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addOutputTokensByModality( + Usage.OutputTokensByModality.builder() + .modality(Usage.OutputTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .addToolUseTokensByModality( + Usage.ToolUseTokensByModality.builder() + .modality(Usage.ToolUseTokensByModality.Modality.TEXT) + .tokens(0) + .build() + ) + .totalCachedTokens(0) + .totalInputTokens(0) + .totalOutputTokens(0) + .totalThoughtTokens(0) + .totalTokens(0) + .totalToolUseTokens(0) + .build() + ) + .webhookConfig( + CreateModelInteractionParams.WebhookConfig.builder() + .addUris("string") + .userMetadata( + CreateModelInteractionParams.WebhookConfig.UserMetadata + .builder() + .putAdditionalProperty("foo", JsonValue.from("bar")) + .build() + ) + .build() + ) + .build() + ) + .build() + ) + + interactionStreamResponse.use { + interactionStreamResponse.stream().forEach { interaction -> interaction.validate() } + } + } + + @Disabled("Mock server tests are disabled") + @Test + fun delete() { + val client = GeminiNextGenApiOkHttpClient.builder().apiKey("My API Key").build() + val interactionService = client.interactions() + + val interaction = + interactionService.delete( + InteractionDeleteParams.builder().apiVersion("api_version").id("id").build() + ) + + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun cancel() { + val client = GeminiNextGenApiOkHttpClient.builder().apiKey("My API Key").build() + val interactionService = client.interactions() + + val interaction = + interactionService.cancel( + InteractionCancelParams.builder().apiVersion("api_version").id("id").build() + ) + + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun get() { + val client = GeminiNextGenApiOkHttpClient.builder().apiKey("My API Key").build() + val interactionService = client.interactions() + + val interaction = + interactionService.get( + InteractionGetParams.builder() + .apiVersion("api_version") + .id("id") + .includeInput(true) + .lastEventId("last_event_id") + .build() + ) + + interaction.validate() + } + + @Disabled("Mock server tests are disabled") + @Test + fun getStreaming() { + val client = GeminiNextGenApiOkHttpClient.builder().apiKey("My API Key").build() + val interactionService = client.interactions() + + val interactionStreamResponse = + interactionService.getStreaming( + InteractionGetParams.builder() + .apiVersion("api_version") + .id("id") + .includeInput(true) + .lastEventId("last_event_id") + .build() + ) + + interactionStreamResponse.use { + interactionStreamResponse.stream().forEach { interaction -> interaction.validate() } + } + } +}