Three examples: - basic.rs: query() one-shot prompt + cost reporting. - with_options.rs: system_prompt, model selection, effort, permission mode. - interactive.rs: Client multi-turn session with two back-to-back sends. Integration tests in tests/transport_end_to_end.rs spawn a tiny POSIX shell script (tests/fake_cli/fake-claude.sh) as a stand-in for the real claude CLI. The fake answers -v with a version, reads one user-message frame on stdin, and emits a fixed assistant + result pair on stdout, then blocks on stdin until close. This proves the spawn / write / read / disconnect lifecycle works without an authenticated claude install. Coverage: - query() round-trip: stream yields Assistant then Result. - Client round-trip: connect + send + drain to Result + disconnect. - CLI-not-found surfaces as a typed Error::CliNotFound. Cargo.lock is committed since this is, in practice, both a library and a binary (the examples link the crate). Locking dev-deps avoids surprise churn in CI.
93 lines
3.1 KiB
Rust
93 lines
3.1 KiB
Rust
//! End-to-end integration tests that wire the transport up to a tiny fake
|
|
//! `claude` binary (`tests/fake_cli/fake-claude.sh`).
|
|
//!
|
|
//! This proves that the spawn/write/read/close lifecycle works against a real
|
|
//! subprocess on a real platform, without needing an authenticated `claude`
|
|
//! install. The fake CLI ignores arguments, reads one stdin frame, and emits
|
|
//! a fixed assistant + result pair on stdout.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use claude_agent_sdk::{ClaudeAgentOptions, Client, ContentBlock, Message, query};
|
|
use tokio_stream::StreamExt;
|
|
|
|
fn fake_cli_path() -> PathBuf {
|
|
let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
p.push("tests");
|
|
p.push("fake_cli");
|
|
p.push("fake-claude.sh");
|
|
assert!(p.is_file(), "fake-claude.sh missing at {}", p.display());
|
|
p
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn query_round_trip_against_fake_cli() {
|
|
let opts = ClaudeAgentOptions::new()
|
|
.with_cli_path(fake_cli_path())
|
|
.with_skip_version_check(true);
|
|
|
|
let mut stream = query("Hi!", opts).await.expect("query start");
|
|
|
|
let mut saw_assistant = false;
|
|
let mut saw_result = false;
|
|
while let Some(item) = stream.next().await {
|
|
let msg = item.expect("frame");
|
|
match msg {
|
|
Message::Assistant(a) => {
|
|
saw_assistant = true;
|
|
assert_eq!(a.message.model, "fake-model");
|
|
match &a.message.content[0] {
|
|
ContentBlock::Text(t) => assert_eq!(t.text, "hello from fake"),
|
|
other => panic!("expected TextBlock, got {other:?}"),
|
|
}
|
|
}
|
|
Message::Result(r) => {
|
|
saw_result = true;
|
|
assert_eq!(r.subtype, "success");
|
|
assert_eq!(r.num_turns, 1);
|
|
break;
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
assert!(saw_assistant);
|
|
assert!(saw_result);
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn client_round_trip_against_fake_cli() {
|
|
let opts = ClaudeAgentOptions::new()
|
|
.with_cli_path(fake_cli_path())
|
|
.with_skip_version_check(true);
|
|
|
|
let mut client = Client::new(opts).await.expect("client new");
|
|
client.connect().await.expect("connect");
|
|
let mut stream = client.messages();
|
|
client.send("ping").await.expect("send");
|
|
|
|
let mut saw_result = false;
|
|
while let Some(item) = stream.next().await {
|
|
let msg = item.expect("frame");
|
|
if matches!(msg, Message::Result(_)) {
|
|
saw_result = true;
|
|
break;
|
|
}
|
|
}
|
|
assert!(saw_result);
|
|
|
|
client.disconnect().await.expect("disconnect");
|
|
}
|
|
|
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
|
async fn cli_not_found_surfaces_typed_error() {
|
|
let bogus = PathBuf::from("/nonexistent/path/to/claude");
|
|
let opts = ClaudeAgentOptions::new()
|
|
.with_cli_path(bogus)
|
|
.with_skip_version_check(true);
|
|
|
|
match query("hi", opts).await {
|
|
Ok(_) => panic!("expected CliNotFound, got Ok"),
|
|
Err(claude_agent_sdk::Error::CliNotFound(_)) => {}
|
|
Err(other) => panic!("expected CliNotFound, got {other:?}"),
|
|
}
|
|
}
|