use super::*;
#[test]
fn ping_watcher_builds_a_ping_request_only_for_watching_clients() {
use rml_rtmp::chunk_io::ChunkDeserializer;
use rml_rtmp::messages::{MessagePayload, RtmpMessage, UserControlEventType};
let mut scheduler = RtmpScheduler::new(1);
assert!(scheduler.ping_watcher(9).is_none());
let waiting_connection_id = 5;
let _ = scheduler.bytes_received(waiting_connection_id, &[]);
assert!(scheduler.ping_watcher(waiting_connection_id).is_none());
let publisher_connection_id = 6;
let _ = scheduler.bytes_received(publisher_connection_id, &[]);
let publisher_client_id = *scheduler
.connection_to_client_map
.get(&publisher_connection_id)
.unwrap();
scheduler
.clients
.get_mut(publisher_client_id)
.unwrap()
.current_action = ClientAction::Publishing(Rc::from("ping_stream"));
assert!(scheduler.ping_watcher(publisher_connection_id).is_none());
fn drain_messages(
deserializer: &mut ChunkDeserializer,
bytes: &[u8],
) -> Vec<MessagePayload> {
let mut messages = Vec::new();
let mut next = deserializer
.get_next_message(bytes)
.expect("valid chunk bytes");
while let Some(payload) = next {
messages.push(payload);
next = deserializer
.get_next_message(&[])
.expect("valid buffered continuation");
}
messages
}
let watcher_connection_id = 7;
let mut deserializer = ChunkDeserializer::new();
let prior = scheduler
.bytes_received(watcher_connection_id, &[])
.expect("create the watcher client");
let mut results = Vec::new();
scheduler.handle_play_requested(
watcher_connection_id,
1,
"app".to_string(),
"ping_stream".to_string(),
1,
&mut results,
);
for result in prior.into_iter().chain(results) {
if let ServerResult::OutboundPacket {
target_connection_id,
bytes,
..
} = result
{
if target_connection_id == watcher_connection_id {
drain_messages(&mut deserializer, &bytes);
}
}
}
let packet = scheduler
.ping_watcher(watcher_connection_id)
.expect("a watching client must be pingable");
let messages = drain_messages(&mut deserializer, &packet.bytes);
assert_eq!(messages.len(), 1, "the ping packet is one message");
let message = messages
.into_iter()
.next()
.expect("length asserted above")
.to_rtmp_message()
.expect("decode the ping payload");
assert!(
matches!(
message,
RtmpMessage::UserControl {
event_type: UserControlEventType::PingRequest,
..
}
),
"the built packet must be a UserControl PingRequest, got {message:?}"
);
}
#[test]
fn test_new_channel_creation() {
let mut scheduler = RtmpScheduler::new(10);
let stream_key = "test_stream".to_string();
let publisher_connection_id = 1;
let result = scheduler.new_channel(stream_key.clone(), publisher_connection_id);
assert!(result, "First channel creation should succeed");
assert!(scheduler.channels.contains_key(&stream_key));
assert!(scheduler
.publisher_to_client_map
.contains_key(&publisher_connection_id));
}
#[test]
fn test_duplicate_channel_rejected() {
let mut scheduler = RtmpScheduler::new(10);
let stream_key = "test_stream".to_string();
let publisher_connection_id_1 = 1;
let publisher_connection_id_2 = 2;
let result1 = scheduler.new_channel(stream_key.clone(), publisher_connection_id_1);
assert!(result1, "First channel creation should succeed");
if let Some(channel) = scheduler.channels.get_mut(&stream_key) {
channel.publishing_client_id = Some(0);
}
let result2 = scheduler.new_channel(stream_key.clone(), publisher_connection_id_2);
assert!(!result2, "Duplicate channel creation should be rejected");
assert!(scheduler
.publisher_to_client_map
.contains_key(&publisher_connection_id_1));
assert!(!scheduler
.publisher_to_client_map
.contains_key(&publisher_connection_id_2));
}
#[test]
fn test_notify_connection_closed() {
let mut scheduler = RtmpScheduler::new(10);
let connection_id = 1;
let _ = scheduler.bytes_received(connection_id, &[]);
assert!(scheduler
.connection_to_client_map
.contains_key(&connection_id));
let client_id = *scheduler
.connection_to_client_map
.get(&connection_id)
.unwrap();
assert!(scheduler.clients.contains(client_id));
scheduler.notify_connection_closed(connection_id);
assert!(!scheduler
.connection_to_client_map
.contains_key(&connection_id));
assert!(!scheduler.clients.contains(client_id));
}
#[test]
fn test_notify_publisher_closed() {
let mut scheduler = RtmpScheduler::new(10);
let stream_key = "test_stream".to_string();
let publisher_connection_id = 1;
let result = scheduler.new_channel(stream_key.clone(), publisher_connection_id);
assert!(result, "Channel creation should succeed");
assert!(scheduler
.publisher_to_client_map
.contains_key(&publisher_connection_id));
let client_id = *scheduler
.publisher_to_client_map
.get(&publisher_connection_id)
.unwrap();
assert!(scheduler.clients.contains(client_id));
scheduler.notify_publisher_closed(publisher_connection_id);
assert!(!scheduler
.publisher_to_client_map
.contains_key(&publisher_connection_id));
assert!(!scheduler.clients.contains(client_id));
assert!(
!scheduler.channels.contains_key(&stream_key),
"Empty channel (no publisher, no watchers) should be removed"
);
}
#[test]
fn test_notify_publisher_closed_with_watchers() {
let mut scheduler = RtmpScheduler::new(10);
let stream_key = "test_stream".to_string();
let publisher_connection_id = 1;
let result = scheduler.new_channel(stream_key.clone(), publisher_connection_id);
assert!(result, "Channel creation should succeed");
if let Some(channel) = scheduler.channels.get_mut(&stream_key) {
channel.watching_client_ids.insert(100);
}
scheduler.notify_publisher_closed(publisher_connection_id);
assert!(!scheduler
.publisher_to_client_map
.contains_key(&publisher_connection_id));
assert!(
scheduler.channels.contains_key(&stream_key),
"Channel with watchers should remain after publisher closes"
);
if let Some(channel) = scheduler.channels.get(&stream_key) {
assert_eq!(channel.publishing_client_id, None);
assert!(channel.watching_client_ids.contains(&100));
}
}
#[test]
fn test_publish_bytes_to_nonexistent_connection() {
let mut scheduler = RtmpScheduler::new(10);
let nonexistent_connection_id = 999;
let result = scheduler.publish_bytes_received(nonexistent_connection_id, vec![0x03]);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_bytes_received_creates_session() {
let mut scheduler = RtmpScheduler::new(10);
let connection_id = 1;
assert!(!scheduler
.connection_to_client_map
.contains_key(&connection_id));
let result = scheduler.bytes_received(connection_id, &[0x03]);
assert!(result.is_ok());
assert!(scheduler
.connection_to_client_map
.contains_key(&connection_id));
let client_id = *scheduler
.connection_to_client_map
.get(&connection_id)
.unwrap();
assert!(scheduler.clients.contains(client_id));
let client = scheduler.clients.get(client_id).unwrap();
assert!(matches!(client.current_action, ClientAction::Waiting));
}
#[test]
fn test_handle_play_request_flow() {
let mut scheduler = RtmpScheduler::new(10);
let stream_key = "test_stream".to_string();
let connection_id = 1;
let _ = scheduler.bytes_received(connection_id, &[]);
assert!(scheduler
.connection_to_client_map
.contains_key(&connection_id));
let client_id = *scheduler
.connection_to_client_map
.get(&connection_id)
.unwrap();
let client = scheduler.clients.get(client_id).unwrap();
assert!(matches!(client.current_action, ClientAction::Waiting));
let mut server_results = Vec::new();
scheduler.handle_play_requested(
connection_id,
1, "test_app".to_string(),
stream_key.clone(),
1, &mut server_results,
);
let client = scheduler.clients.get(client_id).unwrap();
assert!(matches!(
client.current_action,
ClientAction::Watching { .. }
));
assert!(scheduler.channels.contains_key(&stream_key));
let channel = scheduler.channels.get(&stream_key).unwrap();
assert!(channel.watching_client_ids.contains(&client_id));
}
#[test]
fn test_scheduler_error_propagation() {
let mut scheduler = RtmpScheduler::new(10);
let connection_id = 1;
let _ = scheduler.bytes_received(connection_id, &[]);
let invalid_data = vec![0xFF, 0xFF];
let result = scheduler.bytes_received(connection_id, &invalid_data);
match result {
Ok(_) => {
}
Err(_) => {
}
}
}
#[test]
fn test_invalid_stream_key_warning() {
let mut scheduler = RtmpScheduler::new(10);
let stream_key = "nonexistent_stream".to_string();
let mut server_results = Vec::new();
let timestamp = RtmpTimestamp { value: 0 };
let data = Bytes::from(vec![0x17, 0x01, 0x00, 0x00, 0x00]);
scheduler.handle_audio_video_data_received(
&stream_key,
timestamp,
data,
ReceivedDataType::Video,
&mut server_results,
);
assert!(server_results.is_empty());
assert!(!scheduler.channels.contains_key(&stream_key));
}