use super::*;
#[test]
fn test_parses_crystallize_args() {
let cli = Cli::parse_from([
"harn",
"crystallize",
"--from",
"fixtures/crystallize",
"--out",
"workflows/version_bump.harn",
"--report",
"reports/version_bump.json",
"--eval-pack",
"harn.eval.toml",
"--min-examples",
"5",
"--shadow-from",
"fixtures/crystallize/holdout",
"--promotion-min-confidence",
"0.9",
"--workflow-name",
"version_bump",
]);
let Command::Crystallize(args) = cli.command.unwrap() else {
panic!("expected crystallize command");
};
assert!(args.command.is_none());
assert_eq!(args.from.as_deref(), Some("fixtures/crystallize"));
assert_eq!(args.out.as_deref(), Some("workflows/version_bump.harn"));
assert_eq!(args.report.as_deref(), Some("reports/version_bump.json"));
assert_eq!(args.eval_pack.as_deref(), Some("harn.eval.toml"));
assert_eq!(args.min_examples, 5);
assert_eq!(
args.shadow_from,
vec!["fixtures/crystallize/holdout".to_string()]
);
assert_eq!(args.promotion_min_confidence, 0.9);
assert_eq!(args.workflow_name.as_deref(), Some("version_bump"));
}
#[test]
fn test_parses_crystallize_validate_subcommand() {
let cli = Cli::parse_from(["harn", "crystallize", "validate", "bundles/version-bump"]);
let Command::Crystallize(args) = cli.command.unwrap() else {
panic!("expected crystallize command");
};
let Some(CrystallizeCommand::Validate(validate)) = args.command else {
panic!("expected validate subcommand");
};
assert_eq!(validate.bundle_dir, "bundles/version-bump");
}
#[test]
fn test_parses_crystallize_bundle_flag() {
let cli = Cli::parse_from([
"harn",
"crystallize",
"--from",
"fixtures/crystallize",
"--out",
"workflows/version_bump.harn",
"--report",
"reports/version_bump.json",
"--bundle",
"bundles/version-bump",
"--bundle-team",
"platform",
"--bundle-risk-level",
"medium",
]);
let Command::Crystallize(args) = cli.command.unwrap() else {
panic!("expected crystallize command");
};
assert!(args.command.is_none());
assert_eq!(args.bundle.as_deref(), Some("bundles/version-bump"));
assert_eq!(args.bundle_team.as_deref(), Some("platform"));
assert_eq!(args.bundle_risk_level.as_deref(), Some("medium"));
}
#[test]
fn test_parses_package_evals_flag() {
let cli = Cli::parse_from(["harn", "test", "package", "--evals"]);
let Command::Test(args) = cli.command.unwrap() else {
panic!("expected test command");
};
assert_eq!(args.target.as_deref(), Some("package"));
assert!(args.evals);
}
#[test]
fn test_parses_trust_query_flags() {
let cli = Cli::parse_from([
"harn",
"trust",
"query",
"--agent",
"github-triage-bot",
"--action",
"github.issue.opened",
"--since",
"2026-04-19T18:00:00Z",
"--until",
"2026-04-19T19:00:00Z",
"--tier",
"act-auto",
"--outcome",
"success",
"--limit",
"500",
"--grouped-by-trace",
"--json",
"--summary",
]);
let Command::Trust(args) = cli.command.unwrap() else {
panic!("expected trust command");
};
let TrustCommand::Query(query) = args.command else {
panic!("expected trust query");
};
assert_eq!(query.agent.as_deref(), Some("github-triage-bot"));
assert_eq!(query.action.as_deref(), Some("github.issue.opened"));
assert_eq!(query.since.as_deref(), Some("2026-04-19T18:00:00Z"));
assert_eq!(query.until.as_deref(), Some("2026-04-19T19:00:00Z"));
assert!(matches!(query.tier, Some(TrustTierArg::ActAuto)));
assert!(matches!(query.outcome, Some(TrustOutcomeArg::Success)));
assert_eq!(query.limit, Some(500));
assert!(query.grouped_by_trace);
assert!(query.json);
assert!(query.summary);
}
#[test]
fn test_parses_trust_demote_flags() {
let cli = Cli::parse_from([
"harn",
"trust",
"demote",
"github-triage-bot",
"--to",
"shadow",
"--reason",
"unexpected mutation",
]);
let Command::Trust(args) = cli.command.unwrap() else {
panic!("expected trust command");
};
let TrustCommand::Demote(demote) = args.command else {
panic!("expected trust demote");
};
assert_eq!(demote.agent, "github-triage-bot");
assert!(matches!(demote.to, TrustTierArg::Shadow));
assert_eq!(demote.reason, "unexpected mutation");
}
#[test]
fn test_parses_trust_graph_verify_chain() {
let cli = Cli::parse_from(["harn", "trust-graph", "verify-chain", "--json"]);
let Command::TrustGraph(args) = cli.command.unwrap() else {
panic!("expected trust-graph command");
};
let TrustCommand::VerifyChain(verify) = args.command else {
panic!("expected trust-graph verify-chain");
};
assert!(verify.json);
}
#[test]
fn test_parses_pack_signing_flags() {
let cli = Cli::parse_from([
"harn",
"pack",
"examples/hello.harn",
"--sign",
"--key",
"release.pem",
"--out",
"hello.harnpack",
]);
let Command::Pack(args) = cli.command.unwrap() else {
panic!("expected pack command");
};
assert!(args.command.is_none(), "build path takes no subcommand");
assert_eq!(args.entrypoint, Some(PathBuf::from("examples/hello.harn")));
assert_eq!(args.key, Some(PathBuf::from("release.pem")));
assert_eq!(args.out, Some(PathBuf::from("hello.harnpack")));
assert!(args.sign);
assert!(!args.unsigned);
}
#[test]
fn test_parses_pack_exclude_secrets_flag() {
let cli = Cli::parse_from([
"harn",
"pack",
"examples/hello.harn",
"--unsigned",
"--exclude-secrets",
]);
let Command::Pack(args) = cli.command.unwrap() else {
panic!("expected pack command");
};
assert!(args.exclude_secrets);
assert!(!args.include_secrets);
}
#[test]
fn test_parses_pack_verify_subcommand() {
use crate::cli::PackCommand;
let cli = Cli::parse_from([
"harn",
"pack",
"verify",
"bundle.harnpack",
"--allow-unsigned",
"--trust-policy",
"policy.json",
"--require-trusted-signer",
"--strict",
"--json",
]);
let Command::Pack(args) = cli.command.unwrap() else {
panic!("expected pack command");
};
let Some(PackCommand::Verify(verify)) = args.command else {
panic!("expected pack verify subcommand");
};
assert_eq!(verify.bundle, PathBuf::from("bundle.harnpack"));
assert!(verify.allow_unsigned);
assert_eq!(verify.trust_policy, Some(PathBuf::from("policy.json")));
assert!(verify.require_trusted_signer);
assert!(verify.strict);
assert!(verify.json);
}
#[test]
fn test_parses_install_integrity_flags() {
let cli = Cli::parse_from(["harn", "install", "--locked", "--offline"]);
let Command::Install(args) = cli.command.unwrap() else {
panic!("expected install command");
};
assert!(!args.frozen);
assert!(args.locked);
assert!(args.offline);
}
#[test]
fn test_parses_package_cache_subcommands() {
let cli = Cli::parse_from(["harn", "package", "list", "--json"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::List(list) = args.command else {
panic!("expected package list");
};
assert!(list.json);
let cli = Cli::parse_from(["harn", "package", "doctor", "--json"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Doctor(doctor) = args.command else {
panic!("expected package doctor");
};
assert!(doctor.json);
let cli = Cli::parse_from([
"harn",
"package",
"search",
"notion",
"--registry",
"index.toml",
"--json",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Search(search) = args.command else {
panic!("expected package search");
};
assert_eq!(search.query.as_deref(), Some("notion"));
assert_eq!(search.registry.as_deref(), Some("index.toml"));
assert!(search.json);
let cli = Cli::parse_from([
"harn",
"package",
"info",
"@burin/notion-sdk@1.2.3",
"--registry",
"index.toml",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Info(info) = args.command else {
panic!("expected package info");
};
assert_eq!(info.name, "@burin/notion-sdk@1.2.3");
assert_eq!(info.registry.as_deref(), Some("index.toml"));
let cli = Cli::parse_from(["harn", "package", "check", "pkg", "--json"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Check(check) = args.command else {
panic!("expected package check");
};
assert_eq!(check.package, Some(PathBuf::from("pkg")));
assert!(check.json);
let cli = Cli::parse_from(["harn", "package", "pack", "pkg", "--dry-run"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Pack(pack) = args.command else {
panic!("expected package pack");
};
assert_eq!(pack.package, Some(PathBuf::from("pkg")));
assert!(pack.dry_run);
let cli = Cli::parse_from(["harn", "package", "docs", "pkg", "--check"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Docs(docs) = args.command else {
panic!("expected package docs");
};
assert_eq!(docs.package, Some(PathBuf::from("pkg")));
assert!(docs.check);
let cli = Cli::parse_from(["harn", "package", "cache", "list"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Cache(cache) = args.command else {
panic!("expected package cache");
};
assert!(matches!(cache.command, PackageCacheCommand::List));
let cli = Cli::parse_from(["harn", "package", "cache", "clean", "--all"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Cache(cache) = args.command else {
panic!("expected package cache");
};
let PackageCacheCommand::Clean(clean) = cache.command else {
panic!("expected package cache clean");
};
assert!(clean.all);
let cli = Cli::parse_from(["harn", "package", "cache", "verify", "--materialized"]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Cache(cache) = args.command else {
panic!("expected package cache");
};
let PackageCacheCommand::Verify(verify) = cache.command else {
panic!("expected package cache verify");
};
assert!(verify.materialized);
}
#[test]
fn test_parses_publish_git_tag_flow_options() {
let cli = Cli::parse_from([
"harn",
"publish",
"pkg",
"--dry-run",
"--remote",
"upstream",
"--index-repo",
"burin-labs/harn-cloud",
"--index-path",
"package-index/harn-package-index.toml",
"--registry-name",
"@burin/acme-lib",
"--skip-index-pr",
"--json",
]);
let Command::Publish(PublishArgs {
package,
dry_run,
remote,
index_repo,
index_path,
registry_name,
skip_index_pr,
json,
..
}) = cli.command.unwrap()
else {
panic!("expected publish command");
};
assert_eq!(package, Some(PathBuf::from("pkg")));
assert!(dry_run);
assert_eq!(remote, "upstream");
assert_eq!(index_repo, "burin-labs/harn-cloud");
assert_eq!(
index_path,
PathBuf::from("package-index/harn-package-index.toml")
);
assert_eq!(registry_name.as_deref(), Some("@burin/acme-lib"));
assert!(skip_index_pr);
assert!(json);
}
#[test]
fn test_parses_rule_publish_and_search() {
let cli = Cli::parse_from([
"harn",
"rule",
"publish",
"pkg",
"--dry-run",
"--registry-name",
"@acme/rules",
"--skip-index-pr",
"--json",
]);
let Command::Rule(args) = cli.command.unwrap() else {
panic!("expected rule command");
};
let RuleCommand::Publish(publish) = args.command else {
panic!("expected rule publish");
};
assert_eq!(publish.package, Some(PathBuf::from("pkg")));
assert!(publish.dry_run);
assert_eq!(publish.registry_name.as_deref(), Some("@acme/rules"));
assert!(publish.skip_index_pr);
assert!(publish.json);
let cli = Cli::parse_from([
"harn",
"rule",
"search",
"typescript",
"--registry",
"index.toml",
"--json",
]);
let Command::Rule(args) = cli.command.unwrap() else {
panic!("expected rule command");
};
let RuleCommand::Search(search) = args.command else {
panic!("expected rule search");
};
assert_eq!(search.query.as_deref(), Some("typescript"));
assert_eq!(search.registry.as_deref(), Some("index.toml"));
assert!(search.json);
}
#[test]
fn test_parses_package_scaffold_openapi() {
let cli = Cli::parse_from([
"harn",
"package",
"scaffold",
"openapi",
"--name",
"acme-sdk-harn",
"--module-name",
"acme_sdk",
"--client-name",
"AcmeClient",
"--spec",
"./openapi.json",
"--out",
"./acme-sdk-harn",
"--default-base-url",
"https://api.example.test",
"--harn-openapi-path",
"../harn-openapi",
"--harn-openapi-git",
"https://github.com/burin-labs/harn-openapi",
"--harn-openapi-rev",
"abc123",
"--force",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Scaffold(scaffold) = args.command else {
panic!("expected package scaffold");
};
let PackageScaffoldCommand::Openapi(openapi) = scaffold.command;
assert_eq!(openapi.name, "acme-sdk-harn");
assert_eq!(openapi.module_name.as_deref(), Some("acme_sdk"));
assert_eq!(openapi.client_name.as_deref(), Some("AcmeClient"));
assert_eq!(openapi.spec, "./openapi.json");
assert_eq!(openapi.out, Some(PathBuf::from("./acme-sdk-harn")));
assert_eq!(
openapi.default_base_url.as_deref(),
Some("https://api.example.test")
);
assert_eq!(
openapi.harn_openapi_path,
Some(PathBuf::from("../harn-openapi"))
);
assert_eq!(
openapi.harn_openapi_git.as_deref(),
Some("https://github.com/burin-labs/harn-openapi")
);
assert_eq!(openapi.harn_openapi_rev.as_deref(), Some("abc123"));
assert!(openapi.force);
}
#[test]
fn test_parses_package_outdated_audit_artifacts() {
let cli = Cli::parse_from([
"harn",
"package",
"outdated",
"--remote",
"--refresh",
"--registry",
"index.toml",
"--json",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Outdated(outdated) = args.command else {
panic!("expected package outdated");
};
assert!(outdated.remote);
assert!(outdated.refresh);
assert_eq!(outdated.registry.as_deref(), Some("index.toml"));
assert!(outdated.json);
let cli = Cli::parse_from([
"harn",
"package",
"audit",
"--registry",
"index.toml",
"--skip-materialized",
"--json",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Audit(audit) = args.command else {
panic!("expected package audit");
};
assert_eq!(audit.registry.as_deref(), Some("index.toml"));
assert!(audit.skip_materialized);
assert!(audit.json);
let cli = Cli::parse_from([
"harn",
"package",
"artifacts",
"manifest",
"--output",
"vendor/manifest.json",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Artifacts(artifacts) = args.command else {
panic!("expected package artifacts");
};
let PackageArtifactsCommand::Manifest(manifest) = artifacts.command else {
panic!("expected artifacts manifest");
};
assert_eq!(manifest.output, Some(PathBuf::from("vendor/manifest.json")));
let cli = Cli::parse_from([
"harn",
"package",
"artifacts",
"check",
"vendor/manifest.json",
"--json",
]);
let Command::Package(args) = cli.command.unwrap() else {
panic!("expected package command");
};
let PackageCommand::Artifacts(artifacts) = args.command else {
panic!("expected package artifacts");
};
let PackageArtifactsCommand::Check(check) = artifacts.command else {
panic!("expected artifacts check");
};
assert_eq!(check.manifest, PathBuf::from("vendor/manifest.json"));
assert!(check.json);
}
#[test]
fn test_install_and_update_accept_json_flag() {
let cli = Cli::parse_from(["harn", "install", "--frozen", "--json"]);
let Command::Install(install) = cli.command.unwrap() else {
panic!("expected install command");
};
assert!(install.frozen);
assert!(install.json);
let cli = Cli::parse_from(["harn", "update", "--all", "--json"]);
let Command::Update(update) = cli.command.unwrap() else {
panic!("expected update command");
};
assert!(update.all);
assert!(update.json);
}
#[test]
fn test_parses_skills_subcommands() {
let cli = Cli::parse_from(["harn", "skills", "list", "--json"]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::List(list) = args.command else {
panic!("expected skills list");
};
assert!(list.json);
let cli = Cli::parse_from(["harn", "skills", "get", "harn-language", "--full", "--json"]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::Get(get) = args.command else {
panic!("expected skills get");
};
assert_eq!(get.name, "harn-language");
assert!(get.full);
assert!(get.json);
let cli = Cli::parse_from([
"harn",
"skills",
"dump",
"--all",
"--out",
"/tmp/skills",
"--force",
]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::Dump(dump) = args.command else {
panic!("expected skills dump");
};
assert!(dump.all);
assert_eq!(dump.out.as_deref(), Some("/tmp/skills"));
assert!(dump.force);
let cli = Cli::parse_from(["harn", "skills", "resolved", "--json", "--all"]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::Resolved(resolved) = args.command else {
panic!("expected skills resolved");
};
assert!(resolved.json);
assert!(resolved.all);
let cli = Cli::parse_from(["harn", "skills", "match", "deploy the app", "--top-n", "3"]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::Match(matcher) = args.command else {
panic!("expected skills match");
};
assert_eq!(matcher.query, "deploy the app");
assert_eq!(matcher.top_n, 3);
let cli = Cli::parse_from([
"harn",
"skills",
"install",
"https://example.com/acme/harn-skills.git",
"--tag",
"v1.0",
"--namespace",
"acme",
]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::Install(install) = args.command else {
panic!("expected skills install");
};
assert_eq!(install.tag.as_deref(), Some("v1.0"));
assert_eq!(install.namespace.as_deref(), Some("acme"));
let cli = Cli::parse_from([
"harn",
"skills",
"new",
"deploy",
"--description",
"Ship things",
]);
let Command::Skills(args) = cli.command.unwrap() else {
panic!("expected skills command");
};
let SkillsCommand::New(new_args) = args.command else {
panic!("expected skills new");
};
assert_eq!(new_args.name, "deploy");
assert_eq!(new_args.description.as_deref(), Some("Ship things"));
}
#[test]
fn test_parses_skill_provenance_subcommands() {
let cli = Cli::parse_from(["harn", "skill", "key", "generate", "--out", "signer.pem"]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::Key(key_args) = args.command else {
panic!("expected skill key");
};
let SkillKeyCommand::Generate(generate) = key_args.command;
assert_eq!(generate.out, "signer.pem");
let cli = Cli::parse_from(["harn", "skill", "sign", "SKILL.md", "--key", "signer.pem"]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::Sign(sign) = args.command else {
panic!("expected skill sign");
};
assert_eq!(sign.skill, "SKILL.md");
assert_eq!(sign.key, "signer.pem");
let cli = Cli::parse_from([
"harn",
"skill",
"endorse",
"SKILL.md",
"--key",
"auditor.pem",
]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::Endorse(endorse) = args.command else {
panic!("expected skill endorse");
};
assert_eq!(endorse.skill, "SKILL.md");
assert_eq!(endorse.key, "auditor.pem");
let cli = Cli::parse_from(["harn", "skill", "verify", "SKILL.md", "--json"]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::Verify(verify) = args.command else {
panic!("expected skill verify");
};
assert_eq!(verify.skill, "SKILL.md");
assert!(verify.json);
let cli = Cli::parse_from(["harn", "skill", "who-signed", "SKILL.md", "--json"]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::WhoSigned(who_signed) = args.command else {
panic!("expected skill who-signed");
};
assert_eq!(who_signed.skill, "SKILL.md");
assert!(who_signed.json);
let cli = Cli::parse_from([
"harn",
"skill",
"trust",
"add",
"--from",
"https://example.com/signer.pub",
]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::Trust(trust) = args.command else {
panic!("expected skill trust");
};
let SkillTrustCommand::Add(add) = trust.command else {
panic!("expected skill trust add");
};
assert_eq!(add.from, "https://example.com/signer.pub");
let cli = Cli::parse_from(["harn", "skill", "trust", "list"]);
let Command::Skill(args) = cli.command.unwrap() else {
panic!("expected skill command");
};
let SkillCommand::Trust(trust) = args.command else {
panic!("expected skill trust");
};
assert!(matches!(trust.command, SkillTrustCommand::List(_)));
}