// Unit coverage for client upstream bundled media queue behavior. // // Scope: exercise the freshness queue with complete `UpstreamMediaBundle` // values rather than split audio/video packets. // Targets: `client/src/uplink_fresh_queue.rs`. // Why: the upstream sync win depends on dropping stale media as complete A/V // bundles so the server never receives an orphaned tone or flash. #[path = "../../../../client/src/uplink_fresh_queue.rs"] #[allow(warnings)] mod uplink_fresh_queue; use lesavka_common::lesavka::{AudioEncoding, AudioPacket, UpstreamMediaBundle, VideoPacket}; use std::time::Duration; use uplink_fresh_queue::{FreshPacketQueue, FreshQueueConfig, FreshQueuePolicy}; fn bundle(seq: u64, capture_pts_us: u64) -> UpstreamMediaBundle { UpstreamMediaBundle { session_id: 7, seq, capture_start_us: capture_pts_us.saturating_sub(20_000), capture_end_us: capture_pts_us.saturating_add(20_000), video: Some(VideoPacket { seq, pts: capture_pts_us, data: vec![0, 0, 0, 1, 0x26, 0x01, seq as u8], client_capture_pts_us: capture_pts_us, client_send_pts_us: capture_pts_us.saturating_add(5_000), client_queue_age_ms: 5, ..Default::default() }), audio: vec![ AudioPacket { seq: seq.saturating_mul(2), pts: capture_pts_us.saturating_sub(20_000), data: vec![0x11; 1_920], client_capture_pts_us: capture_pts_us.saturating_sub(20_000), client_send_pts_us: capture_pts_us.saturating_add(5_000), client_queue_age_ms: 25, ..Default::default() }, AudioPacket { seq: seq.saturating_mul(2).saturating_add(1), pts: capture_pts_us.saturating_add(20_000), data: vec![0x22; 1_920], client_capture_pts_us: capture_pts_us.saturating_add(20_000), client_send_pts_us: capture_pts_us.saturating_add(25_000), client_queue_age_ms: 5, ..Default::default() }, ], audio_sample_rate: 48_000, audio_channels: 2, audio_encoding: AudioEncoding::PcmS16le as i32, video_width: 1280, video_height: 720, video_fps: 30, } } fn assert_complete_bundle(bundle: &UpstreamMediaBundle, expected_seq: u64) { assert_eq!(bundle.seq, expected_seq); assert!( bundle.video.is_some(), "freshness queue must not split video out of its bundle" ); assert_eq!( bundle.audio.len(), 2, "freshness queue must not split audio out of its bundle" ); let video = bundle.video.as_ref().expect("video packet"); assert!( bundle.capture_start_us <= video.client_capture_pts_us && bundle.capture_end_us >= video.client_capture_pts_us ); assert!(bundle.audio.iter().all(|audio| { bundle.capture_start_us <= audio.client_capture_pts_us && bundle.capture_end_us >= audio.client_capture_pts_us })); } #[tokio::test] async fn latest_only_policy_drops_superseded_bundles_as_complete_av_units() { let queue = FreshPacketQueue::new(FreshQueueConfig { capacity: 8, max_age: Duration::from_secs(1), policy: FreshQueuePolicy::LatestOnly, }); for seq in 1..=3 { let _ = queue.push( bundle(seq, 1_000_000 + seq * 33_333), Duration::from_millis(10), ); } let popped = queue.pop_fresh().await; assert_eq!(popped.dropped_stale, 2); assert_eq!(popped.queue_depth, 0); assert_complete_bundle(&popped.packet.expect("newest bundle"), 3); } #[tokio::test] async fn stale_bundle_drop_never_delivers_audio_or_video_halves() { let queue = FreshPacketQueue::new(FreshQueueConfig { capacity: 4, max_age: Duration::from_millis(200), policy: FreshQueuePolicy::DrainOldest, }); let _ = queue.push(bundle(1, 1_000_000), Duration::from_millis(250)); let _ = queue.push(bundle(2, 1_033_333), Duration::from_millis(25)); let popped = queue.pop_fresh().await; assert_eq!(popped.dropped_stale, 1); assert_complete_bundle(&popped.packet.expect("fresh bundle"), 2); } #[tokio::test] async fn capacity_pressure_drops_oldest_bundles_instead_of_backlogging_media() { let queue = FreshPacketQueue::new(FreshQueueConfig { capacity: 2, max_age: Duration::from_secs(1), policy: FreshQueuePolicy::DrainOldest, }); let first = queue.push(bundle(1, 1_000_000), Duration::from_millis(5)); let second = queue.push(bundle(2, 1_033_333), Duration::from_millis(5)); let third = queue.push(bundle(3, 1_066_666), Duration::from_millis(5)); assert_eq!(first.dropped_queue_full, 0); assert_eq!(second.queue_depth, 2); assert_eq!(third.dropped_queue_full, 1); assert_complete_bundle(&queue.pop_fresh().await.packet.expect("second bundle"), 2); assert_complete_bundle(&queue.pop_fresh().await.packet.expect("third bundle"), 3); }