use std::path::{Path, PathBuf};
use std::process::Command as ProcessCommand;
use std::time::{SystemTime, UNIX_EPOCH};
use wvq_cli::{parse_args, run_with};
use wvq_command_bus::{Command, DoctorCommand, FakeService, InitCommand};
fn argv(parts: &[&str]) -> Vec<String> {
parts.iter().map(|part| (*part).to_owned()).collect()
}
fn fixture_repo() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("..")
.join("..")
.join("fixtures")
.join("openspec")
.join("repo")
}
fn product_repo() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("..").join("..")
}
struct TempRepo(PathBuf);
impl Drop for TempRepo {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
fn live_repo() -> TempRepo {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!("wvq-cli-live-{nanos}"));
for dir in [
"src",
"tests",
".weavatrix-quality",
"openspec/changes/live/specs/arithmetic",
] {
std::fs::create_dir_all(root.join(dir)).unwrap();
}
std::fs::write(
root.join("Cargo.toml"),
"[package]\nname = \"wvq-cli-live\"\nversion = \"0.0.0\"\nedition = \"2024\"\n",
)
.unwrap();
std::fs::write(
root.join("Cargo.lock"),
"# This file is automatically @generated by Cargo.\nversion = 4\n\n[[package]]\nname = \"wvq-cli-live\"\nversion = \"0.0.0\"\n",
)
.unwrap();
std::fs::write(
root.join("src/lib.rs"),
"pub fn add(a:i32,b:i32)->i32{a+b}\n",
)
.unwrap();
std::fs::write(
root.join("tests/addition.rs"),
"use wvq_cli_live::add;\n#[test]\nfn adds(){assert_eq!(add(2,3),5);}\n",
)
.unwrap();
std::fs::write(
root.join("openspec/changes/live/specs/arithmetic/spec.md"),
"# Arithmetic\n\n## ADDED Requirements\n\n### Requirement: Addition\nThe system SHALL add integers.\n\n#### Scenario: Sum\n- GIVEN two integers\n- WHEN added\n- THEN the sum is returned\n",
)
.unwrap();
std::fs::write(
root.join("openspec/changes/live/quality.yaml"),
"quality_contract_v: 1\nchange: live\nrisk:\n default: low\nrequirements:\n - capability: arithmetic\n requirement: addition\n scenarios:\n - scenario: sum\n obligations:\n - id: addition-suite\n kind: invariant\n evidence:\n required: []\n on_failure: []\n",
)
.unwrap();
std::fs::write(
root.join(".weavatrix-quality/config.yaml"),
"quality_policy_v: 1\ntest_bindings:\n - path: tests/addition.rs\n runner: cargo-test\n case: adds\n obligations: [addition-suite]\n",
)
.unwrap();
git(&root, &["init", "-q"]);
git(&root, &["add", "-A"]);
git(
&root,
&[
"-c",
"user.name=WVQ Test",
"-c",
"user.email=wvq@example.invalid",
"commit",
"-qm",
"baseline",
],
);
TempRepo(root)
}
fn git(root: &Path, args: &[&str]) {
let output = ProcessCommand::new("git")
.args(args)
.current_dir(root)
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn init_parses_and_scaffolds_an_empty_repository() {
let parsed = parse_args(&argv(&["init", "--force", "true"])).unwrap();
assert_eq!(parsed.command, Command::Init(InitCommand { force: true }));
let empty = TempRepo({
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!("wvq-cli-init-{nanos}"));
std::fs::create_dir_all(&root).unwrap();
root
});
let output = wvq_cli::run(&argv(&["--repo", empty.0.to_str().unwrap(), "init"]));
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("\"command\": \"init\""));
assert!(output.stdout.contains("config.yaml"));
assert_eq!(output.stdout.matches("runtime_llm_tokens").count(), 1);
assert!(output.stdout.contains("\"runtime_llm_tokens\": 0"));
assert!(empty.0.join(".weavatrix-quality/config.yaml").is_file());
assert!(!empty.0.join(".weavatrix-quality/quality.db").exists());
let again = wvq_cli::run(&argv(&["--repo", empty.0.to_str().unwrap(), "init"]));
assert_ne!(again.code, 0);
assert!(again.stderr.contains("already exists"));
}
#[test]
fn doctor_parses_and_does_not_write() {
let parsed = parse_args(&argv(&["doctor"])).unwrap();
assert_eq!(parsed.command, Command::Doctor(DoctorCommand {}));
let empty = TempRepo({
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let root = std::env::temp_dir().join(format!("wvq-cli-doctor-{nanos}"));
std::fs::create_dir_all(&root).unwrap();
root
});
let output = wvq_cli::run(&argv(&["--repo", empty.0.to_str().unwrap(), "doctor"]));
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("\"command\": \"doctor\""));
assert!(output.stdout.contains("\"authority\": false"));
assert!(output.stdout.contains("\"runtime_llm_tokens\": 0"));
assert!(!empty.0.join(".weavatrix-quality").exists());
}
#[test]
fn doctor_rejects_unknown_flags() {
let err = parse_args(&argv(&["doctor", "--change", "current"])).unwrap_err();
assert!(err.contains("unknown flag --change"), "{err}");
}
#[test]
fn spec_validate_succeeds_on_product_invariants() {
let repo = product_repo();
let output = wvq_cli::run(&argv(&[
"--repo",
repo.to_str().expect("utf-8 path"),
"spec",
"validate",
"--change",
"wvq-invariants",
]));
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("spec_validate"));
assert!(output.stdout.contains("wvq-invariants"));
assert!(output.stdout.contains("\"obligations\": 9"));
}
#[test]
fn doctor_on_product_repo_is_not_authority() {
let repo = product_repo();
let output = wvq_cli::run(&argv(&[
"--repo",
repo.to_str().expect("utf-8 path"),
"doctor",
]));
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("\"authority\": false"));
assert!(output.stdout.contains("wvq-invariants"));
assert!(output.stdout.contains("cargo-test"));
assert!(output.stdout.contains("unmeasured-never-clean"));
assert!(output
.stdout
.contains("crates/wvq-proof/tests/change_verdict.rs"));
assert!(output.stdout.contains("\"runtime_llm_tokens\": 0"));
}
#[test]
fn spec_validate_succeeds_on_fixture() {
let repo = fixture_repo();
let output = wvq_cli::run(&argv(&[
"--repo",
repo.to_str().expect("utf-8 path"),
"spec",
"validate",
"--change",
"sankey-others",
]));
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("spec_validate"));
assert!(output.stdout.contains("sankey-others"));
}
#[test]
fn spec_seal_prints_oracle_seal_id() {
let repo = fixture_repo();
let output = wvq_cli::run(&argv(&[
"--repo",
repo.to_str().expect("utf-8 path"),
"spec",
"seal",
"--change",
"sankey-others",
]));
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("oseal-"));
}
#[test]
fn live_run_is_nonzero_when_repository_has_no_supported_runner() {
let repo = fixture_repo();
let output = wvq_cli::run(&argv(&[
"--repo",
repo.to_str().expect("utf-8 path"),
"run",
"--change",
"sankey-others",
]));
assert_ne!(output.code, 0);
assert!(output.stdout.is_empty());
assert!(output.stderr.contains("no supported registered executor"));
}
#[test]
fn live_cli_run_status_and_verify_share_persistent_evidence() {
let repo = live_repo();
let root = repo.0.to_str().unwrap();
let run = wvq_cli::run(&argv(&[
"--repo", root, "run", "--change", "live", "--scope", "all",
]));
assert_eq!(run.code, 0, "{}", run.stderr);
assert!(run.stdout.contains("\"executed\": true"));
assert!(run.stdout.contains("\"outcome\": \"passed\""));
let status = wvq_cli::run(&argv(&["--repo", root, "status"]));
assert_eq!(status.code, 0, "{}", status.stderr);
assert!(status.stdout.contains("\"outcome\": \"passed\""));
let verify = wvq_cli::run(&argv(&["--repo", root, "verify", "--change", "live"]));
assert_eq!(verify.code, 0, "{}", verify.stderr);
assert!(verify.stdout.contains("PROVEN"));
}
#[test]
fn analyze_debt_select_run_verify_explain_are_wired() {
let fake = FakeService::default();
fake.set_verdict("UNPROVEN");
fake.put_explain(wvq_command_bus::ExplainReply {
id: "others-visible".into(),
kind: "obligation".into(),
summary: "others stay visible".into(),
provenance: vec!["quality.yaml:1".into()],
});
for cmd in ["analyze", "debt", "select", "run", "verify"] {
let output = run_with(&argv(&[cmd, "--change", "sankey-others"]), &fake);
assert_ne!(
output.code, 2,
"{cmd} must not be a usage error: {}",
output.stderr
);
assert!(
output.stderr.is_empty() || output.code != 0,
"{cmd}: {}",
output.stderr
);
}
let explained = run_with(&argv(&["explain", "others-visible"]), &fake);
assert_eq!(explained.code, 0, "{}", explained.stderr);
assert!(explained.stdout.contains("others stay visible"));
let model = run_with(
&argv(&[
"model",
"--change",
"sankey-others",
"--kind",
"runtime",
"--prompt",
"classify this failure",
]),
&fake,
);
assert_eq!(model.code, 0, "{}", model.stderr);
assert!(model.stdout.contains("fake-local-model"));
}
#[test]
fn block_verdict_returns_nonzero() {
let fake = FakeService::default();
fake.set_verdict("CONTRADICTED");
let output = run_with(&argv(&["verify", "--change", "sankey-others"]), &fake);
assert_eq!(output.code, 2, "blocking verdict must fail CI");
assert!(output.stdout.contains("CONTRADICTED"));
}
#[test]
fn observe_only_verify_never_fails_ci() {
let fake = FakeService::default();
fake.set_verdict("CONTRADICTED");
let output = run_with(
&argv(&[
"verify",
"--change",
"sankey-others",
"--observe-only",
"true",
]),
&fake,
);
assert_eq!(output.code, 0, "Stage A observe-only must not fail CI");
assert!(output.stdout.contains("CONTRADICTED"), "{}", output.stdout);
assert!(
output.stdout.contains("\"observe_only\": true"),
"{}",
output.stdout
);
assert!(
output.stdout.contains("\"blocking\": true"),
"{}",
output.stdout
);
}
#[test]
fn proven_verify_is_zero() {
let fake = FakeService::default();
fake.set_verdict("PROVEN");
let output = run_with(&argv(&["verify", "--change", "sankey-others"]), &fake);
assert_eq!(output.code, 0, "{}", output.stderr);
}
#[test]
fn unknown_command_is_nonzero() {
let fake = FakeService::default();
let output = run_with(&argv(&["mutate"]), &fake);
assert_ne!(output.code, 0);
assert!(output.stderr.contains("unknown command"));
}
#[test]
fn explain_requires_id() {
let fake = FakeService::default();
let output = run_with(&argv(&["explain"]), &fake);
assert_ne!(output.code, 0);
}
#[test]
fn model_requires_kind_and_prompt() {
let fake = FakeService::default();
let output = run_with(&argv(&["model", "--kind", "runtime"]), &fake);
assert_ne!(output.code, 0);
assert!(output.stderr.contains("--prompt is required"));
}
#[test]
fn misspelled_or_duplicate_flags_are_rejected() {
let fake = FakeService::default();
let misspelled = run_with(&argv(&["run", "--scop", "all"]), &fake);
assert_ne!(misspelled.code, 0);
assert!(misspelled.stderr.contains("unknown flag --scop"));
let duplicate = run_with(
&argv(&["verify", "--change", "one", "--change", "two"]),
&fake,
);
assert_ne!(duplicate.code, 0);
assert!(duplicate.stderr.contains("more than once"));
}
#[test]
fn committed_pr_range_is_preserved_by_cli() {
let parsed = parse_args(&argv(&[
"run",
"--change",
"sankey-others",
"--base",
"origin/main",
"--head",
"HEAD",
]))
.unwrap();
let Command::Run(command) = parsed.command else {
panic!("expected run command");
};
assert_eq!(command.base, "origin/main");
assert_eq!(command.head, "HEAD");
}
#[test]
fn recovery_range_is_preserved_by_cli() {
let parsed = parse_args(&argv(&[
"recover",
"--change",
"sankey-others",
"--base",
"origin/main",
"--head",
"HEAD",
]))
.unwrap();
let Command::Recovery(command) = parsed.command else {
panic!("expected recovery command");
};
assert_eq!(command.change, "sankey-others");
assert_eq!(command.base, "origin/main");
assert_eq!(command.head, "HEAD");
}
#[test]
fn passive_recording_is_a_first_class_cli_command() {
let parsed = parse_args(&argv(&[
"record",
"--change",
"sankey-others",
"--route",
"/dashboard",
"--headless",
"true",
"--fixtures-json",
r#"{"name":"Alice"}"#,
]))
.unwrap();
let Command::Record(command) = parsed.command else {
panic!("expected record command");
};
assert_eq!(command.route, "/dashboard");
assert_eq!(command.headless, Some(true));
assert_eq!(
command.fixture_values.get("name").map(String::as_str),
Some("Alice")
);
let output = run_with(
&argv(&["record", "--change", "sankey-others"]),
&FakeService::default(),
);
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("recording-fake"));
assert!(output.stdout.contains("preview-recorded-fake"));
}
const CONTINUOUS_JOURNAL: &str = r#"{
"schema_v": 1,
"source": "continuous",
"observed_only": true,
"session_id": "staging-checkout",
"initial": { "route": "/checkout" },
"events": [
{
"action": { "action": "activate", "target": { "test_id": "pay" } },
"after": { "route": "/checkout/done" }
}
]
}"#;
#[test]
fn ingest_journal_is_a_first_class_cli_command_and_never_seals() {
let parsed = parse_args(&argv(&[
"ingest-journal",
"--change",
"sankey-others",
"--journal",
CONTINUOUS_JOURNAL,
]))
.unwrap();
let Command::IngestJournal(command) = parsed.command else {
panic!("expected ingest-journal command");
};
assert_eq!(command.change, "sankey-others");
assert!(command.journal.contains("staging-checkout"));
let output = run_with(
&argv(&[
"ingest-journal",
"--change",
"sankey-others",
"--journal",
CONTINUOUS_JOURNAL,
]),
&FakeService::default(),
);
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("\"observed_only\": true"));
assert!(output.stdout.contains("\"seal_eligible\": false"));
assert!(!output.stdout.contains("preview-recorded-fake"));
}
#[test]
fn ingest_journal_refuses_a_seal_claim() {
let raw = CONTINUOUS_JOURNAL.replace("\"observed_only\": true", "\"observed_only\": false");
let output = run_with(
&argv(&["ingest-journal", "--journal", &raw]),
&FakeService::default(),
);
assert_eq!(output.code, 1, "{}", output.stdout);
assert!(output.stderr.contains("observed_only"), "{}", output.stderr);
}
const NETWORK_HAR: &str = r#"{
"log": {
"version": "1.2",
"entries": [{
"request": {
"method": "GET",
"url": "https://app.example/api/pay",
"headers": [{ "name": "Content-Type", "value": "application/json" }]
},
"response": {
"status": 200,
"content": { "mimeType": "application/json", "text": "{ \"ok\": true }" }
}
}]
}
}"#;
#[test]
fn ingest_cassette_is_a_first_class_cli_command_and_never_enables_replay() {
let parsed = parse_args(&argv(&[
"ingest-cassette",
"--origin",
"https://app.example",
"--har",
NETWORK_HAR,
]))
.unwrap();
let Command::IngestCassette(command) = parsed.command else {
panic!("expected ingest-cassette command");
};
assert_eq!(command.origin, "https://app.example");
let output = run_with(
&argv(&[
"ingest-cassette",
"--origin",
"https://app.example",
"--har",
NETWORK_HAR,
]),
&FakeService::default(),
);
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("\"replay_enabled\": false"));
assert!(output.stdout.contains("\"seal_eligible\": false"));
}
#[test]
fn ingest_cassette_refuses_unknown_har_versions() {
let output = run_with(
&argv(&[
"ingest-cassette",
"--origin",
"https://app.example",
"--har",
r#"{"log":{"version":"1.3","entries":[]}}"#,
]),
&FakeService::default(),
);
assert_eq!(output.code, 1, "{}", output.stdout);
assert!(output.stderr.contains("1.3"), "{}", output.stderr);
}
#[test]
fn baseline_is_a_first_class_cli_command_and_never_seals() {
let parsed = parse_args(&argv(&["baseline", "--change", "sankey-others"])).unwrap();
let Command::Baseline(command) = parsed.command else {
panic!("expected baseline command");
};
assert_eq!(command.change, "sankey-others");
assert_eq!(command.decision, "observed_only");
let output = run_with(
&argv(&["baseline", "--change", "sankey-others"]),
&FakeService::default(),
);
assert_eq!(output.code, 0, "{}", output.stderr);
assert!(output.stdout.contains("\"observed_only\": true"));
assert!(output.stdout.contains("\"seal_eligible\": false"));
assert!(output.stdout.contains("\"legacy-clone\""));
assert!(output.stdout.contains("\"new_unbaselined\": 1"));
}
#[test]
fn baseline_refuses_a_seal_decision() {
let output = run_with(
&argv(&[
"baseline",
"--change",
"sankey-others",
"--decision",
"accept_as_intended",
]),
&FakeService::default(),
);
assert_eq!(output.code, 1, "{}", output.stdout);
assert!(output.stderr.contains("decision"), "{}", output.stderr);
}