Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions codex-rs/core/tests/remote_env_windows/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,23 @@ load("//bazel/rules/testing:wine.bzl", "wine_rust_test")

wine_rust_test(
name = "smoke-test",
timeout = "short",
srcs = ["remote_env_windows_test.rs"],
timeout = "moderate",
srcs = [
"non_native_cwd_tests.rs",
"remote_env_windows_test.rs",
],
crate_name = "remote_env_windows_test",
crate_root = "remote_env_windows_test.rs",
windows_binaries = {
"wine-windows-exec-server": "//codex-rs/exec-server/testing:windows-exec-server",
},
deps = [
"//bazel/rules/testing/wine:wine_test_support",
"//codex-rs/core/tests/common",
"//codex-rs/exec-server",
"//codex-rs/exec-server/testing:wine-exec-server-harness",
"//codex-rs/features",
"//codex-rs/protocol",
"//codex-rs/utils/cargo-bin",
"//codex-rs/utils/path-uri",
"@crates//:anyhow",
"@crates//:pretty_assertions",
"@crates//:serde_json",
Expand Down
142 changes: 142 additions & 0 deletions codex-rs/core/tests/remote_env_windows/non_native_cwd_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use anyhow::Context;
use anyhow::Result;
use codex_exec_server::REMOTE_ENVIRONMENT_ID;
use codex_features::Feature;
use codex_protocol::models::PermissionProfile;
use codex_protocol::protocol::AskForApproval;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::Op;
use codex_protocol::protocol::TurnEnvironmentSelection;
use codex_protocol::protocol::TurnEnvironmentSelections;
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_function_call;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::test_codex::test_codex;
use core_test_support::test_codex::turn_permission_fields;
use core_test_support::wait_for_event;
use serde_json::json;
use wine_exec_server_harness::WineExecServerHarness;

const CALL_ID: &str = "wine-cmd-smoke";
const COMMAND: &str = "echo WINE_BAZEL_OK&&cd";

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn windows_exec_server_rejects_non_native_cwd_uri() -> Result<()> {
let (exec_server, exec_server_url) = WineExecServerHarness::builder().start().await?;

exec_server
.scope(async move {
let server = start_mock_server().await;
let arguments = serde_json::to_string(&json!({
"cmd": COMMAND,
"login": false,
"yield_time_ms": 5_000,
}))?;
let response_mock = mount_sse_sequence(
&server,
vec![
sse(vec![
ev_response_created("resp-1"),
ev_function_call(CALL_ID, "exec_command", &arguments),
ev_completed("resp-1"),
]),
sse(vec![
ev_response_created("resp-2"),
ev_assistant_message("msg-1", "done"),
ev_completed("resp-2"),
]),
],
)
.await;

let mut builder = test_codex()
.with_model("gpt-5.2")
.with_exec_server_url(exec_server_url)
.with_config(|config| {
config.use_experimental_unified_exec_tool = true;
config
.features
.enable(Feature::UnifiedExec)
.expect("test config should allow feature update");
});
let test = builder.build(&server).await?;
let (sandbox_policy, permission_profile) =
turn_permission_fields(PermissionProfile::Disabled, test.config.cwd.as_path());
let environments = TurnEnvironmentSelections::new(
test.config.cwd.clone(),
vec![TurnEnvironmentSelection {
environment_id: REMOTE_ENVIRONMENT_ID.to_string(),
cwd: test.config.cwd.clone(),
}],
);

test.codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "run the Windows smoke command".to_string(),
text_elements: Vec::new(),
}],
final_output_json_schema: None,
responsesapi_client_metadata: None,
additional_context: Default::default(),
thread_settings: codex_protocol::protocol::ThreadSettingsOverrides {
environments: Some(environments),
approval_policy: Some(AskForApproval::Never),
sandbox_policy: Some(sandbox_policy),
permission_profile,
collaboration_mode: Some(codex_protocol::config_types::CollaborationMode {
mode: codex_protocol::config_types::ModeKind::Default,
settings: codex_protocol::config_types::Settings {
model: test.session_configured.model.clone(),
reasoning_effort: None,
developer_instructions: None,
},
}),
..Default::default()
},
})
.await?;

let mut saw_exec_event = false;
loop {
match wait_for_event(&test.codex, |_| true).await {
EventMsg::ExecCommandBegin(event) if event.call_id == CALL_ID => {
saw_exec_event = true
}
EventMsg::ExecCommandEnd(event) if event.call_id == CALL_ID => {
saw_exec_event = true
}
EventMsg::TurnComplete(_) => break,
_ => {}
}
}

assert!(
!saw_exec_event,
"a non-native cwd should be rejected before process lifecycle events",
);

let request = response_mock
.last_request()
.context("model should receive the rejected command output")?;
let (output, success) = request
.function_call_output_content_and_success(CALL_ID)
.context("rejected command output should be present")?;
let output = output.context("rejected command output should contain text")?;
assert!(
output.contains("exec-server rejected request (-32602)")
&& output.contains("cwd URI")
&& output.contains("is not valid on this exec-server host"),
"unexpected command output: {output:?}",
);
assert_ne!(success, Some(true));

Ok(())
})
.await
}
Loading
Loading