iCalendar export: domain service + API + MCP tool (#15)

This commit is contained in:
2026-07-12 14:27:20 +02:00
parent affd7f0223
commit b00272aceb
9 changed files with 315 additions and 3 deletions

View File

@@ -0,0 +1,145 @@
use super::*;
use crate::models::{ProgrammingBlock, ScheduleConfig};
use crate::value_objects::{FillStrategy, MediaFilter, Weekday};
use chrono::NaiveTime;
use std::collections::HashMap;
fn make_config_with_blocks(
entries: Vec<(Weekday, Vec<ProgrammingBlock>)>,
) -> ScheduleConfig {
let day_blocks: HashMap<Weekday, Vec<ProgrammingBlock>> = entries.into_iter().collect();
ScheduleConfig::from_day_blocks(day_blocks)
}
#[test]
fn vcalendar_header() {
let config = ScheduleConfig::new();
let ical = generate_ical("Test Channel", "Europe/Warsaw", &config);
assert!(ical.contains("BEGIN:VCALENDAR\r\n"));
assert!(ical.contains("VERSION:2.0\r\n"));
assert!(ical.contains("PRODID:-//K-TV//Schedule Export//EN\r\n"));
assert!(ical.contains("CALSCALE:GREGORIAN\r\n"));
assert!(ical.contains("X-WR-CALNAME:Test Channel\r\n"));
assert!(ical.contains("X-WR-TIMEZONE:Europe/Warsaw\r\n"));
assert!(ical.contains("END:VCALENDAR\r\n"));
}
#[test]
fn empty_config_produces_no_events() {
let config = ScheduleConfig::new();
let ical = generate_ical("Empty", "UTC", &config);
assert!(!ical.contains("BEGIN:VEVENT"));
}
#[test]
fn algorithmic_block_produces_vevent() {
let block = ProgrammingBlock::new_algorithmic(
"Morning Cartoons",
NaiveTime::from_hms_opt(8, 0, 0).unwrap(),
120,
MediaFilter::default(),
FillStrategy::Random,
);
let config = make_config_with_blocks(vec![(Weekday::Monday, vec![block.clone()])]);
let ical = generate_ical("Kids TV", "UTC", &config);
assert!(ical.contains("BEGIN:VEVENT\r\n"));
assert!(ical.contains("END:VEVENT\r\n"));
assert!(ical.contains("DTSTART:080000\r\n"));
assert!(ical.contains("DURATION:PT2H\r\n"));
assert!(ical.contains("RRULE:FREQ=WEEKLY;BYDAY=MO\r\n"));
assert!(ical.contains("SUMMARY:Morning Cartoons\r\n"));
assert!(ical.contains(&format!("UID:{}\r\n", block.id())));
assert!(ical.contains("X-KTV-STRATEGY:random\r\n"));
assert!(ical.contains("X-KTV-CONTENT:"));
}
#[test]
fn different_days_produce_different_byday() {
let mon_block = ProgrammingBlock::new_algorithmic(
"Monday Block",
NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
60,
MediaFilter::default(),
FillStrategy::Sequential,
);
let fri_block = ProgrammingBlock::new_algorithmic(
"Friday Block",
NaiveTime::from_hms_opt(20, 0, 0).unwrap(),
90,
MediaFilter::default(),
FillStrategy::BestFit,
);
let config = make_config_with_blocks(vec![
(Weekday::Monday, vec![mon_block]),
(Weekday::Friday, vec![fri_block]),
]);
let ical = generate_ical("Multi-Day", "America/New_York", &config);
assert!(ical.contains("RRULE:FREQ=WEEKLY;BYDAY=MO\r\n"));
assert!(ical.contains("RRULE:FREQ=WEEKLY;BYDAY=FR\r\n"));
}
#[test]
fn manual_block_has_no_strategy() {
let block = ProgrammingBlock::new_manual(
"Manual Show",
NaiveTime::from_hms_opt(14, 30, 0).unwrap(),
45,
vec![],
);
let config = make_config_with_blocks(vec![(Weekday::Wednesday, vec![block])]);
let ical = generate_ical("Manual Ch", "UTC", &config);
assert!(ical.contains("SUMMARY:Manual Show\r\n"));
assert!(ical.contains("DTSTART:143000\r\n"));
assert!(ical.contains("DURATION:PT45M\r\n"));
assert!(ical.contains("RRULE:FREQ=WEEKLY;BYDAY=WE\r\n"));
assert!(!ical.contains("X-KTV-STRATEGY:"));
}
#[test]
fn duration_with_hours_and_minutes() {
let block = ProgrammingBlock::new_manual(
"Mixed Duration",
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
150,
vec![],
);
let config = make_config_with_blocks(vec![(Weekday::Sunday, vec![block])]);
let ical = generate_ical("Test", "UTC", &config);
assert!(ical.contains("DURATION:PT2H30M\r\n"));
assert!(ical.contains("RRULE:FREQ=WEEKLY;BYDAY=SU\r\n"));
}
#[test]
fn all_weekdays_mapped() {
let days_and_byday = [
(Weekday::Monday, "MO"),
(Weekday::Tuesday, "TU"),
(Weekday::Wednesday, "WE"),
(Weekday::Thursday, "TH"),
(Weekday::Friday, "FR"),
(Weekday::Saturday, "SA"),
(Weekday::Sunday, "SU"),
];
for (day, byday) in days_and_byday {
let block = ProgrammingBlock::new_manual(
"Test",
NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
60,
vec![],
);
let config = make_config_with_blocks(vec![(day, vec![block])]);
let ical = generate_ical("Test", "UTC", &config);
assert!(
ical.contains(&format!("BYDAY={}\r\n", byday)),
"Expected BYDAY={} for {:?}",
byday,
day
);
}
}