Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,12 @@
<artifactId>kotlin-reflect</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
<!-- interactions SDK dependencies -->
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
Expand All @@ -190,6 +196,37 @@
<artifactId>error_prone_annotations</artifactId>
<version>2.36.0</version>
</dependency>
<!-- Missing test dependencies for Stainless tests -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>3.5.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito.kotlin</groupId>
<artifactId>mockito-kotlin</artifactId>
<version>5.4.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -244,6 +281,9 @@
<configuration>
<reportFormat>plain</reportFormat>
<statelessTestsetInfoReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5StatelessTestsetInfoTreeReporter" />
<excludes>
<exclude>**/ProGuardCompatibilityTest.kt</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
Expand Down Expand Up @@ -345,7 +385,7 @@
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
<jvmTarget>11</jvmTarget>
<args>
<arg>-Xjvm-default=all</arg>
</args>
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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<HttpClient>()

@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)
}
}
133 changes: 133 additions & 0 deletions src/test/java/com/google/genai/interactions/core/ObjectMappersTest.kt
Original file line number Diff line number Diff line change
@@ -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<Boolean>) {

@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<OffsetDateTime>(json)

assertThat(offsetDateTime).isEqualTo(testCase.expectedOffsetDateTime)
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
Loading
Loading