Files
cassady/tests/agent_tests.rs
T

95 lines
2.9 KiB
Rust

use cassady::access::AccessMode;
use cassady::agent::{run_turn, AgentEvent, AgentSettings};
use cassady::config::Config;
use cassady::conversation::{Conversation, Record};
use tempfile::tempdir;
use tokio::sync::mpsc;
use wiremock::matchers::{body_string_contains, method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn sse(body: &str) -> ResponseTemplate {
ResponseTemplate::new(200).set_body_raw(body.as_bytes().to_vec(), "text/event-stream")
}
#[tokio::test]
async fn empty_final_response_is_reprompted_and_persisted() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/chat/completions"))
.and(body_string_contains("previous response contained no user-facing text"))
.respond_with(sse(
"data: {\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Done.\"}}]}\r\n\r\ndata: [DONE]\r\n\r\n",
))
.with_priority(1)
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/chat/completions"))
.respond_with(sse(
"data: {\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\r\n\r\ndata: [DONE]\r\n\r\n",
))
.with_priority(10)
.expect(1)
.mount(&server)
.await;
let root = tempdir().unwrap();
let cwd = tempdir().unwrap();
let docs = tempdir().unwrap();
let mut config = Config::default();
config.root = root.path().to_path_buf();
config.docs_dir = docs.path().to_path_buf();
config.model = "test-model".into();
config.active_provider.base_url = server.uri();
config.active_provider.api_key = "test-key".into();
let conversation = Conversation::create(
&config.conversations_dir(),
&config.model,
cwd.path(),
"base prompt".into(),
)
.unwrap();
let (tx, mut rx) = mpsc::unbounded_channel::<AgentEvent>();
let updated = run_turn(
conversation,
"finish empty once".into(),
AgentSettings {
config,
cwd: cwd.path().to_path_buf(),
mode: AccessMode::ReadOnly,
},
tx,
)
.await
.unwrap();
let mut streamed = String::new();
let mut saw_retry_status = false;
let mut saw_finished = false;
while let Ok(event) = rx.try_recv() {
match event {
AgentEvent::AssistantChunk(chunk) => streamed.push_str(&chunk),
AgentEvent::Status(status) => {
saw_retry_status |= status.contains("empty final response");
}
AgentEvent::TurnFinished => saw_finished = true,
_ => {}
}
}
assert!(saw_retry_status);
assert!(saw_finished);
assert_eq!(streamed, "Done.");
let last = updated.records.last().unwrap();
assert!(matches!(
last,
Record::Assistant { content, tool_calls, .. }
if content == "Done." && tool_calls.is_empty()
));
}