use super::super::Agent;
use super::*;
#[test]
fn parses_step_finish_token_event() {
let task_id = TaskId("t-step".to_string());
let event = parse_json_event(
&task_id,
&serde_json::json!({
"type": "step_finish",
"part": {
"tokens": {
"total": 16125,
"input": 14040,
"output": 2,
"reasoning": 0
},
"cost": 0.0
}
}),
Local::now(),
)
.expect("step_finish events should parse");
assert_eq!(event.event_kind, EventKind::Completion);
assert_eq!(event.detail, "tokens: 14040 in + 2 out = 16125");
assert_eq!(
event.metadata,
Some(serde_json::json!({
"tokens": 16125,
"input_tokens": 14040,
"output_tokens": 2,
"cost_usd": 0.0
}))
);
}
#[test]
fn parses_new_milestone_events() {
for value in [
serde_json::json!({"type": "auto_compact", "message": "compacted session"}),
serde_json::json!({"type": "git_snapshot", "text": "snapshot saved"}),
] {
let event = parse_json_event(&TaskId("t-ms".to_string()), &value, Local::now())
.expect("milestone events should parse");
assert_eq!(event.event_kind, EventKind::Milestone);
assert!(!event.detail.is_empty());
}
}
#[test]
fn build_command_includes_file_flags_for_context_files() {
let opts = RunOpts {
dir: Some("/project".to_string()),
output: None,
result_file: None,
model: Some("test-model".to_string()),
budget: false,
read_only: false,
context_files: vec!["src/types.rs".to_string(), "src/lib.rs".to_string()],
session_id: None,
env: None,
env_forward: None,
};
let cmd = OpenCodeAgent
.build_command("test prompt", &opts)
.expect("command should build");
let args: Vec<String> = cmd
.get_args()
.map(|s: &std::ffi::OsStr| s.to_string_lossy().to_string())
.collect();
assert!(args.contains(&"--thinking".to_string()));
assert!(args.contains(&"-f".to_string()));
let f_indices: Vec<usize> = args
.iter()
.enumerate()
.filter(|(_, a)| *a == "-f")
.map(|(i, _)| i)
.collect();
assert_eq!(f_indices.len(), 2);
assert_eq!(args[f_indices[0] + 1], "src/types.rs");
assert_eq!(args[f_indices[1] + 1], "src/lib.rs");
assert!(args.contains(&"test prompt".to_string()));
}
#[test]
fn extracts_session_id_from_json_event() {
let task_id = TaskId("t-sess".to_string());
let event = parse_json_event(
&task_id,
&serde_json::json!({
"type": "message",
"content": "test",
"sessionID": "ses_abc123"
}),
Local::now(),
)
.expect("message events should parse");
assert_eq!(
event
.metadata
.as_ref()
.and_then(|m| m.get("agent_session_id").and_then(|s| s.as_str())),
Some("ses_abc123")
);
}
#[test]
fn session_flags_appear_in_command() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
context_files: vec![],
session_id: Some("ses_test123".to_string()),
env: None,
env_forward: None,
};
let cmd = OpenCodeAgent
.build_command("test prompt", &opts)
.expect("command should build");
let args: Vec<String> = cmd
.get_args()
.map(|a| a.to_string_lossy().to_string())
.collect();
assert!(args.contains(&"--session".to_string()));
assert!(args.contains(&"ses_test123".to_string()));
assert!(args.contains(&"--continue".to_string()));
assert!(args.contains(&"--fork".to_string()));
}
#[test]
fn budget_mode_sets_minimal_variant() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: true,
read_only: false,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
let cmd = OpenCodeAgent
.build_command("test prompt", &opts)
.expect("command should build");
let args: Vec<String> = cmd
.get_args()
.map(|a| a.to_string_lossy().to_string())
.collect();
assert!(args.windows(2).any(|pair| pair == ["--variant", "minimal"]));
}
#[test]
fn opencode_needs_pty() {
assert!(OpenCodeAgent.needs_pty());
}
#[test]
fn codex_does_not_need_pty() {
assert!(!super::super::codex::CodexAgent.needs_pty());
}
#[test]
fn no_session_flags_when_session_id_absent() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: false,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
let cmd = OpenCodeAgent
.build_command("test prompt", &opts)
.expect("command should build");
let args: Vec<String> = cmd
.get_args()
.map(|a| a.to_string_lossy().to_string())
.collect();
assert!(!args.contains(&"--session".to_string()));
assert!(!args.contains(&"--continue".to_string()));
assert!(!args.contains(&"--fork".to_string()));
assert!(!args.contains(&"--variant".to_string()));
}
#[test]
fn build_command_read_only_prepends_readonly_prefix() {
let opts = RunOpts {
dir: None,
output: None,
result_file: Some("result.md".to_string()),
model: None,
budget: false,
read_only: true,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
let cmd = OpenCodeAgent
.build_command("analyze this code", &opts)
.expect("command should build");
let args: Vec<String> = cmd
.get_args()
.map(|a| a.to_string_lossy().to_string())
.collect();
let last_arg = args.last().expect("should have prompt as last arg");
assert!(last_arg.contains("READ-ONLY MODE"));
assert!(last_arg.starts_with("IMPORTANT: READ-ONLY MODE"));
assert!(last_arg.contains("EXCEPT the result file specified in this prompt"));
assert!(last_arg.contains("analyze this code"));
}
#[test]
fn build_command_read_only_without_result_file_keeps_strict_prefix() {
let opts = RunOpts {
dir: None,
output: None,
result_file: None,
model: None,
budget: false,
read_only: true,
context_files: vec![],
session_id: None,
env: None,
env_forward: None,
};
let cmd = OpenCodeAgent
.build_command("analyze this code", &opts)
.expect("command should build");
let args: Vec<String> = cmd
.get_args()
.map(|a| a.to_string_lossy().to_string())
.collect();
let last_arg = args.last().expect("should have prompt as last arg");
assert!(last_arg.contains("Do NOT modify, create, or delete any files. Only read and analyze."));
}