mod helpers;
mod mock_llm_server;
use helpers::{assert_file_contains, output_to_string, run_xcode_with_env};
use mock_llm_server::{start_mock_server, MockScenario};
use tempfile::TempDir;
#[allow(dead_code)] fn provider_args(addr: std::net::SocketAddr) -> [String; 4] {
[
"--no-sandbox".to_string(),
"--provider-url".to_string(),
format!("http://127.0.0.1:{}/v1", addr.port()),
"--api-key".to_string(),
]
}
#[tokio::test]
async fn test_run_simple_text_response() {
let (addr, _state) = start_mock_server(vec![MockScenario::TextResponse(
"Task completed successfully.".to_string(),
)])
.await;
let project_dir = TempDir::new().unwrap();
let home_dir = TempDir::new().unwrap();
let port = addr.port().to_string();
let project_path = project_dir.path().to_str().unwrap().to_string();
let home_path = home_dir.path().to_str().unwrap().to_string();
let output = run_xcode_with_env(
&[
"run",
"--no-sandbox",
"--provider-url",
&format!("http://127.0.0.1:{}/v1", port),
"--api-key",
"testkey",
"--project",
&project_path,
"hello",
],
&[("HOME", &home_path)],
)
.await;
let text = output_to_string(&output);
assert!(
output.status.success(),
"xcode run failed (exit {:?}).\nOutput:\n{}",
output.status.code(),
text
);
}
#[tokio::test]
async fn test_run_creates_file_via_tool_call() {
let (addr, _state) = start_mock_server(vec![MockScenario::ToolCallResponse {
name: "file_write".to_string(),
args: r#"{"path":"hello.txt","content":"Hello World"}"#.to_string(),
final_text: "Done".to_string(),
}])
.await;
let project_dir = TempDir::new().unwrap();
let home_dir = TempDir::new().unwrap();
let port = addr.port().to_string();
let project_path = project_dir.path().to_str().unwrap().to_string();
let home_path = home_dir.path().to_str().unwrap().to_string();
let output = run_xcode_with_env(
&[
"run",
"--no-sandbox",
"--provider-url",
&format!("http://127.0.0.1:{}/v1", port),
"--api-key",
"testkey",
"--project",
&project_path,
"create hello.txt",
],
&[("HOME", &home_path)],
)
.await;
let text = output_to_string(&output);
assert!(
output.status.success(),
"xcode run failed (exit {:?}).\nOutput:\n{}",
output.status.code(),
text
);
let expected_file = project_dir.path().join("hello.txt");
assert!(
expected_file.exists(),
"Expected file {:?} was not created.\nxcode output:\n{}",
expected_file,
text
);
assert_file_contains(&expected_file, "Hello World");
}
#[tokio::test]
async fn test_run_handles_llm_error() {
let (addr, _state) = start_mock_server(vec![MockScenario::ErrorResponse(500)]).await;
let project_dir = TempDir::new().unwrap();
let home_dir = TempDir::new().unwrap();
let port = addr.port().to_string();
let project_path = project_dir.path().to_str().unwrap().to_string();
let home_path = home_dir.path().to_str().unwrap().to_string();
let output = run_xcode_with_env(
&[
"run",
"--no-sandbox",
"--provider-url",
&format!("http://127.0.0.1:{}/v1", port),
"--api-key",
"testkey",
"--project",
&project_path,
"test error handling",
],
&[("HOME", &home_path), ("XCODEAI_RETRY_MAX", "0")],
)
.await;
assert!(
!output.status.success(),
"Expected non-zero exit code on LLM error, but got success.\nOutput:\n{}",
output_to_string(&output)
);
let combined = output_to_string(&output);
assert!(
!combined.contains("thread 'main' panicked"),
"Binary panicked instead of returning a graceful error.\nOutput:\n{}",
combined
);
}
#[tokio::test]
async fn test_session_persisted_after_run() {
let (addr, _state) = start_mock_server(vec![MockScenario::TextResponse(
"Persisted successfully.".to_string(),
)])
.await;
let home_dir = TempDir::new().unwrap();
let project_dir = TempDir::new().unwrap();
let port = addr.port().to_string();
let project_path = project_dir.path().to_str().unwrap().to_string();
let home_path = home_dir.path().to_str().unwrap().to_string();
let run_output = run_xcode_with_env(
&[
"run",
"--no-sandbox",
"--provider-url",
&format!("http://127.0.0.1:{}/v1", port),
"--api-key",
"testkey",
"--project",
&project_path,
"persist this session",
],
&[("HOME", &home_path)],
)
.await;
assert!(
run_output.status.success(),
"xcode run failed.\nOutput:\n{}",
output_to_string(&run_output)
);
let list_output = run_xcode_with_env(&["session", "list"], &[("HOME", &home_path)]).await;
assert!(
list_output.status.success(),
"xcode session list failed.\nOutput:\n{}",
output_to_string(&list_output)
);
let list_text = output_to_string(&list_output);
assert!(
!list_text.contains("No sessions found"),
"Expected at least one session after run, but got:\n{}",
list_text
);
}