use aethershell::env::Env;
use aethershell::value::Value;
use std::collections::BTreeMap;
fn call(name: &str, args: Vec<Value>) -> Value {
let mut env = Env::new();
aethershell::builtins::call(name, args, &mut env).expect("builtin call")
}
fn op(kind: &str, path: &str, content: Option<&str>) -> Value {
let mut m = BTreeMap::new();
m.insert("op".to_string(), Value::Str(kind.to_string()));
m.insert("path".to_string(), Value::Str(path.to_string()));
if let Some(c) = content {
m.insert("content".to_string(), Value::Str(c.to_string()));
}
Value::Record(m)
}
fn field<'a>(v: &'a Value, k: &str) -> Option<&'a Value> {
match v {
Value::Record(m) => m.get(k),
_ => None,
}
}
fn int(v: &Value, k: &str) -> i64 {
match field(v, k) {
Some(Value::Int(n)) => *n,
other => panic!("expected int at {k}, got {other:?}"),
}
}
fn tmp(tag: &str) -> String {
let p = std::env::temp_dir().join(format!("ae_plandiff_{tag}.txt"));
let _ = std::fs::remove_file(&p);
p.to_string_lossy().to_string()
}
#[test]
fn a_rewrite_reports_only_the_lines_that_change() {
let path = tmp("rewrite");
std::fs::write(&path, "alpha\nbeta\ngamma\n").unwrap();
let out = call(
"plan_diff",
vec![Value::Array(vec![op(
"write",
&path,
Some("alpha\ndelta\ngamma\n"),
)])],
);
assert_eq!(int(&out, "added"), 1, "delta is the only new line");
assert_eq!(int(&out, "removed"), 1, "beta is the only line lost");
}
#[test]
fn writing_a_new_file_is_all_additions_and_says_it_does_not_exist() {
let path = tmp("create");
let out = call(
"plan_diff",
vec![Value::Array(vec![op("write", &path, Some("one\ntwo\n"))])],
);
assert_eq!(int(&out, "added"), 2);
assert_eq!(int(&out, "removed"), 0);
let entry = match field(&out, "diff") {
Some(Value::Array(a)) => a[0].clone(),
other => panic!("expected a diff array, got {other:?}"),
};
assert_eq!(
field(&entry, "exists"),
Some(&Value::Bool(false)),
"a reviewer must be able to tell a create from an overwrite"
);
}
#[test]
fn a_delete_accounts_for_every_line_it_removes() {
let path = tmp("delete");
std::fs::write(&path, "a\nb\nc\nd\n").unwrap();
let out = call("plan_diff", vec![Value::Array(vec![op("rm", &path, None)])]);
assert_eq!(int(&out, "removed"), 4);
assert_eq!(int(&out, "added"), 0);
}
#[test]
fn an_append_does_not_report_the_untouched_body_as_changed() {
let path = tmp("append");
std::fs::write(&path, "keep1\nkeep2\n").unwrap();
let out = call(
"plan_diff",
vec![Value::Array(vec![op("append", &path, Some("added\n"))])],
);
assert_eq!(int(&out, "added"), 1);
assert_eq!(int(&out, "removed"), 0);
}
#[test]
fn a_large_change_is_capped_and_says_how_much_it_elided() {
let path = tmp("large");
let body: String = (0..500).map(|i| format!("line{i}\n")).collect();
let out = call(
"plan_diff",
vec![Value::Array(vec![op("write", &path, Some(&body))])],
);
assert_eq!(int(&out, "added"), 500);
let entry = match field(&out, "diff") {
Some(Value::Array(a)) => a[0].clone(),
other => panic!("expected a diff array, got {other:?}"),
};
let shown = match field(&entry, "lines") {
Some(Value::Array(a)) => a.len() as i64,
other => panic!("expected lines, got {other:?}"),
};
assert!(shown < 500, "the diff must be capped, showed {shown}");
assert_eq!(
int(&entry, "elided"),
500 - shown,
"what was omitted must be counted, not dropped"
);
}
#[test]
fn the_diff_token_matches_the_plans_token() {
let path = tmp("token");
let ops = Value::Array(vec![op("write", &path, Some("x\n"))]);
let planned = call("plan", vec![ops.clone()]);
let diffed = call("plan_diff", vec![ops]);
assert_eq!(
field(&planned, "token"),
field(&diffed, "token"),
"the diff must describe the very plan the token approves"
);
}
#[test]
fn plan_diff_declares_that_it_reads_local_state() {
use aethershell::safety::{effect_is_declared, effect_of, Effect};
assert_eq!(effect_of("plan_diff"), Effect::ReadLocal);
assert!(effect_is_declared("plan_diff"));
}