mod load;
mod unload;
use super::*;
use crate::boundary::reply::{self, Reply};
use crate::registry::tools::Tool;
use crate::tool_host::tests::{budget, engine, impatient, site, tool};
use serde_json::json;
use std::path::Path;
use tempfile::TempDir;
fn quiet() -> AtomicBool {
AtomicBool::new(false)
}
fn row(client: &str, present: bool, tools: Vec<Tool>) -> ClientRow {
ClientRow {
client: client.to_owned(),
present,
tools,
last_seen: None,
}
}
fn against(root: &Path, rows: Vec<ClientRow>, input: &Value) -> Result<String, String> {
let (handle, _seen) = engine(root, &reply::encode(&Reply::Clients(rows)));
let answered = answer(&site(root, budget()), input, &quiet());
handle.join().expect("engine");
answered
}
#[test]
fn the_declared_schema_names_the_four_ops() {
let s = schema();
assert_eq!(
s["properties"]["op"]["enum"],
json!(["list", "get", "load", "unload"])
);
assert_eq!(s["required"], json!(["op"]));
assert!(DESCRIPTION.contains("op=load"));
assert!(DESCRIPTION.contains("op=unload"));
}
#[test]
fn a_well_formed_invocation_reads_as_its_op() {
assert_eq!(parse(&json!({"op": "list"})).expect("list"), Op::List);
assert_eq!(
parse(&json!({"op": "get", "client": "laptop"})).expect("get"),
Op::Get("laptop".to_owned())
);
assert_eq!(
parse(&json!({"op": "load", "client": "laptop", "tools": ["Bash"]})).expect("load"),
Op::Load("laptop".to_owned(), vec!["Bash".to_owned()])
);
assert_eq!(
parse(&json!({"op": "unload", "client": "laptop", "tools": ["Bash"]})).expect("unload"),
Op::Unload("laptop".to_owned(), Some(vec!["Bash".to_owned()]))
);
}
#[test]
fn an_unload_may_omit_its_tools_but_may_not_empty_them() {
assert_eq!(
parse(&json!({"op": "unload", "client": "laptop"})).expect("unload"),
Op::Unload("laptop".to_owned(), None)
);
assert_eq!(
parse(&json!({"op": "unload", "client": "laptop", "tools": null})).expect("unload"),
Op::Unload("laptop".to_owned(), None)
);
let e = parse(&json!({"op": "unload", "client": "laptop", "tools": []})).expect_err("declined");
assert!(e.contains("\"tools\""), "{e}");
}
#[test]
fn a_malformed_invocation_names_the_field() {
let cases = [
(json!(7), "not a JSON object"),
(json!({}), "\"op\""),
(json!({"op": "wat"}), "unknown op"),
(json!({"op": "get"}), "\"client\""),
(json!({"op": "get", "client": ""}), "\"client\""),
(json!({"op": "load", "client": "a"}), "\"tools\""),
(
json!({"op": "load", "client": "a", "tools": []}),
"\"tools\"",
),
(
json!({"op": "load", "client": "a", "tools": ["Bash", 7]}),
"\"tools\"",
),
(json!({"op": "unload"}), "\"client\""),
(
json!({"op": "unload", "client": "a", "tools": "Bash"}),
"\"tools\"",
),
];
for (input, said) in cases {
let e = parse(&input).expect_err("declined");
assert!(e.contains(said), "{input} -> {e}");
}
}
#[test]
fn list_observes_the_roster_at_an_instant() {
let root = TempDir::new().expect("tmp");
let said = against(
root.path(),
vec![
row("laptop", true, vec![tool("Bash")]),
row("phone", false, vec![]),
],
&json!({"op": "list"}),
)
.expect("listed");
assert!(said.contains("observed 1970-01-01 00:00:00Z"), "{said}");
assert!(said.contains("laptop — connected right now, advertising 1 tool\n"));
assert!(said.contains("phone — not connected right now, advertising 0 tools\n"));
assert!(said.contains("op=get with client=<name>"), "{said}");
}
#[test]
fn list_says_so_when_nothing_is_registered() {
let root = TempDir::new().expect("tmp");
let said = against(root.path(), vec![], &json!({"op": "list"})).expect("listed");
assert!(said.contains("(none is registered here)"), "{said}");
}
#[test]
fn get_shows_each_tool_and_the_name_it_loads_as() {
let root = TempDir::new().expect("tmp");
let said = against(
root.path(),
vec![row("laptop", true, vec![tool("Bash"), tool("Read")])],
&json!({"op": "get", "client": "laptop"}),
)
.expect("got");
assert!(
said.contains("client \"laptop\" of workspace \"home\""),
"{said}"
);
assert!(said.contains("It advertises 2 tools:"), "{said}");
assert!(said.contains("Bash — what Bash does"), "{said}");
assert!(said.contains("loads as: laptop_Bash"), "{said}");
assert!(said.contains("op=load with client=\"laptop\""), "{said}");
}
#[test]
fn get_on_a_client_that_advertises_nothing_says_so() {
let root = TempDir::new().expect("tmp");
let said = against(
root.path(),
vec![row("phone", false, vec![])],
&json!({"op": "get", "client": "phone"}),
)
.expect("got");
assert!(said.contains("not connected right now"), "{said}");
assert!(said.contains("It advertises no tools."), "{said}");
}
#[test]
fn an_unregistered_client_is_absent() {
let root = TempDir::new().expect("tmp");
let e = against(
root.path(),
vec![row("laptop", true, vec![])],
&json!({"op": "get", "client": "desk"}),
)
.expect_err("absent");
assert!(e.contains("no client \"desk\" is registered"), "{e}");
assert!(e.contains("the 1 there are"), "{e}");
}
#[test]
fn a_declined_invocation_never_reaches_the_engine() {
let root = TempDir::new().expect("tmp");
let e = answer(
&site(root.path(), impatient()),
&json!({"op": "wat"}),
&quiet(),
)
.expect_err("declined");
assert!(e.contains("unknown op"), "{e}");
assert!(
crate::boundary::deposit::pending(root.path()).is_empty(),
"nothing was deposited"
);
}
#[test]
fn an_unreachable_engine_is_said_in_band() {
let root = TempDir::new().expect("tmp");
let e = answer(
&site(root.path(), impatient()),
&json!({"op": "list"}),
&quiet(),
)
.expect_err("no engine");
assert!(e.contains("no engine answered"), "{e}");
}