use super::*; fn t(h: u32, m: u32) -> NaiveTime { NaiveTime::from_hms_opt(h, m, 0).unwrap() } fn make_block(start: NaiveTime, duration_mins: u32) -> ProgrammingBlock { ProgrammingBlock::new_algorithmic( "test", start, duration_mins, Default::default(), FillStrategy::Random, ) } fn cfg_with_monday_block(start: NaiveTime, dur: u32) -> ScheduleConfig { let mut cfg = ScheduleConfig::default(); cfg.insert_day(Weekday::Monday, vec![make_block(start, dur)]); cfg } #[test] fn find_block_at_finds_active_block() { let cfg = cfg_with_monday_block(t(8, 0), 60); assert!(cfg.find_block_at(Weekday::Monday, t(8, 30)).is_some()); assert!(cfg.find_block_at(Weekday::Monday, t(9, 0)).is_none()); } #[test] fn find_block_at_wrong_day_returns_none() { let cfg = cfg_with_monday_block(t(8, 0), 60); assert!(cfg.find_block_at(Weekday::Tuesday, t(8, 30)).is_none()); } #[test] fn v1_compat_copies_blocks_to_all_days() { let json = r#"{"blocks": []}"#; let compat: ScheduleConfigCompat = serde_json::from_str(json).unwrap(); let cfg: ScheduleConfig = compat.into(); assert_eq!(cfg.day_blocks().len(), 7); } #[test] fn v2_payload_with_unknown_blocks_key_fails() { let json = r#"{"blocks": [], "day_blocks": {}}"#; let result: Result = serde_json::from_str(json); match result { Ok(ScheduleConfigCompat::V2(cfg)) => { let _ = cfg; } Ok(ScheduleConfigCompat::V1(_)) => {} Err(_) => {} } } #[test] fn earliest_block_start_across_days() { let mut cfg = ScheduleConfig::default(); cfg.insert_day(Weekday::Monday, vec![make_block(t(10, 0), 60)]); cfg.insert_day(Weekday::Friday, vec![make_block(t(7, 0), 60)]); assert_eq!(cfg.earliest_block_start(), Some(t(7, 0))); } #[test] fn empty_config_earliest_block_start_is_none() { let cfg = ScheduleConfig::default(); assert!(cfg.earliest_block_start().is_none()); } #[test] fn channel_new_defaults() { let owner = UserId::generate(); let ch = Channel::new(owner, "Test Channel", "America/New_York"); assert_eq!(ch.name(), "Test Channel"); assert_eq!(ch.timezone(), "America/New_York"); assert_eq!(ch.owner_id(), owner); assert!(!ch.auto_schedule()); assert!(ch.description().is_none()); assert_eq!(ch.logo_opacity(), DEFAULT_LOGO_OPACITY); assert_eq!(ch.webhook_poll_interval_secs(), DEFAULT_WEBHOOK_POLL_INTERVAL_SECS); } #[test] fn programming_block_getters() { let block = ProgrammingBlock::new_algorithmic( "Morning", t(8, 0), 120, Default::default(), FillStrategy::BestFit, ); assert_eq!(block.name(), "Morning"); assert_eq!(block.start_time(), t(8, 0)); assert_eq!(block.duration_mins(), 120); assert!(block.loop_on_finish()); assert!(!block.ignore_rotation_policy()); } #[test] fn manual_block_creation() { let items = vec![MediaItemId::new("item1"), MediaItemId::new("item2")]; let block = ProgrammingBlock::new_manual("Manual Block", t(20, 0), 60, items); match block.content() { BlockContent::Manual { items, provider_id } => { assert_eq!(items.len(), 2); assert!(provider_id.is_empty()); } _ => panic!("Expected Manual content"), } }