test(protocol,core,bridge): add 83 unit tests for untested modules (TODO-015,018,019,021)
Protocol DTO: 32 serde roundtrip + edge case tests. Core events: 27 event construction + variant coverage tests. Bridge: 24 error mapping + DTO roundtrip tests. Add serde_json dev-dependency to protocol and bridge crates.
This commit is contained in:
@@ -246,7 +246,7 @@ pub enum SessionEvent {
|
||||
}
|
||||
|
||||
/// Bridge-safe mirror of channel-join projection sync state.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VoiceJoinSyncState {
|
||||
/// Reducer is ready to accept channel actions.
|
||||
Ready,
|
||||
@@ -257,7 +257,7 @@ pub enum VoiceJoinSyncState {
|
||||
}
|
||||
|
||||
/// Bridge-safe mirror of stable channel-join error codes.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VoiceJoinErrorCode {
|
||||
/// Duplicate same-target join intent was coalesced.
|
||||
DuplicateSameTargetCoalesced,
|
||||
@@ -296,3 +296,506 @@ pub enum NetworkState {
|
||||
/// OS reports no networks available.
|
||||
Offline,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn network_state_equality() {
|
||||
assert_eq!(NetworkState::Unknown, NetworkState::Unknown);
|
||||
assert_eq!(NetworkState::Online, NetworkState::Online);
|
||||
assert_eq!(NetworkState::Offline, NetworkState::Offline);
|
||||
assert_ne!(NetworkState::Unknown, NetworkState::Online);
|
||||
assert_ne!(NetworkState::Online, NetworkState::Offline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_ptt_binding_empty() {
|
||||
let binding = PersistedPttBinding::empty();
|
||||
assert_eq!(binding.input_class, "");
|
||||
assert_eq!(binding.key_label, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persisted_ptt_binding_equality() {
|
||||
let a = PersistedPttBinding {
|
||||
input_class: "keyboard".to_string(),
|
||||
key_label: "Space".to_string(),
|
||||
};
|
||||
let b = PersistedPttBinding {
|
||||
input_class: "keyboard".to_string(),
|
||||
key_label: "Space".to_string(),
|
||||
};
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ptt_descriptor_snapshot_fields() {
|
||||
let snap = PttDescriptorSnapshot {
|
||||
level: "L0Focused".to_string(),
|
||||
backend_id: "focused".to_string(),
|
||||
bound_input_class: "keyboard".to_string(),
|
||||
};
|
||||
assert_eq!(snap.level, "L0Focused");
|
||||
assert_eq!(snap.backend_id, "focused");
|
||||
assert_eq!(snap.bound_input_class, "keyboard");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_connected() {
|
||||
let evt = SessionEvent::Connected {
|
||||
server_name: "Test Server".to_string(),
|
||||
};
|
||||
if let SessionEvent::Connected { server_name } = evt {
|
||||
assert_eq!(server_name, "Test Server");
|
||||
} else {
|
||||
panic!("expected Connected variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_lost() {
|
||||
let evt = SessionEvent::Lost {
|
||||
reason: "timeout".to_string(),
|
||||
};
|
||||
if let SessionEvent::Lost { reason } = evt {
|
||||
assert_eq!(reason, "timeout");
|
||||
} else {
|
||||
panic!("expected Lost variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_reconnecting() {
|
||||
let evt = SessionEvent::Reconnecting {
|
||||
attempt: 3,
|
||||
delay_secs: 30,
|
||||
};
|
||||
if let SessionEvent::Reconnecting {
|
||||
attempt,
|
||||
delay_secs,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(attempt, 3);
|
||||
assert_eq!(delay_secs, 30);
|
||||
} else {
|
||||
panic!("expected Reconnecting variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_disconnected() {
|
||||
let evt = SessionEvent::Disconnected {
|
||||
reason: "user".to_string(),
|
||||
};
|
||||
if let SessionEvent::Disconnected { reason } = evt {
|
||||
assert_eq!(reason, "user");
|
||||
} else {
|
||||
panic!("expected Disconnected variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_audio_started_stopped() {
|
||||
let _ = SessionEvent::AudioStarted;
|
||||
let _ = SessionEvent::AudioStopped;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_ptt_capability() {
|
||||
let evt = SessionEvent::PttCapability {
|
||||
level: "L1GlobalShortcut".to_string(),
|
||||
backend_id: "global".to_string(),
|
||||
bound_input_class: "keyboard".to_string(),
|
||||
};
|
||||
if let SessionEvent::PttCapability {
|
||||
level,
|
||||
backend_id,
|
||||
bound_input_class,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(level, "L1GlobalShortcut");
|
||||
assert_eq!(backend_id, "global");
|
||||
assert_eq!(bound_input_class, "keyboard");
|
||||
} else {
|
||||
panic!("expected PttCapability variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_voice_state() {
|
||||
let evt = SessionEvent::VoiceState {
|
||||
in_channel: true,
|
||||
transmit_mode: 1,
|
||||
mute: false,
|
||||
release_tail_ms: 200,
|
||||
current_channel_id: Some(42),
|
||||
pending_target_channel_id: None,
|
||||
can_join: false,
|
||||
can_leave: true,
|
||||
join_sync_state: VoiceJoinSyncState::Ready,
|
||||
join_error_code: None,
|
||||
};
|
||||
if let SessionEvent::VoiceState {
|
||||
in_channel,
|
||||
transmit_mode,
|
||||
mute,
|
||||
release_tail_ms,
|
||||
current_channel_id,
|
||||
pending_target_channel_id,
|
||||
can_join,
|
||||
can_leave,
|
||||
join_sync_state,
|
||||
join_error_code,
|
||||
} = evt
|
||||
{
|
||||
assert!(in_channel);
|
||||
assert_eq!(transmit_mode, 1);
|
||||
assert!(!mute);
|
||||
assert_eq!(release_tail_ms, 200);
|
||||
assert_eq!(current_channel_id, Some(42));
|
||||
assert_eq!(pending_target_channel_id, None);
|
||||
assert!(!can_join);
|
||||
assert!(can_leave);
|
||||
assert_eq!(join_sync_state, VoiceJoinSyncState::Ready);
|
||||
assert!(join_error_code.is_none());
|
||||
} else {
|
||||
panic!("expected VoiceState variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_interruption_state() {
|
||||
let evt = SessionEvent::InterruptionState {
|
||||
began: true,
|
||||
should_resume: false,
|
||||
};
|
||||
if let SessionEvent::InterruptionState {
|
||||
began,
|
||||
should_resume,
|
||||
} = evt
|
||||
{
|
||||
assert!(began);
|
||||
assert!(!should_resume);
|
||||
} else {
|
||||
panic!("expected InterruptionState variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_chat_message() {
|
||||
let evt = SessionEvent::ChatMessage {
|
||||
sender_id: 5,
|
||||
sender_name: "Alice".to_string(),
|
||||
message: "Hello".to_string(),
|
||||
target: chanora_protocol::MessageTarget::Channel,
|
||||
poke_strength: None,
|
||||
};
|
||||
if let SessionEvent::ChatMessage {
|
||||
sender_id,
|
||||
sender_name,
|
||||
message,
|
||||
target,
|
||||
poke_strength,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(sender_id, 5);
|
||||
assert_eq!(sender_name, "Alice");
|
||||
assert_eq!(message, "Hello");
|
||||
assert_eq!(target, chanora_protocol::MessageTarget::Channel);
|
||||
assert!(poke_strength.is_none());
|
||||
} else {
|
||||
panic!("expected ChatMessage variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_chat_message_with_poke() {
|
||||
let evt = SessionEvent::ChatMessage {
|
||||
sender_id: 3,
|
||||
sender_name: "Bob".to_string(),
|
||||
message: "".to_string(),
|
||||
target: chanora_protocol::MessageTarget::Poke(7),
|
||||
poke_strength: Some(chanora_protocol::PokeStrength::Suppressed),
|
||||
};
|
||||
if let SessionEvent::ChatMessage {
|
||||
target,
|
||||
poke_strength,
|
||||
..
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(target, chanora_protocol::MessageTarget::Poke(7));
|
||||
assert_eq!(poke_strength, Some(chanora_protocol::PokeStrength::Suppressed));
|
||||
} else {
|
||||
panic!("expected ChatMessage variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_server_activity() {
|
||||
let evt = SessionEvent::ServerActivity {
|
||||
message: "User joined channel".to_string(),
|
||||
};
|
||||
if let SessionEvent::ServerActivity { message } = &evt {
|
||||
assert_eq!(message, "User joined channel");
|
||||
} else {
|
||||
panic!("expected ServerActivity variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_audio_route_changed() {
|
||||
let evt = SessionEvent::AudioRouteChanged {
|
||||
route: chanora_audio::AudioRoute::Speaker,
|
||||
};
|
||||
if let SessionEvent::AudioRouteChanged { route } = &evt {
|
||||
assert_eq!(*route, chanora_audio::AudioRoute::Speaker);
|
||||
} else {
|
||||
panic!("expected AudioRouteChanged variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_client_moved() {
|
||||
let evt = SessionEvent::ClientMoved {
|
||||
client_id: 1,
|
||||
new_channel_id: 2,
|
||||
};
|
||||
if let SessionEvent::ClientMoved {
|
||||
client_id,
|
||||
new_channel_id,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(client_id, 1);
|
||||
assert_eq!(new_channel_id, 2);
|
||||
} else {
|
||||
panic!("expected ClientMoved variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_client_joined() {
|
||||
let evt = SessionEvent::ClientJoined {
|
||||
client_id: 10,
|
||||
channel_id: 3,
|
||||
name: "NewUser".to_string(),
|
||||
input_muted: false,
|
||||
output_muted: true,
|
||||
is_server_query: false,
|
||||
talk_power: 0,
|
||||
talk_power_granted: false,
|
||||
};
|
||||
if let SessionEvent::ClientJoined {
|
||||
client_id,
|
||||
channel_id,
|
||||
name,
|
||||
input_muted,
|
||||
output_muted,
|
||||
is_server_query,
|
||||
talk_power,
|
||||
talk_power_granted,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(client_id, 10);
|
||||
assert_eq!(channel_id, 3);
|
||||
assert_eq!(name, "NewUser");
|
||||
assert!(!input_muted);
|
||||
assert!(output_muted);
|
||||
assert!(!is_server_query);
|
||||
assert_eq!(talk_power, 0);
|
||||
assert!(!talk_power_granted);
|
||||
} else {
|
||||
panic!("expected ClientJoined variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_client_left() {
|
||||
let evt = SessionEvent::ClientLeft {
|
||||
client_id: 10,
|
||||
name: "Departing".to_string(),
|
||||
};
|
||||
if let SessionEvent::ClientLeft { client_id, name } = evt {
|
||||
assert_eq!(client_id, 10);
|
||||
assert_eq!(name, "Departing");
|
||||
} else {
|
||||
panic!("expected ClientLeft variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_client_updated() {
|
||||
let evt = SessionEvent::ClientUpdated {
|
||||
client_id: 5,
|
||||
input_muted: true,
|
||||
output_muted: false,
|
||||
is_server_query: true,
|
||||
talk_power: 75,
|
||||
talk_power_granted: true,
|
||||
};
|
||||
if let SessionEvent::ClientUpdated {
|
||||
client_id,
|
||||
input_muted,
|
||||
output_muted,
|
||||
is_server_query,
|
||||
talk_power,
|
||||
talk_power_granted,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(client_id, 5);
|
||||
assert!(input_muted);
|
||||
assert!(!output_muted);
|
||||
assert!(is_server_query);
|
||||
assert_eq!(talk_power, 75);
|
||||
assert!(talk_power_granted);
|
||||
} else {
|
||||
panic!("expected ClientUpdated variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_channel_added() {
|
||||
let evt = SessionEvent::ChannelAdded {
|
||||
id: 7,
|
||||
parent: 1,
|
||||
name: "Sub".to_string(),
|
||||
order: 3,
|
||||
has_password: true,
|
||||
needed_talk_power: Some(50),
|
||||
};
|
||||
if let SessionEvent::ChannelAdded {
|
||||
id,
|
||||
parent,
|
||||
name,
|
||||
order,
|
||||
has_password,
|
||||
needed_talk_power,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(id, 7);
|
||||
assert_eq!(parent, 1);
|
||||
assert_eq!(name, "Sub");
|
||||
assert_eq!(order, 3);
|
||||
assert!(has_password);
|
||||
assert_eq!(needed_talk_power, Some(50));
|
||||
} else {
|
||||
panic!("expected ChannelAdded variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_channel_removed() {
|
||||
let evt = SessionEvent::ChannelRemoved { id: 7 };
|
||||
if let SessionEvent::ChannelRemoved { id } = evt {
|
||||
assert_eq!(id, 7);
|
||||
} else {
|
||||
panic!("expected ChannelRemoved variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_channel_updated() {
|
||||
let evt = SessionEvent::ChannelUpdated {
|
||||
id: 7,
|
||||
name: "Renamed".to_string(),
|
||||
has_password: false,
|
||||
needed_talk_power: None,
|
||||
};
|
||||
if let SessionEvent::ChannelUpdated {
|
||||
id,
|
||||
name,
|
||||
has_password,
|
||||
needed_talk_power,
|
||||
} = evt
|
||||
{
|
||||
assert_eq!(id, 7);
|
||||
assert_eq!(name, "Renamed");
|
||||
assert!(!has_password);
|
||||
assert!(needed_talk_power.is_none());
|
||||
} else {
|
||||
panic!("expected ChannelUpdated variant");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_join_sync_state_variants() {
|
||||
let ready = VoiceJoinSyncState::Ready;
|
||||
let init = VoiceJoinSyncState::SynchronizingInitialSnapshot;
|
||||
let reconnect = VoiceJoinSyncState::SynchronizingReconnect;
|
||||
assert_ne!(
|
||||
std::mem::discriminant(&ready),
|
||||
std::mem::discriminant(&init)
|
||||
);
|
||||
assert_ne!(
|
||||
std::mem::discriminant(&init),
|
||||
std::mem::discriminant(&reconnect)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn voice_join_error_code_all_variants() {
|
||||
let codes = [
|
||||
VoiceJoinErrorCode::DuplicateSameTargetCoalesced,
|
||||
VoiceJoinErrorCode::JoinAlreadyPendingDifferentTarget,
|
||||
VoiceJoinErrorCode::JoinDenied,
|
||||
VoiceJoinErrorCode::JoinProtocolFailure,
|
||||
VoiceJoinErrorCode::JoinNetworkFailure,
|
||||
VoiceJoinErrorCode::JoinTimeout,
|
||||
VoiceJoinErrorCode::JoinSupersededByLeave,
|
||||
VoiceJoinErrorCode::JoinStaleOutcomeIgnored,
|
||||
VoiceJoinErrorCode::JoinReconciledDifferentChannel,
|
||||
VoiceJoinErrorCode::JoinCommandRejectedBeforeSend,
|
||||
VoiceJoinErrorCode::JoinCannotStartWhileSynchronizing,
|
||||
];
|
||||
for i in 0..codes.len() {
|
||||
for j in 0..codes.len() {
|
||||
if i == j {
|
||||
assert_eq!(
|
||||
std::mem::discriminant(&codes[i]),
|
||||
std::mem::discriminant(&codes[j])
|
||||
);
|
||||
} else {
|
||||
assert_ne!(
|
||||
std::mem::discriminant(&codes[i]),
|
||||
std::mem::discriminant(&codes[j])
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_clone_preserves_fields() {
|
||||
let evt = SessionEvent::Connected {
|
||||
server_name: "Cloneable".to_string(),
|
||||
};
|
||||
let cloned = evt.clone();
|
||||
if let SessionEvent::Connected { server_name } = cloned {
|
||||
assert_eq!(server_name, "Cloneable");
|
||||
} else {
|
||||
panic!("expected Connected variant after clone");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn session_event_voice_state_with_join_error() {
|
||||
let evt = SessionEvent::VoiceState {
|
||||
in_channel: false,
|
||||
transmit_mode: 0,
|
||||
mute: false,
|
||||
release_tail_ms: 200,
|
||||
current_channel_id: None,
|
||||
pending_target_channel_id: None,
|
||||
can_join: true,
|
||||
can_leave: false,
|
||||
join_sync_state: VoiceJoinSyncState::Ready,
|
||||
join_error_code: Some(VoiceJoinErrorCode::JoinDenied),
|
||||
};
|
||||
if let SessionEvent::VoiceState { join_error_code, .. } = evt {
|
||||
assert_eq!(join_error_code, Some(VoiceJoinErrorCode::JoinDenied));
|
||||
} else {
|
||||
panic!("expected VoiceState variant");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user