diff --git a/codex-rs/mcp-server/tests/suite/archive_conversation.rs b/codex-rs/mcp-server/tests/suite/archive_conversation.rs new file mode 100644 index 0000000000..e54a99896c --- /dev/null +++ b/codex-rs/mcp-server/tests/suite/archive_conversation.rs @@ -0,0 +1,105 @@ +use std::path::Path; + +use codex_core::ARCHIVED_SESSIONS_SUBDIR; +use codex_protocol::mcp_protocol::ArchiveConversationParams; +use codex_protocol::mcp_protocol::ArchiveConversationResponse; +use codex_protocol::mcp_protocol::NewConversationParams; +use codex_protocol::mcp_protocol::NewConversationResponse; +use mcp_test_support::McpProcess; +use mcp_test_support::to_response; +use mcp_types::JSONRPCResponse; +use mcp_types::RequestId; +use tempfile::TempDir; +use tokio::time::timeout; + +const DEFAULT_READ_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10); + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn archive_conversation_moves_rollout_into_archived_directory() { + let codex_home = TempDir::new().expect("create temp dir"); + create_config_toml(codex_home.path()).expect("write config.toml"); + + let mut mcp = McpProcess::new(codex_home.path()) + .await + .expect("spawn mcp process"); + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()) + .await + .expect("initialize timeout") + .expect("initialize request"); + + let new_request_id = mcp + .send_new_conversation_request(NewConversationParams { + model: Some("mock-model".to_string()), + ..Default::default() + }) + .await + .expect("send newConversation"); + let new_response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(new_request_id)), + ) + .await + .expect("newConversation timeout") + .expect("newConversation response"); + + let NewConversationResponse { + conversation_id, + rollout_path, + .. + } = to_response::(new_response) + .expect("deserialize newConversation response"); + + assert!( + rollout_path.exists(), + "expected rollout path {} to exist", + rollout_path.display() + ); + + let archive_request_id = mcp + .send_archive_conversation_request(ArchiveConversationParams { + conversation_id, + rollout_path: rollout_path.clone(), + }) + .await + .expect("send archiveConversation"); + let archive_response: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(archive_request_id)), + ) + .await + .expect("archiveConversation timeout") + .expect("archiveConversation response"); + + let _: ArchiveConversationResponse = + to_response::(archive_response) + .expect("deserialize archiveConversation response"); + + let archived_directory = codex_home.path().join(ARCHIVED_SESSIONS_SUBDIR); + let archived_rollout_path = + archived_directory.join(rollout_path.file_name().unwrap_or_else(|| { + panic!("rollout path {} missing file name", rollout_path.display()) + })); + + assert!( + !rollout_path.exists(), + "expected rollout path {} to be moved", + rollout_path.display() + ); + assert!( + archived_rollout_path.exists(), + "expected archived rollout path {} to exist", + archived_rollout_path.display() + ); +} + +fn create_config_toml(codex_home: &Path) -> std::io::Result<()> { + let config_toml = codex_home.join("config.toml"); + std::fs::write(config_toml, config_contents()) +} + +fn config_contents() -> &'static str { + r#"model = "mock-model" +approval_policy = "never" +sandbox_mode = "read-only" +"# +}