Watch
0
0
Fork
You've already forked hyperhive
0

hive-agent: test the turn-end events and the claude error mapping

This commit is contained in:
damocles 2026-10-27 08:14:19 +01:00 • committed by mara
commit 21a217c75c

View file

@ -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)));