From fccfb4ac3e33dfbceab44cea99298d60f30f17ac Mon Sep 17 00:00:00 2001 From: pakrym-oai Date: Tue, 21 Oct 2025 11:03:29 -0700 Subject: [PATCH] Add a baseline test for resume initial messages --- codex-rs/core/tests/common/test_codex.rs | 74 +++++++++++++++++------- codex-rs/core/tests/suite/mod.rs | 1 + codex-rs/core/tests/suite/resume.rs | 64 ++++++++++++++++++++ 3 files changed, 118 insertions(+), 21 deletions(-) create mode 100644 codex-rs/core/tests/suite/resume.rs diff --git a/codex-rs/core/tests/common/test_codex.rs b/codex-rs/core/tests/common/test_codex.rs index 0e07d82228d..56f57a5e2b4 100644 --- a/codex-rs/core/tests/common/test_codex.rs +++ b/codex-rs/core/tests/common/test_codex.rs @@ -6,7 +6,6 @@ use codex_core::CodexAuth; use codex_core::CodexConversation; use codex_core::ConversationManager; use codex_core::ModelProviderInfo; -use codex_core::NewConversation; use codex_core::built_in_model_providers; use codex_core::config::Config; use codex_core::protocol::SessionConfiguredEvent; @@ -30,14 +29,59 @@ impl TestCodexBuilder { } pub async fn build(&mut self, server: &wiremock::MockServer) -> anyhow::Result { - // Build config pointing to the mock server and spawn Codex. + let home = Arc::new(TempDir::new()?); + self.build_with_home(server, home, None).await + } + + pub async fn resume( + &mut self, + server: &wiremock::MockServer, + home: Arc, + rollout_path: PathBuf, + ) -> anyhow::Result { + self.build_with_home(server, home, Some(rollout_path)).await + } + + async fn build_with_home( + &mut self, + server: &wiremock::MockServer, + home: Arc, + resume_from: Option, + ) -> anyhow::Result { + let (config, cwd) = self.prepare_config(server, &home).await?; + let conversation_manager = ConversationManager::with_auth(CodexAuth::from_api_key("dummy")); + + let new_conversation = match resume_from { + Some(path) => { + let auth_manager = codex_core::AuthManager::from_auth_for_testing( + CodexAuth::from_api_key("dummy"), + ); + conversation_manager + .resume_conversation_from_rollout(config, path, auth_manager) + .await? + } + None => conversation_manager.new_conversation(config).await?, + }; + + Ok(TestCodex { + home, + cwd, + codex: new_conversation.conversation, + session_configured: new_conversation.session_configured, + }) + } + + async fn prepare_config( + &mut self, + server: &wiremock::MockServer, + home: &TempDir, + ) -> anyhow::Result<(Config, Arc)> { let model_provider = ModelProviderInfo { base_url: Some(format!("{}/v1", server.uri())), ..built_in_model_providers()["openai"].clone() }; - let home = TempDir::new()?; - let cwd = TempDir::new()?; - let mut config = load_default_config_for_test(&home); + let cwd = Arc::new(TempDir::new()?); + let mut config = load_default_config_for_test(home); config.cwd = cwd.path().to_path_buf(); config.model_provider = model_provider; config.codex_linux_sandbox_exe = Some(PathBuf::from( @@ -48,29 +92,17 @@ impl TestCodexBuilder { let mut mutators = vec![]; swap(&mut self.config_mutators, &mut mutators); - for mutator in mutators { - mutator(&mut config) + mutator(&mut config); } - let conversation_manager = ConversationManager::with_auth(CodexAuth::from_api_key("dummy")); - let NewConversation { - conversation, - session_configured, - .. - } = conversation_manager.new_conversation(config).await?; - Ok(TestCodex { - home, - cwd, - codex: conversation, - session_configured, - }) + Ok((config, cwd)) } } pub struct TestCodex { - pub home: TempDir, - pub cwd: TempDir, + pub home: Arc, + pub cwd: Arc, pub codex: Arc, pub session_configured: SessionConfiguredEvent, } diff --git a/codex-rs/core/tests/suite/mod.rs b/codex-rs/core/tests/suite/mod.rs index 22442d10d7b..9b79ac8385b 100644 --- a/codex-rs/core/tests/suite/mod.rs +++ b/codex-rs/core/tests/suite/mod.rs @@ -20,6 +20,7 @@ mod model_tools; mod otel; mod prompt_caching; mod read_file; +mod resume; mod review; mod rmcp_client; mod rollout_list_find; diff --git a/codex-rs/core/tests/suite/resume.rs b/codex-rs/core/tests/suite/resume.rs new file mode 100644 index 00000000000..9b1707a05e1 --- /dev/null +++ b/codex-rs/core/tests/suite/resume.rs @@ -0,0 +1,64 @@ +use anyhow::Result; +use codex_core::protocol::EventMsg; +use codex_core::protocol::Op; +use codex_protocol::user_input::UserInput; +use core_test_support::responses::ev_assistant_message; +use core_test_support::responses::ev_completed; +use core_test_support::responses::ev_response_created; +use core_test_support::responses::mount_sse_once_match; +use core_test_support::responses::sse; +use core_test_support::responses::start_mock_server; +use core_test_support::skip_if_no_network; +use core_test_support::test_codex::test_codex; +use core_test_support::wait_for_event; +use std::sync::Arc; +use wiremock::matchers::any; + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn resume_includes_initial_messages_from_rollout_events() -> Result<()> { + skip_if_no_network!(Ok(())); + + let server = start_mock_server().await; + let mut builder = test_codex(); + let initial = builder.build(&server).await?; + let codex = Arc::clone(&initial.codex); + let home = initial.home.clone(); + let rollout_path = initial.session_configured.rollout_path.clone(); + + let initial_sse = sse(vec![ + ev_response_created("resp-initial"), + ev_assistant_message("msg-1", "Completed first turn"), + ev_completed("resp-initial"), + ]); + mount_sse_once_match(&server, any(), initial_sse).await; + + codex + .submit(Op::UserInput { + items: vec![UserInput::Text { + text: "Record some messages".into(), + }], + }) + .await?; + + wait_for_event(&codex, |event| matches!(event, EventMsg::TaskComplete(_))).await; + + let resumed = builder.resume(&server, home, rollout_path).await?; + let initial_messages = resumed + .session_configured + .initial_messages + .expect("expected initial messages to be present for resumed session"); + match initial_messages.as_slice() { + [ + EventMsg::UserMessage(first_user), + EventMsg::TokenCount(_), + EventMsg::AgentMessage(assistant_message), + EventMsg::TokenCount(_), + ] => { + assert_eq!(first_user.message, "Record some messages"); + assert_eq!(assistant_message.message, "Completed first turn"); + } + other => panic!("unexpected initial messages after resume: {other:#?}"), + } + + Ok(()) +}