use std::path::PathBuf;
use std::process::Command;
use std::time::Duration;
use tempfile::tempdir;
use verifyos_cli::core::engine::Engine;
use verifyos_cli::profiles::{register_rules, RuleSelection, ScanProfile};
use verifyos_cli::report::build_report;
use verifyos_cli::rules::core::{RuleStatus, Severity};
fn get_example_path(filename: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("examples")
.join(filename)
}
fn toml_path_literal(path: &std::path::Path) -> String {
path.display().to_string().replace('\\', "\\\\")
}
fn create_engine() -> Engine {
let mut engine = Engine::new();
register_rules(&mut engine, ScanProfile::Full, &RuleSelection::default());
engine
}
#[test]
fn test_bad_app_fails_rules() {
let bad_app = get_example_path("bad_app.ipa");
let engine = create_engine();
let run = engine.run(&bad_app).expect("Engine orchestrator failed");
let results = run.results;
let mut has_errors = false;
for res in results {
if let Severity::Error = res.severity {
if matches!(res.report, Ok(ref report) if report.status == RuleStatus::Fail)
|| res.report.is_err()
{
has_errors = true;
}
}
}
assert!(
has_errors,
"Expected bad_app.ipa to trigger rule errors, but it passed cleanly."
);
}
#[test]
fn test_good_app_passes_rules() {
let good_app = get_example_path("good_app.ipa");
let mut engine = Engine::new();
let mut selection = RuleSelection::default();
selection
.exclude
.insert("RULE_LAUNCH_SCREEN_STORYBOARD".to_string());
selection
.exclude
.insert("RULE_BINARY_STRIPPING".to_string());
selection.exclude.insert("RULE_APP_ICON_ALPHA".to_string());
register_rules(&mut engine, ScanProfile::Full, &selection);
let run = engine.run(&good_app).expect("Engine orchestrator failed");
let results = run.results;
let mut has_errors = false;
for res in results {
if let Severity::Error = res.severity {
match res.report {
Ok(report) if report.status == RuleStatus::Fail => {
has_errors = true;
println!(
"Rule failed in good_app: {} - {:?}",
res.rule_id, report.message
);
}
Err(err) => {
// Ignore MachO errors in mock binaries for this test
if !format!("{:?}", err).contains("MachO") {
has_errors = true;
println!("Unexpected error in good_app: {} - {:?}", res.rule_id, err);
}
}
_ => {}
}
}
}
assert!(
!has_errors,
"Expected good_app.ipa to pass all rules, but it triggered an error."
);
}
#[test]
fn test_help_shows_verify_os_banner() {
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.arg("--help")
.output()
.expect("help should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
assert!(stdout.contains("verify-OS"));
assert!(stdout.contains("|___/\\____/\\____/"));
}
#[test]
fn test_list_rules_table_output() {
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.arg("--list-rules")
.output()
.expect("list-rules should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
assert!(stdout.contains("Rule ID"));
assert!(stdout.contains("RULE_PRIVACY_MANIFEST"));
assert!(stdout.contains("basic, full"));
}
#[test]
fn test_list_rules_json_output() {
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args(["--list-rules", "--format", "json"])
.output()
.expect("list-rules json should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
let value: serde_json::Value = serde_json::from_str(&stdout).expect("valid json");
let first = value
.as_array()
.and_then(|items| items.first())
.expect("items");
assert!(first.get("rule_id").is_some());
assert!(first.get("name").is_some());
assert!(first.get("severity").is_some());
assert!(first.get("category").is_some());
assert!(first.get("default_profiles").is_some());
}
#[test]
fn test_show_rule_table_output() {
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args(["--show-rule", "RULE_PRIVATE_API"])
.output()
.expect("show-rule should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
assert!(stdout.contains("Rule ID"));
assert!(stdout.contains("RULE_PRIVATE_API"));
assert!(stdout.contains("Recommendation"));
}
#[test]
fn test_show_rule_json_output() {
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args(["--show-rule", "RULE_PRIVATE_API", "--format", "json"])
.output()
.expect("show-rule json should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("stdout should be utf8");
let value: serde_json::Value = serde_json::from_str(&stdout).expect("valid json");
assert_eq!(value["rule_id"], "RULE_PRIVATE_API");
assert!(value.get("recommendation").is_some());
assert!(value.get("default_profiles").is_some());
}
#[test]
fn test_scan_subcommand_json_matches_legacy_scan() {
let legacy = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--format",
"json",
"--no-progress",
"--fail-on",
"off",
])
.output()
.expect("legacy scan should run");
let explicit = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"scan",
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--format",
"json",
"--no-progress",
"--fail-on",
"off",
])
.output()
.expect("scan subcommand should run");
assert!(legacy.status.success());
assert!(explicit.status.success());
let legacy_json: serde_json::Value =
serde_json::from_slice(&legacy.stdout).expect("legacy stdout should be json");
let explicit_json: serde_json::Value =
serde_json::from_slice(&explicit.stdout).expect("explicit stdout should be json");
assert_eq!(
legacy_json["results"].as_array().map(Vec::len),
explicit_json["results"].as_array().map(Vec::len)
);
assert_eq!(
legacy_json["scanned_targets"],
explicit_json["scanned_targets"]
);
}
#[test]
fn test_pr_comment_subcommand_prefers_existing_comment_file() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
std::fs::create_dir_all(&output_dir).expect("create output dir");
std::fs::write(
output_dir.join("pr-comment.md"),
"## verifyOS review summary\n\n- Findings in scope: `1`\n",
)
.expect("write comment");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"pr-comment",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--sticky-marker",
])
.output()
.expect("pr-comment should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("utf8");
assert!(stdout.contains("<!-- voc-analysis-comment -->"));
assert!(stdout.contains("## verifyOS review summary"));
}
#[test]
fn test_pr_comment_subcommand_falls_back_to_reports() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let agent_dir = output_dir.join(".verifyos-agent");
std::fs::create_dir_all(&agent_dir).expect("create agent dir");
std::fs::write(
agent_dir.join("agent-pack.json"),
r#"{"generated_at_unix":1,"total_findings":2,"findings":[]}"#,
)
.expect("write agent pack");
std::fs::write(
output_dir.join("doctor.json"),
r#"{"checks":[{"name":"Config","status":"Pass","detail":"ok"}]}"#,
)
.expect("write doctor report");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"pr-comment",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--scan-exit",
"1",
"--doctor-exit",
"0",
])
.output()
.expect("pr-comment should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("utf8");
assert!(stdout.contains("## voc analysis"));
assert!(stdout.contains("Findings: **2**"));
assert!(stdout.contains("Scan exit code: `1`"));
assert!(stdout.contains("Doctor exit code: `0`"));
}
#[test]
fn test_pr_comment_subcommand_can_render_from_repair_plan() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
std::fs::create_dir_all(&output_dir).expect("create output dir");
std::fs::write(
output_dir.join("repair-plan.md"),
"# verifyOS Repair Plan\n\n## Context\n\n- Source: `fresh-scan`\n",
)
.expect("write repair plan");
std::fs::write(
output_dir.join("pr-comment.md"),
"## verifyOS review summary\n\n- Findings in scope: `1`\n",
)
.expect("write comment");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"pr-comment",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-plan",
])
.output()
.expect("pr-comment --from-plan should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("utf8");
assert!(stdout.contains("# verifyOS Repair Plan"));
assert!(!stdout.contains("## verifyOS review summary"));
}
#[test]
fn test_pr_comment_subcommand_can_use_explicit_plan_path() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let plan_dir = dir.path().join("plans");
std::fs::create_dir_all(&output_dir).expect("create output dir");
std::fs::create_dir_all(&plan_dir).expect("create plan dir");
let plan_path = plan_dir.join("repair-plan.md");
std::fs::write(
&plan_path,
"# verifyOS Repair Plan\n\n## Context\n\n- Source: `existing-assets`\n",
)
.expect("write repair plan");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"pr-comment",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-plan",
"--plan-path",
plan_path.to_str().expect("utf8 plan path"),
])
.output()
.expect("pr-comment --plan-path should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("utf8");
assert!(stdout.contains("# verifyOS Repair Plan"));
assert!(stdout.contains("existing-assets"));
}
#[test]
fn test_handoff_subcommand_writes_full_bundle() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("handoff-output");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"handoff",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--profile",
"basic",
])
.output()
.expect("handoff should run");
assert!(output.status.success());
assert!(output_dir.join("AGENTS.md").exists());
assert!(output_dir.join("fix-prompt.md").exists());
assert!(output_dir.join("repair-plan.md").exists());
assert!(output_dir.join("pr-brief.md").exists());
assert!(output_dir.join("pr-comment.md").exists());
assert!(output_dir.join("handoff.json").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.json").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.md").exists());
assert!(output_dir.join(".verifyos-agent/next-steps.sh").exists());
let manifest = std::fs::read_to_string(output_dir.join("handoff.json"))
.expect("handoff manifest should exist");
assert!(manifest.contains("\"app_path\""));
assert!(manifest.contains("\"assets\""));
assert!(manifest.contains("repair-plan.md"));
}
#[test]
fn test_analyze_size_subcommand_reports_json_breakdown() {
let dir = tempdir().expect("temp dir");
let app = dir.path().join("Demo.app");
std::fs::create_dir_all(app.join("Frameworks/Foo.framework")).expect("framework dir");
std::fs::write(app.join("Demo"), vec![0u8; 10]).expect("write binary");
std::fs::write(app.join("Assets.car"), vec![0u8; 20]).expect("write assets");
std::fs::write(app.join("Frameworks/Foo.framework/Foo"), vec![0u8; 30])
.expect("write framework");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"analyze-size",
"--app",
app.to_str().expect("utf8 app path"),
"--format",
"json",
"--top",
"2",
])
.output()
.expect("analyze-size should run");
assert!(output.status.success());
let stdout = String::from_utf8(output.stdout).expect("utf8");
let value: serde_json::Value = serde_json::from_str(&stdout).expect("json");
assert_eq!(value["total_bytes"], 60);
assert_eq!(value["top_files"].as_array().expect("top files").len(), 2);
assert_eq!(value["top_files"][0]["category"], "framework");
assert!(value["categories"]
.as_array()
.expect("categories")
.iter()
.any(|item| item["category"] == "asset"));
}
#[test]
fn test_quiet_scan_suppresses_stdout_and_still_writes_markdown() {
let dir = tempdir().expect("temp dir");
let markdown = dir.path().join("report.md");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"scan",
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--quiet",
"--md-out",
markdown.to_str().expect("utf8 markdown path"),
"--fail-on",
"off",
])
.output()
.expect("quiet scan should run");
assert!(output.status.success());
assert_eq!(String::from_utf8(output.stdout).expect("utf8 stdout"), "");
let contents = std::fs::read_to_string(markdown).expect("markdown output should exist");
assert!(contents.contains("# verifyOS-cli Report"));
}
#[test]
fn test_summary_subcommand_summarizes_report_json() {
let dir = tempdir().expect("temp dir");
let report_path = dir.path().join("report.json");
let scan = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"scan",
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--format",
"json",
"--no-progress",
"--fail-on",
"off",
])
.output()
.expect("scan should run");
assert!(scan.status.success());
std::fs::write(&report_path, &scan.stdout).expect("write report");
let summary = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"summary",
"--report",
report_path.to_str().expect("utf8 path"),
])
.output()
.expect("summary should run");
assert!(summary.status.success());
let stdout = String::from_utf8(summary.stdout).expect("utf8");
assert!(stdout.contains("# verifyOS summary"));
assert!(stdout.contains("Status breakdown"));
assert!(stdout.contains("Top findings"));
}
#[test]
fn test_agent_pack_writes_fix_json() {
let dir = tempdir().expect("temp dir");
let output_path = dir.path().join("fixes.json");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--agent-pack",
output_path.to_str().expect("utf8 output path"),
])
.output()
.expect("agent-pack run should succeed");
assert!(
!output.status.success(),
"bad_app should still fail exit threshold"
);
let contents = std::fs::read_to_string(&output_path).expect("agent pack should be written");
let value: serde_json::Value = serde_json::from_str(&contents).expect("valid agent pack");
assert!(value["total_findings"].as_u64().unwrap_or_default() >= 1);
assert!(value["findings"].as_array().is_some());
assert!(value["findings"][0].get("rule_id").is_some());
assert!(value["findings"][0].get("suggested_fix_scope").is_some());
assert!(value["findings"][0].get("target_files").is_some());
assert!(value["findings"][0].get("patch_hint").is_some());
assert!(value["findings"][0].get("why_it_fails_review").is_some());
}
#[test]
fn test_agent_pack_writes_markdown() {
let dir = tempdir().expect("temp dir");
let output_path = dir.path().join("fixes.md");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--agent-pack",
output_path.to_str().expect("utf8 output path"),
"--agent-pack-format",
"markdown",
])
.output()
.expect("agent-pack markdown run should succeed");
assert!(
!output.status.success(),
"bad_app should still fail exit threshold"
);
let contents = std::fs::read_to_string(&output_path).expect("agent markdown pack exists");
assert!(contents.contains("# verifyOS Agent Fix Pack"));
assert!(contents.contains("## Findings by Fix Scope"));
}
#[test]
fn test_agent_pack_bundle_writes_json_and_markdown() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("agent-pack");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"--app",
get_example_path("bad_app.ipa").to_str().expect("utf8 path"),
"--agent-pack",
output_dir.to_str().expect("utf8 output dir"),
"--agent-pack-format",
"bundle",
])
.output()
.expect("agent-pack bundle run should succeed");
assert!(
!output.status.success(),
"bad_app should still fail exit threshold"
);
assert!(output_dir.join("agent-pack.json").exists());
assert!(output_dir.join("agent-pack.md").exists());
}
#[test]
fn test_init_creates_agents_file() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
])
.output()
.expect("init should run");
assert!(output.status.success());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("## verifyOS-cli"));
assert!(contents.contains("RULE_PRIVACY_MANIFEST"));
assert!(contents.contains("<!-- verifyos-cli:agents:start -->"));
}
#[test]
fn test_init_updates_existing_agents_file_without_removing_custom_content() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
std::fs::write(
&agents_path,
"# AGENTS.md\n\nMy custom note\n\n<!-- verifyos-cli:agents:start -->\nold\n<!-- verifyos-cli:agents:end -->\n\nKeep me\n",
)
.expect("write existing AGENTS.md");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
])
.output()
.expect("init update should run");
assert!(output.status.success());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("My custom note"));
assert!(contents.contains("Keep me"));
assert!(!contents.contains("\nold\n"));
assert_eq!(
contents
.matches("<!-- verifyos-cli:agents:start -->")
.count(),
1
);
}
#[test]
fn test_init_from_scan_injects_current_project_risks() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
])
.output()
.expect("init from scan should run");
assert!(output.status.success());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("### Current Project Risks"));
assert!(contents.contains("#### Suggested Patch Order"));
assert!(contents.contains("Missing Privacy Manifest"));
}
#[test]
fn test_init_from_scan_with_baseline_keeps_only_new_risks() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let baseline_path = dir.path().join("baseline.json");
let engine = create_engine();
let run = engine
.run(get_example_path("bad_app.ipa"))
.expect("engine should scan bad app");
let report = build_report(run.results, run.total_duration_ms, run.cache_stats);
std::fs::write(
&baseline_path,
serde_json::to_string_pretty(&report).expect("baseline json"),
)
.expect("write baseline");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--baseline",
baseline_path.to_str().expect("utf8 baseline path"),
])
.output()
.expect("init from scan with baseline should run");
assert!(output.status.success());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("### Current Project Risks"));
assert!(contents.contains("No new or regressed risks"));
assert!(!contents.contains("| `high` | `RULE_PRIVACY_MANIFEST` |"));
assert!(!contents.contains("- **Missing Privacy Manifest** (`RULE_PRIVACY_MANIFEST`)"));
}
#[test]
fn test_init_from_scan_with_agent_pack_dir_writes_bundle_and_links_it() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let pack_dir = dir.path().join(".verifyos-agent");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--agent-pack-dir",
pack_dir.to_str().expect("utf8 agent pack dir"),
])
.output()
.expect("init from scan with pack dir should run");
assert!(output.status.success());
assert!(pack_dir.join("agent-pack.json").exists());
assert!(pack_dir.join("agent-pack.md").exists());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("Agent bundle:"));
assert!(contents.contains(&format!("{}/agent-pack.md", pack_dir.display())));
}
#[test]
fn test_init_write_commands_injects_follow_up_commands() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let pack_dir = dir.path().join(".verifyos-agent");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--agent-pack-dir",
pack_dir.to_str().expect("utf8 agent pack dir"),
"--profile",
"basic",
"--write-commands",
])
.output()
.expect("init write commands should run");
assert!(output.status.success());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("### Next Commands"));
assert!(contents.contains("voc --app"));
assert!(contents.contains("--profile basic"));
assert!(contents.contains("voc doctor --output-dir"));
assert!(contents.contains("--fix --from-scan"));
assert!(contents.contains(&pack_dir.display().to_string()));
assert!(contents.contains("agent-pack-format bundle"));
}
#[test]
fn test_init_shell_script_writes_next_steps_and_mentions_it() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let pack_dir = dir.path().join(".verifyos-agent");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--agent-pack-dir",
pack_dir.to_str().expect("utf8 agent pack dir"),
"--shell-script",
])
.output()
.expect("init shell script should run");
assert!(output.status.success());
let script_path = pack_dir.join("next-steps.sh");
assert!(script_path.exists());
let script = std::fs::read_to_string(&script_path).expect("script should exist");
assert!(script.contains("#!/usr/bin/env bash"));
assert!(script.contains("voc --app"));
assert!(script.contains("--agent-pack-format bundle"));
assert!(script.contains("voc doctor --output-dir"));
assert!(script.contains("--fix --from-scan"));
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains("### Next Commands"));
assert!(contents.contains("next-steps.sh"));
}
#[test]
fn test_init_shell_script_without_agent_pack_dir_creates_default_bundle() {
let dir = tempdir().expect("temp dir");
let agents_path = dir.path().join("AGENTS.md");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.current_dir(dir.path())
.args([
"init",
"--path",
agents_path.to_str().expect("utf8 agents path"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--shell-script",
])
.output()
.expect("init shell script without dir should run");
assert!(output.status.success());
let default_dir = dir.path().join(".verifyos-agent");
assert!(default_dir.join("agent-pack.json").exists());
assert!(default_dir.join("agent-pack.md").exists());
assert!(default_dir.join("next-steps.sh").exists());
let contents = std::fs::read_to_string(&agents_path).expect("agents file should exist");
assert!(contents.contains(".verifyos-agent/agent-pack.md"));
assert!(contents.contains(".verifyos-agent/next-steps.sh"));
}
#[test]
fn test_init_output_dir_writes_assets_under_one_root() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--agent-pack-dir",
output_dir
.join(".verifyos-agent")
.to_str()
.expect("utf8 agent pack dir"),
"--fix-prompt",
"--shell-script",
])
.output()
.expect("init output dir should run");
assert!(output.status.success());
assert!(output_dir.join("AGENTS.md").exists());
assert!(output_dir.join("fix-prompt.md").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.json").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.md").exists());
assert!(output_dir.join(".verifyos-agent/next-steps.sh").exists());
}
#[test]
fn test_init_fix_prompt_writes_prompt_file() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--fix-prompt",
])
.output()
.expect("init fix prompt should run");
assert!(output.status.success());
let prompt =
std::fs::read_to_string(output_dir.join("fix-prompt.md")).expect("fix prompt should exist");
assert!(prompt.contains("# verifyOS Fix Prompt"));
assert!(prompt.contains("Repair plan:"));
assert!(prompt.contains("## Related Artifacts"));
assert!(prompt.contains("## Findings"));
assert!(prompt.contains("## Validation Commands"));
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents should exist");
assert!(agents.contains("fix-prompt.md"));
}
#[test]
fn test_init_uses_verifyos_toml_defaults() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("config-output");
let config_path = dir.path().join("verifyos.toml");
std::fs::write(
&config_path,
format!(
r#"
[init]
output_dir = "{}"
write_commands = true
shell_script = true
fix_prompt = true
profile = "basic"
"#,
toml_path_literal(&output_dir)
),
)
.expect("write config");
let output = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"--config",
config_path.to_str().expect("utf8 config path"),
"init",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
])
.output()
.expect("init with config defaults should run");
assert!(output.status.success());
assert!(output_dir.join("AGENTS.md").exists());
assert!(output_dir.join("fix-prompt.md").exists());
assert!(output_dir.join(".verifyos-agent/next-steps.sh").exists());
let agents = std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents exist");
assert!(agents.contains("--profile basic"));
assert!(agents.contains("### Next Commands"));
}
#[test]
fn test_doctor_detects_healthy_init_assets() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let init = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"init",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--fix-prompt",
"--shell-script",
])
.output()
.expect("init should run");
assert!(init.status.success());
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
])
.output()
.expect("doctor should run");
assert!(doctor.status.success());
let stdout = String::from_utf8(doctor.stdout).expect("utf8");
assert!(stdout.contains("Config"));
assert!(stdout.contains("AGENTS.md"));
assert!(stdout.contains("Referenced assets"));
}
#[test]
fn test_doctor_plan_lists_selected_repairs() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--repair",
"pr-comment",
"--plan",
"--format",
"json",
])
.output()
.expect("doctor plan should run");
assert!(doctor.status.success());
let stdout = String::from_utf8(doctor.stdout).expect("utf8");
let value: serde_json::Value = serde_json::from_str(&stdout).expect("valid json");
let plan = value["repair_plan"].as_array().expect("repair plan array");
assert!(plan.iter().any(|item| item["target"] == "pr-comment"));
}
#[test]
fn test_doctor_plan_from_scan_includes_context() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
std::fs::create_dir_all(&output_dir).expect("create output dir");
let baseline_path = output_dir.join("baseline.json");
let freshness_path = output_dir.join("report.json");
std::fs::write(
&baseline_path,
r#"{"ruleset_version":"1","generated_at_unix":0,"total_duration_ms":0,"cache_stats":{"nested_bundles":{"hits":0,"misses":0},"usage_scan":{"hits":0,"misses":0},"private_api_scan":{"hits":0,"misses":0},"sdk_scan":{"hits":0,"misses":0},"capability_scan":{"hits":0,"misses":0},"signature_summary":{"hits":0,"misses":0},"bundle_plist":{"hits":0,"misses":0},"entitlements":{"hits":0,"misses":0},"provisioning_profile":{"hits":0,"misses":0},"bundle_files":{"hits":0,"misses":0},"instrumentation_scan":{"hits":0,"misses":0}},"slow_rules":[],"results":[]}"#,
)
.expect("write baseline");
std::fs::write(&freshness_path, "{}").expect("write freshness source");
let app_path = get_example_path("bad_app.ipa");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-scan",
app_path.to_str().expect("utf8 app path"),
"--baseline",
baseline_path.to_str().expect("utf8 baseline path"),
"--freshness-against",
freshness_path.to_str().expect("utf8 freshness path"),
"--repair",
"pr-comment",
"--plan",
"--format",
"json",
])
.output()
.expect("doctor plan from scan should run");
assert!(doctor.status.success());
let stdout = String::from_utf8(doctor.stdout).expect("utf8");
let value: serde_json::Value = serde_json::from_str(&stdout).expect("valid json");
let context = value["plan_context"].as_object().expect("plan context");
assert_eq!(context["source"], "fresh-scan");
assert_eq!(context["scan_artifact"], app_path.display().to_string());
assert_eq!(
context["baseline_path"],
baseline_path.display().to_string()
);
assert_eq!(
context["freshness_source"],
freshness_path.display().to_string()
);
assert_eq!(
context["repair_targets"]
.as_array()
.expect("repair targets"),
&vec![serde_json::Value::String("pr-comment".to_string())]
);
}
#[test]
fn test_doctor_plan_can_write_markdown_file() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let plan_path = output_dir.join("repair-plan.md");
std::fs::create_dir_all(&output_dir).expect("create output dir");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--repair",
"pr-comment",
"--plan",
"--plan-out",
plan_path.to_str().expect("utf8 plan path"),
])
.output()
.expect("doctor plan markdown should run");
assert!(doctor.status.success());
let markdown = std::fs::read_to_string(plan_path).expect("plan markdown should exist");
assert!(markdown.contains("# verifyOS Repair Plan"));
assert!(markdown.contains("## Context"));
assert!(markdown.contains("fresh-scan"));
assert!(markdown.contains("pr-comment"));
}
#[test]
fn test_doctor_uses_verifyos_toml_defaults() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("doctor-output");
let plan_out = dir.path().join("repair-plan.md");
let config_path = dir.path().join("verifyos.toml");
std::fs::write(
&config_path,
format!(
r#"
[doctor]
output_dir = "{}"
fix = true
repair = ["pr-comment"]
freshness_against = "doctor-report.json"
plan_out = "{}"
profile = "basic"
open_pr_brief = true
open_pr_comment = true
"#,
toml_path_literal(&output_dir),
toml_path_literal(&plan_out)
),
)
.expect("write config");
std::fs::create_dir_all(&output_dir).expect("create output dir");
std::fs::write(output_dir.join("doctor-report.json"), "{}").expect("write freshness report");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"--config",
config_path.to_str().expect("utf8 config path"),
"doctor",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
])
.output()
.expect("doctor with config defaults should run");
assert!(doctor.status.success());
assert!(!output_dir.join("AGENTS.md").exists());
assert!(output_dir.join("pr-comment.md").exists());
assert!(!output_dir.join("pr-brief.md").exists());
assert!(plan_out.exists());
}
#[test]
fn test_doctor_uses_explicit_freshness_source() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let agent_dir = output_dir.join(".verifyos-agent");
std::fs::create_dir_all(&agent_dir).expect("create agent dir");
std::fs::write(
output_dir.join("AGENTS.md"),
"## verifyOS-cli\n\n- Shortcut script: `.verifyos-agent/next-steps.sh`\n",
)
.expect("write agents");
std::fs::write(
agent_dir.join("next-steps.sh"),
"voc --app path/to/app.ipa --profile basic\n",
)
.expect("write script");
std::thread::sleep(Duration::from_secs(1));
std::fs::write(output_dir.join("custom-report.json"), "{}").expect("write report");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--freshness-against",
"custom-report.json",
"--format",
"json",
])
.output()
.expect("doctor should run");
assert!(doctor.status.success());
let stdout = String::from_utf8(doctor.stdout).expect("utf8");
let value: serde_json::Value = serde_json::from_str(&stdout).expect("valid json");
let freshness = value["checks"]
.as_array()
.expect("checks")
.iter()
.find(|item| item["name"] == "Asset freshness")
.expect("freshness check");
assert_eq!(freshness["status"], "Warn");
assert!(freshness["detail"]
.as_str()
.expect("detail")
.contains("custom-report.json"));
}
#[test]
fn test_doctor_fails_when_agents_references_missing_assets() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
std::fs::create_dir_all(&output_dir).expect("create output dir");
std::fs::write(
output_dir.join("AGENTS.md"),
"### Current Project Risks\n\n- Agent bundle: `.verifyos-agent/agent-pack.json` and `.verifyos-agent/agent-pack.md`\n\n### Next Commands\n\n```bash\nvoc --app example.ipa --profile full\n```\n",
)
.expect("write agents");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
])
.output()
.expect("doctor should run");
assert!(!doctor.status.success());
let stdout = String::from_utf8(doctor.stdout).expect("utf8");
assert!(stdout.contains("Referenced assets"));
assert!(stdout.contains("FAIL"));
}
#[test]
fn test_doctor_fix_bootstraps_missing_agent_assets() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
])
.output()
.expect("doctor --fix should run");
assert!(doctor.status.success());
assert!(output_dir.join("AGENTS.md").exists());
assert!(output_dir.join("fix-prompt.md").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.json").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.md").exists());
assert!(output_dir.join(".verifyos-agent/next-steps.sh").exists());
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents file should exist");
assert!(agents.contains(".verifyos-agent/agent-pack.md"));
assert!(agents.contains(".verifyos-agent/next-steps.sh"));
assert!(agents.contains("fix-prompt.md"));
}
#[test]
fn test_doctor_fix_can_repair_only_pr_comment() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let initial = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--profile",
"basic",
])
.output()
.expect("initial doctor run should succeed");
assert!(initial.status.success());
let fix_prompt_mtime = std::fs::metadata(output_dir.join("fix-prompt.md"))
.expect("fix prompt exists")
.modified()
.expect("mtime");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--repair",
"pr-comment",
])
.output()
.expect("doctor selective pr-comment repair should run");
assert!(doctor.status.success());
assert!(output_dir.join("pr-comment.md").exists());
let next_fix_prompt_mtime = std::fs::metadata(output_dir.join("fix-prompt.md"))
.expect("fix prompt still exists")
.modified()
.expect("mtime");
assert_eq!(fix_prompt_mtime, next_fix_prompt_mtime);
}
#[test]
fn test_doctor_fix_can_repair_only_agent_bundle() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let initial = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
])
.output()
.expect("initial doctor run should succeed");
assert!(initial.status.success());
std::fs::remove_dir_all(output_dir.join(".verifyos-agent")).expect("remove agent bundle");
std::fs::remove_file(output_dir.join("fix-prompt.md")).expect("remove fix prompt");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--repair",
"agent-bundle",
])
.output()
.expect("doctor selective agent-bundle repair should run");
assert!(!doctor.status.success());
assert!(output_dir.join(".verifyos-agent/agent-pack.json").exists());
assert!(output_dir.join(".verifyos-agent/agent-pack.md").exists());
assert!(output_dir.join(".verifyos-agent/next-steps.sh").exists());
assert!(!output_dir.join("fix-prompt.md").exists());
}
#[test]
fn test_doctor_fix_repairs_managed_block_paths_and_keeps_custom_notes() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
std::fs::create_dir_all(&output_dir).expect("create output dir");
std::fs::write(
output_dir.join("AGENTS.md"),
"# AGENTS.md\n\nKeep me\n\n<!-- verifyos-cli:agents:start -->\n## verifyOS-cli\n\n- Agent bundle: `broken/agent-pack.json` and `broken/agent-pack.md`\n- Shortcut script: `broken/next-steps.sh`\n- Agent fix prompt: `broken/fix-prompt.md`\n<!-- verifyos-cli:agents:end -->\n",
)
.expect("write agents");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
])
.output()
.expect("doctor --fix should run");
assert!(doctor.status.success());
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents file should exist");
assert!(agents.contains("Keep me"));
assert!(agents.contains(".verifyos-agent/agent-pack.md"));
assert!(agents.contains(".verifyos-agent/next-steps.sh"));
assert!(agents.contains("fix-prompt.md"));
assert!(!agents.contains("broken/agent-pack.md"));
}
#[test]
fn test_doctor_fix_from_scan_refreshes_assets_with_real_findings() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--profile",
"basic",
])
.output()
.expect("doctor --fix --from-scan should run");
assert!(doctor.status.success());
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents file should exist");
assert!(agents.contains("### Current Project Risks"));
assert!(agents.contains("Missing Privacy Manifest"));
assert!(agents.contains("voc --app"));
assert!(agents.contains("bad_app.ipa"));
assert!(agents.contains("--profile basic"));
let pack = std::fs::read_to_string(output_dir.join(".verifyos-agent/agent-pack.json"))
.expect("agent pack should exist");
let value: serde_json::Value = serde_json::from_str(&pack).expect("valid json");
assert!(value["total_findings"].as_u64().unwrap_or_default() >= 1);
let prompt =
std::fs::read_to_string(output_dir.join("fix-prompt.md")).expect("prompt should exist");
assert!(prompt.contains("# verifyOS Fix Prompt"));
assert!(prompt.contains("Missing Privacy Manifest"));
}
#[test]
fn test_doctor_fix_from_scan_can_generate_pr_brief() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--profile",
"basic",
"--open-pr-brief",
])
.output()
.expect("doctor --fix --open-pr-brief should run");
assert!(doctor.status.success());
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents file should exist");
assert!(agents.contains("pr-brief.md"));
assert!(agents.contains("--open-pr-brief"));
let brief =
std::fs::read_to_string(output_dir.join("pr-brief.md")).expect("pr brief should exist");
assert!(brief.contains("# verifyOS PR Brief"));
assert!(brief.contains("Repair plan:"));
assert!(brief.contains("## Related Artifacts"));
assert!(brief.contains("## Summary"));
assert!(brief.contains("## Current Risks"));
assert!(brief.contains("## Validation Commands"));
assert!(brief.contains("Missing Privacy Manifest"));
assert!(brief.contains("bad_app.ipa"));
let script = std::fs::read_to_string(output_dir.join(".verifyos-agent/next-steps.sh"))
.expect("script should exist");
assert!(script.contains("--open-pr-brief"));
}
#[test]
fn test_doctor_fix_from_scan_can_generate_pr_comment() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let doctor = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--profile",
"basic",
"--open-pr-comment",
])
.output()
.expect("doctor --fix --open-pr-comment should run");
assert!(doctor.status.success());
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents file should exist");
assert!(agents.contains("pr-comment.md"));
assert!(agents.contains("repair-plan.md"));
assert!(agents.contains("--open-pr-comment"));
let comment =
std::fs::read_to_string(output_dir.join("pr-comment.md")).expect("pr comment should exist");
assert!(comment.contains("## verifyOS review summary"));
assert!(comment.contains("## Related Artifacts"));
assert!(comment.contains("### Top risks"));
assert!(comment.contains("### Validation"));
assert!(comment.contains("Missing Privacy Manifest"));
assert!(comment.contains("bad_app.ipa"));
let script = std::fs::read_to_string(output_dir.join(".verifyos-agent/next-steps.sh"))
.expect("script should exist");
assert!(script.contains("--open-pr-comment"));
}
#[test]
fn test_doctor_fix_preserves_existing_context_without_from_scan() {
let dir = tempdir().expect("temp dir");
let output_dir = dir.path().join("artifacts");
let initial = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
"--from-scan",
get_example_path("bad_app.ipa")
.to_str()
.expect("utf8 app path"),
"--profile",
"basic",
"--open-pr-brief",
])
.output()
.expect("initial doctor fix should run");
assert!(initial.status.success());
std::fs::remove_file(output_dir.join("fix-prompt.md")).expect("remove fix prompt");
let repair = Command::new(env!("CARGO_BIN_EXE_voc"))
.args([
"doctor",
"--output-dir",
output_dir.to_str().expect("utf8 output dir"),
"--fix",
])
.output()
.expect("doctor repair should run");
assert!(repair.status.success());
let agents =
std::fs::read_to_string(output_dir.join("AGENTS.md")).expect("agents file should exist");
assert!(agents.contains("bad_app.ipa"));
assert!(agents.contains("voc doctor --output-dir"));
assert!(agents.contains("--fix --from-scan"));
assert!(agents.contains("--profile basic"));
assert!(agents.contains("--open-pr-brief"));
}