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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
PermissionAsk, TurnError, acp_permits, apply_telemetry, nonzero_or, parse_u64,
|
PermissionAsk, TurnError, TurnOutcome, acp_permits, apply_telemetry, emit_turn_end,
|
||||||
report_compact, runtime_error_to_turn, with_turn_end_compact,
|
error_to_turn, nonzero_or, parse_u64, report_compact, runtime_error_to_turn,
|
||||||
|
with_turn_end_compact,
|
||||||
};
|
};
|
||||||
use crate::events::{Bus, LiveEvent};
|
use crate::events::{Bus, LiveEvent};
|
||||||
use hive_runtime::{AcpError, Error};
|
use hive_runtime::{AcpError, Error};
|
||||||
|
|
@ -1041,6 +1042,89 @@ mod tests {
|
||||||
assert_eq!(notes, ["/compact done"]);
|
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]
|
#[test]
|
||||||
fn a_failed_turn_end_compact_keeps_the_turns_own_compaction() {
|
fn a_failed_turn_end_compact_keeps_the_turns_own_compaction() {
|
||||||
assert!(matches!(with_turn_end_compact(Ok(true), false), Ok(true)));
|
assert!(matches!(with_turn_end_compact(Ok(true), false), Ok(true)));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue