use serde::{Deserialize, Serialize};
use crate::acp::StopReason;
use crate::session_key::SessionKey;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub enum ChannelEvent {
DeliverMessage {
session_key: SessionKey,
content: serde_json::Value,
},
SessionComplete {
session_key: SessionKey,
stop_reason: StopReason,
},
RoutePermission {
session_key: SessionKey,
request_id: String,
description: String,
options: Vec<crate::permission::PermissionOption>,
},
AckMessage {
session_key: SessionKey,
channel_name: String,
peer_id: String,
message_id: Option<String>,
},
DispatchStarted {
session_key: SessionKey,
},
}
#[cfg(test)]
mod tests {
use super::*;
use rstest::rstest;
#[test]
fn when_deliver_message_event_serialized_then_deserializes_correctly() {
let event = ChannelEvent::DeliverMessage {
session_key: SessionKey::new("debug-http", "local", "dev"),
content: serde_json::json!({"text": "hello"}),
};
let json = serde_json::to_value(&event).unwrap();
let deser: ChannelEvent = serde_json::from_value(json).unwrap();
assert!(matches!(deser, ChannelEvent::DeliverMessage { .. }));
}
#[test]
fn when_route_permission_event_serialized_then_deserializes_correctly() {
let event = ChannelEvent::RoutePermission {
session_key: SessionKey::new("telegram", "direct", "alice"),
request_id: "req-1".into(),
description: "Allow file write?".into(),
options: vec![crate::permission::PermissionOption {
option_id: "allow".into(),
label: "Allow".into(),
}],
};
let json = serde_json::to_value(&event).unwrap();
let deser: ChannelEvent = serde_json::from_value(json).unwrap();
assert!(matches!(deser, ChannelEvent::RoutePermission { .. }));
}
#[test]
fn when_route_permission_options_round_trip_then_typed_vec() {
let event = ChannelEvent::RoutePermission {
session_key: SessionKey::new("telegram", "direct", "alice"),
request_id: "req-2".into(),
description: "Allow network?".into(),
options: vec![
crate::permission::PermissionOption {
option_id: "allow_once".into(),
label: "Allow once".into(),
},
crate::permission::PermissionOption {
option_id: "deny".into(),
label: "Deny".into(),
},
],
};
let json = serde_json::to_string(&event).unwrap();
let back: ChannelEvent = serde_json::from_str(&json).unwrap();
if let ChannelEvent::RoutePermission { options, .. } = back {
assert_eq!(options.len(), 2);
assert_eq!(options[0].option_id, "allow_once");
assert_eq!(options[1].label, "Deny");
} else {
panic!("expected RoutePermission");
}
}
#[test]
fn when_ack_message_event_serialized_then_deserializes_correctly() {
let event = ChannelEvent::AckMessage {
session_key: SessionKey::new("telegram", "direct", "alice"),
channel_name: "telegram".into(),
peer_id: "alice".into(),
message_id: Some("msg-123".into()),
};
let json = serde_json::to_value(&event).unwrap();
let deser: ChannelEvent = serde_json::from_value(json).unwrap();
assert!(matches!(deser, ChannelEvent::AckMessage { .. }));
}
#[test]
fn when_ack_message_has_no_message_id_then_serializes_as_null() {
let event = ChannelEvent::AckMessage {
session_key: SessionKey::new("debug-http", "local", "dev"),
channel_name: "debug-http".into(),
peer_id: "dev".into(),
message_id: None,
};
let json = serde_json::to_value(&event).unwrap();
let deser: ChannelEvent = serde_json::from_value(json).unwrap();
assert!(matches!(
deser,
ChannelEvent::AckMessage {
message_id: None,
..
}
));
}
#[rstest]
#[case::deliver_message(ChannelEvent::DeliverMessage {
session_key: SessionKey::new("debug-http", "local", "dev"),
content: serde_json::json!({"text": "hello"}),
})]
#[case::session_complete(ChannelEvent::SessionComplete {
session_key: SessionKey::new("telegram", "direct", "alice"),
stop_reason: StopReason::EndTurn,
})]
#[case::ack_message_with_id(ChannelEvent::AckMessage {
session_key: SessionKey::new("telegram", "direct", "bob"),
channel_name: "telegram".into(),
peer_id: "bob".into(),
message_id: Some("msg-99".into()),
})]
#[case::ack_message_without_id(ChannelEvent::AckMessage {
session_key: SessionKey::new("debug-http", "local", "dev"),
channel_name: "debug-http".into(),
peer_id: "dev".into(),
message_id: None,
})]
#[case::dispatch_started(ChannelEvent::DispatchStarted {
session_key: SessionKey::new("telegram", "direct", "alice"),
})]
fn when_channel_event_variant_round_trips_then_deserializes_to_same_variant(
#[case] original: ChannelEvent,
) {
let json = serde_json::to_string(&original).unwrap();
let restored: ChannelEvent = serde_json::from_str(&json).unwrap();
let original_json = serde_json::to_value(&original).unwrap();
let restored_json = serde_json::to_value(&restored).unwrap();
assert_eq!(original_json, restored_json);
}
#[rstest]
fn when_route_permission_event_round_trips_then_identical() {
let original = ChannelEvent::RoutePermission {
session_key: SessionKey::new("telegram", "direct", "alice"),
request_id: "req-1".into(),
description: "Allow file write?".into(),
options: vec![
crate::permission::PermissionOption {
option_id: "allow".into(),
label: "Allow".into(),
},
crate::permission::PermissionOption {
option_id: "deny".into(),
label: "Deny".into(),
},
],
};
let json = serde_json::to_string(&original).unwrap();
let restored: ChannelEvent = serde_json::from_str(&json).unwrap();
let original_json = serde_json::to_value(&original).unwrap();
let restored_json = serde_json::to_value(&restored).unwrap();
assert_eq!(original_json, restored_json);
}
}