diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamRedactionTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamRedactionTest.kt new file mode 100644 index 000000000..cd5fded7d --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamRedactionTest.kt @@ -0,0 +1,199 @@ +package com.openai.models.beta.agents.vaults.credentials + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.JsonMissing +import com.openai.core.JsonValue +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +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.ValueSource + +internal class CredentialAuthCreateParamRedactionTest { + @ParameterizedTest + @ValueSource( + strings = + ["{\"token\":\"fake-malformed-secret\"}", "[\"fake-malformed-secret\"]", "123", "true"] + ) + fun environmentVariableValidationRedactsMalformedSecret(secretJson: String) { + val mapper = jsonMapper() + val raw = mapper.readValue(secretJson, jacksonTypeRef()) + val original = + CredentialAuthCreateParam.EnvironmentVariable.builder() + .networkingUnrestricted() + .secretName("SDK_TEST_KEY") + .secretValue(raw) + .build() + val serialized = + mapper.writeValueAsString(CredentialAuthCreateParam.ofEnvironmentVariable(original)) + val auth = mapper.readValue(serialized, jacksonTypeRef()) + val params = + CredentialCreateParams.builder() + .vaultId("vault_test") + .name("Test credential") + .auth(auth) + .build() + + for (environmentVariable in listOf(original, auth.asEnvironmentVariable())) { + val accessorError = + assertThrows { environmentVariable.secretValue() } + val validationError = + assertThrows { environmentVariable.validate() } + for (error in listOf(accessorError, validationError)) { + assertThat(error) + .hasMessage("`secret_value` is invalid, received [REDACTED]") + .hasNoCause() + } + assertThat(environmentVariable._secretValue()).isEqualTo(raw) + assertThat(environmentVariable.isValid()).isFalse() + } + for (error in + listOf( + assertThrows { auth.validate() }, + assertThrows { params._body().validate() }, + )) { + assertThat(error) + .hasMessage("`secret_value` is invalid, received [REDACTED]") + .hasNoCause() + } + assertThat(mapper.readTree(mapper.writeValueAsString(auth))) + .isEqualTo(mapper.readTree(serialized)) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun environmentVariablePreservesMissingAndNullSecretDiagnostics(isNull: Boolean) { + val raw = if (isNull) JsonValue.from(null) else JsonMissing.of() + val environmentVariable = + CredentialAuthCreateParam.EnvironmentVariable.builder() + .networkingUnrestricted() + .secretName("SDK_TEST_KEY") + .secretValue(raw) + .build() + val expected = if (isNull) "`secret_value` is null" else "`secret_value` is not set" + + assertThat(assertThrows { environmentVariable.secretValue() }) + .hasMessage(expected) + .hasNoCause() + assertThat(assertThrows { environmentVariable.validate() }) + .hasMessage(expected) + .hasNoCause() + assertThat(environmentVariable._secretValue()).isEqualTo(raw) + } + + @ParameterizedTest + @ValueSource(strings = ["", "future_environment_variable"]) + fun unknownStringsRedactSecretWithoutChangingSerialization(type: String) { + val secret = "fake-unknown-environment-secret" + val fields = + mutableMapOf( + "secret_value" to JsonValue.from(secret), + "future_field" to JsonValue.from("preserved"), + ) + if (type.isNotEmpty()) { + fields["type"] = JsonValue.from(type) + } + val raw = JsonValue.from(fields) + val mapper = jsonMapper() + val original = mapper.writeValueAsString(raw) + val auth = mapper.readValue(original, jacksonTypeRef()) + val params = + CredentialCreateParams.builder() + .vaultId("vault_test") + .name("Test credential") + .auth(auth) + .build() + + assertThat(auth.environmentVariable()).isEmpty + for (value in listOf(auth, params)) { + assertThat(value.toString()) + .doesNotContain(secret) + .contains("[REDACTED]", "future_field", "preserved") + } + assertThat(auth._json()).contains(raw) + assertThat(params._body().auth()._json()).contains(raw) + assertThat(mapper.readTree(mapper.writeValueAsString(auth))) + .isEqualTo(mapper.readTree(original)) + } + + @ParameterizedTest + @ValueSource( + strings = + [ + "\"fake-additional-secret\"", + "{\"token\":\"fake-additional-secret\"}", + "[\"fake-additional-secret\"]", + ] + ) + fun environmentVariableStringsRedactAdditionalSecretWithoutChangingSerialization( + secretJson: String + ) { + val mapper = jsonMapper() + val raw = mapper.readValue(secretJson, jacksonTypeRef()) + val secret = "fake-typed-secret" + val environmentVariable = + CredentialAuthCreateParam.EnvironmentVariable.builder() + .networkingUnrestricted() + .secretName("SDK_TEST_KEY") + .secretValue(secret) + .putAdditionalProperty("secret_value", raw) + .putAdditionalProperty("future_field", JsonValue.from("preserved")) + .build() + val auth = CredentialAuthCreateParam.ofEnvironmentVariable(environmentVariable) + val params = + CredentialCreateParams.builder() + .vaultId("vault_test") + .name("Test credential") + .auth(auth) + .build() + val serialized = mapper.writeValueAsString(auth) + + for (value in listOf(environmentVariable, auth, params)) { + assertThat(value.toString()) + .doesNotContain(secret, "fake-additional-secret") + .contains( + "secretValue=[REDACTED]", + "secret_value", + "[REDACTED]", + "future_field", + "preserved", + ) + } + assertThat(environmentVariable.secretValue()).isEqualTo(secret) + assertThat(environmentVariable._additionalProperties()["secret_value"]).isEqualTo(raw) + assertThat(params._body().auth().asEnvironmentVariable()).isEqualTo(environmentVariable) + assertThat(mapper.writeValueAsString(auth)).isEqualTo(serialized) + assertThat(serialized).contains(secret, "fake-additional-secret") + assertThat(mapper.readTree(serialized).get("secret_value")) + .isEqualTo(mapper.readTree(secretJson)) + } + + @Test + fun environmentVariableStringsRedactSecretWithoutChangingSerialization() { + val secret = "fake-environment-secret-do-not-log" + val environmentVariable = + CredentialAuthCreateParam.EnvironmentVariable.builder() + .networkingUnrestricted() + .secretName("SDK_TEST_KEY") + .secretValue(secret) + .build() + val auth = CredentialAuthCreateParam.ofEnvironmentVariable(environmentVariable) + val params = + CredentialCreateParams.builder() + .vaultId("vault_test") + .name("Test credential") + .auth(auth) + .build() + + for (value in listOf(environmentVariable, auth, params)) { + assertThat(value.toString()).doesNotContain(secret).contains("secretValue=[REDACTED]") + } + assertThat(environmentVariable.secretValue()).isEqualTo(secret) + val mapper = jsonMapper() + assertThat(mapper.readTree(mapper.writeValueAsString(auth)).get("secret_value").asText()) + .isEqualTo(secret) + assertThat(params._body().auth().asEnvironmentVariable().secretValue()).isEqualTo(secret) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamTest.kt index 1dfabd2c2..f0bb4457b 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthCreateParamTest.kt @@ -3,7 +3,6 @@ package com.openai.models.beta.agents.vaults.credentials import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import com.openai.core.JsonMissing import com.openai.core.JsonValue import com.openai.core.jsonMapper import com.openai.errors.OpenAIInvalidDataException @@ -12,195 +11,9 @@ 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 -import org.junit.jupiter.params.provider.ValueSource internal class CredentialAuthCreateParamTest { - @ParameterizedTest - @ValueSource( - strings = - ["{\"token\":\"fake-malformed-secret\"}", "[\"fake-malformed-secret\"]", "123", "true"] - ) - fun environmentVariableValidationRedactsMalformedSecret(secretJson: String) { - val mapper = jsonMapper() - val raw = mapper.readValue(secretJson, jacksonTypeRef()) - val original = - CredentialAuthCreateParam.EnvironmentVariable.builder() - .networkingUnrestricted() - .secretName("SDK_TEST_KEY") - .secretValue(raw) - .build() - val serialized = - mapper.writeValueAsString(CredentialAuthCreateParam.ofEnvironmentVariable(original)) - val auth = mapper.readValue(serialized, jacksonTypeRef()) - val params = - CredentialCreateParams.builder() - .vaultId("vault_test") - .name("Test credential") - .auth(auth) - .build() - - for (environmentVariable in listOf(original, auth.asEnvironmentVariable())) { - val accessorError = - assertThrows { environmentVariable.secretValue() } - val validationError = - assertThrows { environmentVariable.validate() } - for (error in listOf(accessorError, validationError)) { - assertThat(error) - .hasMessage("`secret_value` is invalid, received [REDACTED]") - .hasNoCause() - } - assertThat(environmentVariable._secretValue()).isEqualTo(raw) - assertThat(environmentVariable.isValid()).isFalse() - } - for (error in - listOf( - assertThrows { auth.validate() }, - assertThrows { params._body().validate() }, - )) { - assertThat(error) - .hasMessage("`secret_value` is invalid, received [REDACTED]") - .hasNoCause() - } - assertThat(mapper.readTree(mapper.writeValueAsString(auth))) - .isEqualTo(mapper.readTree(serialized)) - } - - @ParameterizedTest - @ValueSource(booleans = [false, true]) - fun environmentVariablePreservesMissingAndNullSecretDiagnostics(isNull: Boolean) { - val raw = if (isNull) JsonValue.from(null) else JsonMissing.of() - val environmentVariable = - CredentialAuthCreateParam.EnvironmentVariable.builder() - .networkingUnrestricted() - .secretName("SDK_TEST_KEY") - .secretValue(raw) - .build() - val expected = if (isNull) "`secret_value` is null" else "`secret_value` is not set" - - assertThat(assertThrows { environmentVariable.secretValue() }) - .hasMessage(expected) - .hasNoCause() - assertThat(assertThrows { environmentVariable.validate() }) - .hasMessage(expected) - .hasNoCause() - assertThat(environmentVariable._secretValue()).isEqualTo(raw) - } - - @ParameterizedTest - @ValueSource(strings = ["", "future_environment_variable"]) - fun unknownStringsRedactSecretWithoutChangingSerialization(type: String) { - val secret = "fake-unknown-environment-secret" - val fields = - mutableMapOf( - "secret_value" to JsonValue.from(secret), - "future_field" to JsonValue.from("preserved"), - ) - if (type.isNotEmpty()) { - fields["type"] = JsonValue.from(type) - } - val raw = JsonValue.from(fields) - val mapper = jsonMapper() - val original = mapper.writeValueAsString(raw) - val auth = mapper.readValue(original, jacksonTypeRef()) - val params = - CredentialCreateParams.builder() - .vaultId("vault_test") - .name("Test credential") - .auth(auth) - .build() - - assertThat(auth.environmentVariable()).isEmpty - for (value in listOf(auth, params)) { - assertThat(value.toString()) - .doesNotContain(secret) - .contains("[REDACTED]", "future_field", "preserved") - } - assertThat(auth._json()).contains(raw) - assertThat(params._body().auth()._json()).contains(raw) - assertThat(mapper.readTree(mapper.writeValueAsString(auth))) - .isEqualTo(mapper.readTree(original)) - } - - @ParameterizedTest - @ValueSource( - strings = - [ - "\"fake-additional-secret\"", - "{\"token\":\"fake-additional-secret\"}", - "[\"fake-additional-secret\"]", - ] - ) - fun environmentVariableStringsRedactAdditionalSecretWithoutChangingSerialization( - secretJson: String - ) { - val mapper = jsonMapper() - val raw = mapper.readValue(secretJson, jacksonTypeRef()) - val secret = "fake-typed-secret" - val environmentVariable = - CredentialAuthCreateParam.EnvironmentVariable.builder() - .networkingUnrestricted() - .secretName("SDK_TEST_KEY") - .secretValue(secret) - .putAdditionalProperty("secret_value", raw) - .putAdditionalProperty("future_field", JsonValue.from("preserved")) - .build() - val auth = CredentialAuthCreateParam.ofEnvironmentVariable(environmentVariable) - val params = - CredentialCreateParams.builder() - .vaultId("vault_test") - .name("Test credential") - .auth(auth) - .build() - val serialized = mapper.writeValueAsString(auth) - - for (value in listOf(environmentVariable, auth, params)) { - assertThat(value.toString()) - .doesNotContain(secret, "fake-additional-secret") - .contains( - "secretValue=[REDACTED]", - "secret_value", - "[REDACTED]", - "future_field", - "preserved", - ) - } - assertThat(environmentVariable.secretValue()).isEqualTo(secret) - assertThat(environmentVariable._additionalProperties()["secret_value"]).isEqualTo(raw) - assertThat(params._body().auth().asEnvironmentVariable()).isEqualTo(environmentVariable) - assertThat(mapper.writeValueAsString(auth)).isEqualTo(serialized) - assertThat(serialized).contains(secret, "fake-additional-secret") - assertThat(mapper.readTree(serialized).get("secret_value")) - .isEqualTo(mapper.readTree(secretJson)) - } - - @Test - fun environmentVariableStringsRedactSecretWithoutChangingSerialization() { - val secret = "fake-environment-secret-do-not-log" - val environmentVariable = - CredentialAuthCreateParam.EnvironmentVariable.builder() - .networkingUnrestricted() - .secretName("SDK_TEST_KEY") - .secretValue(secret) - .build() - val auth = CredentialAuthCreateParam.ofEnvironmentVariable(environmentVariable) - val params = - CredentialCreateParams.builder() - .vaultId("vault_test") - .name("Test credential") - .auth(auth) - .build() - - for (value in listOf(environmentVariable, auth, params)) { - assertThat(value.toString()).doesNotContain(secret).contains("secretValue=[REDACTED]") - } - assertThat(environmentVariable.secretValue()).isEqualTo(secret) - val mapper = jsonMapper() - assertThat(mapper.readTree(mapper.writeValueAsString(auth)).get("secret_value").asText()) - .isEqualTo(secret) - assertThat(params._body().auth().asEnvironmentVariable().secretValue()).isEqualTo(secret) - } - @Test fun ofMcpOAuth() { val mcpOauth = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRedactionTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRedactionTest.kt new file mode 100644 index 000000000..af5df254d --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRedactionTest.kt @@ -0,0 +1,130 @@ +package com.openai.models.beta.agents.vaults.credentials + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.JsonValue +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +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.ValueSource + +internal class CredentialAuthRedactionTest { + @ParameterizedTest + @ValueSource(strings = ["", "future_environment_variable"]) + fun unknownStringsRedactSecretWithoutChangingSerialization(type: String) { + val secret = "fake-unknown-response-secret" + val fields = + mutableMapOf( + "secret_value" to JsonValue.from(secret), + "future_field" to JsonValue.from("preserved"), + ) + if (type.isNotEmpty()) { + fields["type"] = JsonValue.from(type) + } + val raw = JsonValue.from(fields) + val mapper = jsonMapper() + val original = mapper.writeValueAsString(raw) + val auth = mapper.readValue(original, jacksonTypeRef()) + val credential = + Credential.builder() + .id("credential_test") + .auth(auth) + .createdAt(0L) + .metadata(Credential.Metadata.builder().build()) + .name("Test credential") + .updatedAt(0L) + .vaultId("vault_test") + .build() + + assertThat(auth.environmentVariable()).isEmpty + for (value in listOf(auth, credential)) { + assertThat(value.toString()) + .doesNotContain(secret) + .contains("[REDACTED]", "future_field", "preserved") + } + val error = assertThrows { auth.validate() } + assertThat(error).hasNoCause() + assertThat(error).hasMessage("Unknown CredentialAuth") + assertThat(auth._json()).contains(raw) + assertThat(credential.auth()._json()).contains(raw) + assertThat(mapper.readTree(mapper.writeValueAsString(auth))) + .isEqualTo(mapper.readTree(original)) + } + + @ParameterizedTest + @ValueSource(strings = ["{}", "{\"type\":\"future\",\"future_field\":\"preserved\"}"]) + fun unknownDiagnosticsOmitPayloadWithoutChangingToString(json: String) { + val mapper = jsonMapper() + val raw = mapper.readValue(json, jacksonTypeRef()) + val auth = mapper.readValue(json, jacksonTypeRef()) + + assertThat(auth.toString()).isEqualTo("CredentialAuth{_unknown=$raw}") + assertThat(assertThrows { auth.validate() }) + .hasMessage("Unknown CredentialAuth") + } + + @ParameterizedTest + @ValueSource( + strings = + [ + "\"fake-response-secret\"", + "{\"token\":\"fake-response-secret\"}", + "[\"fake-response-secret\"]", + ] + ) + fun environmentVariableStringsRedactUnexpectedSecretWithoutChangingSerialization( + secretJson: String + ) { + val mapper = jsonMapper() + val raw = mapper.readValue(secretJson, jacksonTypeRef()) + val original = + CredentialAuth.EnvironmentVariable.builder() + .networkingUnrestricted() + .secretName("SDK_TEST_KEY") + .putAdditionalProperty("secret_value", raw) + .putAdditionalProperty("future_field", JsonValue.from("preserved")) + .build() + val serialized = mapper.writeValueAsString(CredentialAuth.ofEnvironmentVariable(original)) + val auth = mapper.readValue(serialized, jacksonTypeRef()) + val environmentVariable = auth.asEnvironmentVariable() + val credential = + Credential.builder() + .id("credential_test") + .auth(auth) + .createdAt(0L) + .metadata(Credential.Metadata.builder().build()) + .name("Test credential") + .updatedAt(0L) + .vaultId("vault_test") + .build() + + for (value in listOf(original, environmentVariable, auth, credential)) { + assertThat(value.toString()) + .doesNotContain("fake-response-secret") + .contains("secret_value", "[REDACTED]", "future_field", "preserved") + } + assertThat(environmentVariable._additionalProperties()["secret_value"]).isEqualTo(raw) + assertThat(original._additionalProperties()["secret_value"]).isEqualTo(raw) + assertThat(environmentVariable).isEqualTo(original) + assertThat(mapper.readTree(mapper.writeValueAsString(auth))) + .isEqualTo(mapper.readTree(serialized)) + } + + @Test + fun environmentVariableStringsPreserveAdditionalPropertiesWithoutSecret() { + val environmentVariable = + CredentialAuth.EnvironmentVariable.builder() + .networkingUnrestricted() + .secretName("SDK_TEST_KEY") + .putAdditionalProperty("future_field", JsonValue.from("preserved")) + .build() + + assertThat(environmentVariable.toString()) + .contains("future_field", "preserved") + .doesNotContain("secret_value", "[REDACTED]") + assertThat(environmentVariable._additionalProperties()) + .containsExactlyEntriesOf(mapOf("future_field" to JsonValue.from("preserved"))) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamRedactionTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamRedactionTest.kt new file mode 100644 index 000000000..7a8c3b7ba --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamRedactionTest.kt @@ -0,0 +1,187 @@ +package com.openai.models.beta.agents.vaults.credentials + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.JsonMissing +import com.openai.core.JsonValue +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +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.ValueSource + +internal class CredentialAuthRotateParamRedactionTest { + @ParameterizedTest + @ValueSource( + strings = + ["{\"token\":\"fake-malformed-secret\"}", "[\"fake-malformed-secret\"]", "123", "true"] + ) + fun environmentVariableValidationRedactsMalformedSecret(secretJson: String) { + val mapper = jsonMapper() + val raw = mapper.readValue(secretJson, jacksonTypeRef()) + val original = + CredentialAuthRotateParam.EnvironmentVariable.builder().secretValue(raw).build() + val serialized = + mapper.writeValueAsString(CredentialAuthRotateParam.ofEnvironmentVariable(original)) + val auth = mapper.readValue(serialized, jacksonTypeRef()) + val params = + CredentialUpdateParams.builder() + .vaultId("vault_test") + .credentialId("credential_test") + .auth(auth) + .build() + + for (environmentVariable in listOf(original, auth.asEnvironmentVariable())) { + val accessorError = + assertThrows { environmentVariable.secretValue() } + val validationError = + assertThrows { environmentVariable.validate() } + for (error in listOf(accessorError, validationError)) { + assertThat(error) + .hasMessage("`secret_value` is invalid, received [REDACTED]") + .hasNoCause() + } + assertThat(environmentVariable._secretValue()).isEqualTo(raw) + assertThat(environmentVariable.isValid()).isFalse() + } + for (error in + listOf( + assertThrows { auth.validate() }, + assertThrows { params._body().validate() }, + )) { + assertThat(error) + .hasMessage("`secret_value` is invalid, received [REDACTED]") + .hasNoCause() + } + assertThat(mapper.readTree(mapper.writeValueAsString(auth))) + .isEqualTo(mapper.readTree(serialized)) + } + + @ParameterizedTest + @ValueSource(booleans = [false, true]) + fun environmentVariablePreservesMissingAndNullSecretDiagnostics(isNull: Boolean) { + val raw = if (isNull) JsonValue.from(null) else JsonMissing.of() + val environmentVariable = + CredentialAuthRotateParam.EnvironmentVariable.builder().secretValue(raw).build() + val expected = if (isNull) "`secret_value` is null" else "`secret_value` is not set" + + assertThat(assertThrows { environmentVariable.secretValue() }) + .hasMessage(expected) + .hasNoCause() + assertThat(assertThrows { environmentVariable.validate() }) + .hasMessage(expected) + .hasNoCause() + assertThat(environmentVariable._secretValue()).isEqualTo(raw) + } + + @ParameterizedTest + @ValueSource(strings = ["", "future_environment_variable"]) + fun unknownStringsRedactSecretWithoutChangingSerialization(type: String) { + val secret = "fake-unknown-environment-secret" + val fields = + mutableMapOf( + "secret_value" to JsonValue.from(secret), + "future_field" to JsonValue.from("preserved"), + ) + if (type.isNotEmpty()) { + fields["type"] = JsonValue.from(type) + } + val raw = JsonValue.from(fields) + val mapper = jsonMapper() + val original = mapper.writeValueAsString(raw) + val auth = mapper.readValue(original, jacksonTypeRef()) + val params = + CredentialUpdateParams.builder() + .vaultId("vault_test") + .credentialId("credential_test") + .auth(auth) + .build() + + assertThat(auth.environmentVariable()).isEmpty + for (value in listOf(auth, params)) { + assertThat(value.toString()) + .doesNotContain(secret) + .contains("[REDACTED]", "future_field", "preserved") + } + assertThat(auth._json()).contains(raw) + assertThat(params._body().auth().get()._json()).contains(raw) + assertThat(mapper.readTree(mapper.writeValueAsString(auth))) + .isEqualTo(mapper.readTree(original)) + } + + @ParameterizedTest + @ValueSource( + strings = + [ + "\"fake-additional-secret\"", + "{\"token\":\"fake-additional-secret\"}", + "[\"fake-additional-secret\"]", + ] + ) + fun environmentVariableStringsRedactAdditionalSecretWithoutChangingSerialization( + secretJson: String + ) { + val mapper = jsonMapper() + val raw = mapper.readValue(secretJson, jacksonTypeRef()) + val secret = "fake-typed-secret" + val environmentVariable = + CredentialAuthRotateParam.EnvironmentVariable.builder() + .secretValue(secret) + .putAdditionalProperty("secret_value", raw) + .putAdditionalProperty("future_field", JsonValue.from("preserved")) + .build() + val auth = CredentialAuthRotateParam.ofEnvironmentVariable(environmentVariable) + val params = + CredentialUpdateParams.builder() + .vaultId("vault_test") + .credentialId("credential_test") + .auth(auth) + .build() + val serialized = mapper.writeValueAsString(auth) + + for (value in listOf(environmentVariable, auth, params)) { + assertThat(value.toString()) + .doesNotContain(secret, "fake-additional-secret") + .contains( + "secretValue=[REDACTED]", + "secret_value", + "[REDACTED]", + "future_field", + "preserved", + ) + } + assertThat(environmentVariable.secretValue()).isEqualTo(secret) + assertThat(environmentVariable._additionalProperties()["secret_value"]).isEqualTo(raw) + assertThat(params._body().auth().get().asEnvironmentVariable()) + .isEqualTo(environmentVariable) + assertThat(mapper.writeValueAsString(auth)).isEqualTo(serialized) + assertThat(serialized).contains(secret, "fake-additional-secret") + assertThat(mapper.readTree(serialized).get("secret_value")) + .isEqualTo(mapper.readTree(secretJson)) + } + + @Test + fun environmentVariableStringsRedactSecretWithoutChangingSerialization() { + val secret = "fake-replacement-secret-do-not-log" + val environmentVariable = + CredentialAuthRotateParam.EnvironmentVariable.builder().secretValue(secret).build() + val auth = CredentialAuthRotateParam.ofEnvironmentVariable(environmentVariable) + val params = + CredentialUpdateParams.builder() + .vaultId("vault_test") + .credentialId("credential_test") + .auth(auth) + .build() + + for (value in listOf(environmentVariable, auth, params)) { + assertThat(value.toString()).doesNotContain(secret).contains("secretValue=[REDACTED]") + } + assertThat(environmentVariable.secretValue()).isEqualTo(secret) + val mapper = jsonMapper() + assertThat(mapper.readTree(mapper.writeValueAsString(auth)).get("secret_value").asText()) + .isEqualTo(secret) + assertThat(params._body().auth().get().asEnvironmentVariable().secretValue()) + .isEqualTo(secret) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamTest.kt index 320b2655f..3c192c31a 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthRotateParamTest.kt @@ -3,7 +3,6 @@ package com.openai.models.beta.agents.vaults.credentials import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import com.openai.core.JsonMissing import com.openai.core.JsonValue import com.openai.core.jsonMapper import com.openai.errors.OpenAIInvalidDataException @@ -12,183 +11,9 @@ 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 -import org.junit.jupiter.params.provider.ValueSource internal class CredentialAuthRotateParamTest { - @ParameterizedTest - @ValueSource( - strings = - ["{\"token\":\"fake-malformed-secret\"}", "[\"fake-malformed-secret\"]", "123", "true"] - ) - fun environmentVariableValidationRedactsMalformedSecret(secretJson: String) { - val mapper = jsonMapper() - val raw = mapper.readValue(secretJson, jacksonTypeRef()) - val original = - CredentialAuthRotateParam.EnvironmentVariable.builder().secretValue(raw).build() - val serialized = - mapper.writeValueAsString(CredentialAuthRotateParam.ofEnvironmentVariable(original)) - val auth = mapper.readValue(serialized, jacksonTypeRef()) - val params = - CredentialUpdateParams.builder() - .vaultId("vault_test") - .credentialId("credential_test") - .auth(auth) - .build() - - for (environmentVariable in listOf(original, auth.asEnvironmentVariable())) { - val accessorError = - assertThrows { environmentVariable.secretValue() } - val validationError = - assertThrows { environmentVariable.validate() } - for (error in listOf(accessorError, validationError)) { - assertThat(error) - .hasMessage("`secret_value` is invalid, received [REDACTED]") - .hasNoCause() - } - assertThat(environmentVariable._secretValue()).isEqualTo(raw) - assertThat(environmentVariable.isValid()).isFalse() - } - for (error in - listOf( - assertThrows { auth.validate() }, - assertThrows { params._body().validate() }, - )) { - assertThat(error) - .hasMessage("`secret_value` is invalid, received [REDACTED]") - .hasNoCause() - } - assertThat(mapper.readTree(mapper.writeValueAsString(auth))) - .isEqualTo(mapper.readTree(serialized)) - } - - @ParameterizedTest - @ValueSource(booleans = [false, true]) - fun environmentVariablePreservesMissingAndNullSecretDiagnostics(isNull: Boolean) { - val raw = if (isNull) JsonValue.from(null) else JsonMissing.of() - val environmentVariable = - CredentialAuthRotateParam.EnvironmentVariable.builder().secretValue(raw).build() - val expected = if (isNull) "`secret_value` is null" else "`secret_value` is not set" - - assertThat(assertThrows { environmentVariable.secretValue() }) - .hasMessage(expected) - .hasNoCause() - assertThat(assertThrows { environmentVariable.validate() }) - .hasMessage(expected) - .hasNoCause() - assertThat(environmentVariable._secretValue()).isEqualTo(raw) - } - - @ParameterizedTest - @ValueSource(strings = ["", "future_environment_variable"]) - fun unknownStringsRedactSecretWithoutChangingSerialization(type: String) { - val secret = "fake-unknown-environment-secret" - val fields = - mutableMapOf( - "secret_value" to JsonValue.from(secret), - "future_field" to JsonValue.from("preserved"), - ) - if (type.isNotEmpty()) { - fields["type"] = JsonValue.from(type) - } - val raw = JsonValue.from(fields) - val mapper = jsonMapper() - val original = mapper.writeValueAsString(raw) - val auth = mapper.readValue(original, jacksonTypeRef()) - val params = - CredentialUpdateParams.builder() - .vaultId("vault_test") - .credentialId("credential_test") - .auth(auth) - .build() - - assertThat(auth.environmentVariable()).isEmpty - for (value in listOf(auth, params)) { - assertThat(value.toString()) - .doesNotContain(secret) - .contains("[REDACTED]", "future_field", "preserved") - } - assertThat(auth._json()).contains(raw) - assertThat(params._body().auth().get()._json()).contains(raw) - assertThat(mapper.readTree(mapper.writeValueAsString(auth))) - .isEqualTo(mapper.readTree(original)) - } - - @ParameterizedTest - @ValueSource( - strings = - [ - "\"fake-additional-secret\"", - "{\"token\":\"fake-additional-secret\"}", - "[\"fake-additional-secret\"]", - ] - ) - fun environmentVariableStringsRedactAdditionalSecretWithoutChangingSerialization( - secretJson: String - ) { - val mapper = jsonMapper() - val raw = mapper.readValue(secretJson, jacksonTypeRef()) - val secret = "fake-typed-secret" - val environmentVariable = - CredentialAuthRotateParam.EnvironmentVariable.builder() - .secretValue(secret) - .putAdditionalProperty("secret_value", raw) - .putAdditionalProperty("future_field", JsonValue.from("preserved")) - .build() - val auth = CredentialAuthRotateParam.ofEnvironmentVariable(environmentVariable) - val params = - CredentialUpdateParams.builder() - .vaultId("vault_test") - .credentialId("credential_test") - .auth(auth) - .build() - val serialized = mapper.writeValueAsString(auth) - - for (value in listOf(environmentVariable, auth, params)) { - assertThat(value.toString()) - .doesNotContain(secret, "fake-additional-secret") - .contains( - "secretValue=[REDACTED]", - "secret_value", - "[REDACTED]", - "future_field", - "preserved", - ) - } - assertThat(environmentVariable.secretValue()).isEqualTo(secret) - assertThat(environmentVariable._additionalProperties()["secret_value"]).isEqualTo(raw) - assertThat(params._body().auth().get().asEnvironmentVariable()) - .isEqualTo(environmentVariable) - assertThat(mapper.writeValueAsString(auth)).isEqualTo(serialized) - assertThat(serialized).contains(secret, "fake-additional-secret") - assertThat(mapper.readTree(serialized).get("secret_value")) - .isEqualTo(mapper.readTree(secretJson)) - } - - @Test - fun environmentVariableStringsRedactSecretWithoutChangingSerialization() { - val secret = "fake-replacement-secret-do-not-log" - val environmentVariable = - CredentialAuthRotateParam.EnvironmentVariable.builder().secretValue(secret).build() - val auth = CredentialAuthRotateParam.ofEnvironmentVariable(environmentVariable) - val params = - CredentialUpdateParams.builder() - .vaultId("vault_test") - .credentialId("credential_test") - .auth(auth) - .build() - - for (value in listOf(environmentVariable, auth, params)) { - assertThat(value.toString()).doesNotContain(secret).contains("secretValue=[REDACTED]") - } - assertThat(environmentVariable.secretValue()).isEqualTo(secret) - val mapper = jsonMapper() - assertThat(mapper.readTree(mapper.writeValueAsString(auth)).get("secret_value").asText()) - .isEqualTo(secret) - assertThat(params._body().auth().get().asEnvironmentVariable().secretValue()) - .isEqualTo(secret) - } - @Test fun ofMcpOAuth() { val mcpOauth = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthTest.kt index 8925e2074..c763964ac 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/agents/vaults/credentials/CredentialAuthTest.kt @@ -11,127 +11,9 @@ 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 -import org.junit.jupiter.params.provider.ValueSource internal class CredentialAuthTest { - @ParameterizedTest - @ValueSource(strings = ["", "future_environment_variable"]) - fun unknownStringsRedactSecretWithoutChangingSerialization(type: String) { - val secret = "fake-unknown-response-secret" - val fields = - mutableMapOf( - "secret_value" to JsonValue.from(secret), - "future_field" to JsonValue.from("preserved"), - ) - if (type.isNotEmpty()) { - fields["type"] = JsonValue.from(type) - } - val raw = JsonValue.from(fields) - val mapper = jsonMapper() - val original = mapper.writeValueAsString(raw) - val auth = mapper.readValue(original, jacksonTypeRef()) - val credential = - Credential.builder() - .id("credential_test") - .auth(auth) - .createdAt(0L) - .metadata(Credential.Metadata.builder().build()) - .name("Test credential") - .updatedAt(0L) - .vaultId("vault_test") - .build() - - assertThat(auth.environmentVariable()).isEmpty - for (value in listOf(auth, credential)) { - assertThat(value.toString()) - .doesNotContain(secret) - .contains("[REDACTED]", "future_field", "preserved") - } - val error = assertThrows { auth.validate() } - assertThat(error).hasNoCause() - assertThat(error).hasMessage("Unknown CredentialAuth") - assertThat(auth._json()).contains(raw) - assertThat(credential.auth()._json()).contains(raw) - assertThat(mapper.readTree(mapper.writeValueAsString(auth))) - .isEqualTo(mapper.readTree(original)) - } - - @ParameterizedTest - @ValueSource(strings = ["{}", "{\"type\":\"future\",\"future_field\":\"preserved\"}"]) - fun unknownDiagnosticsOmitPayloadWithoutChangingToString(json: String) { - val mapper = jsonMapper() - val raw = mapper.readValue(json, jacksonTypeRef()) - val auth = mapper.readValue(json, jacksonTypeRef()) - - assertThat(auth.toString()).isEqualTo("CredentialAuth{_unknown=$raw}") - assertThat(assertThrows { auth.validate() }) - .hasMessage("Unknown CredentialAuth") - } - - @ParameterizedTest - @ValueSource( - strings = - [ - "\"fake-response-secret\"", - "{\"token\":\"fake-response-secret\"}", - "[\"fake-response-secret\"]", - ] - ) - fun environmentVariableStringsRedactUnexpectedSecretWithoutChangingSerialization( - secretJson: String - ) { - val mapper = jsonMapper() - val raw = mapper.readValue(secretJson, jacksonTypeRef()) - val original = - CredentialAuth.EnvironmentVariable.builder() - .networkingUnrestricted() - .secretName("SDK_TEST_KEY") - .putAdditionalProperty("secret_value", raw) - .putAdditionalProperty("future_field", JsonValue.from("preserved")) - .build() - val serialized = mapper.writeValueAsString(CredentialAuth.ofEnvironmentVariable(original)) - val auth = mapper.readValue(serialized, jacksonTypeRef()) - val environmentVariable = auth.asEnvironmentVariable() - val credential = - Credential.builder() - .id("credential_test") - .auth(auth) - .createdAt(0L) - .metadata(Credential.Metadata.builder().build()) - .name("Test credential") - .updatedAt(0L) - .vaultId("vault_test") - .build() - - for (value in listOf(original, environmentVariable, auth, credential)) { - assertThat(value.toString()) - .doesNotContain("fake-response-secret") - .contains("secret_value", "[REDACTED]", "future_field", "preserved") - } - assertThat(environmentVariable._additionalProperties()["secret_value"]).isEqualTo(raw) - assertThat(original._additionalProperties()["secret_value"]).isEqualTo(raw) - assertThat(environmentVariable).isEqualTo(original) - assertThat(mapper.readTree(mapper.writeValueAsString(auth))) - .isEqualTo(mapper.readTree(serialized)) - } - - @Test - fun environmentVariableStringsPreserveAdditionalPropertiesWithoutSecret() { - val environmentVariable = - CredentialAuth.EnvironmentVariable.builder() - .networkingUnrestricted() - .secretName("SDK_TEST_KEY") - .putAdditionalProperty("future_field", JsonValue.from("preserved")) - .build() - - assertThat(environmentVariable.toString()) - .contains("future_field", "preserved") - .doesNotContain("secret_value", "[REDACTED]") - assertThat(environmentVariable._additionalProperties()) - .containsExactlyEntriesOf(mapOf("future_field" to JsonValue.from("preserved"))) - } - @Test fun ofMcpO() { val mcpO = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseAccessProgramsTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseAccessProgramsTest.kt new file mode 100644 index 000000000..1f0b019e4 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseAccessProgramsTest.kt @@ -0,0 +1,34 @@ +package com.openai.models.beta.responses + +import com.openai.core.JsonNull +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class BetaResponseAccessProgramsTest { + @Test + fun buildWithoutAccessPrograms() { + val response = + BetaResponse.builder() + .id("response-id") + .createdAt(0.0) + .error(null) + .incompleteDetails(null) + .instructions(null) + .metadata(null) + .model("test-model") + .output(listOf()) + .parallelToolCalls(false) + .temperature(null) + .toolChoice(JsonNull.of()) + .tools(listOf()) + .topP(null) + .build() + + assertThat(response.accessPrograms()).isEmpty() + assertThat(response._accessPrograms()).isEqualTo(JsonNull.of()) + val json = jsonMapper().readTree(jsonMapper().writeValueAsString(response)) + assertThat(json.has("access_programs")).isTrue() + assertThat(json.get("access_programs").isNull).isTrue() + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchStatusTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchStatusTest.kt new file mode 100644 index 000000000..61c77a8d4 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchStatusTest.kt @@ -0,0 +1,32 @@ +package com.openai.models.beta.responses + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class BetaResponseFunctionWebSearchStatusTest { + @Test + fun deserializeIncompleteStatus() { + val betaResponseFunctionWebSearch = + jsonMapper() + .readValue( + """ + { + "id": "ws_incomplete", + "type": "web_search_call", + "status": "incomplete", + "action": {"type": "search", "query": "synthetic query"} + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + betaResponseFunctionWebSearch.validate() + assertThat(betaResponseFunctionWebSearch.status().value()) + .isEqualTo(BetaResponseFunctionWebSearch.Status.Value.INCOMPLETE) + assertThat(betaResponseFunctionWebSearch.status().known()) + .isEqualTo(BetaResponseFunctionWebSearch.Status.Known.INCOMPLETE) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchTest.kt index 2f485b8d5..b0bcffe47 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseFunctionWebSearchTest.kt @@ -9,30 +9,6 @@ import org.junit.jupiter.api.Test internal class BetaResponseFunctionWebSearchTest { - @Test - fun deserializeIncompleteStatus() { - val betaResponseFunctionWebSearch = - jsonMapper() - .readValue( - """ - { - "id": "ws_incomplete", - "type": "web_search_call", - "status": "incomplete", - "action": {"type": "search", "query": "synthetic query"} - } - """ - .trimIndent(), - jacksonTypeRef(), - ) - - betaResponseFunctionWebSearch.validate() - assertThat(betaResponseFunctionWebSearch.status().value()) - .isEqualTo(BetaResponseFunctionWebSearch.Status.Value.INCOMPLETE) - assertThat(betaResponseFunctionWebSearch.status().known()) - .isEqualTo(BetaResponseFunctionWebSearch.Status.Known.INCOMPLETE) - } - @Test fun create() { val betaResponseFunctionWebSearch = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseTest.kt index fc3f502e6..dd4c35e50 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponseTest.kt @@ -3,7 +3,6 @@ package com.openai.models.beta.responses import com.fasterxml.jackson.module.kotlin.jacksonTypeRef -import com.openai.core.JsonNull import com.openai.core.JsonValue import com.openai.core.jsonMapper import org.assertj.core.api.Assertions.assertThat @@ -11,32 +10,6 @@ import org.junit.jupiter.api.Test internal class BetaResponseTest { - @Test - fun buildWithoutAccessPrograms() { - val response = - BetaResponse.builder() - .id("response-id") - .createdAt(0.0) - .error(null) - .incompleteDetails(null) - .instructions(null) - .metadata(null) - .model("test-model") - .output(listOf()) - .parallelToolCalls(false) - .temperature(null) - .toolChoice(JsonNull.of()) - .tools(listOf()) - .topP(null) - .build() - - assertThat(response.accessPrograms()).isEmpty() - assertThat(response._accessPrograms()).isEqualTo(JsonNull.of()) - val json = jsonMapper().readTree(jsonMapper().writeValueAsString(response)) - assertThat(json.has("access_programs")).isTrue() - assertThat(json.get("access_programs").isNull).isTrue() - } - @Test fun create() { val betaResponse = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventStreamIdTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventStreamIdTest.kt new file mode 100644 index 000000000..165658e20 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventStreamIdTest.kt @@ -0,0 +1,111 @@ +package com.openai.models.beta.responses + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +internal class BetaResponsesServerEventStreamIdTest { + @Test + fun websocketStreamId() { + val betaResponsesServerEvent = + jsonMapper() + .readValue( + """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":"stream_id"}""", + jacksonTypeRef(), + ) + + assertThat(betaResponsesServerEvent.streamId()).contains("stream_id") + assertThat(betaResponsesServerEvent.responseAudioDelta()).isPresent + assertThat(betaResponsesServerEvent.asResponseAudioDelta().delta()).isEqualTo("delta") + assertThat(jsonMapper().writeValueAsString(betaResponsesServerEvent)).contains("stream_id") + assertThat(betaResponsesServerEvent.validate()).isSameAs(betaResponsesServerEvent) + assertThat(betaResponsesServerEvent.isValid()).isTrue() + } + + @Test + fun websocketStreamIdRejectsMalformedValue() { + val betaResponsesServerEvent = + jsonMapper() + .readValue( + """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":42}""", + jacksonTypeRef(), + ) + + assertThrows { betaResponsesServerEvent.streamId() } + assertThrows { betaResponsesServerEvent.validate() } + assertThat(betaResponsesServerEvent.isValid()).isFalse() + } + + @Test + fun shellCallCommandAddedPreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_command.added","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" + val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallCommandAdded()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallCommandDeltaPreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_command.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","delta":"ls"}""" + val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallCommandDelta()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallCommandDonePreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_command.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" + val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallCommandDone()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallOutputContentDeltaPreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_output_content.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","delta":{"stdout":"ok"}}""" + val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallOutputContentDelta()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallOutputContentDonePreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_output_content.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","output":[]}""" + val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallOutputContentDone()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventTest.kt index 05cdb8474..469a2ac39 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/beta/responses/BetaResponsesServerEventTest.kt @@ -11007,105 +11007,4 @@ internal class BetaResponsesServerEventTest { val e = assertThrows { betaResponsesServerEvent.validate() } assertThat(e).hasMessageStartingWith("Unknown ") } - - @Test - fun websocketStreamId() { - val betaResponsesServerEvent = - jsonMapper() - .readValue( - """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":"stream_id"}""", - jacksonTypeRef(), - ) - - assertThat(betaResponsesServerEvent.streamId()).contains("stream_id") - assertThat(betaResponsesServerEvent.responseAudioDelta()).isPresent - assertThat(betaResponsesServerEvent.asResponseAudioDelta().delta()).isEqualTo("delta") - assertThat(jsonMapper().writeValueAsString(betaResponsesServerEvent)).contains("stream_id") - assertThat(betaResponsesServerEvent.validate()).isSameAs(betaResponsesServerEvent) - assertThat(betaResponsesServerEvent.isValid()).isTrue() - } - - @Test - fun websocketStreamIdRejectsMalformedValue() { - val betaResponsesServerEvent = - jsonMapper() - .readValue( - """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":42}""", - jacksonTypeRef(), - ) - - assertThrows { betaResponsesServerEvent.streamId() } - assertThrows { betaResponsesServerEvent.validate() } - assertThat(betaResponsesServerEvent.isValid()).isFalse() - } - - @Test - fun shellCallCommandAddedPreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_command.added","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" - val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallCommandAdded()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallCommandDeltaPreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_command.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","delta":"ls"}""" - val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallCommandDelta()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallCommandDonePreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_command.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" - val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallCommandDone()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallOutputContentDeltaPreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_output_content.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","delta":{"stdout":"ok"}}""" - val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallOutputContentDelta()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallOutputContentDonePreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_output_content.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","output":[]}""" - val event = mapper.readValue(json, BetaResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallOutputContentDone()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), BetaResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } } diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchStatusTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchStatusTest.kt new file mode 100644 index 000000000..dbbf6131f --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchStatusTest.kt @@ -0,0 +1,32 @@ +package com.openai.models.responses + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class ResponseFunctionWebSearchStatusTest { + @Test + fun deserializeIncompleteStatus() { + val responseFunctionWebSearch = + jsonMapper() + .readValue( + """ + { + "id": "ws_incomplete", + "type": "web_search_call", + "status": "incomplete", + "action": {"type": "search", "query": "synthetic query"} + } + """ + .trimIndent(), + jacksonTypeRef(), + ) + + responseFunctionWebSearch.validate() + assertThat(responseFunctionWebSearch.status().value()) + .isEqualTo(ResponseFunctionWebSearch.Status.Value.INCOMPLETE) + assertThat(responseFunctionWebSearch.status().known()) + .isEqualTo(ResponseFunctionWebSearch.Status.Known.INCOMPLETE) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchTest.kt index 61e6197b6..93d404c60 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponseFunctionWebSearchTest.kt @@ -9,30 +9,6 @@ import org.junit.jupiter.api.Test internal class ResponseFunctionWebSearchTest { - @Test - fun deserializeIncompleteStatus() { - val responseFunctionWebSearch = - jsonMapper() - .readValue( - """ - { - "id": "ws_incomplete", - "type": "web_search_call", - "status": "incomplete", - "action": {"type": "search", "query": "synthetic query"} - } - """ - .trimIndent(), - jacksonTypeRef(), - ) - - responseFunctionWebSearch.validate() - assertThat(responseFunctionWebSearch.status().value()) - .isEqualTo(ResponseFunctionWebSearch.Status.Value.INCOMPLETE) - assertThat(responseFunctionWebSearch.status().known()) - .isEqualTo(ResponseFunctionWebSearch.Status.Known.INCOMPLETE) - } - @Test fun create() { val responseFunctionWebSearch = diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventStreamIdTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventStreamIdTest.kt new file mode 100644 index 000000000..d62a92c67 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventStreamIdTest.kt @@ -0,0 +1,111 @@ +package com.openai.models.responses + +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.openai.core.jsonMapper +import com.openai.errors.OpenAIInvalidDataException +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +internal class ResponsesServerEventStreamIdTest { + @Test + fun websocketStreamId() { + val responsesServerEvent = + jsonMapper() + .readValue( + """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":"stream_id"}""", + jacksonTypeRef(), + ) + + assertThat(responsesServerEvent.streamId()).contains("stream_id") + assertThat(responsesServerEvent.responseAudioDelta()).isPresent + assertThat(responsesServerEvent.asResponseAudioDelta().delta()).isEqualTo("delta") + assertThat(jsonMapper().writeValueAsString(responsesServerEvent)).contains("stream_id") + assertThat(responsesServerEvent.validate()).isSameAs(responsesServerEvent) + assertThat(responsesServerEvent.isValid()).isTrue() + } + + @Test + fun websocketStreamIdRejectsMalformedValue() { + val responsesServerEvent = + jsonMapper() + .readValue( + """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":42}""", + jacksonTypeRef(), + ) + + assertThrows { responsesServerEvent.streamId() } + assertThrows { responsesServerEvent.validate() } + assertThat(responsesServerEvent.isValid()).isFalse() + } + + @Test + fun shellCallCommandAddedPreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_command.added","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" + val event = mapper.readValue(json, ResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallCommandAdded()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallCommandDeltaPreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_command.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","delta":"ls"}""" + val event = mapper.readValue(json, ResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallCommandDelta()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallCommandDonePreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_command.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" + val event = mapper.readValue(json, ResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallCommandDone()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallOutputContentDeltaPreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_output_content.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","delta":{"stdout":"ok"}}""" + val event = mapper.readValue(json, ResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallOutputContentDelta()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } + + @Test + fun shellCallOutputContentDonePreservesStreamId() { + val mapper = jsonMapper() + val json = + """{"type":"response.shell_call_output_content.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","output":[]}""" + val event = mapper.readValue(json, ResponsesServerEvent::class.java) + event.validate() + assertThat(event.isResponseShellCallOutputContentDone()).isTrue() + assertThat(event.streamId()).contains("lane") + val roundtrip = + mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) + assertThat(roundtrip).isEqualTo(event) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventTest.kt index f998e44e3..fd6effeea 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/responses/ResponsesServerEventTest.kt @@ -9844,105 +9844,4 @@ internal class ResponsesServerEventTest { val e = assertThrows { responsesServerEvent.validate() } assertThat(e).hasMessageStartingWith("Unknown ") } - - @Test - fun websocketStreamId() { - val responsesServerEvent = - jsonMapper() - .readValue( - """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":"stream_id"}""", - jacksonTypeRef(), - ) - - assertThat(responsesServerEvent.streamId()).contains("stream_id") - assertThat(responsesServerEvent.responseAudioDelta()).isPresent - assertThat(responsesServerEvent.asResponseAudioDelta().delta()).isEqualTo("delta") - assertThat(jsonMapper().writeValueAsString(responsesServerEvent)).contains("stream_id") - assertThat(responsesServerEvent.validate()).isSameAs(responsesServerEvent) - assertThat(responsesServerEvent.isValid()).isTrue() - } - - @Test - fun websocketStreamIdRejectsMalformedValue() { - val responsesServerEvent = - jsonMapper() - .readValue( - """{"type":"response.audio.delta","delta":"delta","sequence_number":0,"stream_id":42}""", - jacksonTypeRef(), - ) - - assertThrows { responsesServerEvent.streamId() } - assertThrows { responsesServerEvent.validate() } - assertThat(responsesServerEvent.isValid()).isFalse() - } - - @Test - fun shellCallCommandAddedPreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_command.added","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" - val event = mapper.readValue(json, ResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallCommandAdded()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallCommandDeltaPreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_command.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","delta":"ls"}""" - val event = mapper.readValue(json, ResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallCommandDelta()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallCommandDonePreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_command.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","command":"ls"}""" - val event = mapper.readValue(json, ResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallCommandDone()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallOutputContentDeltaPreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_output_content.delta","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","delta":{"stdout":"ok"}}""" - val event = mapper.readValue(json, ResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallOutputContentDelta()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } - - @Test - fun shellCallOutputContentDonePreservesStreamId() { - val mapper = jsonMapper() - val json = - """{"type":"response.shell_call_output_content.done","sequence_number":1,"output_index":0,"command_index":0,"stream_id":"lane","item_id":"item","output":[]}""" - val event = mapper.readValue(json, ResponsesServerEvent::class.java) - event.validate() - assertThat(event.isResponseShellCallOutputContentDone()).isTrue() - assertThat(event.streamId()).contains("lane") - val roundtrip = - mapper.readValue(mapper.writeValueAsString(event), ResponsesServerEvent::class.java) - assertThat(roundtrip).isEqualTo(event) - } } diff --git a/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretRedactionTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretRedactionTest.kt new file mode 100644 index 000000000..c293333c3 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretRedactionTest.kt @@ -0,0 +1,31 @@ +package com.openai.models.webhooks + +import com.openai.core.jsonMapper +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class WebhookEndpointWithSecretRedactionTest { + @Test + fun toStringRedactsSigningSecret() { + val secret = "whsec_test_only_do_not_log" + val webhookEndpointWithSecret = + WebhookEndpointWithSecret.builder() + .id("whe_test") + .createdAt(0L) + .addEventType("response.completed") + .name("Test webhook") + .signingSecret(secret) + .signingSecretHint(null as String?) + .url("https://example.com/webhook") + .build() + + assertThat(webhookEndpointWithSecret.toString()) + .doesNotContain(secret) + .contains("signingSecret=[REDACTED]") + assertThat(webhookEndpointWithSecret.signingSecret()).isEqualTo(secret) + + val mapper = jsonMapper() + val serialized = mapper.readTree(mapper.writeValueAsString(webhookEndpointWithSecret)) + assertThat(serialized.get("signing_secret").asText()).isEqualTo(secret) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretTest.kt b/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretTest.kt index 24db4ce5c..53bb09874 100644 --- a/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/models/webhooks/WebhookEndpointWithSecretTest.kt @@ -9,30 +9,6 @@ import org.junit.jupiter.api.Test internal class WebhookEndpointWithSecretTest { - @Test - fun toStringRedactsSigningSecret() { - val secret = "whsec_test_only_do_not_log" - val webhookEndpointWithSecret = - WebhookEndpointWithSecret.builder() - .id("whe_test") - .createdAt(0L) - .addEventType("response.completed") - .name("Test webhook") - .signingSecret(secret) - .signingSecretHint(null as String?) - .url("https://example.com/webhook") - .build() - - assertThat(webhookEndpointWithSecret.toString()) - .doesNotContain(secret) - .contains("signingSecret=[REDACTED]") - assertThat(webhookEndpointWithSecret.signingSecret()).isEqualTo(secret) - - val mapper = jsonMapper() - val serialized = mapper.readTree(mapper.writeValueAsString(webhookEndpointWithSecret)) - assertThat(serialized.get("signing_secret").asText()).isEqualTo(secret) - } - @Test fun create() { val webhookEndpointWithSecret = diff --git a/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt index 3de6222b2..b72654765 100644 --- a/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookServiceAsyncTest.kt @@ -19,11 +19,7 @@ import com.openai.models.webhooks.WebhookRotateSecretParams import com.openai.models.webhooks.WebhookTestParams import com.openai.models.webhooks.WebhookUpdateParams import com.openai.models.webhooks.WebhookVerificationParams -import java.time.Clock -import java.time.Instant -import java.time.ZoneOffset import java.util.concurrent.atomic.AtomicInteger -import kotlin.test.assertTrue import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows @@ -37,43 +33,9 @@ internal class WebhookServiceAsyncTest { private val testPayload = """{"id": "evt_685c059ae3a481909bdc86819b066fb6", "object": "event", "created_at": 1750861210, "type": "response.completed", "data": {"id": "resp_123"}}""" - private val testSecret = "whsec_RdvaYFYUXuIFuEbvZHwMfYFhUf7aMYjYcmM24+Aj40c=" private val fixedTimestamp = "1750861210" private val webhookId = "wh_685c059ae39c8190af8c71ed1022a24d" private val validSignatureForSecret = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY=" - private val fixedClock = - Clock.fixed(Instant.ofEpochSecond(fixedTimestamp.toLong()), ZoneOffset.UTC) - - @Test - fun unwrapWithValidSignatureAndSecret() { - val client = - OpenAIOkHttpClientAsync.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .clock(fixedClock) - .build() - val webhookServiceAsync = client.webhooks() - - val headers = - Headers.builder() - .put("webhook-signature", validSignatureForSecret) - .put("webhook-timestamp", fixedTimestamp) - .put("webhook-id", webhookId) - .build() - - val event = - webhookServiceAsync - .unwrap( - WebhookVerificationParams.builder() - .payload(testPayload) - .headers(headers) - .secret(testSecret) - .build() - ) - .validate() - - assertTrue(event.isResponseCompleted()) - } @Test fun create() { diff --git a/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookSignatureVerificationAsyncTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookSignatureVerificationAsyncTest.kt new file mode 100644 index 000000000..69efe5b07 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/services/async/WebhookSignatureVerificationAsyncTest.kt @@ -0,0 +1,53 @@ +package com.openai.services.async + +import com.openai.client.okhttp.OpenAIOkHttpClientAsync +import com.openai.core.http.Headers +import com.openai.models.webhooks.WebhookVerificationParams +import java.time.Clock +import java.time.Instant +import java.time.ZoneOffset +import kotlin.test.assertTrue +import org.junit.jupiter.api.Test + +internal class WebhookSignatureVerificationAsyncTest { + + private val testPayload = + """{"id": "evt_685c059ae3a481909bdc86819b066fb6", "object": "event", "created_at": 1750861210, "type": "response.completed", "data": {"id": "resp_123"}}""" + private val testSecret = "whsec_RdvaYFYUXuIFuEbvZHwMfYFhUf7aMYjYcmM24+Aj40c=" + private val fixedTimestamp = "1750861210" + private val webhookId = "wh_685c059ae39c8190af8c71ed1022a24d" + private val validSignatureForSecret = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY=" + private val fixedClock = + Clock.fixed(Instant.ofEpochSecond(fixedTimestamp.toLong()), ZoneOffset.UTC) + + @Test + fun unwrapWithValidSignatureAndSecret() { + val client = + OpenAIOkHttpClientAsync.builder() + .baseUrl("http://localhost") + .apiKey("My API Key") + .clock(fixedClock) + .build() + val webhookServiceAsync = client.webhooks() + + val headers = + Headers.builder() + .put("webhook-signature", validSignatureForSecret) + .put("webhook-timestamp", fixedTimestamp) + .put("webhook-id", webhookId) + .build() + + val event = + webhookServiceAsync + .unwrap( + WebhookVerificationParams.builder() + .payload(testPayload) + .headers(headers) + .secret(testSecret) + .build() + ) + .validate() + + assertTrue(event.isResponseCompleted()) + } +} diff --git a/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt index 820bc40fe..aacd5c846 100644 --- a/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt +++ b/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookServiceTest.kt @@ -12,7 +12,6 @@ import com.github.tomakehurst.wiremock.client.WireMock.stubFor import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest import com.openai.TestServerExtension -import com.openai.client.OpenAIClient import com.openai.client.okhttp.OpenAIOkHttpClient import com.openai.core.http.Headers import com.openai.models.webhooks.WebhookCreateParams @@ -20,9 +19,6 @@ import com.openai.models.webhooks.WebhookRotateSecretParams import com.openai.models.webhooks.WebhookTestParams import com.openai.models.webhooks.WebhookUpdateParams import com.openai.models.webhooks.WebhookVerificationParams -import java.time.Clock -import java.time.Instant -import java.time.ZoneOffset import kotlin.test.assertTrue import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -35,55 +31,8 @@ import org.junit.jupiter.api.parallel.ResourceLock @ResourceLock("https://github.com/wiremock/wiremock/issues/169") internal class WebhookServiceTest { - // Standardized test data (matches TypeScript implementation) private val testPayload = """{"id": "evt_685c059ae3a481909bdc86819b066fb6", "object": "event", "created_at": 1750861210, "type": "response.completed", "data": {"id": "resp_123"}}""" - private val testSecret = "whsec_RdvaYFYUXuIFuEbvZHwMfYFhUf7aMYjYcmM24+Aj40c=" - - // Use a fixed timestamp for consistent testing (matches the created_at in payload) - private val fixedTimestamp = "1750861210" - private val webhookId = "wh_685c059ae39c8190af8c71ed1022a24d" - - // Pre-computed valid signatures for consistent testing - private val validSignatureForSecret = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY=" - - // Fixed clock for deterministic testing - private val fixedClock = - Clock.fixed(Instant.ofEpochSecond(fixedTimestamp.toLong()), ZoneOffset.UTC) - - private fun createClientWithFixedClock(): OpenAIClient { - return OpenAIOkHttpClient.builder() - .baseUrl(TestServerExtension.BASE_URL) - .apiKey("My API Key") - .clock(fixedClock) - .build() - } - - @Test - fun unwrapWithValidSignatureAndSecret() { - val client = createClientWithFixedClock() - val webhookService = client.webhooks() - - val headers = - Headers.builder() - .put("webhook-signature", validSignatureForSecret) - .put("webhook-timestamp", fixedTimestamp) - .put("webhook-id", webhookId) - .build() - - val event = - webhookService - .unwrap( - WebhookVerificationParams.builder() - .payload(testPayload) - .headers(headers) - .secret(testSecret) - .build() - ) - .validate() - - assertTrue(event.isResponseCompleted()) - } @Test fun create() { diff --git a/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookSignatureVerificationTest.kt b/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookSignatureVerificationTest.kt new file mode 100644 index 000000000..19e08d5e0 --- /dev/null +++ b/openai-java-core/src/test/kotlin/com/openai/services/blocking/WebhookSignatureVerificationTest.kt @@ -0,0 +1,64 @@ +package com.openai.services.blocking + +import com.openai.client.OpenAIClient +import com.openai.client.okhttp.OpenAIOkHttpClient +import com.openai.core.http.Headers +import com.openai.models.webhooks.WebhookVerificationParams +import java.time.Clock +import java.time.Instant +import java.time.ZoneOffset +import kotlin.test.assertTrue +import org.junit.jupiter.api.Test + +internal class WebhookSignatureVerificationTest { + + // Standardized test data (matches TypeScript implementation) + private val testPayload = + """{"id": "evt_685c059ae3a481909bdc86819b066fb6", "object": "event", "created_at": 1750861210, "type": "response.completed", "data": {"id": "resp_123"}}""" + private val testSecret = "whsec_RdvaYFYUXuIFuEbvZHwMfYFhUf7aMYjYcmM24+Aj40c=" + + // Use a fixed timestamp for consistent testing (matches the created_at in payload) + private val fixedTimestamp = "1750861210" + private val webhookId = "wh_685c059ae39c8190af8c71ed1022a24d" + + // Pre-computed valid signatures for consistent testing + private val validSignatureForSecret = "v1,gUAg4R2hWouRZqRQG4uJypNS8YK885G838+EHb4nKBY=" + + // Fixed clock for deterministic testing + private val fixedClock = + Clock.fixed(Instant.ofEpochSecond(fixedTimestamp.toLong()), ZoneOffset.UTC) + + private fun createClientWithFixedClock(): OpenAIClient { + return OpenAIOkHttpClient.builder() + .baseUrl("http://localhost") + .apiKey("My API Key") + .clock(fixedClock) + .build() + } + + @Test + fun unwrapWithValidSignatureAndSecret() { + val client = createClientWithFixedClock() + val webhookService = client.webhooks() + + val headers = + Headers.builder() + .put("webhook-signature", validSignatureForSecret) + .put("webhook-timestamp", fixedTimestamp) + .put("webhook-id", webhookId) + .build() + + val event = + webhookService + .unwrap( + WebhookVerificationParams.builder() + .payload(testPayload) + .headers(headers) + .secret(testSecret) + .build() + ) + .validate() + + assertTrue(event.isResponseCompleted()) + } +}