Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
0ad3200
add draft contributing.md
dtmeadows Jun 27, 2025
d063ba7
remove a few things
dtmeadows Jun 27, 2025
88f4f5a
Apply suggestions from code review
dtmeadows Jul 2, 2025
7897cce
Merge branch 'next' into dmeadows/java-contributing-md
dtmeadows Jul 15, 2025
a9752cf
address more comments
dtmeadows Jul 15, 2025
66d80ec
Merge branch 'dmeadows/java-contributing-md' of https://github.com/st…
dtmeadows Jul 15, 2025
39d98f6
Set up TestContainers instead of running the mock server from a script.
jdubois Jul 16, 2025
3786a1e
Update CONTRIBUTING.md
dtmeadows Jul 16, 2025
abe671e
Apply suggestions from code review
dtmeadows Jul 16, 2025
1905703
pr comments
dtmeadows Jul 16, 2025
608947c
chore(internal): Add CONTRIBUTING.md for SDK developers
dtmeadows Jul 16, 2025
3239c2d
chore(internal): allow running specific example from cli
stainless-app[bot] Jul 16, 2025
a00c39b
fix(client): ensure error handling always occurs
stainless-app[bot] Jul 17, 2025
77f54fd
feat(client): add `ResponseAccumulator` (#391)
damo Jul 17, 2025
2d185ba
chore(client): remove non-existent method
TomerAberbach Jul 17, 2025
6fa0700
Set up TestContainers instead of running the mock server from a script.
jdubois Jul 16, 2025
32e22a4
Merge remote-tracking branch 'origin/fix-54' into fix-54
jdubois Jul 18, 2025
d97e27d
Use Stainless version of Prism
jdubois Jul 18, 2025
1f5bb24
Merge branch 'next' into fix-54
TomerAberbach Jul 18, 2025
5e02837
chore: format
TomerAberbach Jul 18, 2025
fdeac0b
chore: keep ./scripts/test
TomerAberbach Jul 18, 2025
dd9c8c1
chore: remove dupe test impls
TomerAberbach Jul 18, 2025
ac635a9
chore: get rid of ./scripts/mock
TomerAberbach Jul 18, 2025
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/create-releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ jobs:
run: |
./gradlew :openai-java-core:compileJava :openai-java-core:compileTestJava -x test

- name: Run the Prism server
run: |
./scripts/mock --daemon

- name: Setup GraalVM
uses: graalvm/setup-graalvm@v1
with:
Expand Down
8 changes: 1 addition & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,12 @@ JAR files will be available in each module's `build/libs/` directory.

Most tests require [our mock server](https://github.com/stoplightio/prism) to be running against the OpenAPI spec to work.

The test script will automatically start the mock server for you (if it's not already running) and run the tests against it:
The test script will automatically start the mock server for you and run the tests against it:

```sh
$ ./scripts/test
```

You can also manually start the mock server if you want to run tests repeatedly:

```sh
$ ./scripts/mock
```

Then run the tests:

```sh
Expand Down
2 changes: 2 additions & 0 deletions openai-java-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ dependencies {
testImplementation("org.mockito:mockito-core:5.14.2")
testImplementation("org.mockito:mockito-junit-jupiter:5.14.2")
testImplementation("org.mockito.kotlin:mockito-kotlin:4.1.0")
testImplementation("org.testcontainers:testcontainers:1.19.8")
testImplementation("org.testcontainers:junit-jupiter:1.19.8")
}

if (project.hasProperty("graalvmAgent")) {
Expand Down
149 changes: 123 additions & 26 deletions openai-java-core/src/test/kotlin/com/openai/TestServerExtension.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,143 @@
package com.openai

import java.io.File
import java.lang.RuntimeException
import java.net.URL
import java.time.Duration
import org.junit.jupiter.api.extension.BeforeAllCallback
import org.junit.jupiter.api.extension.ConditionEvaluationResult
import org.junit.jupiter.api.extension.ExecutionCondition
import org.junit.jupiter.api.extension.ExtensionContext
import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.utility.DockerImageName
import org.testcontainers.utility.MountableFile

class TestServerExtension : BeforeAllCallback, ExecutionCondition {

override fun beforeAll(context: ExtensionContext?) {
try {
URL(BASE_URL).openConnection().connect()
} catch (e: Exception) {
throw RuntimeException(
"""
The test suite will not run without a mock Prism server running against your OpenAPI spec.
companion object {
private const val INTERNAL_PORT = 4010 // Port inside the container

You can set the environment variable `SKIP_MOCK_TESTS` to `true` to skip running any tests
that require the mock server.
val BASE_URL: String
get() = "http://${prismContainer.host}:${prismContainer.getMappedPort(INTERNAL_PORT)}"

To fix:
const val SKIP_TESTS_ENV: String = "SKIP_MOCK_TESTS"
private const val NODEJS_IMAGE = "node:22"
private const val PRISM_CLI_VERSION = "5.8.5"
private const val API_SPEC_PATH = "/app/openapi.yml" // Path inside the container

1. Install Prism (requires Node 16+):
// Track if the container has been started
private var containerStarted = false

private fun getOpenApiSpecPath(): String {
// First check environment variable
val envPath = System.getenv("OPENAPI_SPEC_PATH")
if (envPath != null) {
return envPath
}

// Try to read from .stats.yml file
try {
val statsFile = File("../.stats.yml")
if (statsFile.exists()) {
val content = statsFile.readText()
val urlLine = content.lines().find { it.startsWith("openapi_spec_url:") }
if (urlLine != null) {
val url = urlLine.substringAfter("openapi_spec_url:").trim()
if (url.isNotEmpty()) {
return url
}
}
}
} catch (e: Exception) {
println(
"Could not read .stats.yml fails, fall back to default. Error is: ${e.message}"
)
}
return "/tmp/openapi.yml"
}

private val prismContainer: GenericContainer<*> by lazy {
val apiSpecPath = getOpenApiSpecPath()
println("Using OpenAPI spec path: $apiSpecPath")
val isUrl = apiSpecPath.startsWith("http://") || apiSpecPath.startsWith("https://")

// Create container with or without copying the file based on whether apiSpecPath is a
// URL
val container =
GenericContainer(DockerImageName.parse(NODEJS_IMAGE))
.withExposedPorts(INTERNAL_PORT)
.withCommand(
"npm",
"exec",
"--package=@stainless-api/prism-cli@$PRISM_CLI_VERSION",
"--",
"prism",
"mock",
apiSpecPath,
"--host",
"0.0.0.0",
"--port",
INTERNAL_PORT.toString(),
)
.withReuse(true)

// Only copy the file to the container if apiSpecPath is a local file
if (!isUrl) {
try {
val file = File(apiSpecPath)
if (file.exists()) {
container.withCopyToContainer(
MountableFile.forHostPath(apiSpecPath),
API_SPEC_PATH,
)
} else {
println("OpenAPI spec file not found at: $apiSpecPath")
throw RuntimeException("OpenAPI spec file not found at: $apiSpecPath")
}
} catch (e: Exception) {
println("Error reading OpenAPI spec file: ${e.message}")
throw RuntimeException("Error reading OpenAPI spec file: $apiSpecPath", e)
}
}

// Add waiting strategy
container.waitingFor(
Wait.forLogMessage(".*Prism is listening.*", 1)
.withStartupTimeout(Duration.ofSeconds(300))
)

With npm:
$ npm install -g @stoplight/prism-cli
// Start the container here once during lazy initialization
container.start()
containerStarted = true
println(
"Prism container started at: ${container.host}:${container.getMappedPort(INTERNAL_PORT)}"
)

With yarn:
$ yarn global add @stoplight/prism-cli
container
}

2. Run the mock server
// Method to ensure container is started, can be called from beforeAll
fun ensureContainerStarted() {
if (!containerStarted) {
// This will trigger lazy initialization and start the container
prismContainer
}
}
}

To run the server, pass in the path of your OpenAPI spec to the prism command:
$ prism mock path/to/your.openapi.yml
override fun beforeAll(context: ExtensionContext?) {
try {
// Use the companion method to ensure container is started only once
ensureContainerStarted()
} catch (e: Exception) {
throw RuntimeException(
"""
Failed to connect to Prism mock server running in TestContainer.

You can set the environment variable `SKIP_MOCK_TESTS` to `true` to skip running any tests
that require the mock server.

You may also need to set `OPENAPI_SPEC_PATH` to the path of your OpenAPI spec file.
"""
.trimIndent(),
e,
Expand All @@ -52,11 +156,4 @@ class TestServerExtension : BeforeAllCallback, ExecutionCondition {
)
}
}

companion object {

val BASE_URL = System.getenv("TEST_API_BASE_URL") ?: "http://localhost:4010"

const val SKIP_TESTS_ENV: String = "SKIP_MOCK_TESTS"
}
}
41 changes: 0 additions & 41 deletions scripts/mock

This file was deleted.

48 changes: 0 additions & 48 deletions scripts/test
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,5 @@ set -e

cd "$(dirname "$0")/.."

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color

function prism_is_running() {
curl --silent "http://localhost:4010" >/dev/null 2>&1
}

kill_server_on_port() {
pids=$(lsof -t -i tcp:"$1" || echo "")
if [ "$pids" != "" ]; then
kill "$pids"
echo "Stopped $pids."
fi
}

function is_overriding_api_base_url() {
[ -n "$TEST_API_BASE_URL" ]
}

if ! is_overriding_api_base_url && ! prism_is_running ; then
# When we exit this script, make sure to kill the background mock server process
trap 'kill_server_on_port 4010' EXIT

# Start the dev server
./scripts/mock --daemon
fi

if is_overriding_api_base_url ; then
echo -e "${GREEN}✔ Running tests against ${TEST_API_BASE_URL}${NC}"
echo
elif ! prism_is_running ; then
echo -e "${RED}ERROR:${NC} The test suite will not run without a mock Prism server"
echo -e "running against your OpenAPI spec."
echo
echo -e "To run the server, pass in the path or url of your OpenAPI"
echo -e "spec to the prism command:"
echo
echo -e " \$ ${YELLOW}npm exec --package=@stoplight/prism-cli@~5.3.2 -- prism mock path/to/your.openapi.yml${NC}"
echo

exit 1
else
echo -e "${GREEN}✔ Mock prism server is running with your OpenAPI spec${NC}"
echo
fi

echo "==> Running tests"
./gradlew test
Loading