Compare commits

...

1 Commits

Author SHA1 Message Date
Liang-Ting Jiang
5b0a195e48 test(core): cover code mode app file uploads
Add integration coverage that exercises Code Mode's nested MCP tool path through openai/fileParams rewriting for Codex Apps file uploads.

Co-authored-by: Codex <noreply@openai.com>
2026-05-12 08:07:51 +00:00

View File

@@ -15,11 +15,13 @@ use core_test_support::apps_test_server::DOCUMENT_EXTRACT_TEXT_RESOURCE_URI;
use core_test_support::hooks::trust_discovered_hooks;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_custom_tool_call;
use core_test_support::responses::ev_function_call_with_namespace;
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::skip_if_no_network;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use serde_json::Value;
@@ -245,3 +247,139 @@ async fn codex_apps_file_params_upload_local_paths_before_mcp_tool_call() -> Res
server.verify().await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_nested_mcp_file_params_upload_local_paths_before_tool_call() -> Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let apps_server = AppsTestServer::mount(&server).await?;
Mock::given(method("POST"))
.and(path("/files"))
.and(header("chatgpt-account-id", "account_id"))
.and(body_json(json!({
"file_name": "report.txt",
"file_size": 11,
"use_case": "codex",
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"file_id": "file_123",
"upload_url": format!("{}/upload/file_123", server.uri()),
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path("/upload/file_123"))
.and(header("content-length", "11"))
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/files/file_123/uploaded"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"status": "success",
"download_url": format!("{}/download/file_123", server.uri()),
"file_name": "report.txt",
"mime_type": "text/plain",
"file_size_bytes": 11,
})))
.expect(1)
.mount(&server)
.await;
let code = r#"
const result = await tools.mcp__codex_apps__calendar_extract_text({
file: "report.txt",
});
text(result.content?.[0]?.text ?? "missing result");
"#;
mount_sse_sequence(
&server,
vec![
sse(vec![
ev_response_created("resp-1"),
ev_custom_tool_call("call-1", "exec", code),
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_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_config(move |config| {
configure_apps(config, apps_server.chatgpt_base_url.as_str());
config
.features
.enable(Feature::CodeMode)
.expect("test config should allow feature update");
});
let test = builder.build(&server).await?;
tokio::fs::write(test.cwd.path().join("report.txt"), b"hello world").await?;
test.submit_turn_with_approval_and_permission_profile(
"Use code mode to extract the report text with the app tool.",
AskForApproval::Never,
PermissionProfile::Disabled,
)
.await?;
let apps_tool_call = server
.received_requests()
.await
.unwrap_or_default()
.into_iter()
.find_map(|request| {
let body: Value = serde_json::from_slice(&request.body).ok()?;
(request.url.path() == "/api/codex/apps"
&& body.get("method").and_then(Value::as_str) == Some("tools/call")
&& body.pointer("/params/name").and_then(Value::as_str)
== Some("calendar_extract_text"))
.then_some(body)
})
.expect("apps calendar_extract_text tools/call request should be recorded");
assert_eq!(
apps_tool_call.pointer("/params/arguments/file"),
Some(&json!({
"download_url": format!("{}/download/file_123", server.uri()),
"file_id": "file_123",
"mime_type": "text/plain",
"file_name": "report.txt",
"uri": "sediment://file_123",
"file_size_bytes": 11,
}))
);
let codex_apps_meta = apps_tool_call
.pointer("/params/_meta/_codex_apps")
.expect("codex apps metadata should be forwarded");
assert!(
codex_apps_meta
.pointer("/call_id")
.and_then(Value::as_str)
.is_some_and(|call_id| call_id.starts_with("exec-"))
);
assert_eq!(
codex_apps_meta.pointer("/resource_uri"),
Some(&json!(DOCUMENT_EXTRACT_TEXT_RESOURCE_URI))
);
assert_eq!(
codex_apps_meta.pointer("/contains_mcp_source"),
Some(&json!(true))
);
assert_eq!(
codex_apps_meta.pointer("/connector_id"),
Some(&json!("calendar"))
);
server.verify().await;
Ok(())
}