hive-agent: test the turn-end events and the claude error mapping
This commit is contained in:
parent
09d89507a9
commit
21a217c75c
1 changed files with 86 additions and 2 deletions
|
|
@ -857,8 +857,9 @@ fn archive_session(bus: &Bus, session: &AgentSession) {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
PermissionAsk, TurnError, acp_permits, apply_telemetry, nonzero_or, parse_u64,
|
||||
report_compact, runtime_error_to_turn, with_turn_end_compact,
|
||||
PermissionAsk, TurnError, TurnOutcome, acp_permits, apply_telemetry, emit_turn_end,
|
||||
error_to_turn, nonzero_or, parse_u64, report_compact, runtime_error_to_turn,
|
||||
with_turn_end_compact,
|
||||
};
|
||||
use crate::events::{Bus, LiveEvent};
|
||||
use hive_runtime::{AcpError, Error};
|
||||
|
|
@ -1041,6 +1042,89 @@ mod tests {
|
|||
assert_eq!(notes, ["/compact done"]);
|
||||
}
|
||||
|
||||
/// The `TurnEnd` events one `emit_turn_end` call puts on a fresh bus, as
|
||||
/// `(ok, note)`.
|
||||
fn turn_ends(outcome: &TurnOutcome) -> Vec<(bool, Option<String>)> {
|
||||
crate::paths::with_harness_dir(|| {
|
||||
let bus = Bus::new();
|
||||
let mut rx = bus.subscribe();
|
||||
emit_turn_end(&bus, outcome);
|
||||
let mut ends = Vec::new();
|
||||
while let Ok(ev) = rx.try_recv() {
|
||||
if let LiveEvent::TurnEnd { ok, note } = ev.event {
|
||||
ends.push((ok, note));
|
||||
}
|
||||
}
|
||||
ends
|
||||
})
|
||||
}
|
||||
|
||||
/// The single `(ok, note)` a failed outcome must produce.
|
||||
fn failed_note(error: TurnError) -> String {
|
||||
let ends = turn_ends(&Err(error));
|
||||
assert_eq!(ends.len(), 1, "one TurnEnd per turn: {ends:?}");
|
||||
let (ok, note) = ends.into_iter().next().expect("one end");
|
||||
assert!(!ok, "a failed outcome never reports ok");
|
||||
note.expect("a failed outcome always carries a note")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_finished_turn_ends_ok_with_no_note() {
|
||||
assert_eq!(turn_ends(&Ok(true)), [(true, None)]);
|
||||
assert_eq!(turn_ends(&Ok(false)), [(true, None)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn each_recoverable_failure_names_what_the_serve_loop_will_do() {
|
||||
assert!(failed_note(TurnError::PromptTooLong).contains("retrying"));
|
||||
assert!(failed_note(TurnError::RateLimited).contains("parking until quota resets"));
|
||||
assert!(failed_note(TurnError::AuthFailed).contains("waiting for re-login"));
|
||||
assert!(failed_note(TurnError::SessionNotFound).contains("requeueing"));
|
||||
assert!(failed_note(TurnError::ApiStall).contains("API stall suspected"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_agent_stall_note_keeps_the_watchdogs_own_text() {
|
||||
assert_eq!(
|
||||
failed_note(TurnError::AgentStall("no output for 600s".to_owned())),
|
||||
"no output for 600s — parking + requeueing"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hard_failure_note_is_the_full_error_chain() {
|
||||
let error = anyhow::anyhow!("inner cause").context("outer step");
|
||||
assert_eq!(
|
||||
failed_note(TurnError::Failed(error)),
|
||||
"outer step: inner cause"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn claude_errors_map_to_the_turn_error_the_serve_loop_reacts_to() {
|
||||
use hive_claude::Error as C;
|
||||
assert!(matches!(
|
||||
error_to_turn(C::PromptTooLong),
|
||||
Err(TurnError::PromptTooLong)
|
||||
));
|
||||
assert!(matches!(
|
||||
error_to_turn(C::RateLimited),
|
||||
Err(TurnError::RateLimited)
|
||||
));
|
||||
assert!(matches!(
|
||||
error_to_turn(C::AuthFailed),
|
||||
Err(TurnError::AuthFailed)
|
||||
));
|
||||
assert!(matches!(
|
||||
error_to_turn(C::SessionNotFound),
|
||||
Err(TurnError::SessionNotFound)
|
||||
));
|
||||
assert!(matches!(
|
||||
error_to_turn(C::IdleTimeout),
|
||||
Err(TurnError::ApiStall)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_turn_end_compact_keeps_the_turns_own_compaction() {
|
||||
assert!(matches!(with_turn_end_compact(Ok(true), false), Ok(true)));
|
||||
|
|
|
|||
Loading…
Reference in a new issue