Isolate realtime conversation tests from Responses prewarm connections (#49275)

## Why

Responses prewarm can reconnect and consume scripted WebSocket replies intended for realtime conversations when both share a test server.

## What changed

- Give realtime conversations a separate scripted server in tests for runtime replacement, prompts, and voice selection.
- Disable Responses WebSocket prewarm in the audio/text round-trip test while preserving coverage of the default realtime endpoint.
- Update connection counts and request assertions to address only realtime connections.

GitOrigin-RevId: 8e3b5ac86ee80777cf94063db468c80203966999
This commit is contained in:
felixxia-oai
2026-09-29 14:45:09 +00:00
committed by copyberry
parent 0b1b78a4f1
commit 677b07eca0
+139 -104
View File
@@ -272,41 +272,45 @@ async fn seed_recent_thread(
async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_websocket_server(vec![
let api_server = start_mock_server().await;
let server = start_websocket_server(vec![vec![
vec![json!({
"type": "session.updated",
"session": { "id": "sess_1", "instructions": "backend prompt" }
})],
vec![],
vec![
vec![json!({
"type": "session.updated",
"session": { "id": "sess_1", "instructions": "backend prompt" }
})],
vec![],
vec![
json!({
"type": "conversation.output_audio.delta",
"delta": "AQID",
"sample_rate": 24000,
"channels": 1
}),
json!({
"type": "conversation.item.added",
"item": {
"type": "message",
"role": "assistant",
"content": [{"type": "text", "text": "hi"}]
}
}),
],
json!({
"type": "conversation.output_audio.delta",
"delta": "AQID",
"sample_rate": 24000,
"channels": 1
}),
json!({
"type": "conversation.item.added",
"item": {
"type": "message",
"role": "assistant",
"content": [{"type": "text", "text": "hi"}]
}
}),
],
])
]])
.await;
let mut builder = test_codex();
let test = builder.build_with_websocket_server(&server).await?;
assert!(
server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
let mut builder = test_codex().with_config({
let provider_base_url = format!("{}/v1", server.uri());
move |config| {
// Exercise the default Realtime endpoint without Responses prewarm
// connections competing for this server's scripted replies.
config.model_provider.base_url = Some(provider_base_url);
config.model_provider.supports_websockets = false;
config.experimental_realtime_ws_base_url = None;
config.experimental_realtime_ws_model = Some("realtime-test-model".to_string());
config.realtime.version = RealtimeWsVersion::V1;
}
});
let test = builder.build_with_auto_env(&api_server).await?;
test.codex
.submit(Op::RealtimeConversationStart(ConversationStartParams {
@@ -384,8 +388,8 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
assert_eq!(audio_out.data, "AQID");
let connections = server.connections();
assert_eq!(connections.len(), 2);
let connection = &connections[1];
assert_eq!(connections.len(), 1);
let connection = &connections[0];
assert_eq!(connection.len(), 3);
assert_eq!(
connection[0].body_json()["type"].as_str(),
@@ -399,7 +403,7 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
.expect("initial session update instructions");
assert!(initial_instructions.starts_with("backend prompt"));
assert_eq!(
server.handshakes()[1]
server.handshakes()[0]
.header("x-session-id")
.expect("session.update x-session-id header"),
started
@@ -408,11 +412,11 @@ async fn conversation_start_audio_text_close_round_trip() -> Result<()> {
.expect("started session id should be present")
);
assert_eq!(
server.handshakes()[1].header("authorization").as_deref(),
server.handshakes()[0].header("authorization").as_deref(),
Some("Bearer dummy")
);
assert_eq!(
server.handshakes()[1].uri(),
server.handshakes()[0].uri(),
"/v1/realtime?intent=quicksilver&model=realtime-test-model"
);
let mut request_types = [
@@ -2497,8 +2501,9 @@ async fn conversation_text_before_start_emits_error() -> Result<()> {
async fn conversation_second_start_replaces_runtime() -> Result<()> {
skip_if_no_network!(Ok(()));
// Responses prewarm can reconnect; give realtime its own scripted connections.
let startup_server = start_websocket_server(vec![vec![]]).await;
let server = start_websocket_server(vec![
vec![],
vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_old", "instructions": "old" }
@@ -2517,10 +2522,15 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
],
])
.await;
let mut builder = test_codex();
let test = builder.build_with_websocket_server(&server).await?;
let mut builder = test_codex().with_config({
let realtime_base_url = server.uri().to_string();
move |config| {
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
}
});
let test = builder.build_with_websocket_server(&startup_server).await?;
assert!(
server
startup_server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
@@ -2621,28 +2631,29 @@ async fn conversation_second_start_replaces_runtime() -> Result<()> {
.await;
let connections = server.connections();
assert_eq!(connections.len(), 3);
assert_eq!(connections[1].len(), 1);
assert_eq!(connections.len(), 2);
assert_eq!(connections[0].len(), 1);
let old_instructions =
websocket_request_instructions(&connections[1][0]).expect("old session instructions");
websocket_request_instructions(&connections[0][0]).expect("old session instructions");
assert!(old_instructions.starts_with("old"));
assert_eq!(
server.handshakes()[1].header("x-session-id").as_deref(),
server.handshakes()[0].header("x-session-id").as_deref(),
Some("conv_old")
);
assert_eq!(connections[2].len(), 2);
assert_eq!(connections[1].len(), 2);
let new_instructions =
websocket_request_instructions(&connections[2][0]).expect("new session instructions");
websocket_request_instructions(&connections[1][0]).expect("new session instructions");
assert!(new_instructions.starts_with("new"));
assert_eq!(
server.handshakes()[2].header("x-session-id").as_deref(),
server.handshakes()[1].header("x-session-id").as_deref(),
Some("conv_new")
);
assert_eq!(
connections[2][1].body_json()["type"].as_str(),
connections[1][1].body_json()["type"].as_str(),
Some("input_audio_buffer.append")
);
startup_server.shutdown().await;
server.shutdown().await;
Ok(())
}
@@ -2729,22 +2740,25 @@ async fn conversation_uses_experimental_realtime_ws_base_url_override() -> Resul
async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_websocket_server(vec![
vec![],
vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_default", "instructions": "default" }
})]],
])
// Responses prewarm can reconnect; give realtime its own scripted connections.
let startup_server = start_websocket_server(vec![vec![]]).await;
let server = start_websocket_server(vec![vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_default", "instructions": "default" }
})]]])
.await;
let mut builder = test_codex().with_config(|config| {
config.experimental_realtime_ws_startup_context =
Some("controlled startup context".to_string());
let mut builder = test_codex().with_config({
let realtime_base_url = server.uri().to_string();
move |config| {
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
config.experimental_realtime_ws_startup_context =
Some("controlled startup context".to_string());
}
});
let test = builder.build_with_websocket_server(&server).await?;
let test = builder.build_with_websocket_server(&startup_server).await?;
assert!(
server
startup_server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
@@ -2788,9 +2802,9 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
assert_eq!(session_updated, "sess_default");
let connections = server.connections();
assert_eq!(connections.len(), 2);
assert_eq!(connections.len(), 1);
let instructions =
websocket_request_instructions(&connections[1][0]).expect("default session instructions");
websocket_request_instructions(&connections[0][0]).expect("default session instructions");
assert_eq!(
instructions,
format!(
@@ -2799,6 +2813,7 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
)
);
startup_server.shutdown().await;
server.shutdown().await;
Ok(())
}
@@ -2807,8 +2822,9 @@ async fn conversation_uses_default_realtime_backend_prompt() -> Result<()> {
async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Result<()> {
skip_if_no_network!(Ok(()));
// Responses prewarm can reconnect; give realtime its own scripted connections.
let startup_server = start_websocket_server(vec![vec![]]).await;
let server = start_websocket_server(vec![
vec![],
vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_null", "instructions": "" }
@@ -2820,12 +2836,16 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
])
.await;
let mut builder = test_codex().with_config(|config| {
config.experimental_realtime_ws_startup_context = Some(String::new());
let mut builder = test_codex().with_config({
let realtime_base_url = server.uri().to_string();
move |config| {
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
config.experimental_realtime_ws_startup_context = Some(String::new());
}
});
let test = builder.build_with_websocket_server(&server).await?;
let test = builder.build_with_websocket_server(&startup_server).await?;
assert!(
server
startup_server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
@@ -2881,14 +2901,15 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
}
let connections = server.connections();
assert_eq!(connections.len(), 3);
assert_eq!(connections.len(), 2);
let null_instructions =
websocket_request_instructions(&connections[1][0]).expect("null prompt instructions");
websocket_request_instructions(&connections[0][0]).expect("null prompt instructions");
let empty_instructions =
websocket_request_instructions(&connections[2][0]).expect("empty prompt instructions");
websocket_request_instructions(&connections[1][0]).expect("empty prompt instructions");
assert_eq!(null_instructions, "");
assert_eq!(empty_instructions, "");
startup_server.shutdown().await;
server.shutdown().await;
Ok(())
}
@@ -2897,17 +2918,22 @@ async fn conversation_uses_empty_instructions_for_null_or_empty_prompt() -> Resu
async fn conversation_uses_explicit_start_voice() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_websocket_server(vec![
vec![],
vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_voice", "instructions": "backend prompt" }
})]],
])
// Responses prewarm can reconnect; give realtime its own scripted connections.
let startup_server = start_websocket_server(vec![vec![]]).await;
let server = start_websocket_server(vec![vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_voice", "instructions": "backend prompt" }
})]]])
.await;
let test = test_codex().build_with_websocket_server(&server).await?;
let mut builder = test_codex().with_config({
let realtime_base_url = server.uri().to_string();
move |config| {
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
}
});
let test = builder.build_with_websocket_server(&startup_server).await?;
assert!(
server
startup_server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
@@ -2952,10 +2978,11 @@ async fn conversation_uses_explicit_start_voice() -> Result<()> {
let connections = server.connections();
assert_eq!(
connections[1][0].body_json()["session"]["audio"]["output"]["voice"],
connections[0][0].body_json()["session"]["audio"]["output"]["voice"],
"breeze"
);
startup_server.shutdown().await;
server.shutdown().await;
Ok(())
}
@@ -2964,20 +2991,23 @@ async fn conversation_uses_explicit_start_voice() -> Result<()> {
async fn conversation_uses_configured_realtime_voice() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_websocket_server(vec![
vec![],
vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_config_voice", "instructions": "backend prompt" }
})]],
])
// Responses prewarm can reconnect; give realtime its own scripted connections.
let startup_server = start_websocket_server(vec![vec![]]).await;
let server = start_websocket_server(vec![vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_config_voice", "instructions": "backend prompt" }
})]]])
.await;
let mut builder = test_codex().with_config(|config| {
config.realtime.voice = Some(RealtimeVoice::Cove);
let mut builder = test_codex().with_config({
let realtime_base_url = server.uri().to_string();
move |config| {
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
config.realtime.voice = Some(RealtimeVoice::Cove);
}
});
let test = builder.build_with_websocket_server(&server).await?;
let test = builder.build_with_websocket_server(&startup_server).await?;
assert!(
server
startup_server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
@@ -3022,10 +3052,11 @@ async fn conversation_uses_configured_realtime_voice() -> Result<()> {
let connections = server.connections();
assert_eq!(
connections[1][0].body_json()["session"]["audio"]["output"]["voice"],
connections[0][0].body_json()["session"]["audio"]["output"]["voice"],
"cove"
);
startup_server.shutdown().await;
server.shutdown().await;
Ok(())
}
@@ -3080,21 +3111,24 @@ async fn conversation_rejects_voice_for_wrong_realtime_version() -> Result<()> {
async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_websocket_server(vec![
vec![],
vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_override", "instructions": "prompt from config" }
})]],
])
// Responses prewarm can reconnect; give realtime its own scripted connections.
let startup_server = start_websocket_server(vec![vec![]]).await;
let server = start_websocket_server(vec![vec![vec![json!({
"type": "session.updated",
"session": { "id": "sess_override", "instructions": "prompt from config" }
})]]])
.await;
let mut builder = test_codex().with_config(|config| {
config.experimental_realtime_ws_backend_prompt = Some("prompt from config".to_string());
let mut builder = test_codex().with_config({
let realtime_base_url = server.uri().to_string();
move |config| {
config.experimental_realtime_ws_base_url = Some(realtime_base_url);
config.experimental_realtime_ws_backend_prompt = Some("prompt from config".to_string());
}
});
let test = builder.build_with_websocket_server(&server).await?;
let test = builder.build_with_websocket_server(&startup_server).await?;
assert!(
server
startup_server
.wait_for_handshakes(/*expected*/ 1, Duration::from_secs(2))
.await
);
@@ -3138,11 +3172,12 @@ async fn conversation_uses_experimental_realtime_ws_backend_prompt_override() ->
assert_eq!(session_updated, "sess_override");
let connections = server.connections();
assert_eq!(connections.len(), 2);
let overridden_instructions = websocket_request_instructions(&connections[1][0])
assert_eq!(connections.len(), 1);
let overridden_instructions = websocket_request_instructions(&connections[0][0])
.expect("overridden session instructions");
assert!(overridden_instructions.starts_with("prompt from config"));
startup_server.shutdown().await;
server.shutdown().await;
Ok(())
}