diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 3174ce5ecd..ce362f0978 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -887,7 +887,6 @@ "enum-as-inner_0.6.1": "{\"dependencies\":[{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", "enumflags2_0.7.12": "{\"dependencies\":[{\"name\":\"enumflags2_derive\",\"req\":\"=0.7.12\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.0\"}],\"features\":{\"std\":[]}}", "enumflags2_derive_0.7.12": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"default_features\":false,\"features\":[\"parsing\",\"printing\",\"derive\",\"proc-macro\"],\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{}}", - "env-flags_0.1.1": "{\"dependencies\":[],\"features\":{}}", "env_filter_1.0.0": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.8\"},{\"default_features\":false,\"features\":[\"std\",\"perf\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6\"}],\"features\":{\"default\":[\"regex\"],\"regex\":[\"dep:regex\"]}}", "env_filter_1.0.1": "{\"dependencies\":[{\"features\":[\"std\"],\"name\":\"log\",\"req\":\"^0.4.29\"},{\"default_features\":false,\"features\":[\"std\",\"perf\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.12.3\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"regex\"],\"regex\":[\"dep:regex\"]}}", "env_home_0.1.0": "{\"dependencies\":[],\"features\":{}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index dbd754435f..10b1042be3 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -2536,7 +2536,6 @@ dependencies = [ "ctor 0.6.3", "dirs", "dunce", - "env-flags", "eventsource-stream", "futures", "http 1.4.0", @@ -3791,6 +3790,7 @@ dependencies = [ "codex-utils-string", "codex-windows-sandbox", "color-eyre", + "core_test_support", "cpal", "crossterm", "derive_more 2.1.1", @@ -3841,6 +3841,7 @@ dependencies = [ "which 8.0.0", "windows-sys 0.52.0", "winsplit", + "wiremock", ] [[package]] @@ -5389,12 +5390,6 @@ dependencies = [ "syn 2.0.114", ] -[[package]] -name = "env-flags" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbfd0e7fc632dec5e6c9396a27bc9f9975b4e039720e1fd3e34021d3ce28c415" - [[package]] name = "env_filter" version = "1.0.0" diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index d8251598de..612ca8a7ea 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -280,7 +280,6 @@ dotenvy = "0.15.7" dunce = "1.0.4" ed25519-dalek = { version = "2.2.0", features = ["pkcs8"] } encoding_rs = "0.8.35" -env-flags = "0.1.1" env_logger = "0.11.9" eventsource-stream = "0.2.3" flate2 = "1.1.8" diff --git a/codex-rs/codex-api/src/lib.rs b/codex-rs/codex-api/src/lib.rs index e6f097db38..d7017c8d3f 100644 --- a/codex-rs/codex-api/src/lib.rs +++ b/codex-rs/codex-api/src/lib.rs @@ -63,7 +63,6 @@ pub use crate::provider::Provider; pub use crate::provider::RetryConfig; pub use crate::provider::is_azure_responses_provider; pub use crate::requests::Compression; -pub use crate::sse::stream_from_fixture; pub use crate::telemetry::SseTelemetry; pub use crate::telemetry::WebsocketTelemetry; pub use codex_protocol::protocol::RealtimeAudioFrame; diff --git a/codex-rs/codex-api/src/sse/mod.rs b/codex-rs/codex-api/src/sse/mod.rs index 06b9855890..441078bdcb 100644 --- a/codex-rs/codex-api/src/sse/mod.rs +++ b/codex-rs/codex-api/src/sse/mod.rs @@ -3,4 +3,3 @@ pub(crate) mod responses; pub(crate) use responses::ResponsesStreamEvent; pub(crate) use responses::process_responses_event; pub use responses::spawn_response_stream; -pub use responses::stream_from_fixture; diff --git a/codex-rs/codex-api/src/sse/responses.rs b/codex-rs/codex-api/src/sse/responses.rs index 4e949e43ae..c9f35e5a4e 100644 --- a/codex-rs/codex-api/src/sse/responses.rs +++ b/codex-rs/codex-api/src/sse/responses.rs @@ -5,24 +5,19 @@ use crate::rate_limits::parse_all_rate_limits; use crate::telemetry::SseTelemetry; use codex_client::ByteStream; use codex_client::StreamResponse; -use codex_client::TransportError; use codex_protocol::models::ResponseItem; use codex_protocol::protocol::ModelVerification; use codex_protocol::protocol::TokenUsage; use eventsource_stream::Eventsource; use futures::StreamExt; -use futures::TryStreamExt; use serde::Deserialize; use serde_json::Value; -use std::io::BufRead; -use std::path::Path; use std::sync::Arc; use std::sync::OnceLock; use std::time::Duration; use tokio::sync::mpsc; use tokio::time::Instant; use tokio::time::timeout; -use tokio_util::io::ReaderStream; use tracing::debug; use tracing::trace; @@ -31,35 +26,6 @@ const OPENAI_MODEL_HEADER: &str = "openai-model"; const REQUEST_ID_HEADER: &str = "x-request-id"; const TRUSTED_ACCESS_FOR_CYBER_VERIFICATION: &str = "trusted_access_for_cyber"; -/// Streams SSE events from an on-disk fixture for tests. -pub fn stream_from_fixture( - path: impl AsRef, - idle_timeout: Duration, -) -> Result { - let file = - std::fs::File::open(path.as_ref()).map_err(|err| ApiError::Stream(err.to_string()))?; - let mut content = String::new(); - for line in std::io::BufReader::new(file).lines() { - let line = line.map_err(|err| ApiError::Stream(err.to_string()))?; - content.push_str(&line); - content.push_str("\n\n"); - } - - let reader = std::io::Cursor::new(content); - let stream = ReaderStream::new(reader).map_err(|err| TransportError::Network(err.to_string())); - let (tx_event, rx_event) = mpsc::channel::>(1600); - tokio::spawn(process_sse( - Box::pin(stream), - tx_event, - idle_timeout, - /*telemetry*/ None, - )); - Ok(ResponseStream { - rx_event, - upstream_request_id: None, - }) -} - pub fn spawn_response_stream( stream_response: StreamResponse, idle_timeout: Duration, @@ -593,8 +559,10 @@ mod tests { use assert_matches::assert_matches; use bytes::Bytes; use codex_client::StreamResponse; + use codex_client::TransportError; use codex_protocol::models::MessagePhase; use codex_protocol::models::ResponseItem; + use futures::TryStreamExt; use futures::stream; use http::HeaderMap; use http::HeaderValue; @@ -603,6 +571,7 @@ mod tests { use serde_json::json; use tokio::sync::mpsc; use tokio_test::io::Builder as IoBuilder; + use tokio_util::io::ReaderStream; async fn collect_events(chunks: &[&[u8]]) -> Vec> { let mut builder = IoBuilder::new(); diff --git a/codex-rs/core/BUILD.bazel b/codex-rs/core/BUILD.bazel index e826027cba..361e958e86 100644 --- a/codex-rs/core/BUILD.bazel +++ b/codex-rs/core/BUILD.bazel @@ -1,13 +1,5 @@ load("//:defs.bzl", "codex_rust_crate") -filegroup( - name = "model_availability_nux_fixtures", - srcs = [ - "tests/cli_responses_fixture.sse", - ], - visibility = ["//visibility:public"], -) - codex_rust_crate( name = "core", crate_name = "codex_core", diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index ec82a53d08..28e8eaed93 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -77,7 +77,6 @@ codex-windows-sandbox = { package = "codex-windows-sandbox", path = "../windows- csv = { workspace = true } dirs = { workspace = true } dunce = { workspace = true } -env-flags = { workspace = true } eventsource-stream = { workspace = true } futures = { workspace = true } http = { workspace = true } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 545d131c69..f604a63458 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -112,7 +112,6 @@ use crate::client_common::Prompt; use crate::client_common::ResponseEvent; use crate::client_common::ResponseStream; use crate::feedback_tags; -use crate::flags::CODEX_RS_SSE_FIXTURE; use crate::util::emit_feedback_auth_recovery_tags; use codex_api::map_api_error; use codex_feedback::FeedbackRequestTags; @@ -772,7 +771,6 @@ impl ModelClient { pub fn responses_websocket_enabled(&self) -> bool { if !self.state.provider.info().supports_websockets || self.state.disable_websockets.load(Ordering::Relaxed) - || (*CODEX_RS_SSE_FIXTURE).is_some() { return false; } @@ -1194,8 +1192,7 @@ impl ModelClientSession { /// Streams a turn via the OpenAI Responses API. /// - /// Handles SSE fixtures, reasoning summaries, verbosity, and the - /// `text` controls used for output schemas. + /// Handles reasoning summaries, verbosity, and the `text` controls used for output schemas. #[allow(clippy::too_many_arguments)] #[instrument( name = "model_client.stream_responses_api", @@ -1221,21 +1218,6 @@ impl ModelClientSession { turn_metadata_header: Option<&str>, inference_trace: &InferenceTraceContext, ) -> Result { - if let Some(path) = &*CODEX_RS_SSE_FIXTURE { - warn!(path, "Streaming from fixture"); - let stream = codex_api::stream_from_fixture( - path, - self.client.state.provider.info().stream_idle_timeout(), - ) - .map_err(map_api_error)?; - let (stream, _last_request_rx) = map_response_stream( - stream, - session_telemetry.clone(), - InferenceTraceAttempt::disabled(), - ); - return Ok(stream); - } - let auth_manager = self.client.state.provider.auth_manager(); let mut auth_recovery = auth_manager .as_ref() diff --git a/codex-rs/core/src/config/config_tests.rs b/codex-rs/core/src/config/config_tests.rs index 76520d23e7..481e03b98a 100644 --- a/codex-rs/core/src/config/config_tests.rs +++ b/codex-rs/core/src/config/config_tests.rs @@ -7250,8 +7250,7 @@ model_verbosity = "high" /// 2. as part of a profile, where the `--profile` is specified via a CLI /// (or in the config file itself) /// 3. as an entry in `config.toml`, e.g. `model = "o3"` -/// 4. the default value for a required field defined in code, e.g., -/// `crate::flags::OPENAI_DEFAULT_MODEL` +/// 4. the default value for a required field defined in code. /// /// Note that profiles are the recommended way to specify a group of /// configuration options together. diff --git a/codex-rs/core/src/flags.rs b/codex-rs/core/src/flags.rs deleted file mode 100644 index de693868df..0000000000 --- a/codex-rs/core/src/flags.rs +++ /dev/null @@ -1,6 +0,0 @@ -use env_flags::env_flags; - -env_flags! { - /// Fixture path for offline tests (see client.rs). - pub CODEX_RS_SSE_FIXTURE: Option<&str> = None; -} diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 3ec34694cb..b449227108 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -35,7 +35,6 @@ mod environment_selection; pub mod exec; pub mod exec_env; mod exec_policy; -mod flags; #[cfg(test)] mod git_info_tests; mod goals; diff --git a/codex-rs/core/tests/cli_responses_fixture.sse b/codex-rs/core/tests/cli_responses_fixture.sse deleted file mode 100644 index d297ebafb2..0000000000 --- a/codex-rs/core/tests/cli_responses_fixture.sse +++ /dev/null @@ -1,8 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"id":"resp1"}} - -event: response.output_item.done -data: {"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"fixture hello"}]}} - -event: response.completed -data: {"type":"response.completed","response":{"id":"resp1","output":[]}} diff --git a/codex-rs/core/tests/common/BUILD.bazel b/codex-rs/core/tests/common/BUILD.bazel index 983a2012b0..f41a727b72 100644 --- a/codex-rs/core/tests/common/BUILD.bazel +++ b/codex-rs/core/tests/common/BUILD.bazel @@ -4,9 +4,6 @@ codex_rust_crate( name = "common", crate_name = "core_test_support", crate_srcs = glob(["*.rs"]), - lib_data_extra = [ - "//codex-rs/core:model_availability_nux_fixtures", - ], deps_extra = [ "@crates//:similar", ], diff --git a/codex-rs/core/tests/suite/cli_stream.rs b/codex-rs/core/tests/suite/cli_stream.rs index 156dd4a817..bcf4919737 100644 --- a/codex-rs/core/tests/suite/cli_stream.rs +++ b/codex-rs/core/tests/suite/cli_stream.rs @@ -2,7 +2,6 @@ use assert_cmd::Command as AssertCommand; use codex_git_utils::collect_git_info; use codex_login::CODEX_API_KEY_ENV_VAR; use codex_protocol::protocol::GitInfo; -use codex_utils_cargo_bin::find_resource; use core_test_support::fs_wait; use core_test_support::responses; use core_test_support::skip_if_no_network; @@ -16,9 +15,12 @@ fn repo_root() -> std::path::PathBuf { codex_utils_cargo_bin::repo_root().expect("failed to resolve repo root") } -fn cli_responses_fixture() -> std::path::PathBuf { - #[expect(clippy::expect_used)] - find_resource!("tests/cli_responses_fixture.sse").expect("failed to resolve fixture path") +fn cli_sse_response() -> String { + responses::sse(vec![ + responses::ev_response_created("resp-fixture"), + responses::ev_assistant_message("msg-fixture", "fixture hello"), + responses::ev_completed("resp-fixture"), + ]) } /// Tests streaming the Responses API through the CLI using a mock server. @@ -262,37 +264,36 @@ async fn exec_cli_profile_applies_model_instructions_file() { ); } -/// Tests streaming responses through the CLI using a local SSE fixture file. -/// This test: -/// 1. Uses a pre-recorded SSE response fixture instead of a live server -/// 2. Configures codex to read from this fixture via CODEX_RS_SSE_FIXTURE env var -/// 3. Sends a "hello?" prompt and verifies the response -/// 4. Ensures the fixture content is correctly streamed through the CLI +/// Tests streaming responses through the CLI using a local Responses API server. #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn responses_api_stream_cli() { skip_if_no_network!(); - let fixture = cli_responses_fixture(); + let server = MockServer::start().await; + let resp_mock = responses::mount_sse_once(&server, cli_sse_response()).await; let repo_root = repo_root(); let home = TempDir::new().unwrap(); let bin = codex_utils_cargo_bin::cargo_bin("codex").unwrap(); let mut cmd = AssertCommand::new(bin); + cmd.timeout(Duration::from_secs(30)); cmd.arg("exec") .arg("--skip-git-repo-check") .arg("-c") - .arg("openai_base_url=\"http://unused.local\"") + .arg(format!("openai_base_url=\"{}/v1\"", server.uri())) .arg("-C") .arg(&repo_root) .arg("hello?"); cmd.env("CODEX_HOME", home.path()) - .env("OPENAI_API_KEY", "dummy") - .env("CODEX_RS_SSE_FIXTURE", fixture); + .env("OPENAI_API_KEY", "dummy"); let output = cmd.output().unwrap(); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("fixture hello")); + + let request = resp_mock.single_request(); + assert_eq!(request.path(), "/v1/responses"); } /// End-to-end: create a session (writes rollout), verify the file, then resume and confirm append. @@ -308,23 +309,25 @@ async fn integration_creates_and_checks_session_file() -> anyhow::Result<()> { let marker = format!("integration-test-{}", Uuid::new_v4()); let prompt = format!("echo {marker}"); - // 3. Use the same offline SSE fixture as responses_api_stream_cli so the test is hermetic. - let fixture = cli_responses_fixture(); + // 3. Serve two hermetic SSE responses, one for the initial run and one for resume. + let server = MockServer::start().await; + let resp_mock = + responses::mount_sse_sequence(&server, vec![cli_sse_response(), cli_sse_response()]).await; let repo_root = repo_root(); // 4. Run the codex CLI and invoke `exec`, which is what records a session. let bin = codex_utils_cargo_bin::cargo_bin("codex").unwrap(); let mut cmd = AssertCommand::new(bin); + cmd.timeout(Duration::from_secs(30)); cmd.arg("exec") .arg("--skip-git-repo-check") .arg("-c") - .arg("openai_base_url=\"http://unused.local\"") + .arg(format!("openai_base_url=\"{}/v1\"", server.uri())) .arg("-C") .arg(&repo_root) .arg(&prompt); cmd.env("CODEX_HOME", home.path()) - .env(CODEX_API_KEY_ENV_VAR, "dummy") - .env("CODEX_RS_SSE_FIXTURE", &fixture); + .env(CODEX_API_KEY_ENV_VAR, "dummy"); let output = cmd.output().unwrap(); assert!( @@ -436,21 +439,22 @@ async fn integration_creates_and_checks_session_file() -> anyhow::Result<()> { let prompt2 = format!("echo {marker2}"); let bin2 = codex_utils_cargo_bin::cargo_bin("codex").unwrap(); let mut cmd2 = AssertCommand::new(bin2); + cmd2.timeout(Duration::from_secs(30)); cmd2.arg("exec") .arg("--skip-git-repo-check") .arg("-c") - .arg("openai_base_url=\"http://unused.local\"") + .arg(format!("openai_base_url=\"{}/v1\"", server.uri())) .arg("-C") .arg(&repo_root) .arg(&prompt2) .arg("resume") .arg("--last"); cmd2.env("CODEX_HOME", home.path()) - .env("OPENAI_API_KEY", "dummy") - .env("CODEX_RS_SSE_FIXTURE", &fixture); + .env("OPENAI_API_KEY", "dummy"); let output2 = cmd2.output().unwrap(); assert!(output2.status.success(), "resume codex-cli run failed"); + assert_eq!(resp_mock.requests().len(), 2); // Find the new session file containing the resumed marker. let marker2_clone = marker2.clone(); diff --git a/codex-rs/deny.toml b/codex-rs/deny.toml index a1ae5e96b3..ae710a6607 100644 --- a/codex-rs/deny.toml +++ b/codex-rs/deny.toml @@ -120,7 +120,7 @@ allow = [ # Used by: transitive only "ISC", # MIT - https://opensource.org/license/mit - # Used by: allocative, ansi-to-tui, anyhow, arboard, assert_cmd, assert_matches, async-channel, async-stream, async-trait, axum, base64, bytes, chardetng, chrono, clap, clap_complete, color-eyre, crossterm, ctor, derive_more, diffy, dirs, dotenvy, encoding_rs, env-flags, env_logger, escargot, eventsource-stream, futures, http, ignore, image, indexmap, itertools, keyring, landlock, lazy_static, libc, log, lru, maplit, mime_guess, multimap, once_cell, openssl-sys, os_info, owo-colors, path-absolutize, pathdiff, portable-pty, predicates, pretty_assertions, pulldown-cmark, rand, ratatui, ratatui-macros, regex-lite, reqwest, rmcp, schemars, serde, serde_json, serde_with, serial_test, sha1, sha2, shlex, socket2, strum, strum_macros, sys-locale, tempfile, test-log, textwrap, thiserror, time, tiny_http, tokio, tokio-stream, tokio-test, tokio-util, toml, toml_edit, tonic, tracing, tracing-appender, tracing-subscriber, tracing-test, tree-sitter, tree-sitter-bash, tree-sitter-highlight, ts-rs, uds_windows, unicode-segmentation, unicode-width, url, urlencoding, uuid, vt100, walkdir, webbrowser, which, wildmatch, wiremock, zeroize + # Used by: allocative, ansi-to-tui, anyhow, arboard, assert_cmd, assert_matches, async-channel, async-stream, async-trait, axum, base64, bytes, chardetng, chrono, clap, clap_complete, color-eyre, crossterm, ctor, derive_more, diffy, dirs, dotenvy, encoding_rs, env_logger, escargot, eventsource-stream, futures, http, ignore, image, indexmap, itertools, keyring, landlock, lazy_static, libc, log, lru, maplit, mime_guess, multimap, once_cell, openssl-sys, os_info, owo-colors, path-absolutize, pathdiff, portable-pty, predicates, pretty_assertions, pulldown-cmark, rand, ratatui, ratatui-macros, regex-lite, reqwest, rmcp, schemars, serde, serde_json, serde_with, serial_test, sha1, sha2, shlex, socket2, strum, strum_macros, sys-locale, tempfile, test-log, textwrap, thiserror, time, tiny_http, tokio, tokio-stream, tokio-test, tokio-util, toml, toml_edit, tonic, tracing, tracing-appender, tracing-subscriber, tracing-test, tree-sitter, tree-sitter-bash, tree-sitter-highlight, ts-rs, uds_windows, unicode-segmentation, unicode-width, url, urlencoding, uuid, vt100, walkdir, webbrowser, which, wildmatch, wiremock, zeroize "MIT", # MIT-0 - https://opensource.org/license/mit-0 # Used by: dunce diff --git a/codex-rs/exec/tests/fixtures/cli_responses_fixture.sse b/codex-rs/exec/tests/fixtures/cli_responses_fixture.sse deleted file mode 100644 index 660c56b1d8..0000000000 --- a/codex-rs/exec/tests/fixtures/cli_responses_fixture.sse +++ /dev/null @@ -1,10 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"id":"resp1"}} - -event: response.output_item.done -data: {"type":"response.output_item.done","item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"fixture hello"}]}} - -event: response.completed -data: {"type":"response.completed","response":{"id":"resp1","output":[]}} - - diff --git a/codex-rs/exec/tests/suite/ephemeral.rs b/codex-rs/exec/tests/suite/ephemeral.rs index e6921f53af..ee3016fa65 100644 --- a/codex-rs/exec/tests/suite/ephemeral.rs +++ b/codex-rs/exec/tests/suite/ephemeral.rs @@ -1,9 +1,19 @@ #![cfg(not(target_os = "windows"))] #![allow(clippy::expect_used, clippy::unwrap_used)] -use codex_utils_cargo_bin::find_resource; +use core_test_support::responses; +use core_test_support::skip_if_no_network; use core_test_support::test_codex_exec::test_codex_exec; use walkdir::WalkDir; +use wiremock::MockServer; + +fn exec_sse_response() -> String { + responses::sse(vec![ + responses::ev_response_created("resp-ephemeral"), + responses::ev_assistant_message("msg-ephemeral", "ephemeral response"), + responses::ev_completed("resp-ephemeral"), + ]) +} fn session_rollout_count(home_path: &std::path::Path) -> usize { let sessions_dir = home_path.join("sessions"); @@ -19,13 +29,15 @@ fn session_rollout_count(home_path: &std::path::Path) -> usize { .count() } -#[test] -fn persists_rollout_file_by_default() -> anyhow::Result<()> { - let test = test_codex_exec(); - let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?; +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn persists_rollout_file_by_default() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + let test = test_codex_exec(); + let server = MockServer::start().await; + let _response_mock = responses::mount_sse_once(&server, exec_sse_response()).await; + + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("default persistence behavior") .assert() @@ -35,13 +47,15 @@ fn persists_rollout_file_by_default() -> anyhow::Result<()> { Ok(()) } -#[test] -fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()> { - let test = test_codex_exec(); - let fixture = find_resource!("tests/fixtures/cli_responses_fixture.sse")?; +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn does_not_persist_rollout_file_in_ephemeral_mode() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + let test = test_codex_exec(); + let server = MockServer::start().await; + let _response_mock = responses::mount_sse_once(&server, exec_sse_response()).await; + + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("--ephemeral") .arg("ephemeral behavior") diff --git a/codex-rs/exec/tests/suite/resume.rs b/codex-rs/exec/tests/suite/resume.rs index cfaa7aa81a..2bf6416ccc 100644 --- a/codex-rs/exec/tests/suite/resume.rs +++ b/codex-rs/exec/tests/suite/resume.rs @@ -1,6 +1,7 @@ #![allow(clippy::unwrap_used, clippy::expect_used)] use anyhow::Context; -use codex_utils_cargo_bin::find_resource; +use core_test_support::responses; +use core_test_support::skip_if_no_network; use core_test_support::test_codex_exec::test_codex_exec; use pretty_assertions::assert_eq; use serde_json::Value; @@ -8,6 +9,7 @@ use std::string::ToString; use tempfile::TempDir; use uuid::Uuid; use walkdir::WalkDir; +use wiremock::MockServer; /// Utility: scan the sessions dir for a rollout file that contains `marker` /// in any response_item.message.content entry. Returns the absolute path. @@ -104,26 +106,41 @@ fn last_user_image_count(path: &std::path::Path) -> usize { last_count } -fn exec_fixture() -> anyhow::Result { - Ok(find_resource!("tests/fixtures/cli_responses_fixture.sse")?) -} - fn exec_repo_root() -> anyhow::Result { Ok(codex_utils_cargo_bin::repo_root()?) } -#[test] -fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> { +fn exec_sse_response(index: usize) -> String { + let response_id = format!("resp-exec-{index}"); + let message_id = format!("msg-exec-{index}"); + responses::sse(vec![ + responses::ev_response_created(&response_id), + responses::ev_assistant_message(&message_id, "exec response"), + responses::ev_completed(&response_id), + ]) +} + +async fn mount_exec_responses( + server: &MockServer, + count: usize, +) -> core_test_support::responses::ResponseMock { + responses::mount_sse_sequence(server, (0..count).map(exec_sse_response).collect()).await +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 2).await; let repo_root = exec_repo_root()?; // 1) First run: create a session with a unique marker in the content. let marker = format!("resume-last-{}", Uuid::new_v4()); let prompt = format!("echo {marker}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -140,8 +157,7 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> { let marker2 = format!("resume-last-2-{}", Uuid::new_v4()); let prompt2 = format!("echo {marker2}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -164,18 +180,20 @@ fn exec_resume_last_appends_to_existing_file() -> anyhow::Result<()> { Ok(()) } -#[test] -fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 2).await; let repo_root = exec_repo_root()?; // 1) First run: create a session with a unique marker in the content. let marker = format!("resume-last-json-{}", Uuid::new_v4()); let prompt = format!("echo {marker}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -192,8 +210,7 @@ fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<( let marker2 = format!("resume-last-json-2-{}", Uuid::new_v4()); let prompt2 = format!("echo {marker2}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -216,18 +233,20 @@ fn exec_resume_last_accepts_prompt_after_flag_in_json_mode() -> anyhow::Result<( Ok(()) } -#[test] -fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 5).await; let dir_a = TempDir::new()?; let dir_b = TempDir::new()?; let marker_a = format!("resume-cwd-a-{}", Uuid::new_v4()); let prompt_a = format!("echo {marker_a}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(dir_a.path()) @@ -237,8 +256,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { let marker_b = format!("resume-cwd-b-{}", Uuid::new_v4()); let prompt_b = format!("echo {marker_b}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(dir_b.path()) @@ -260,8 +278,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { let session_id_b = extract_conversation_id(&path_b); let marker_b_touch = format!("resume-cwd-b-touch-{}", Uuid::new_v4()); let prompt_b_touch = format!("echo {marker_b_touch}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(dir_b.path()) @@ -278,8 +295,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { let marker_b2 = format!("resume-cwd-b-2-{}", Uuid::new_v4()); let prompt_b2 = format!("echo {marker_b2}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(dir_a.path()) @@ -299,8 +315,7 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { let marker_a2 = format!("resume-cwd-a-2-{}", Uuid::new_v4()); let prompt_a2 = format!("echo {marker_a2}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(dir_a.path()) @@ -323,24 +338,29 @@ fn exec_resume_last_respects_cwd_filter_and_all_flag() -> anyhow::Result<()> { Ok(()) } -#[test] -fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 2).await; // Seed a session. - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("echo seed-resume-session") .assert() .success(); // Resume while passing global flags after the subcommand to ensure clap accepts them. + let base = format!("{}/v1", server.uri()); + let base_config = format!("openai_base_url={}", serde_json::to_string(&base)?); test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) .arg("resume") .arg("--last") + .arg("--config") + .arg(base_config) .arg("--json") .arg("--model") .arg("gpt-5.2-codex") @@ -355,18 +375,20 @@ fn exec_resume_accepts_global_flags_after_subcommand() -> anyhow::Result<()> { Ok(()) } -#[test] -fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 2).await; let repo_root = exec_repo_root()?; // 1) First run: create a session let marker = format!("resume-by-id-{}", Uuid::new_v4()); let prompt = format!("echo {marker}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -387,8 +409,7 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> { let marker2 = format!("resume-by-id-2-{}", Uuid::new_v4()); let prompt2 = format!("echo {marker2}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -410,17 +431,19 @@ fn exec_resume_by_id_appends_to_existing_file() -> anyhow::Result<()> { Ok(()) } -#[test] -fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 2).await; let repo_root = exec_repo_root()?; let marker = format!("resume-config-{}", Uuid::new_v4()); let prompt = format!("echo {marker}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("--sandbox") .arg("workspace-write") @@ -440,8 +463,7 @@ fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> { let prompt2 = format!("echo {marker2}"); let output = test - .cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + .cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("--sandbox") .arg("workspace-write") @@ -484,17 +506,19 @@ fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> { Ok(()) } -#[test] -fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()> { +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()> { + skip_if_no_network!(Ok(())); + let test = test_codex_exec(); - let fixture = exec_fixture()?; + let server = MockServer::start().await; + let _response_mock = mount_exec_responses(&server, /*count*/ 2).await; let repo_root = exec_repo_root()?; let marker = format!("resume-image-{}", Uuid::new_v4()); let prompt = format!("echo {marker}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) @@ -516,8 +540,7 @@ fn exec_resume_accepts_images_after_subcommand() -> anyhow::Result<()> { let marker2 = format!("resume-image-2-{}", Uuid::new_v4()); let prompt2 = format!("echo {marker2}"); - test.cmd() - .env("CODEX_RS_SSE_FIXTURE", &fixture) + test.cmd_with_server(&server) .arg("--skip-git-repo-check") .arg("-C") .arg(&repo_root) diff --git a/codex-rs/tui/BUILD.bazel b/codex-rs/tui/BUILD.bazel index d0aab0805a..07495c4308 100644 --- a/codex-rs/tui/BUILD.bazel +++ b/codex-rs/tui/BUILD.bazel @@ -18,7 +18,7 @@ codex_rust_crate( test_data_extra = glob([ "src/**/*.rs", "src/**/snapshots/**", - ]) + ["//codex-rs/core:model_availability_nux_fixtures"], + ]), integration_compile_data_extra = ["src/test_backend.rs"], extra_binaries = [ "//codex-rs/cli:codex", diff --git a/codex-rs/tui/Cargo.toml b/codex-rs/tui/Cargo.toml index 92062f88ab..f539db5f5c 100644 --- a/codex-rs/tui/Cargo.toml +++ b/codex-rs/tui/Cargo.toml @@ -152,6 +152,7 @@ arboard = { workspace = true } [dev-dependencies] codex-cli = { workspace = true } codex-mcp = { workspace = true } +core_test_support = { workspace = true } codex-utils-cargo-bin = { workspace = true } codex-utils-pty = { workspace = true } assert_matches = { workspace = true } @@ -162,3 +163,4 @@ rand = { workspace = true } serial_test = { workspace = true } vt100 = { workspace = true } uuid = { workspace = true } +wiremock = { workspace = true } diff --git a/codex-rs/tui/tests/suite/model_availability_nux.rs b/codex-rs/tui/tests/suite/model_availability_nux.rs index 5a384e8280..836dfe7ea4 100644 --- a/codex-rs/tui/tests/suite/model_availability_nux.rs +++ b/codex-rs/tui/tests/suite/model_availability_nux.rs @@ -4,11 +4,14 @@ use std::time::Duration; use anyhow::Context; use anyhow::Result; use codex_models_manager::bundled_models_response; +use core_test_support::responses; +use core_test_support::skip_if_no_network; use serde_json::Value as JsonValue; use tempfile::tempdir; use tokio::select; use tokio::time::sleep; use tokio::time::timeout; +use wiremock::MockServer; #[tokio::test] async fn resume_startup_does_not_consume_model_availability_nux_count() -> Result<()> { @@ -16,6 +19,7 @@ async fn resume_startup_does_not_consume_model_availability_nux_count() -> Resul if cfg!(windows) { return Ok(()); } + skip_if_no_network!(Ok(())); let repo_root = codex_utils_cargo_bin::repo_root()?; let codex_home = tempdir()?; @@ -68,8 +72,14 @@ trust_level = "trusted" ); std::fs::write(codex_home.path().join("config.toml"), config_contents)?; - let fixture_path = - codex_utils_cargo_bin::find_resource!("../core/tests/cli_responses_fixture.sse")?; + let server = MockServer::start().await; + let sse = responses::sse(vec![ + responses::ev_response_created("resp-seed-session"), + responses::ev_assistant_message("msg-seed-session", "seed session response"), + responses::ev_completed("resp-seed-session"), + ]); + let _response_mock = responses::mount_sse_once(&server, sse).await; + let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri()); let codex = if let Ok(path) = codex_utils_cargo_bin::cargo_bin("codex") { path } else { @@ -85,12 +95,13 @@ trust_level = "trusted" let exec_output = std::process::Command::new(&codex) .arg("exec") .arg("--skip-git-repo-check") + .arg("-c") + .arg(&openai_base_url_config) .arg("-C") .arg(&repo_root) .arg("seed session for resume") .env("CODEX_HOME", codex_home.path()) .env("OPENAI_API_KEY", "dummy") - .env("CODEX_RS_SSE_FIXTURE", fixture_path) .output() .context("failed to execute codex exec")?; anyhow::ensure!( @@ -114,6 +125,8 @@ trust_level = "trusted" repo_root.display().to_string(), "-c".to_string(), "analytics.enabled=false".to_string(), + "-c".to_string(), + openai_base_url_config, ]; let spawned = codex_utils_pty::spawn_pty_process( diff --git a/codex-rs/tui/tests/suite/resize_reflow.rs b/codex-rs/tui/tests/suite/resize_reflow.rs index 53c1c5da94..af5ed445b2 100644 --- a/codex-rs/tui/tests/suite/resize_reflow.rs +++ b/codex-rs/tui/tests/suite/resize_reflow.rs @@ -8,14 +8,18 @@ use std::time::Instant; use anyhow::Context; use anyhow::Result; +use core_test_support::responses; +use core_test_support::skip_if_no_network; use tempfile::tempdir; +use wiremock::MockServer; -#[test] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore = "requires tmux and a locally built codex binary; run with --ignored for manual resize smoke"] -fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Result<()> { +async fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Result<()> { if cfg!(windows) { return Ok(()); } + skip_if_no_network!(Ok(())); if Command::new("tmux").arg("-V").output().is_err() { eprintln!("skipping resize smoke because tmux is unavailable"); return Ok(()); @@ -24,9 +28,9 @@ fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Resu let repo_root = codex_utils_cargo_bin::repo_root()?; let codex = codex_binary(&repo_root)?; let codex_home = tempdir()?; - let fixture_dir = tempdir()?; - let fixture = fixture_dir.path().join("resize-reflow.sse"); - write_fixture(&fixture)?; + let server = MockServer::start().await; + let _response_mock = responses::mount_sse_once(&server, resize_reflow_sse()).await; + let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri()); write_config( codex_home.path(), &repo_root, @@ -57,10 +61,11 @@ fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Resu .arg("env") .arg(format!("CODEX_HOME={}", codex_home.path().display())) .arg("OPENAI_API_KEY=dummy") - .arg(format!("CODEX_RS_SSE_FIXTURE={}", fixture.display())) .arg(codex) .arg("-c") .arg("analytics.enabled=false") + .arg("-c") + .arg(&openai_base_url_config) .arg("--no-alt-screen") .arg("-C") .arg(&repo_root) @@ -162,29 +167,31 @@ fn tmux_split_preserves_fresh_session_composer_row_after_resize_reflow() -> Resu Ok(()) } -#[test] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore = "requires tmux and a locally built codex binary; run with --ignored for manual resize smoke"] -fn tmux_repeated_resizes_do_not_push_composer_down() -> Result<()> { +async fn tmux_repeated_resizes_do_not_push_composer_down() -> Result<()> { if cfg!(windows) { return Ok(()); } + skip_if_no_network!(Ok(())); if Command::new("tmux").arg("-V").output().is_err() { eprintln!("skipping resize smoke because tmux is unavailable"); return Ok(()); } - run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ false)?; - run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ true)?; + run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ false).await?; + run_repeated_resize_smoke(/*terminal_resize_reflow_enabled*/ true).await?; Ok(()) } -#[test] +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[ignore = "requires tmux and a locally built codex binary; run with --ignored for manual resize smoke"] -fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> { +async fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> { if cfg!(windows) { return Ok(()); } + skip_if_no_network!(Ok(())); if Command::new("tmux").arg("-V").output().is_err() { eprintln!("skipping resize smoke because tmux is unavailable"); return Ok(()); @@ -193,9 +200,9 @@ fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> { let repo_root = codex_utils_cargo_bin::repo_root()?; let codex = codex_binary(&repo_root)?; let codex_home = tempdir()?; - let fixture_dir = tempdir()?; - let fixture = fixture_dir.path().join("resize-reflow.sse"); - write_fixture(&fixture)?; + let server = MockServer::start().await; + let _response_mock = responses::mount_sse_once(&server, resize_reflow_sse()).await; + let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri()); write_config( codex_home.path(), &repo_root, @@ -226,10 +233,11 @@ fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> { .arg("env") .arg(format!("CODEX_HOME={}", codex_home.path().display())) .arg("OPENAI_API_KEY=dummy") - .arg(format!("CODEX_RS_SSE_FIXTURE={}", fixture.display())) .arg(codex) .arg("-c") .arg("analytics.enabled=false") + .arg("-c") + .arg(&openai_base_url_config) .arg("--no-alt-screen") .arg("-C") .arg(&repo_root) @@ -312,13 +320,13 @@ fn tmux_width_resize_restore_keeps_visible_content_anchored() -> Result<()> { Ok(()) } -fn run_repeated_resize_smoke(terminal_resize_reflow_enabled: bool) -> Result<()> { +async fn run_repeated_resize_smoke(terminal_resize_reflow_enabled: bool) -> Result<()> { let repo_root = codex_utils_cargo_bin::repo_root()?; let codex = codex_binary(&repo_root)?; let codex_home = tempdir()?; - let fixture_dir = tempdir()?; - let fixture = fixture_dir.path().join("resize-reflow.sse"); - write_fixture(&fixture)?; + let server = MockServer::start().await; + let _response_mock = responses::mount_sse_once(&server, resize_reflow_sse()).await; + let openai_base_url_config = format!("openai_base_url=\"{}/v1\"", server.uri()); write_config( codex_home.path(), &repo_root, @@ -354,10 +362,11 @@ fn run_repeated_resize_smoke(terminal_resize_reflow_enabled: bool) -> Result<()> .arg("env") .arg(format!("CODEX_HOME={}", codex_home.path().display())) .arg("OPENAI_API_KEY=dummy") - .arg(format!("CODEX_RS_SSE_FIXTURE={}", fixture.display())) .arg(codex) .arg("-c") .arg("analytics.enabled=false") + .arg("-c") + .arg(&openai_base_url_config) .arg("--no-alt-screen") .arg("-C") .arg(&repo_root) @@ -510,33 +519,13 @@ fn write_auth(codex_home: &Path) -> Result<()> { Ok(()) } -fn write_fixture(path: &Path) -> Result<()> { +fn resize_reflow_sse() -> String { let text = "resize reflow sentinel says hi. This paragraph is intentionally long enough to exercise terminal wrapping, scrollback redraw, and pane resize behavior without requiring a live model response. It includes enough ordinary prose to wrap across several rows in a narrow tmux pane, then keep going so repeated split and restore cycles have visible history above the composer. If a resize path accidentally inserts blank rows or anchors the viewport lower on each pass, the composer row will drift after the pane returns to its original height."; - let created = serde_json::json!({ - "type": "response.created", - "response": { "id": "resp-resize-smoke" }, - }); - let done = serde_json::json!({ - "type": "response.output_item.done", - "item": { - "type": "message", - "role": "assistant", - "content": [ - { "type": "output_text", "text": text } - ], - }, - }); - let completed = serde_json::json!({ - "type": "response.completed", - "response": { "id": "resp-resize-smoke", "output": [] }, - }); - let fixture = format!( - "event: response.created\ndata: {created}\n\n\ - event: response.output_item.done\ndata: {done}\n\n\ - event: response.completed\ndata: {completed}\n\n" - ); - std::fs::write(path, fixture)?; - Ok(()) + responses::sse(vec![ + responses::ev_response_created("resp-resize-smoke"), + responses::ev_assistant_message("msg-resize-smoke", text), + responses::ev_completed("resp-resize-smoke"), + ]) } fn wait_for_capture_contains(pane: &str, needle: &str, timeout: Duration) -> Result {