use std::fs;
use serde_json::json;
use super::super::threads::build_turn_input_items;
use super::support::bootstrap_test_state;
use crate::bridge_protocol::{SendTurnInputItem, SendTurnRequest};
#[test]
fn send_turn_request_compatibility_supports_legacy_text_and_structured_items() {
let legacy = serde_json::from_value::<SendTurnRequest>(json!({
"threadId": "thread-1",
"text": "继续处理"
}))
.expect("legacy send_turn 请求应可解析");
assert_eq!(legacy.thread_id, "thread-1");
assert_eq!(legacy.text, "继续处理");
assert!(legacy.input_items.is_none());
let structured = serde_json::from_value::<SendTurnRequest>(json!({
"threadId": "thread-1",
"text": "",
"inputItems": [
{
"type": "text",
"text": "看这张图"
},
{
"type": "localImage",
"path": "/tmp/codex-mobile-test.png"
}
]
}))
.expect("structured send_turn 请求应可解析");
assert_eq!(structured.thread_id, "thread-1");
assert_eq!(structured.input_items.as_ref().map(Vec::len), Some(2));
}
#[tokio::test]
async fn stage_input_image_writes_file_under_staging_root() {
let state = bootstrap_test_state().await;
let response = state
.handle_request(
"stage_input_image",
json!({
"fileName": "diagram.png",
"mimeType": "image/png",
"base64Data": "aGVsbG8="
}),
)
.await
.expect("stage_input_image 请求失败");
let local_path = response["image"]["localPath"]
.as_str()
.expect("应返回 localPath");
assert!(local_path.starts_with(&state.staging_root().to_string_lossy().to_string()));
let content = fs::read(local_path).expect("应写入 staging 文件");
assert_eq!(content, b"hello");
state
.cleanup_staged_paths([local_path.into()])
.expect("清理 staging 文件失败");
}
#[test]
fn build_turn_input_items_accepts_text_and_local_image() {
let staging_dir =
std::env::temp_dir().join(format!("codex-mobile-turn-inputs-{}", std::process::id()));
fs::create_dir_all(&staging_dir).expect("创建 staging 目录失败");
let image_path = staging_dir.join("image.png");
fs::write(&image_path, b"png").expect("写入测试图片失败");
let request = SendTurnRequest {
thread_id: "thread-1".to_string(),
text: String::new(),
input_items: Some(vec![
SendTurnInputItem::Text {
text: "请分析这张图".to_string(),
},
SendTurnInputItem::LocalImage {
path: image_path.to_string_lossy().to_string(),
},
]),
};
let (input_items, staged_paths) =
build_turn_input_items(&request, &staging_dir).expect("应接受图文输入");
assert_eq!(input_items.len(), 2);
assert_eq!(input_items[0]["type"], "text");
assert_eq!(input_items[1]["type"], "localImage");
assert_eq!(staged_paths, vec![image_path.clone()]);
fs::remove_file(image_path).expect("删除测试图片失败");
}
#[test]
fn build_turn_input_items_rejects_non_staged_image_path() {
let staging_dir =
std::env::temp_dir().join(format!("codex-mobile-turn-inputs-{}", std::process::id()));
fs::create_dir_all(&staging_dir).expect("创建 staging 目录失败");
let external_path = std::env::temp_dir().join(format!(
"codex-mobile-turn-inputs-external-{}.png",
std::process::id()
));
fs::write(&external_path, b"png").expect("写入测试图片失败");
let request = SendTurnRequest {
thread_id: "thread-1".to_string(),
text: String::new(),
input_items: Some(vec![SendTurnInputItem::LocalImage {
path: external_path.to_string_lossy().to_string(),
}]),
};
let error = build_turn_input_items(&request, &staging_dir).expect_err("应拒绝 staging 外路径");
assert!(error.to_string().contains("staging"));
fs::remove_file(external_path).expect("删除测试图片失败");
}