test: zot-rpc driver smoke (end-to-end, ZOT_BIN-gated) #160
1 changed files with 110 additions and 0 deletions
110
crates/colibri-daemon/tests/zot_rpc_smoke.rs
Normal file
110
crates/colibri-daemon/tests/zot_rpc_smoke.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
//! zot-rpc driver smoke — end-to-end proof of the colibri#143 spawn driver.
|
||||
//!
|
||||
//! Spawns a real `zot rpc` subprocess through the Colibri `Spawner` (with
|
||||
//! `rpc_stdin`), sends a prompt over the driver's `RpcSender`, and reads the
|
||||
//! agent's stdout JSONL back. Validates the full path: stdin piped → framed
|
||||
//! prompt delivered → zot runs the loop → events stream out.
|
||||
//!
|
||||
//! A placeholder DeepSeek key is enough: zot still acks the request, echoes the
|
||||
//! prompt as `user_message`, runs a turn, and emits `done` (the turn fails 401,
|
||||
//! which is the deterministic no-key outcome — we assert on the driver wiring,
|
||||
//! not on a successful completion).
|
||||
//!
|
||||
//! Ignored by default — needs a built zot binary. Run with:
|
||||
//! ZOT_BIN=/path/to/zot cargo test -p colibri-daemon --test zot_rpc_smoke \
|
||||
//! -- --ignored --nocapture
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use colibri_daemon::spawner::{AgentSpawnConfig, Provider, Spawner};
|
||||
use colibri_daemon::DaemonConfig;
|
||||
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||
use tokio::time::timeout;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "needs a built zot binary; set ZOT_BIN"]
|
||||
async fn zot_rpc_driver_delivers_prompt_and_streams_events() {
|
||||
let Ok(zot_bin) = std::env::var("ZOT_BIN") else {
|
||||
eprintln!("ZOT_BIN not set; skipping zot rpc driver smoke");
|
||||
return;
|
||||
};
|
||||
assert!(
|
||||
std::path::Path::new(&zot_bin).exists(),
|
||||
"ZOT_BIN does not exist: {zot_bin}"
|
||||
);
|
||||
|
||||
let mut env = HashMap::new();
|
||||
// Placeholder key: the turn 401s deterministically; the driver wiring (our
|
||||
// subject) still runs fully and emits the ack → user_message → done.
|
||||
env.insert(
|
||||
"DEEPSEEK_API_KEY".to_string(),
|
||||
"placeholder-not-real".to_string(),
|
||||
);
|
||||
|
||||
let cfg = AgentSpawnConfig {
|
||||
binary: zot_bin,
|
||||
args: vec![
|
||||
"rpc".to_string(),
|
||||
"--provider".to_string(),
|
||||
"deepseek".to_string(),
|
||||
"--model".to_string(),
|
||||
"deepseek-v4-pro".to_string(),
|
||||
],
|
||||
env,
|
||||
// Local provider → no API-key gate / fallback routing in the spawner.
|
||||
provider: Provider::Local,
|
||||
model: "deepseek-v4-pro".to_string(),
|
||||
// The subject under test: pipe stdin and keep the writer.
|
||||
rpc_stdin: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let spawner = Spawner::new(Arc::new(DaemonConfig::from_env()));
|
||||
let handle = spawner.spawn(cfg).await.expect("spawn zot rpc");
|
||||
|
||||
// The driver must expose an RpcSender for an rpc_stdin agent.
|
||||
let sender = handle
|
||||
.rpc_sender()
|
||||
.expect("rpc agent must expose an RpcSender");
|
||||
|
||||
let prompt = "say hi from the colibri rpc driver";
|
||||
let req_id = sender.send_prompt(prompt).await.expect("send rpc prompt");
|
||||
assert_eq!(req_id, "1", "first request id should be 1");
|
||||
|
||||
let stdout = handle.take_stdout().await.expect("rpc agent stdout");
|
||||
let mut lines = BufReader::new(stdout).lines();
|
||||
|
||||
let mut saw_response_ack = false;
|
||||
let mut saw_our_prompt = false;
|
||||
let mut saw_done = false;
|
||||
|
||||
// Read until `done` (or timeout). The no-key turn is fast.
|
||||
let read = timeout(Duration::from_secs(30), async {
|
||||
while let Ok(Some(line)) = lines.next_line().await {
|
||||
eprintln!("zot> {line}");
|
||||
if line.contains(r#""type":"response""#) && line.contains(r#""success":true"#) {
|
||||
saw_response_ack = true;
|
||||
}
|
||||
if line.contains(r#""type":"user_message""#) && line.contains(prompt) {
|
||||
saw_our_prompt = true;
|
||||
}
|
||||
if line.contains(r#""type":"done""#) {
|
||||
saw_done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
.await;
|
||||
assert!(read.is_ok(), "timed out waiting for zot rpc `done`");
|
||||
|
||||
let _ = handle.kill().await;
|
||||
|
||||
assert!(saw_response_ack, "missing the response ack to our request");
|
||||
assert!(
|
||||
saw_our_prompt,
|
||||
"zot did not echo our prompt — the driver's stdin prompt was not delivered"
|
||||
);
|
||||
assert!(saw_done, "missing the terminal `done` event");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue