use hunch::{Confidence, Pipeline, hunch_with_context};
#[test]
fn western_episode_series_cross_file() {
let target = "Breaking.Bad.S05E16.720p.BluRay.x264-DEMAND.mkv";
let siblings = &[
"Breaking.Bad.S05E14.720p.BluRay.x264-DEMAND.mkv",
"Breaking.Bad.S05E15.720p.BluRay.x264-DEMAND.mkv",
];
let result = hunch_with_context(target, siblings);
assert_eq!(result.title(), Some("Breaking Bad"));
}
#[test]
fn western_movie_no_siblings_fallback() {
let result = hunch_with_context(
"The.Matrix.1999.1080p.BluRay.x264-GROUP.mkv",
&[] as &[&str],
);
assert_eq!(result.title(), Some("The Matrix"));
assert_eq!(result.year(), Some(1999));
}
#[test]
fn western_one_sibling() {
let target = "Show.S01E03.720p.mkv";
let siblings = &["Show.S01E01.720p.mkv"];
let result = hunch_with_context(target, siblings);
assert_eq!(result.title(), Some("Show"));
}
#[test]
fn cjk_fansub_cross_file_title() {
let target = "(BD)\u{5341}\u{4e8c}\u{56fd}\u{8a18} \u{7b2c}13\u{8a71}\u{300c}\u{6708}\u{306e}\u{5f71} \u{5f71}\u{306e}\u{6d77}\u{3000}\u{7d42}\u{7ae0}\u{300d}(1440x1080 x264-10bpp flac).mkv";
let siblings = &[
"(BD)\u{5341}\u{4e8c}\u{56fd}\u{8a18} \u{7b2c}01\u{8a71}\u{300c}\u{6708}\u{306e}\u{5f71} \u{5f71}\u{306e}\u{6d77}\u{3000}\u{4e00}\u{7ae0}\u{300d}(1440x1080 x264-10bpp flac).mkv",
"(BD)\u{5341}\u{4e8c}\u{56fd}\u{8a18} \u{7b2c}02\u{8a71}\u{300c}\u{6708}\u{306e}\u{5f71} \u{5f71}\u{306e}\u{6d77}\u{3000}\u{4e8c}\u{7ae0}\u{300d}(1440x1080 x264-10bpp flac).mkv",
];
let result = hunch_with_context(target, siblings);
let title = result.title().expect("title should be detected");
assert!(
title.contains('\u{5341}') && title.contains('\u{56fd}'),
"title should contain 十二国記, got: {title}"
);
}
#[test]
fn pipeline_run_with_context_reuse() {
let pipeline = Pipeline::new();
let r1 = pipeline.run_with_context(
"Show.S01E01.720p.mkv",
&["Show.S01E02.720p.mkv", "Show.S01E03.720p.mkv"],
);
assert_eq!(r1.title(), Some("Show"));
let r2 = pipeline.run_with_context(
"Other.Show.S02E05.1080p.mkv",
&["Other.Show.S02E06.1080p.mkv"],
);
assert_eq!(r2.title(), Some("Other Show"));
}
#[test]
fn confidence_high_with_anchors() {
let result = hunch::hunch("Movie.2024.1080p.BluRay.x264-GROUP.mkv");
assert_eq!(result.confidence(), Confidence::High);
}
#[test]
fn confidence_high_with_cross_file() {
let target = "Show.S01E03.720p.mkv";
let siblings = &["Show.S01E01.720p.mkv", "Show.S01E02.720p.mkv"];
let result = hunch_with_context(target, siblings);
assert_eq!(result.confidence(), Confidence::High);
}
#[test]
fn confidence_low_minimal_input() {
let result = hunch::hunch("x.mkv");
assert!(result.confidence() <= Confidence::Medium);
}
#[test]
fn confidence_medium_some_anchors() {
let result = hunch::hunch("SomeShow.720p.mkv");
assert!(result.confidence() >= Confidence::Medium);
}
#[test]
fn cross_file_preserves_tech_properties() {
let target = "Show.S01E03.720p.BluRay.x264-GROUP.mkv";
let siblings = &[
"Show.S01E01.720p.BluRay.x264-GROUP.mkv",
"Show.S01E02.720p.BluRay.x264-GROUP.mkv",
];
let result = hunch_with_context(target, siblings);
assert_eq!(result.title(), Some("Show"));
assert_eq!(result.season(), Some(1));
assert_eq!(result.episode(), Some(3));
assert_eq!(result.screen_size(), Some("720p"));
assert_eq!(result.source(), Some("Blu-ray"));
assert_eq!(result.video_codec(), Some("H.264"));
assert_eq!(result.release_group(), Some("GROUP"));
assert_eq!(result.container(), Some("mkv"));
}
#[test]
fn invariant_year_suppressed_2001() {
let r = hunch_with_context(
"2001.A.Space.Odyssey.1080p.BluRay.mkv",
&["2001.A.Space.Odyssey.720p.BluRay.mkv"],
);
let title = r.title().expect("should have title");
assert!(
title.contains("2001"),
"title should contain '2001', got: {title}"
);
assert_ne!(
r.year(),
Some(2001),
"2001 should not be extracted as year (it's part of the title)"
);
}
#[test]
fn invariant_year_suppressed_1917() {
let r = hunch_with_context("1917.2019.1080p.BluRay.mkv", &["1917.2019.720p.BluRay.mkv"]);
let title = r.title().expect("should have title");
assert!(
title.contains("1917"),
"title should contain '1917', got: {title}"
);
}
#[test]
fn variant_year_preserved() {
let r = hunch_with_context(
"Movie.Collection.2023.1080p.mkv",
&["Movie.Collection.2024.1080p.mkv"],
);
assert!(
r.year().is_some(),
"variant year should be preserved as metadata"
);
}
#[test]
fn bare_episode_injected_sequential() {
let r = hunch_with_context(
"Show.Name.03.720p.mkv",
&["Show.Name.04.720p.mkv", "Show.Name.05.720p.mkv"],
);
assert_eq!(r.title(), Some("Show Name"));
assert_eq!(
r.episode(),
Some(3),
"sequential bare number should be injected as episode"
);
}
#[test]
fn bare_episode_three_digit_absolute() {
let r = hunch_with_context(
"Naruto.Shippuden.501.720p.mkv",
&[
"Naruto.Shippuden.502.720p.mkv",
"Naruto.Shippuden.503.720p.mkv",
],
);
assert_eq!(
r.episode(),
Some(501),
"3-digit sequential should be injected as episode"
);
}
#[test]
fn invariant_number_not_injected_as_episode() {
let r = hunch_with_context("Show.42.720p.mkv", &["Show.42.1080p.mkv"]);
assert_ne!(
r.episode(),
Some(42),
"invariant number should not be injected as episode"
);
}
#[test]
fn sxxexx_not_clobbered_by_invariance() {
let r = hunch_with_context(
"Show.S01E03.720p.mkv",
&["Show.S01E01.720p.mkv", "Show.S01E02.720p.mkv"],
);
assert_eq!(r.season(), Some(1));
assert_eq!(r.episode(), Some(3));
assert_eq!(r.title(), Some("Show"));
}
#[test]
fn heuristic_decomposition_caps_confidence() {
let r = hunch::hunch("Movie.Title.501.720p.BluRay.x264-GROUP.mkv");
assert!(
r.confidence() <= Confidence::Medium,
"heuristic-only decomposition should cap confidence at Medium, got {:?}",
r.confidence()
);
}
#[test]
fn context_episode_gets_high_confidence() {
let r = hunch_with_context(
"Show.03.720p.BluRay.mkv",
&["Show.04.720p.BluRay.mkv", "Show.05.720p.BluRay.mkv"],
);
assert_eq!(r.confidence(), Confidence::High);
}
#[test]
fn year_and_episode_both_detected() {
let r = hunch_with_context(
"Show.2024.03.720p.mkv",
&["Show.2024.04.720p.mkv", "Show.2024.05.720p.mkv"],
);
let title = r.title().expect("should have title");
assert!(
title.contains("Show"),
"title should contain 'Show', got: {title}"
);
assert_eq!(r.episode(), Some(3), "bare episode should be injected");
}
#[test]
fn no_siblings_no_invariance_signals() {
let r = hunch_with_context("Show.03.720p.mkv", &[] as &[&str]);
assert!(r.title().is_some());
}
#[test]
fn non_sequential_variant_not_injected() {
let r = hunch_with_context("Show.03.720p.mkv", &["Show.17.720p.mkv"]);
assert_eq!(r.title(), Some("Show"));
}
#[test]
fn cjk_episode_with_path_context() {
let r = hunch::hunch("tv/Japanese/\u{5341}\u{4e8c}\u{56fd}\u{8a18}/\u{7b2c}13\u{8a71}.mkv");
assert_eq!(r.episode(), Some(13));
assert_eq!(r.media_type(), Some(hunch::MediaType::Episode));
}
#[test]
fn invariance_with_cjk_siblings() {
let r = hunch_with_context(
"tv/\u{5341}\u{4e8c}\u{56fd}\u{8a18} \u{7b2c}03\u{8a71}.mkv",
&[
"tv/\u{5341}\u{4e8c}\u{56fd}\u{8a18} \u{7b2c}01\u{8a71}.mkv",
"tv/\u{5341}\u{4e8c}\u{56fd}\u{8a18} \u{7b2c}02\u{8a71}.mkv",
],
);
assert_eq!(r.media_type(), Some(hunch::MediaType::Episode));
assert_eq!(r.confidence(), Confidence::High);
}
#[test]
fn empty_input_with_context_no_panic() {
let r = hunch_with_context("", &["sibling.mkv"]);
let _ = r.title(); }
#[test]
fn extension_only_with_context_no_panic() {
let r = hunch_with_context(".mkv", &["sibling.mkv"]);
let _ = r.title();
}