use serde_json::{Map, Value};
use super::tests::session;
use lean_ctx::shell::containment::tests::{
caller_killed_before_its_guard, caller_standin_marker, external_fixture, run_external_fixture,
};
fn marking_call(marker: &std::path::Path) -> Map<String, Value> {
Map::from_iter([
(
"argv".into(),
serde_json::json!([
"sh",
"-c",
format!("echo ran > '{}'; sleep 60", marker.display())
]),
),
("cwd".into(), Value::String(".".into())),
("env".into(), serde_json::json!({})),
("timeout_ms".into(), Value::from(20_000)),
])
}
#[test]
fn a_shell_call_never_runs_before_its_guard_is_ready() {
run_external_fixture(module_path!(), "shell_call_caller_killed_fixture");
}
#[test]
#[ignore = "fixture body of a_shell_call_never_runs_before_its_guard_is_ready"]
fn shell_call_caller_killed_fixture() {
let fixture = external_fixture();
caller_killed_before_its_guard(module_path!(), "shell_call_caller_standin");
fixture.complete();
}
#[test]
#[ignore = "stand-in caller of shell_call_caller_killed_fixture"]
fn shell_call_caller_standin() {
let marker = caller_standin_marker();
let root = tempfile::tempdir().unwrap();
let mut session = session(root.path(), false, true);
session.policy.allowed_executables = vec!["sh".to_string()];
let _ = session.call_shell(&marking_call(&marker));
}
#[test]
fn a_shell_call_without_a_guard_is_refused_and_never_runs() {
use lean_ctx::shell::containment::tests::{GuardFault, set_guard_fault_for_test};
let root = tempfile::tempdir().unwrap();
let marker = root.path().join("ran");
let mut session = session(root.path(), false, true);
session.policy.allowed_executables = vec!["sh".to_string()];
for fault in [GuardFault::CannotJoin, GuardFault::WrongGroup] {
set_guard_fault_for_test(Some(fault));
let refused = session.call_shell(&marking_call(&marker));
set_guard_fault_for_test(None);
let Err(error) = refused else {
panic!("{fault:?}: a call without a guard is refused");
};
assert_eq!(error.message, "shell process could not be contained");
assert!(!marker.exists(), "the refused call's program ran");
}
}
#[test]
fn a_governed_session_call_is_receipted_as_its_frame_or_not_written() {
use lean_ctx::core::context_admission::receipt_store::{self, observed, trace};
const SESSION: &str = concat!(
r#"{"id":"1","op":"hello","schema_version":1,"transport_version":1,"#,
r#""agent_tools_interface_version":"1.0.0","sdk_version":"1.1.0"}"#,
"\n",
r#"{"id":"2","op":"call","tool":"ctx_read","#,
r#""arguments":{"path":"sample.txt","mode":"full"}}"#,
"\n",
);
let _data = lean_ctx::core::data_dir::isolated_data_dir();
let _community = lean_ctx::core::policy::runtime::TestPolicyOverride::set(None);
let config = lean_ctx::core::paths::config_dir_read_only().expect("config dir");
std::fs::create_dir_all(&config).expect("config dir");
std::fs::write(
config.join("config.toml"),
"[context_gateway]\nmode = \"governed\"\n",
)
.expect("config");
let root = tempfile::tempdir().expect("project");
let root = root.path().canonicalize().expect("canonical root");
std::fs::write(root.join("sample.txt"), "session_marker alpha\nbeta\n").expect("source");
let session = session(&root, false, false);
let root = root.to_string_lossy().into_owned();
let call_frame = |output: &[u8]| -> Vec<u8> {
let frames: Vec<&[u8]> = output.split_inclusive(|byte| *byte == b'\n').collect();
assert_eq!(frames.len(), 2, "hello and call");
frames[1].to_vec()
};
let mut output = Vec::new();
super::serve(&session, SESSION.as_bytes(), &mut output).expect("served");
let frame = call_frame(&output);
let answer: Value = serde_json::from_slice(&frame).expect("frame");
assert_eq!(answer["ok"], true, "{answer}");
let found = receipt_store::by_output(&root, &receipt_store::output_digest(&frame));
assert_eq!(found.len(), 1, "one receipt binds the frame's bytes");
let stored = found[0].1.as_ref().expect("a verified receipt");
let trace::StoredTrace::Bound { sidecar, .. } = trace::load(&stored.digest) else {
panic!("a mandatory receipt has its sidecar");
};
assert_eq!(sidecar.transport, trace::Transport::ToolSession);
assert_eq!(
sidecar.release_prepared.channel,
trace::Channel::SessionFrame
);
assert!(matches!(
observed::load(&stored.digest),
observed::StoredObservation::Bound {
observed: observed::Observed::ReturnedToTransport { .. },
..
}
));
let hello = output.len() - frame.len();
let hello = receipt_store::output_digest(&output[..hello]);
assert!(receipt_store::by_output(&root, &hello).is_empty());
let gateway = lean_ctx::core::data_dir::lean_ctx_data_dir()
.expect("data dir")
.join("gateway");
std::fs::remove_dir_all(&gateway).expect("the store of the first call");
std::fs::write(&gateway, b"not a directory").expect("blocked store");
let mut output = Vec::new();
super::serve(&session, SESSION.as_bytes(), &mut output).expect("served");
let answer: Value = serde_json::from_slice(&call_frame(&output)).expect("frame");
assert_eq!(answer["ok"], false, "{answer}");
assert_eq!(answer["error"]["code"], "tool_error");
assert!(
answer["error"]["message"]
.as_str()
.is_some_and(|message| message.contains(receipt_store::NOT_PERSISTED_CODE)),
"{answer}"
);
assert!(!String::from_utf8_lossy(&output).contains("session_marker"));
}