use std::fs;
use std::path::PathBuf;
use streamtop::engine::dash::{classify_content_protection, parse_dash_mpd};
use streamtop::engine::linter::{next_blocking_targets, scan_ll_hls};
use streamtop::engine::playlist_parser::{detect_and_parse, ParsedInput};
use url::Url;
fn fixture(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fixtures")
.join(name)
}
fn load(name: &str) -> Vec<u8> {
fs::read(fixture(name)).unwrap_or_else(|e| panic!("load {name}: {e}"))
}
#[test]
fn router_hls_master_is_single_stream() {
let body = load("hls_master.m3u8");
let parsed = detect_and_parse("https://cdn.example/master.m3u8", &body, None).unwrap();
assert!(
matches!(parsed, ParsedInput::SingleStream { .. }),
"expected SingleStream for HLS master"
);
}
#[test]
fn router_hls_media_with_targetduration_is_single_stream() {
let body = load("hls_media.m3u8");
let parsed = detect_and_parse("https://cdn.example/index.m3u8", &body, None).unwrap();
assert!(matches!(parsed, ParsedInput::SingleStream { .. }));
}
#[test]
fn router_ll_hls_media_is_single_stream() {
let body = load("ll_hls_media.m3u8");
let parsed = detect_and_parse("https://cdn.example/ll.m3u8", &body, None).unwrap();
assert!(matches!(parsed, ParsedInput::SingleStream { .. }));
let text = String::from_utf8_lossy(&body);
let ll = scan_ll_hls(&text);
assert!(ll.is_ll_hls);
assert!(ll.can_block_reload);
assert!(ll.has_preload_hint);
assert!(ll.part_count >= 2);
assert_eq!(ll.last_part_sequence, Some(ll.part_count));
assert_eq!(ll.last_part_duration_ms, Some(333));
let badge = ll.header_badge().unwrap();
assert!(badge.contains("[LL-HLS]"));
assert!(badge.contains("333ms"));
assert!(badge.contains("seq="));
let (msn, part) = next_blocking_targets(100, 1, ll.part_count);
assert_eq!(msn, 100);
assert_eq!(part, Some(u64::from(ll.part_count)));
}
#[test]
fn router_iptv_m3u_is_channel_list_never_hls() {
let body = load("iptv_channels.m3u");
let parsed = detect_and_parse(
"https://raw.githubusercontent.com/example/tr.m3u",
&body,
None,
)
.unwrap();
match parsed {
ParsedInput::IptvChannels { channels, .. } => {
assert_eq!(channels.len(), 2);
assert!(channels[0].name.contains("TRT"));
}
ParsedInput::SingleStream { .. } => panic!("IPTV must not become SingleStream"),
}
}
#[test]
fn router_dash_mpd_is_single_stream() {
let body = load("dash_live.mpd");
let parsed = detect_and_parse(
"https://cdn.example/live.mpd",
&body,
Some("application/dash+xml"),
)
.unwrap();
assert!(matches!(parsed, ParsedInput::SingleStream { .. }));
}
#[test]
fn dash_fixture_parses_widevine_content_protection() {
let body = load("dash_live.mpd");
let xml = String::from_utf8_lossy(&body);
let base = Url::parse("https://cdn.example/live.mpd").unwrap();
let summary = parse_dash_mpd(&xml, &base).unwrap();
assert!(summary.type_live);
assert!(summary.drm.present);
assert_eq!(summary.drm.method.as_deref(), Some("Widevine"));
assert!(!summary.variants.is_empty());
assert!((summary.segment_duration_hint_secs - 10.0).abs() < 0.01);
assert_eq!(summary.variants[0].frame_rate, Some(25.0));
let d = classify_content_protection("urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95").unwrap();
assert_eq!(d.method.as_deref(), Some("PlayReady"));
}