use super::*;
#[test]
fn labels_are_read_from_the_parts_and_refusals_carry_the_call() {
use agentplane::core::{Label, SourceId};
let model = Label::untrusted(SourceId::new("model:x/y"));
assert!(
refusal("denied: `src/x` is protected".to_owned(), &model)
.label()
.is_untrusted()
);
let input = Tainted::object([
("bundle", Tainted::trusted(json!({}))),
(
"digest",
Tainted::with_label(json!({}), provenance::from(provenance::VERIFIER)),
),
(
"settings",
Tainted::object([("nudges", Tainted::trusted(json!([])))]),
),
]);
let label = conversation_label(&input);
assert!(
label
.provenance
.contains(&SourceId::new(provenance::VERIFIER))
);
let flat = Tainted::with_label(json!({ "bundle": {} }), model.clone());
assert_eq!(conversation_label(&flat).provenance, model.provenance);
}
#[test]
fn absolute_paths_inside_the_root_become_relative_and_others_stay() {
let mut a = json!({ "path": "/work/repo/specs/001-x/plan.md" });
relativise(&mut a, Some("/work/repo"));
assert_eq!(a["path"], "specs/001-x/plan.md");
let mut outside = json!({ "path": "/work/repository-two/x" });
relativise(&mut outside, Some("/work/repo"));
assert_eq!(outside["path"], "/work/repository-two/x");
let mut none = json!({ "path": "/etc/passwd" });
relativise(&mut none, None);
assert_eq!(none["path"], "/etc/passwd");
}
#[test]
fn a_stop_stands_on_evidence_speccoder_checks() {
let gate = StopGate {
spec: "Rows are sorted\n ascending by name.".to_owned(),
frozen: vec!["tests/test_us3.py".to_owned()],
rejected: false,
};
let call =
|kind: &str, evidence: &str| json!({ "kind": kind, "evidence": evidence, "reason": "why" });
let judge =
|args: &Value, refused: bool, asked: bool| judge_stop(&gate, args, refused, asked, 5);
let unproven = |s: Stop| matches!(s, Stop::Refused(_));
assert!(unproven(judge(
&json!({ "reason": "beyond the task's module scope" }),
false,
false
)));
assert!(unproven(judge(&call("scope", "src/cli"), false, false)));
assert!(unproven(judge(
&call("wrong_test", "tests/new_test.py"),
false,
false
)));
assert!(unproven(judge(
&call("spec_conflict", "sorted descending by name"),
false,
false
)));
assert!(unproven(judge(
&call("spec_conflict", "sorted"),
false,
false
)));
assert!(unproven(judge(
&call("refused", "cargo add x"),
false,
false
)));
let quote = call("spec_conflict", "sorted ascending by name");
assert!(
matches!(judge("e, false, false), Stop::Asked(t) if t.contains("5 turns are left"))
);
assert_eq!(
judge("e, false, true),
Stop::Stopped("why — the spec says “sorted ascending by name”".to_owned())
);
assert_eq!(
judge(&call("wrong_test", "./tests/test_us3.py"), false, true),
Stop::Stopped("why — frozen test `./tests/test_us3.py`".to_owned())
);
assert!(matches!(
judge(&call("refused", "cargo add x"), true, false),
Stop::Stopped(_)
));
let q = call("needs_information", "which locale?");
assert!(matches!(
judge_stop(&gate, &q, false, false, 0),
Stop::Stopped(_)
));
}
#[test]
fn a_replayed_step_says_nothing() {
#[derive(Debug, Default)]
struct Seen(std::sync::Mutex<Vec<u32>>);
impl crate::progress::Progress for Seen {
fn event(&self, e: &crate::progress::Event) {
if let crate::progress::Event::ToolCall { turn, .. } = e {
self.0.lock().unwrap().push(*turn);
}
}
}
let seen = std::sync::Arc::new(Seen::default());
let replaying = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(true));
let quiet = Quiet {
replaying: std::sync::Arc::clone(&replaying),
next: seen.clone(),
};
let call = |turn| crate::progress::Event::ToolCall {
task: "T012".to_owned(),
turn,
tool: "run_command".to_owned(),
target: String::new(),
decision: "allow".to_owned(),
ok: Some(true),
tail: Vec::new(),
contained: false,
};
crate::progress::Progress::event(&quiet, &call(1));
replaying.store(false, std::sync::atomic::Ordering::SeqCst);
crate::progress::Progress::event(&quiet, &call(2));
assert_eq!(*seen.0.lock().unwrap(), [2]);
}