fix(core): model refreshed seasons

This commit is contained in:
Miguel Palhas
2026-08-22 22:15:58 +01:00
parent 9b3edcb047
commit de0d533a49
+73 -29
View File
@@ -1,12 +1,25 @@
use crate::{Episode, Series};
use crate::{Episode, Season, Series};
/// Applies a series' tracking rule to episodes discovered in a new season.
/// A season returned by metadata refresh, classified against persisted seasons.
#[derive(Debug)]
pub struct RefreshedSeason {
pub season: Season,
pub episodes: Vec<Episode>,
pub is_new: bool,
}
/// Applies a series' tracking rule to newly revealed seasons.
///
/// Callers pass only episodes from a newly revealed season. Existing seasons
/// keep their leaf-level intent unchanged.
pub fn apply_new_season_auto_track(series: &Series, episodes: &mut [Episode]) {
if series.auto_track {
for episode in episodes {
/// Existing seasons keep their tracking rule and leaf-level intent unchanged.
/// Season zero is treated like every other newly revealed season.
pub fn apply_auto_track(series: &Series, seasons: &mut [RefreshedSeason]) {
if !series.auto_track {
return;
}
for refreshed in seasons.iter_mut().filter(|season| season.is_new) {
refreshed.season.tracked = true;
for episode in &mut refreshed.episodes {
episode.wanted = true;
}
}
@@ -16,9 +29,9 @@ pub fn apply_new_season_auto_track(series: &Series, episodes: &mut [Episode]) {
mod tests {
use std::time::SystemTime;
use super::apply_new_season_auto_track;
use super::{apply_auto_track, RefreshedSeason};
use crate::{
Episode, EpisodeId, Language, MediaState, RootId, SeasonId, Series, SeriesId,
Episode, EpisodeId, Language, MediaState, RootId, Season, SeasonId, Series, SeriesId,
TitleOverrides,
};
@@ -51,36 +64,67 @@ mod tests {
}
}
#[test]
fn auto_track_marks_new_season_episodes_wanted() {
let mut new_season = vec![episode(3, 1, false), episode(3, 2, false)];
apply_new_season_auto_track(&series(true), &mut new_season);
assert!(new_season.iter().all(|episode| episode.wanted));
fn refreshed_season(number: u16, is_new: bool, wanted: &[bool]) -> RefreshedSeason {
let id = i64::from(number);
RefreshedSeason {
season: Season {
id: SeasonId(id),
series_id: SeriesId(1),
number,
tracked: false,
},
episodes: wanted
.iter()
.enumerate()
.map(|(index, wanted)| {
episode(
id,
u16::try_from(index + 1).expect("episode number fits"),
*wanted,
)
})
.collect(),
is_new,
}
}
#[test]
fn untracked_series_keeps_leaf_intent_only() {
let manually_wanted_season = [
episode(2, 1, true),
episode(2, 2, true),
episode(2, 3, true),
fn auto_track_marks_only_new_seasons_and_their_episodes() {
let mut refresh = [
refreshed_season(2, false, &[false, false]),
refreshed_season(3, true, &[false, false]),
];
let mut new_season = vec![episode(3, 1, false), episode(3, 2, false)];
apply_new_season_auto_track(&series(false), &mut new_season);
apply_auto_track(&series(true), &mut refresh);
assert!(manually_wanted_season.iter().all(|episode| episode.wanted));
assert!(new_season.iter().all(|episode| !episode.wanted));
assert!(!refresh[0].season.tracked);
assert!(refresh[0].episodes.iter().all(|episode| !episode.wanted));
assert!(refresh[1].season.tracked);
assert!(refresh[1].episodes.iter().all(|episode| episode.wanted));
}
#[test]
fn auto_track_does_not_clear_explicit_leaf_intent() {
let mut new_season = vec![episode(3, 1, true), episode(3, 2, false)];
fn untracked_series_keeps_manual_leaf_intent_only() {
let mut refresh = [
refreshed_season(2, false, &[true, true, true]),
refreshed_season(3, true, &[false, false]),
];
apply_new_season_auto_track(&series(true), &mut new_season);
apply_auto_track(&series(false), &mut refresh);
assert!(new_season.iter().all(|episode| episode.wanted));
assert!(refresh[0].episodes.iter().all(|episode| episode.wanted));
assert!(!refresh[0].season.tracked);
assert!(refresh[1].episodes.iter().all(|episode| !episode.wanted));
assert!(!refresh[1].season.tracked);
}
#[test]
fn season_zero_follows_the_same_rule() {
let mut refresh = [refreshed_season(0, true, &[false])];
apply_auto_track(&series(true), &mut refresh);
assert!(refresh[0].season.tracked);
assert!(refresh[0].episodes[0].wanted);
}
}