use std::sync::atomic::Ordering;
use kcode_k1_chat_core::{ChatError, ChatView, PendingAction};
use crate::ActorState;
#[test]
fn initial_view_and_jobs_follow_monotonic_order() {
let mut state = ActorState::new("primary".into(), false);
assert_eq!(
state.view(),
ChatView {
primary: "primary".into(),
pending: String::new(),
history: vec![],
actions: vec![],
}
);
assert_eq!(state.begin_worker("worker".into()), 1);
assert_eq!(state.begin_tool("tool".into()), 2);
let (job, frozen) = state.begin_compaction();
assert_eq!((job, frozen), (3, "primary".into()));
assert_eq!(
state.view().actions,
vec![
PendingAction::Worker {
llm: "worker".into()
},
PendingAction::Tool {
name: "tool".into()
},
PendingAction::Compaction,
]
);
}
#[test]
fn inference_delta_attempt_and_output_boundary_are_exact() {
let mut state = ActorState::new("base".into(), false);
state.append_pending("+ask".into());
let (job, delta, attempt) = state.begin_inference().unwrap();
assert_eq!((job, delta), (1, "base+ask".into()));
attempt.store(4, Ordering::Relaxed);
assert_eq!(
state.view().actions,
vec![PendingAction::Inference { attempt: 4 }]
);
assert!(!state.finish_inference(8));
state.commit_output("+output");
assert!(state.finish_inference(job));
state.append_pending("+next".into());
let (next, delta, _) = state.begin_inference().unwrap();
assert_eq!((next, delta), (2, "+next".into()));
}
#[test]
fn halt_is_first_write_and_restart_observes_state() {
let mut state = ActorState::new("p".into(), false);
assert_eq!(state.restart(), Err(ChatError::NotStalled));
assert!(state.halt("first".into()));
assert!(!state.halt("second".into()));
assert!(state.halted());
assert_eq!(state.take_halt(), Some("first".into()));
assert_eq!(state.restart(), Err(ChatError::NotStalled));
assert!(state.halt("again".into()));
state.append_pending("q".into());
let tool = state.begin_tool("t".into());
assert_eq!(state.restart(), Err(ChatError::Busy));
state.complete_action(tool, "r".into());
assert_eq!(state.restart(), Ok(()));
assert!(!state.halted());
let (_, delta, _) = state.begin_inference().unwrap();
assert_eq!(delta, "pqr");
}
#[test]
fn batched_replies_sort_prepend_chunk_and_track_live_jobs() {
let mut state = ActorState::new(String::new(), false);
state.append_pending("å°¾".into());
let first = state.begin_tool("a".into());
let second = state.begin_tool("b".into());
let third = state.begin_tool("c".into());
state.begin_batch();
state.apply_tool_replies(
vec![(2, second, "B".into(), false), (1, first, "A".into(), true)],
false,
);
assert_eq!(state.view().pending, "ABå°¾");
assert_eq!(
state.view().actions,
vec![
PendingAction::Tool { name: "b".into() },
PendingAction::Tool { name: "c".into() },
]
);
state.apply_tool_replies(
vec![(3, third, "C".into(), true), (2, second, "β".into(), true)],
true,
);
assert_eq!(state.view().pending, "ABβC尾");
assert!(state.view().actions.is_empty());
state.apply_tool_replies(vec![(0, 99, "ignored".into(), true)], true);
assert_eq!(state.view().pending, "ABβC尾");
}
#[test]
fn updates_deduplicate_and_stale_actions_do_nothing() {
let mut state = ActorState::new(String::new(), false);
let job = state.begin_worker("llm".into());
state.apply_append_update(job, 7, "a".into());
state.apply_append_update(job, 7, "duplicate".into());
assert!(state.accept_activity_update(job, 8));
assert!(!state.accept_activity_update(job, 8));
state.complete_action(job + 1, "stale".into());
state.complete_action(job, "z".into());
state.apply_append_update(job, 9, "late".into());
assert!(!state.accept_activity_update(job, 10));
state.complete_action(job, "late".into());
assert_eq!(state.view().pending, "az");
}
#[test]
fn rejected_compaction_prepends_exact_text() {
let mut state = ActorState::new("p".into(), false);
state.append_pending("å°¾".into());
state.reject_compaction_batch();
assert_eq!(state.view().pending, "compaction must be the sole callå°¾");
let (_, delta, _) = state.begin_inference().unwrap();
assert_eq!(delta, "pcompaction must be the sole callå°¾");
}
#[test]
fn compaction_success_records_history_pending_and_resets_delta() {
let mut state = ActorState::new("old".into(), false);
state.append_pending("+queued".into());
let (job, frozen) = state.begin_compaction();
state.complete_compaction(job + 1, frozen.clone(), Ok("ignored".into()));
assert_eq!(state.view().primary, "old");
state.complete_compaction(job, frozen, Ok("new".into()));
let view = state.view();
assert_eq!(view.history, vec!["old"]);
assert_eq!(view.primary, "new+queued");
assert_eq!(view.pending, "");
let (_, delta, _) = state.begin_inference().unwrap();
assert_eq!(delta, "new+queued");
}
#[test]
fn compaction_failure_preserves_history_and_sent_boundary() {
let mut state = ActorState::new("first".into(), true);
let (first, frozen) = state.begin_compaction();
state.complete_compaction(first, frozen, Ok("second".into()));
let (inference, delta, _) = state.begin_inference().unwrap();
assert_eq!(delta, "second");
state.commit_output("+answer");
assert!(state.finish_inference(inference));
let (job, frozen) = state.begin_compaction();
state.append_pending("+queued".into());
state.complete_compaction(job, frozen, Err("+error".into()));
let view = state.view();
assert_eq!(view.history, vec!["first"]);
assert_eq!(view.primary, "second+answer+error+queued");
let (_, delta, _) = state.begin_inference().unwrap();
assert_eq!(delta, "+error+queued");
}
#[test]
fn quiet_tracks_jobs_pending_force_and_batches_but_not_halt() {
let mut state = ActorState::new("p".into(), false);
assert!(state.quiet());
state.halt("halt".into());
assert!(state.quiet());
state.take_halt();
state.force_inference();
assert!(!state.quiet());
let (job, _, _) = state.begin_inference().unwrap();
assert!(!state.quiet());
assert!(state.finish_inference(job));
assert!(state.quiet());
state.append_pending("x".into());
assert!(!state.quiet());
let (job, _, _) = state.begin_inference().unwrap();
assert!(state.finish_inference(job));
assert!(state.quiet());
state.begin_batch();
assert!(!state.quiet());
state.apply_tool_replies(vec![], true);
assert!(!state.quiet());
let (job, _, _) = state.begin_inference().unwrap();
assert!(state.finish_inference(job));
assert!(state.quiet());
}