mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
add test
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
use codex_core::ConversationManager;
|
||||
use codex_core::NewConversation;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::ExecOutputStream;
|
||||
use codex_core::protocol::Op;
|
||||
use codex_core::protocol::TurnAbortReason;
|
||||
use codex_core::protocol::UserCommandEndEvent;
|
||||
use core_test_support::load_default_config_for_test;
|
||||
use core_test_support::responses;
|
||||
use core_test_support::wait_for_event;
|
||||
use core_test_support::wait_for_event_match;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
use std::process::Stdio;
|
||||
@@ -138,3 +141,115 @@ async fn user_shell_cmd_can_be_interrupted() {
|
||||
};
|
||||
assert_eq!(ev.reason, TurnAbortReason::Interrupted);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn user_shell_command_history_is_persisted_and_shared_with_model() -> anyhow::Result<()> {
|
||||
let command = if cfg!(target_os = "windows") {
|
||||
"$val = $env:CODEX_SANDBOX; if ($null -eq $val -or $val -eq '') { Write-Output 'not-set' } else { Write-Output $val }".to_string()
|
||||
} else {
|
||||
"if [ -n \"$CODEX_SANDBOX\" ]; then printf %s \"$CODEX_SANDBOX\"; else printf %s not-set; fi"
|
||||
.to_string()
|
||||
};
|
||||
|
||||
let server = responses::start_mock_server().await;
|
||||
let mut builder = core_test_support::test_codex::test_codex();
|
||||
let test = builder.build(&server).await?;
|
||||
|
||||
test.codex
|
||||
.submit(Op::RunUserShellCommand {
|
||||
command: command.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
let begin_event = wait_for_event_match(&test.codex, |ev| match ev {
|
||||
EventMsg::UserCommandBegin(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
assert_eq!(begin_event.command.last(), Some(&command));
|
||||
|
||||
let delta_event = wait_for_event_match(&test.codex, |ev| match ev {
|
||||
EventMsg::UserCommandOutputDelta(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
assert_eq!(delta_event.stream, ExecOutputStream::Stdout);
|
||||
let chunk_text =
|
||||
String::from_utf8(delta_event.chunk.clone()).expect("user command chunk is valid utf-8");
|
||||
assert_eq!(chunk_text.trim(), "not-set");
|
||||
|
||||
let end_event = wait_for_event_match(&test.codex, |ev| match ev {
|
||||
EventMsg::UserCommandEnd(event) => Some(event.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.await;
|
||||
assert_eq!(end_event.exit_code, 0);
|
||||
assert_eq!(end_event.stdout.trim(), "not-set");
|
||||
|
||||
let responses = vec![responses::sse(vec![
|
||||
responses::ev_response_created("resp-1"),
|
||||
responses::ev_assistant_message("msg-1", "done"),
|
||||
responses::ev_completed("resp-1"),
|
||||
])];
|
||||
let mock = responses::mount_sse_sequence(&server, responses).await;
|
||||
|
||||
test.submit_turn("follow-up after shell command").await?;
|
||||
|
||||
let request = mock.single_request();
|
||||
let items = request.input();
|
||||
|
||||
fn find_user_text(items: &[serde_json::Value], marker: &str) -> Option<String> {
|
||||
items.iter().find_map(|item| {
|
||||
if item.get("type").and_then(serde_json::Value::as_str) != Some("message") {
|
||||
return None;
|
||||
}
|
||||
if item.get("role").and_then(serde_json::Value::as_str) != Some("user") {
|
||||
return None;
|
||||
}
|
||||
let content = item.get("content")?.as_array()?;
|
||||
content.iter().find_map(|span| {
|
||||
if span.get("type").and_then(serde_json::Value::as_str) == Some("input_text") {
|
||||
let text = span.get("text").and_then(serde_json::Value::as_str)?;
|
||||
if text.contains(marker) {
|
||||
return Some(text.to_string());
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
let command_message = find_user_text(&items, "<user_shell_command>")
|
||||
.expect("command message recorded in request");
|
||||
assert!(
|
||||
command_message.contains(&command),
|
||||
"command message should include shell invocation: {command_message}"
|
||||
);
|
||||
|
||||
let output_message = find_user_text(&items, "<user_shell_command_output>")
|
||||
.expect("output message recorded in request");
|
||||
let payload = output_message
|
||||
.strip_prefix("<user_shell_command_output>\n")
|
||||
.and_then(|text| text.strip_suffix("\n</user_shell_command_output>"))
|
||||
.expect("shell command output payload present");
|
||||
let parsed: serde_json::Value =
|
||||
serde_json::from_str(payload).expect("parse shell command output payload");
|
||||
assert_eq!(
|
||||
parsed
|
||||
.get("metadata")
|
||||
.and_then(|meta| meta.get("exit_code"))
|
||||
.and_then(serde_json::Value::as_i64),
|
||||
Some(0),
|
||||
"expected exit_code metadata to be present and zero",
|
||||
);
|
||||
let output_text = parsed
|
||||
.get("output")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.expect("model-facing output string present");
|
||||
assert!(
|
||||
output_text.contains("not-set"),
|
||||
"model-facing output should include stdout content: {output_text:?}"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user