use super::*;
#[test]
fn test_parses_agents_conformance_target_url() {
let cli = Cli::parse_from([
"harn",
"test",
"agents-conformance",
"--target",
"http://localhost:8080",
"--api-key",
"test-key",
"--category",
"core,streaming",
"--json",
]);
let Command::Test(args) = cli.command.unwrap() else {
panic!("expected test command");
};
assert_eq!(args.target.as_deref(), Some("agents-conformance"));
assert_eq!(args.agents_target.as_deref(), Some("http://localhost:8080"));
assert_eq!(args.agents_api_key.as_deref(), Some("test-key"));
assert_eq!(args.agents_category, vec!["core,streaming"]);
assert!(args.json);
}
#[test]
fn test_run_rejects_deny_allow_conflict() {
let err = Cli::try_parse_from([
"harn",
"run",
"--deny",
"read_file",
"--allow",
"exec",
"main.harn",
])
.unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
}
#[test]
fn test_run_parses_read_only_roots_and_rejects_no_sandbox_conflict() {
let cli = Cli::parse_from([
"harn",
"run",
"--read-only-root",
"../shared",
"--read-only-root",
"/tmp/assets",
"main.harn",
]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert_eq!(
args.read_only_root,
vec![PathBuf::from("../shared"), PathBuf::from("/tmp/assets")]
);
let err = Cli::try_parse_from([
"harn",
"run",
"--no-sandbox",
"--read-only-root",
"../shared",
"main.harn",
])
.unwrap_err();
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
}
#[test]
fn test_parses_run_llm_mock_flags() {
let cli = Cli::parse_from(["harn", "run", "--llm-mock", "fixtures.jsonl", "main.harn"]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert_eq!(args.llm_mock.as_deref(), Some("fixtures.jsonl"));
assert_eq!(args.llm_mock_record, None);
let cli = Cli::parse_from(["harn", "run", "--llm-mock-record", "out.jsonl", "main.harn"]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert_eq!(args.llm_mock_record.as_deref(), Some("out.jsonl"));
assert_eq!(args.llm_mock, None);
}
#[test]
fn test_parses_run_summary_flags() {
let cli = Cli::parse_from([
"harn",
"run",
"--emit-summary-json",
"--summary-file",
"summary.jsonl",
"main.harn",
]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert!(args.emit_summary_json);
assert_eq!(
args.summary_file.as_deref(),
Some(std::path::Path::new("summary.jsonl"))
);
assert_eq!(args.summary_fd, None);
let cli = Cli::parse_from([
"harn",
"run",
"--emit-summary-json",
"--summary-fd",
"3",
"main.harn",
]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert_eq!(args.summary_fd, Some(3));
}
#[test]
fn test_parses_run_phase_and_rusage_flags() {
let cli = Cli::parse_from([
"harn",
"run",
"--emit-phase-json",
"--phase-file",
"phases.jsonl",
"--emit-rusage-json",
"--rusage-fd",
"4",
"main.harn",
]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert!(args.emit_phase_json);
assert_eq!(
args.phase_file.as_deref(),
Some(std::path::Path::new("phases.jsonl"))
);
assert_eq!(args.phase_fd, None);
assert!(args.emit_rusage_json);
assert_eq!(args.rusage_file, None);
assert_eq!(args.rusage_fd, Some(4));
}
#[test]
fn test_parses_eval_tool_calls_args() {
let cli = Cli::parse_from([
"harn",
"eval",
"tool-calls",
"--dataset",
"conformance/tool-call-eval",
"--planner",
"provider=mock,model=mock",
"--binder",
"mock:mock-binder",
"--output",
".harn-runs/tool-call-eval/latest",
"--max-cases",
"3",
]);
let Command::Eval(args) = cli.command.unwrap() else {
panic!("expected eval command");
};
let Some(EvalCommand::ToolCalls(tool_calls)) = args.command else {
panic!("expected tool-calls command");
};
assert_eq!(
tool_calls.dataset,
PathBuf::from("conformance/tool-call-eval")
);
assert_eq!(
tool_calls.planner.as_deref(),
Some("provider=mock,model=mock")
);
assert_eq!(tool_calls.binder.as_deref(), Some("mock:mock-binder"));
assert_eq!(tool_calls.max_cases, Some(3));
let cli = Cli::parse_from([
"harn",
"eval",
"tool-calls",
"regression-check",
"--planner",
"mock:mock",
"--against",
"baseline.json",
"--max-drop-pp",
"1.5",
]);
let Command::Eval(args) = cli.command.unwrap() else {
panic!("expected eval command");
};
let Some(EvalCommand::ToolCalls(tool_calls)) = args.command else {
panic!("expected tool-calls command");
};
let Some(EvalToolCallsCommand::RegressionCheck(regression)) = tool_calls.command else {
panic!("expected regression-check command");
};
assert_eq!(regression.planner.as_deref(), Some("mock:mock"));
assert_eq!(regression.against, PathBuf::from("baseline.json"));
assert_eq!(regression.max_drop_pp, 1.5);
}
#[test]
fn test_parses_eval_context_args() {
let cli = Cli::parse_from([
"harn",
"eval",
"context",
"examples/evals/context-engineering-smoke.json",
"--output",
".harn-runs/context-eval/smoke",
"--json",
]);
let Command::Eval(args) = cli.command.unwrap() else {
panic!("expected eval command");
};
let Some(EvalCommand::Context(context)) = args.command else {
panic!("expected context command");
};
assert_eq!(
context.manifest,
PathBuf::from("examples/evals/context-engineering-smoke.json")
);
assert_eq!(
context.output,
Some(PathBuf::from(".harn-runs/context-eval/smoke"))
);
assert!(context.json);
}
#[test]
fn test_parses_eval_skill_gate_args() {
let cli = Cli::parse_from([
"harn",
"eval",
"skill-gate",
"examples/evals/skill-gate/smoke/manifest.json",
"--output",
".harn-runs/skill-gate/smoke",
"--json",
]);
let Command::Eval(args) = cli.command.unwrap() else {
panic!("expected eval command");
};
let Some(EvalCommand::SkillGate(skill_gate)) = args.command else {
panic!("expected skill-gate command");
};
assert_eq!(
skill_gate.manifest,
PathBuf::from("examples/evals/skill-gate/smoke/manifest.json")
);
assert_eq!(
skill_gate.output,
Some(PathBuf::from(".harn-runs/skill-gate/smoke"))
);
assert!(skill_gate.json);
}
#[test]
fn test_parses_eval_scope_triage_args() {
let cli = Cli::parse_from([
"harn",
"eval",
"scope_triage",
"--dataset",
"evals/scope_triage/dataset.json",
"--output",
".harn-runs/scope-triage/smoke",
"--max-cases",
"5",
"--confidence-threshold",
"0.8",
"--live",
"--json",
]);
let Command::Eval(args) = cli.command.unwrap() else {
panic!("expected eval command");
};
let Some(EvalCommand::ScopeTriage(scope)) = args.command else {
panic!("expected scope_triage command");
};
assert_eq!(
scope.dataset,
PathBuf::from("evals/scope_triage/dataset.json")
);
assert_eq!(
scope.output,
Some(PathBuf::from(".harn-runs/scope-triage/smoke"))
);
assert_eq!(scope.max_cases, Some(5));
assert_eq!(scope.confidence_threshold, 0.8);
assert!(scope.live);
assert!(scope.json);
}
#[test]
fn test_parses_run_yes_flag() {
let cli = Cli::parse_from(["harn", "run", "--yes", "main.harn"]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert!(args.yes);
}
#[test]
fn test_parses_run_explain_cost_flag() {
let cli = Cli::parse_from(["harn", "run", "--explain-cost", "main.harn"]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert!(args.explain_cost);
assert_eq!(args.file.as_deref(), Some("main.harn"));
}
#[test]
fn test_parses_run_attestation_flags() {
let cli = Cli::parse_from([
"harn",
"run",
"--attest",
"--receipt-out",
"receipt.json",
"--attest-agent",
"agent-1",
"main.harn",
]);
let Command::Run(args) = cli.command.unwrap() else {
panic!("expected run command");
};
assert!(args.attest);
assert_eq!(args.receipt_out.as_deref(), Some("receipt.json"));
assert_eq!(args.attest_agent.as_deref(), Some("agent-1"));
}
#[test]
fn test_parses_verify_receipt() {
let cli = Cli::parse_from(["harn", "verify", "receipt.json", "--json"]);
let Command::Verify(args) = cli.command.unwrap() else {
panic!("expected verify command");
};
assert_eq!(args.receipt, "receipt.json");
assert!(args.json);
}
#[test]
fn test_parses_pipeline_lab_template() {
let cli = Cli::parse_from([
"harn",
"new",
"pipeline-lab-demo",
"--template",
"pipeline-lab",
]);
let Command::New(args) = cli.command.unwrap() else {
panic!("expected new command");
};
assert_eq!(args.template, Some(ProjectTemplate::PipelineLab));
}
#[test]
fn test_parses_chat_template() {
let cli = Cli::parse_from(["harn", "new", "chat-demo", "--template", "chat"]);
let Command::New(args) = cli.command.unwrap() else {
panic!("expected new command");
};
assert_eq!(args.first.as_deref(), Some("chat-demo"));
assert_eq!(args.template, Some(ProjectTemplate::Chat));
}
#[test]
fn test_parses_playground_args() {
let cli = Cli::parse_from([
"harn",
"playground",
"--host",
"examples/playground/host.harn",
"--script",
"examples/playground/echo.harn",
"--task",
"hi",
"--llm",
"ollama:qwen2.5-coder:latest",
"--yes",
"--watch",
]);
let Command::Playground(args) = cli.command.unwrap() else {
panic!("expected playground command");
};
assert_eq!(args.host, "examples/playground/host.harn");
assert_eq!(args.script, "examples/playground/echo.harn");
assert_eq!(args.task.as_deref(), Some("hi"));
assert_eq!(args.llm.as_deref(), Some("ollama:qwen2.5-coder:latest"));
assert_eq!(args.llm_mock, None);
assert_eq!(args.llm_mock_record, None);
assert!(args.yes);
assert!(args.watch);
}
#[test]
fn test_parses_try_command() {
let cli = Cli::parse_from([
"harn",
"try",
"hi",
"--max-iterations",
"7",
"--tool-format",
"text",
"--override-reason",
"compare native drift",
]);
let Command::Try(args) = cli.command.unwrap() else {
panic!("expected try command");
};
assert_eq!(args.prompt, "hi");
assert_eq!(args.max_iterations, 7);
assert_eq!(args.tool_format.as_deref(), Some("text"));
assert_eq!(
args.override_reason.as_deref(),
Some("compare native drift")
);
}
#[test]
fn test_parses_playground_llm_mock_flags() {
let cli = Cli::parse_from([
"harn",
"playground",
"--llm-mock",
"fixtures.jsonl",
"--host",
"host.harn",
]);
let Command::Playground(args) = cli.command.unwrap() else {
panic!("expected playground command");
};
assert_eq!(args.llm_mock.as_deref(), Some("fixtures.jsonl"));
assert_eq!(args.llm_mock_record, None);
let cli = Cli::parse_from(["harn", "playground", "--llm-mock-record", "recorded.jsonl"]);
let Command::Playground(args) = cli.command.unwrap() else {
panic!("expected playground command");
};
assert_eq!(args.llm_mock, None);
assert_eq!(args.llm_mock_record.as_deref(), Some("recorded.jsonl"));
}
#[test]
fn test_parses_bench_args() {
let cli = Cli::parse_from([
"harn",
"bench",
"main.harn",
"--iterations",
"25",
"--profile",
"--profile-json",
"bench.json",
]);
let Command::Bench(args) = cli.command.unwrap() else {
panic!("expected bench command");
};
assert_eq!(args.file.as_deref(), Some("main.harn"));
assert_eq!(args.iterations, 25);
assert!(args.profile.text);
assert_eq!(
args.profile.json_path.as_deref(),
Some(std::path::Path::new("bench.json"))
);
}
#[test]
fn test_parses_bench_replay_args() {
let cli = Cli::parse_from([
"harn",
"bench",
"replay",
"benchmarks/replay/suite.json",
"--json",
"--output",
"replay-benchmark.json",
"--filter",
"permission",
"--adapter",
"opencode-jsonl",
"--external-first",
"first.jsonl",
"--external-second",
"second.jsonl",
"--external-name",
"opencode-permission",
]);
let Command::Bench(args) = cli.command.unwrap() else {
panic!("expected bench command");
};
let Some(crate::cli::BenchCommand::Replay(replay)) = args.command else {
panic!("expected bench replay command");
};
assert_eq!(
replay.selection.as_deref(),
Some(std::path::Path::new("benchmarks/replay/suite.json"))
);
assert!(replay.json);
assert_eq!(
replay.output.as_deref(),
Some(std::path::Path::new("replay-benchmark.json"))
);
assert_eq!(replay.filter.as_deref(), Some("permission"));
assert_eq!(replay.adapter.as_deref(), Some("opencode-jsonl"));
assert_eq!(
replay.external_first.as_deref(),
Some(std::path::Path::new("first.jsonl"))
);
assert_eq!(
replay.external_second.as_deref(),
Some(std::path::Path::new("second.jsonl"))
);
assert_eq!(replay.external_name, "opencode-permission");
}
#[test]
fn test_profile_env_aliases_apply_to_supported_commands() {
let _env = crate::tests::common::env_lock::lock_env().blocking_lock();
struct EnvRestore {
saved: [(&'static str, Option<String>); 3],
}
impl Drop for EnvRestore {
fn drop(&mut self) {
for (name, value) in self.saved.iter() {
match value {
Some(value) => std::env::set_var(name, value),
None => std::env::remove_var(name),
}
}
}
}
let _restore = EnvRestore {
saved: [
("HARN_PROFILE", std::env::var("HARN_PROFILE").ok()),
("HARN_PROFILE_JSON", std::env::var("HARN_PROFILE_JSON").ok()),
("HARN_TRACE", std::env::var("HARN_TRACE").ok()),
],
};
std::env::set_var("HARN_PROFILE", "1");
std::env::set_var("HARN_PROFILE_JSON", "env-profile.json");
std::env::set_var("HARN_TRACE", "1");
let run = Cli::parse_from(["harn", "run", "main.harn"]);
let Command::Run(run_args) = run.command.unwrap() else {
panic!("expected run command");
};
assert!(run_args.trace);
assert!(run_args.profile.text);
assert_eq!(
run_args.profile.json_path.as_deref(),
Some(std::path::Path::new("env-profile.json"))
);
let bench = Cli::parse_from(["harn", "bench", "main.harn"]);
let Command::Bench(bench_args) = bench.command.unwrap() else {
panic!("expected bench command");
};
assert!(bench_args.profile.text);
assert_eq!(
bench_args.profile.json_path.as_deref(),
Some(std::path::Path::new("env-profile.json"))
);
let serve = Cli::parse_from(["harn", "serve", "acp", "agent.harn"]);
let Command::Serve(serve_args) = serve.command.unwrap() else {
panic!("expected serve command");
};
let crate::cli::ServeCommand::Acp(acp_args) = serve_args.command else {
panic!("expected serve acp");
};
assert!(acp_args.trace);
assert!(acp_args.profile.text);
assert_eq!(
acp_args.profile.json_path.as_deref(),
Some(std::path::Path::new("env-profile.json"))
);
}
#[test]
fn test_parses_quickstart_args() {
let cli = Cli::parse_from([
"harn",
"quickstart",
"--non-interactive",
"--provider",
"ollama",
"--model",
"qwen2.5-coder:latest",
]);
let Command::Quickstart(args) = cli.command.unwrap() else {
panic!("expected quickstart command");
};
assert!(args.non_interactive);
assert_eq!(args.provider.as_deref(), Some("ollama"));
assert_eq!(args.model.as_deref(), Some("qwen2.5-coder:latest"));
}
#[test]
fn test_parses_demo_no_args_lists_scenarios() {
let cli = Cli::parse_from(["harn", "demo"]);
let Command::Demo(args) = cli.command.unwrap() else {
panic!("expected demo command");
};
assert_eq!(args.scenario, None);
assert!(!args.list);
assert!(!args.live);
assert!(!args.replay);
assert!(!args.json);
}
#[test]
fn test_parses_demo_named_scenario_replay_default() {
let cli = Cli::parse_from(["harn", "demo", "merge-captain"]);
let Command::Demo(args) = cli.command.unwrap() else {
panic!("expected demo command");
};
assert_eq!(args.scenario.as_deref(), Some("merge-captain"));
assert!(!args.live);
}
#[test]
fn test_parses_demo_live_and_json_flags() {
let cli = Cli::parse_from([
"harn",
"demo",
"provider-race",
"--live",
"--json",
"--no-record",
]);
let Command::Demo(args) = cli.command.unwrap() else {
panic!("expected demo command");
};
assert_eq!(args.scenario.as_deref(), Some("provider-race"));
assert!(args.live);
assert!(args.json);
assert!(args.no_record);
}
#[test]
fn test_parses_demo_live_and_replay_are_mutually_exclusive() {
let result = Cli::try_parse_from(["harn", "demo", "merge-captain", "--live", "--replay"]);
assert!(
result.is_err(),
"--live and --replay must be mutually exclusive"
);
}