Files
kyle 469be2c0a1 Add BrokerPort: loopd asks brokerd for every tool call
Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
2026-09-20 19:40:35 -07:00

128 lines
3.9 KiB
Rust

//! Tests for `BrokerPort` when the broker's frames break the protocol. Every one ends in the same
//! plain failure and one printed line. Do not edit.
#[path = "support/broker.rs"]
mod fake;
use std::io::Write;
use std::os::unix::net::UnixStream;
use std::thread;
use std::time::Duration;
use fake::{assert_unavailable, broker, call, frame, in_ms, request, result, send};
use proto::{Envelope, ErrorCode, Message, PROTOCOL_VERSION, ToolResponse, WireError, write_frame};
#[test]
fn frames_that_break_the_protocol_are_unavailable() {
type Script = Box<dyn FnOnce(&mut UnixStream, &Envelope) + Send>;
let pending = |approval| ToolResponse::PendingApproval {
approval,
expires: in_ms(60_000),
};
let cases: Vec<(&str, Script)> = vec![
(
"an answer for another request id",
Box::new(|s, r| send(s, r.id + 1, true, result("x"))),
),
(
"a final frame that is pending",
Box::new(move |s, r| send(s, r.id, true, pending(1))),
),
(
"an answer that is not final",
Box::new(|s, r| send(s, r.id, false, result("x"))),
),
(
"a second pending frame",
Box::new(move |s, r| {
send(s, r.id, false, pending(1));
send(s, r.id, false, pending(2));
thread::sleep(Duration::from_millis(100));
}),
),
(
"pending, then another id",
Box::new(move |s, r| {
send(s, r.id, false, pending(1));
send(s, r.id + 1, true, result("x"));
}),
),
(
"an error message",
Box::new(|s, r| {
let error = Message::Error(WireError {
code: ErrorCode::Forbidden,
detail: "tool requests only".to_string(),
});
let _ = write_frame(s, &frame(r.id, true, error));
}),
),
(
"a message of another kind",
Box::new(|s, r| {
let echo = Message::ToolRequest(request());
let _ = write_frame(s, &frame(r.id, true, echo));
}),
),
(
"another protocol version",
Box::new(|s, r| {
let mut env = frame(r.id, true, Message::ToolResponse(result("x")));
env.v = PROTOCOL_VERSION + 1;
let _ = write_frame(s, &env);
}),
),
(
"a zero length",
Box::new(|s, _| {
let _ = s.write_all(&[0, 0, 0, 0]);
}),
),
(
"a length over the maximum",
Box::new(|s, _| {
let _ = s.write_all(&[0xff, 0xff, 0xff, 0xff]);
}),
),
(
"a body that is not JSON",
Box::new(|s, _| {
let _ = s.write_all(&[0, 0, 0, 5]);
let _ = s.write_all(b"hello");
}),
),
(
"a body cut short",
Box::new(|s, _| {
let _ = s.write_all(&[0, 0, 0, 50]);
let _ = s.write_all(b"{\"v\":1");
}),
),
];
for (why, script) in cases {
let (socket, broker) = broker(script);
let got = call(socket, 1_000, &request());
assert_unavailable(&got, why);
broker.join().unwrap();
}
}
#[test]
fn the_error_message_the_broker_sent_is_in_the_line() {
let (socket, broker) = broker(|s, r| {
let error = Message::Error(WireError {
code: ErrorCode::Internal,
detail: "the ledger is gone".to_string(),
});
let _ = write_frame(s, &frame(r.id, true, error));
});
let got = call(socket, 1_000, &request());
assert_unavailable(&got, "an error message");
assert!(
got.lines[0].contains("the ledger is gone"),
"{}",
got.lines[0]
);
broker.join().unwrap();
}