use claude_codes::{
assert_fully_wrapped, ClaudeOutput, KnownSystemEvent, PermissionCheckStatus, RateLimitScope,
RateLimitStatus, RateLimitWindow, SystemSubtype, TaskHandback,
};
use serde_json::json;
#[test]
fn permission_check_status_is_typed() {
let frame = json!({
"type": "system",
"subtype": "permission_check_status",
"tool_use_id": "toolu_01",
"agent_id": "a1",
"status": "checking",
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::System(sys) = serde_json::from_value(frame).unwrap() else {
panic!("expected System");
};
assert_eq!(sys.subtype, SystemSubtype::PermissionCheckStatus);
assert!(sys.is_permission_check_status());
let check = sys.as_permission_check_status().expect("typed accessor");
assert_eq!(check.tool_use_id, "toolu_01");
assert_eq!(check.agent_id.as_deref(), Some("a1"));
assert_eq!(check.status, PermissionCheckStatus::Checking);
assert!(matches!(
sys.as_known_system_event(),
Some(KnownSystemEvent::PermissionCheckStatus(_))
));
}
#[test]
fn permission_check_status_values_round_trip() {
for (wire, typed) in [
("checking", PermissionCheckStatus::Checking),
("done", PermissionCheckStatus::Done),
(
"some_future_status",
PermissionCheckStatus::Unknown("some_future_status".into()),
),
] {
let parsed: PermissionCheckStatus = serde_json::from_value(json!(wire)).unwrap();
assert_eq!(parsed, typed);
assert_eq!(serde_json::to_value(&parsed).unwrap(), json!(wire));
}
}
#[test]
fn assistant_carries_agent_id_and_usage_limit_info() {
let frame = json!({
"type": "assistant",
"message": {
"id": "msg_01",
"type": "message",
"role": "assistant",
"model": "claude-opus-5-5",
"content": [{"type": "text", "text": "You've hit your limit."}],
"stop_reason": "stop_sequence",
"stop_sequence": "",
"usage": {"input_tokens": 0, "output_tokens": 0}
},
"parent_tool_use_id": null,
"agent_id": "a1",
"error": "rate_limit",
"is_api_error_message": true,
"api_error": "usage_limit_reached",
"api_error_params": {
"rate_limit_info": {
"status": "rejected",
"resetsAt": 1760000000,
"rateLimitType": "seven_day",
"isUsingOverage": false,
"limitScope": "group_pool"
}
},
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::Assistant(msg) = serde_json::from_value(frame).unwrap() else {
panic!("expected Assistant");
};
assert_eq!(msg.agent_id.as_deref(), Some("a1"));
assert_eq!(msg.api_error.as_deref(), Some("usage_limit_reached"));
let info = msg
.api_error_params
.and_then(|p| p.rate_limit_info)
.expect("rate_limit_info");
assert_eq!(info.status, RateLimitStatus::Rejected);
assert_eq!(info.rate_limit_type, Some(RateLimitWindow::SevenDay));
assert_eq!(info.resets_at, Some(1760000000));
assert_eq!(info.limit_scope, Some(RateLimitScope::GroupPool));
}
#[test]
fn user_carries_agent_id() {
let frame = json!({
"type": "user",
"message": {
"role": "user",
"content": [{"type": "text", "text": "Compute 6 times 7."}]
},
"parent_tool_use_id": "toolu_01",
"agent_id": "ab0fdef6f23603198",
"subagent_type": "general-purpose",
"task_description": "Compute 6 times 7",
"timestamp": "2026-10-06T20:30:02.748Z",
"uuid": "fa290d23-e2b8-4663-a67c-0f73bd952bdc",
"session_id": "01a59841-f4cd-468a-96d4-53a255cac787"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::User(user) = serde_json::from_value(frame).unwrap() else {
panic!("expected User");
};
assert_eq!(user.agent_id.as_deref(), Some("ab0fdef6f23603198"));
}
#[test]
fn rate_limit_scope_round_trips() {
for (wire, typed) in [
("service", RateLimitScope::Service),
("channel", RateLimitScope::Channel),
("group_pool", RateLimitScope::GroupPool),
("team", RateLimitScope::Unknown("team".into())),
] {
let parsed: RateLimitScope = serde_json::from_value(json!(wire)).unwrap();
assert_eq!(parsed, typed);
assert_eq!(serde_json::to_value(&parsed).unwrap(), json!(wire));
}
}
#[test]
fn result_carries_safety_stops() {
let frame = json!({
"type": "result",
"subtype": "success",
"is_error": false,
"duration_ms": 1200,
"duration_api_ms": 900,
"num_turns": 1,
"result": "done",
"stop_reason": "end_turn",
"session_id": "s1",
"uuid": "u1",
"total_cost_usd": 0.01,
"usage": {
"input_tokens": 10,
"output_tokens": 38,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 0
},
"modelUsage": {},
"permission_denials": [],
"safety_stops": 2
});
assert_fully_wrapped(&frame);
let ClaudeOutput::Result(res) = serde_json::from_value(frame).unwrap() else {
panic!("expected Result");
};
assert_eq!(res.safety_stops, Some(2));
}
#[test]
fn stream_event_carries_abandoned_blocks() {
let frame = json!({
"type": "stream_event",
"event": {"type": "message_stop"},
"parent_tool_use_id": null,
"abandoned_blocks": {"api_message_id": "msg_01", "from_block_index": 1},
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::StreamEvent(ev) = serde_json::from_value(frame).unwrap() else {
panic!("expected StreamEvent");
};
let abandoned = ev.abandoned_blocks.expect("abandoned_blocks");
assert_eq!(abandoned.api_message_id, "msg_01");
assert_eq!(abandoned.from_block_index, 1);
}
#[test]
fn task_started_carries_run_and_parent() {
let frame = json!({
"type": "system",
"subtype": "task_started",
"task_id": "t2",
"run_id": "r1",
"parent_task_id": "t1",
"description": "nested agent",
"task_type": "local_agent",
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::System(sys) = serde_json::from_value(frame).unwrap() else {
panic!("expected System");
};
let started = sys.as_task_started().expect("task_started");
assert_eq!(started.run_id.as_deref(), Some("r1"));
assert_eq!(started.parent_task_id.as_deref(), Some("t1"));
}
#[test]
fn task_updated_and_progress_carry_run_id() {
let updated = json!({
"type": "system",
"subtype": "task_updated",
"task_id": "t1",
"run_id": "r1",
"patch": {"status": "running"},
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&updated);
let ClaudeOutput::System(sys) = serde_json::from_value(updated).unwrap() else {
panic!("expected System");
};
assert_eq!(
sys.as_task_updated()
.expect("task_updated")
.run_id
.as_deref(),
Some("r1")
);
let progress = json!({
"type": "system",
"subtype": "task_progress",
"task_id": "t1",
"run_id": "r1",
"description": "working",
"usage": {"total_tokens": 10, "tool_uses": 1, "duration_ms": 100},
"uuid": "u2",
"session_id": "s1"
});
assert_fully_wrapped(&progress);
let ClaudeOutput::System(sys) = serde_json::from_value(progress).unwrap() else {
panic!("expected System");
};
assert_eq!(
sys.as_task_progress()
.expect("task_progress")
.run_id
.as_deref(),
Some("r1")
);
}
#[test]
fn task_notification_carries_handback_report() {
let frame = json!({
"type": "system",
"subtype": "task_notification",
"task_id": "t1",
"run_id": "r1",
"status": "completed",
"output_file": "",
"summary": "harness notes",
"handback": "flagged",
"handback_report": {"text": "the report", "warning": "SECURITY WARNING"},
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::System(sys) = serde_json::from_value(frame).unwrap() else {
panic!("expected System");
};
let note = sys.as_task_notification().expect("task_notification");
assert_eq!(note.run_id.as_deref(), Some("r1"));
assert_eq!(note.handback, Some(TaskHandback::Flagged));
let report = note.handback_report.expect("handback_report");
assert_eq!(report.text, "the report");
assert_eq!(report.warning.as_deref(), Some("SECURITY WARNING"));
let withheld: TaskHandback = serde_json::from_value(json!("withheld")).unwrap();
assert_eq!(withheld, TaskHandback::Withheld);
let future: TaskHandback = serde_json::from_value(json!("later")).unwrap();
assert_eq!(future, TaskHandback::Unknown("later".into()));
assert_eq!(serde_json::to_value(&future).unwrap(), json!("later"));
}
#[test]
fn background_tasks_changed_entries_carry_run_and_parent() {
let frame = json!({
"type": "system",
"subtype": "background_tasks_changed",
"tasks": [{
"task_id": "t2",
"run_id": "r1",
"task_type": "local_agent",
"description": "nested agent",
"parent_task_id": "t1"
}],
"uuid": "u1",
"session_id": "s1"
});
assert_fully_wrapped(&frame);
let ClaudeOutput::System(sys) = serde_json::from_value(frame).unwrap() else {
panic!("expected System");
};
let Some(KnownSystemEvent::BackgroundTasksChanged(changed)) = sys.as_known_system_event()
else {
panic!("expected BackgroundTasksChanged");
};
assert_eq!(changed.tasks[0].run_id.as_deref(), Some("r1"));
assert_eq!(changed.tasks[0].parent_task_id.as_deref(), Some("t1"));
}