use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
fn ev() -> Command {
Command::new(env!("CARGO_BIN_EXE_ev"))
}
fn repo() -> std::path::PathBuf {
static N: AtomicU64 = AtomicU64::new(0);
let p = std::env::temp_dir().join(format!(
"ev-correct-{}-{}",
std::process::id(),
N.fetch_add(1, Ordering::Relaxed)
));
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).unwrap();
assert!(ev()
.arg("init")
.current_dir(&p)
.output()
.unwrap()
.status
.success());
p
}
fn run(repo: &std::path::Path, args: &[&str]) -> std::process::Output {
ev().args(args).current_dir(repo).output().unwrap()
}
fn repo_with_stale_ruling() -> (std::path::PathBuf, String) {
let r = repo();
let line = "{\"kind\":\"ev-decision-intake\",\"decision\":\"#247/#1458 Insights scope\",\"grounds\":[],\"blame\":\"Mac\",\"source_ref\":\"#247/#1458\",\"provenance\":\"imported\"}\n";
let path = r.join("p.jsonl");
std::fs::write(&path, line).unwrap();
assert!(run(
&r,
&[
"migrate",
"--source",
&format!("canonical:{}", path.display())
]
)
.status
.success());
let id = std::fs::read_dir(r.join(".evolving/ticks"))
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name().into_string().unwrap())
.find(|n| n.len() == 12)
.expect("one tick");
(r, id)
}
#[test]
fn correct_should_surface_a_decision_in_brief_after_its_authority_is_corrected_to_user_ruled() {
let (r, id) = repo_with_stale_ruling();
assert!(String::from_utf8_lossy(&run(&r, &["brief"]).stdout).contains("no user-ruled"));
let out = run(
&r,
&[
"correct",
&id,
"--authority",
"user-ruled",
"--blame",
"You",
],
);
assert!(
out.status.success(),
"stderr: {}",
String::from_utf8_lossy(&out.stderr)
);
let brief = run(&r, &["brief"]);
assert!(
String::from_utf8_lossy(&brief.stdout).contains("#247/#1458 Insights scope"),
"the corrected ruling must surface in brief"
);
}
#[test]
fn correct_should_keep_the_target_tick_immutable_and_append_a_child() {
let (r, id) = repo_with_stale_ruling();
let before = std::fs::read_to_string(r.join(".evolving/ticks").join(&id)).unwrap();
assert!(run(
&r,
&[
"correct",
&id,
"--authority",
"user-ruled",
"--blame",
"You"
]
)
.status
.success());
let after = std::fs::read_to_string(r.join(".evolving/ticks").join(&id)).unwrap();
assert_eq!(
before, after,
"the corrected target must never be rewritten in place"
);
let count = std::fs::read_dir(r.join(".evolving/ticks"))
.unwrap()
.filter(|e| e.as_ref().unwrap().path().is_file())
.count();
assert_eq!(count, 2, "a corrective child is appended (target + child)");
}
#[test]
fn correct_should_round_trip_clean_through_verify() {
let (r, id) = repo_with_stale_ruling();
assert!(run(
&r,
&[
"correct",
&id,
"--authority",
"user-ruled",
"--blame",
"You"
]
)
.status
.success());
assert!(
run(&r, &["verify"]).status.success(),
"the corrected chain must verify clean"
);
}
#[test]
fn correct_should_refuse_a_no_op_when_the_tag_is_already_set() {
let (r, id) = repo_with_stale_ruling();
let out = run(
&r,
&[
"correct",
&id,
"--authority",
"user-ruled",
"--blame",
"You",
],
);
let child = String::from_utf8_lossy(&out.stdout)
.split_whitespace()
.nth(1)
.unwrap()
.to_string();
let noop = run(
&r,
&[
"correct",
&child,
"--authority",
"user-ruled",
"--blame",
"You",
],
);
assert!(!noop.status.success(), "a no-op correction must be refused");
assert!(String::from_utf8_lossy(&noop.stderr).contains("nothing to correct"));
}
#[test]
fn correct_should_require_at_least_one_tag() {
let (r, id) = repo_with_stale_ruling();
let out = run(&r, &["correct", &id, "--blame", "You"]);
assert!(!out.status.success(), "correct needs at least one tag");
}
#[test]
fn correct_should_fail_on_an_unknown_tick_id() {
let (r, _id) = repo_with_stale_ruling();
let out = run(
&r,
&[
"correct",
"deadbeefdead",
"--authority",
"user-ruled",
"--blame",
"You",
],
);
assert!(!out.status.success(), "an unknown tick id must fail");
}