use super::invoking::{AGENT, FakeEnv, Scene};
use crate::prompt::dispatch::door;
use crate::prompt::tool::{ENV_CONV_BRANCH, ENV_CONV_REPO};
use crate::test_support::{litany_binary, with_litany_home};
use std::collections::HashMap;
use std::io::Write;
use std::sync::atomic::AtomicBool;
#[test]
fn a_malformed_block_is_refused_naming_the_expected_shape() {
let scene = Scene::new();
let (code, _) = scene.invoke("{\"id\":\"tu_1\"}");
let err = code
.expect_err("a block with no name cannot be run")
.to_string();
assert!(err.contains("malformed tool_use block"), "{err}");
assert!(err.contains("\"name\": \"<tool>\""), "{err}");
}
struct FailingRead;
impl std::io::Read for FailingRead {
fn read(&mut self, _: &mut [u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("pipe broke"))
}
}
#[test]
fn an_unreadable_stdin_is_refused_rather_than_read_as_an_empty_block() {
let scene = Scene::new();
let mut stdout: Vec<u8> = Vec::new();
let err = scene
.drive(&mut FailingRead, &mut stdout, &AtomicBool::new(false))
.expect_err("an unreadable stdin has no block")
.to_string();
assert!(err.contains("read the tool_use block from stdin"), "{err}");
}
struct FailingWrite;
impl std::io::Write for FailingWrite {
fn write(&mut self, _: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("stdout closed"))
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn an_unwritable_stdout_is_surfaced_rather_than_dropped() {
let scene = Scene::new();
let mut stdin = r#"{"id":"tu_1","name":"nosuch","input":{}}"#.as_bytes();
let err = scene
.drive(&mut stdin, &mut FailingWrite, &AtomicBool::new(false))
.expect_err("the product could not be written")
.to_string();
assert!(err.contains("write the result envelope"), "{err}");
assert!(FailingWrite.flush().is_ok());
}
#[test]
fn a_missing_contract_var_is_declined_naming_it() {
let scene = Scene::new();
let block = r#"{"id":"tu_1","name":"bash","input":{}}"#;
for (key, env) in [
(ENV_CONV_REPO, FakeEnv(HashMap::new())),
(
ENV_CONV_BRANCH,
FakeEnv(HashMap::from([(
ENV_CONV_REPO,
scene.ws.as_os_str().to_owned(),
)])),
),
(ENV_CONV_BRANCH, {
let mut env = FakeEnv::at(&scene.ws);
env.0.insert(
ENV_CONV_BRANCH,
std::os::unix::ffi::OsStringExt::from_vec(vec![0xff, 0xfe]),
);
env
}),
] {
let mut stdin = block.as_bytes();
let mut stdout: Vec<u8> = Vec::new();
let adapter = scene.ws.join("no-adapter");
let err = with_litany_home(&scene.home, || {
door::cli::run(
&env,
&mut stdin,
&mut stdout,
&litany_binary(),
Some(&adapter),
&AtomicBool::new(false),
None,
)
})
.expect_err("the contract var is missing")
.to_string();
assert!(err.contains(key), "{err}");
}
}
#[test]
fn an_agent_with_no_step_has_nowhere_to_record_and_is_refused() {
let scene = Scene::new();
std::fs::remove_dir_all(&scene.step).unwrap();
let (code, _) = scene.invoke(r#"{"id":"tu_1","name":"bash","input":{}}"#);
let err = code.expect_err("no step, no record").to_string();
assert!(err.contains("has no step under"), "{err}");
}
#[test]
fn a_worktree_that_is_gone_is_the_executor_s_refusal_not_a_silent_run() {
let scene = Scene::new();
std::fs::remove_dir_all(crate::workspace::agent_worktree(&scene.ws, AGENT)).unwrap();
let (code, _) = scene.invoke(r#"{"id":"tu_w","name":"bash","input":{"command":"echo hi"}}"#);
let err = code.expect_err("no worktree, no tool").to_string();
assert!(err.contains("worktree"), "{err}");
}
#[test]
fn a_control_that_cannot_answer_fails_closed() {
let scene = Scene::new();
super::invoking_gates::control(&scene, "exit 1");
let (code, out) = scene.invoke(r#"{"id":"tu_c","name":"bash","input":{"command":"echo ran"}}"#);
let err = code
.expect_err("a faulting control is not a pass")
.to_string();
assert!(err.contains("tool control"), "{err}{out}");
assert!(
!scene.recorded("tu_c"),
"nothing ran behind a faulting control"
);
}