Files
2026-10-15 02:24:15 -03:00

79 lines
3.3 KiB
Rust

//! Robustness against a hostile or broken agent: malformed lines, oversized
//! lines, unknown update shapes and unsupported agent requests.
mod common;
use std::time::Duration;
use common::{collect_turn, collect_until, handshake, spawn_stub};
use notescraft_acp::{Event, PromptBlock, SessionUpdate};
const TIMEOUT: Duration = Duration::from_secs(15);
#[test]
fn malformed_lines_become_diagnostics_and_the_turn_still_finishes() {
let process = spawn_stub("garbage");
let session_id = handshake(&process);
process.prompt(&session_id, &[PromptBlock::Text("hello".to_string())]).unwrap_or_else(|err| panic!("prompt failed: {err}"));
let events = collect_turn(&process, Some("allow"), TIMEOUT);
let diagnostics: Vec<&str> = events
.iter()
.filter_map(|event| match event {
Event::Error(message) => Some(message.as_str()),
_ => None,
})
.collect();
assert_eq!(diagnostics.len(), 2, "expected one diagnostic per bad line: {events:?}");
assert!(diagnostics.iter().all(|message| !message.is_empty()));
assert!(matches!(events.last(), Some(Event::PromptDone { stop_reason }) if stop_reason == "end_turn"), "the turn must still end: {events:?}");
let chunks: String = events
.iter()
.filter_map(|event| match event {
Event::Update(SessionUpdate::AgentMessageChunk(text)) => Some(text.as_str()),
_ => None,
})
.collect();
assert_eq!(chunks, "Hello world");
}
#[test]
fn oversized_line_is_dropped_with_a_diagnostic_and_the_stream_continues() {
let process = spawn_stub("giant");
let session_id = handshake(&process);
process.prompt(&session_id, &[PromptBlock::Text("hello".to_string())]).unwrap_or_else(|err| panic!("prompt failed: {err}"));
let events = collect_turn(&process, Some("allow"), TIMEOUT);
let dropped = events.iter().any(|event| matches!(event, Event::Error(message) if message.contains("longer than")));
assert!(dropped, "expected an oversized-line diagnostic: {events:?}");
let chunks: String = events
.iter()
.filter_map(|event| match event {
Event::Update(SessionUpdate::AgentMessageChunk(text)) => Some(text.as_str()),
_ => None,
})
.collect();
assert_eq!(chunks, "Hello world");
assert!(matches!(events.last(), Some(Event::PromptDone { .. })));
}
#[test]
fn unsupported_agent_requests_get_method_not_found() {
let process = spawn_stub("unsupported");
let session_id = handshake(&process);
process.prompt(&session_id, &[PromptBlock::Text("write a note".to_string())]).unwrap_or_else(|err| panic!("prompt failed: {err}"));
let events = collect_until(process.events(), TIMEOUT, |event| {
matches!(
event,
Event::Update(SessionUpdate::Other(value))
if value.get("sessionUpdate").and_then(serde_json::Value::as_str) == Some("ack")
)
});
let Some(Event::Update(SessionUpdate::Other(ack))) = events.iter().find(|event| matches!(event, Event::Update(SessionUpdate::Other(_)))) else {
panic!("no ack update: {events:?}");
};
assert_eq!(ack.get("code").and_then(serde_json::Value::as_i64), Some(-32_601), "expected -32601 method not found: {ack:?}");
assert_eq!(ack.get("optionId").and_then(serde_json::Value::as_str), Some("cancelled"));
}