use std::sync::{Arc, Mutex};
use opi_ai::test_support::{self, MockProvider};
use opi_coding_agent::config::OpiConfig;
use opi_coding_agent::runner::{ExitCode, NonInteractiveRunner};
#[tokio::test]
async fn runner_text_prompt_stdout_exit0() {
let response = test_support::text_response("Hello from runner!");
let provider = MockProvider::new("mock", vec![response]);
let mut runner = NonInteractiveRunner::new(
Box::new(provider),
"mock-model".into(),
OpiConfig::default(),
std::env::current_dir().unwrap(),
false,
None,
Vec::new(),
);
let result = runner.run("Hi there").await;
assert_eq!(result.exit_code, ExitCode::Success as i32, "should exit 0");
assert!(
result.stdout.contains("Hello from runner!"),
"stdout should contain assistant text, got: {:?}",
result.stdout
);
}
#[tokio::test]
async fn runner_readonly_tool_succeeds() {
let first = test_support::tool_call_response(
"tc-1",
"read",
r#"{"path":"Cargo.toml","offset":1,"limit":5}"#,
);
let second = test_support::text_response("The file contains workspace config.");
let provider = MockProvider::new("mock", vec![first, second]);
let mut runner = NonInteractiveRunner::new(
Box::new(provider),
"mock-model".into(),
OpiConfig::default(),
std::env::current_dir().unwrap(),
false,
None,
Vec::new(),
);
let result = runner.run("Read the Cargo.toml").await;
assert_eq!(result.exit_code, ExitCode::Success as i32, "should exit 0");
assert!(
result.stdout.contains("workspace config"),
"stdout should contain tool result text, got: {:?}",
result.stdout
);
}
#[tokio::test]
async fn runner_provider_error_stderr_exit4() {
let response = test_support::error_response("connection refused");
let provider = MockProvider::new("mock", vec![response]);
let mut runner = NonInteractiveRunner::new(
Box::new(provider),
"mock-model".into(),
OpiConfig::default(),
std::env::current_dir().unwrap(),
false,
None,
Vec::new(),
);
let result = runner.run("Do something").await;
assert_eq!(
result.exit_code,
ExitCode::ProviderFailure as i32,
"should exit 4 on provider error"
);
assert!(
result.stderr.contains("connection refused"),
"stderr should contain error message, got: {:?}",
result.stderr
);
}
#[tokio::test]
async fn runner_resume_forwards_compaction_summary_to_provider() {
use opi_agent::message::{AgentMessage, CompactionSummaryMessage};
use opi_ai::message::{InputContent, Message};
let response = test_support::text_response("ack");
let provider = MockProvider::new("mock", vec![response]);
let call_log = provider.call_log_handle();
let summary_text = "Earlier we discussed the quarterly compaction strategy.";
let initial_messages = vec![AgentMessage::CompactionSummary(CompactionSummaryMessage {
summary: summary_text.into(),
first_kept_entry_id: "msg-42".into(),
tokens_before: 1000,
tokens_after: 200,
})];
let mut runner = NonInteractiveRunner::new(
Box::new(provider),
"mock-model".into(),
OpiConfig::default(),
std::env::current_dir().unwrap(),
false,
None,
initial_messages,
);
let result = runner.run("continue please").await;
assert_eq!(result.exit_code, ExitCode::Success as i32);
let log = call_log.lock().unwrap();
let first_request = log.first().expect("provider was called at least once");
let mut saw_summary = false;
for msg in &first_request.messages {
if let Message::User(u) = msg {
for content in &u.content {
if let InputContent::Text { text } = content
&& text.contains(summary_text)
{
saw_summary = true;
}
}
}
}
assert!(
saw_summary,
"provider request messages must include compacted summary text; got: {:?}",
first_request.messages
);
}
#[test]
fn format_persist_errors_unit() {
let errors = Arc::new(Mutex::new(Vec::<String>::new()));
let result = opi_coding_agent::runner::format_persist_errors(&errors);
assert!(
result.is_empty(),
"expected empty for no errors, got: {result:?}"
);
{
let mut guard = errors.lock().unwrap();
guard.push("disk full".into());
guard.push("permission denied".into());
}
let result = opi_coding_agent::runner::format_persist_errors(&errors);
assert!(
result.contains("session persist error: disk full"),
"should contain first error, got: {result:?}"
);
assert!(
result.contains("session persist error: permission denied"),
"should contain second error, got: {result:?}"
);
}