refactor: move domain inline tests to separate files under tests/
Some checks failed
CI / Check / Test (push) Failing after 44s

Match the application crate convention: each source file references its
tests via #[cfg(test)] #[path = "tests/filename.rs"] mod tests; with
the test code in a sibling tests/ directory.

- events.rs       -> tests/events.rs
- value_objects.rs -> tests/value_objects.rs
- models/mod.rs   -> models/tests/mod.rs  (renamed from tests.rs)
- models/person.rs -> models/tests/person.rs
- models/goal.rs   -> models/tests/goal.rs
- models/watch_event.rs -> models/tests/watch_event.rs
- services/review_history.rs -> services/tests/review_history.rs
This commit is contained in:
2026-06-10 02:55:47 +02:00
parent b882569ee1
commit 956e51530e
14 changed files with 528 additions and 526 deletions

View File

@@ -111,96 +111,5 @@ impl GoalWithProgress {
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value_objects::UserId;
fn make_goal(year: u16, target: u32) -> Result<Goal, DomainError> {
Goal::new(UserId::generate(), year, target, GoalType::Movies)
}
#[test]
fn new_goal_valid() {
let g = make_goal(2024, 52);
assert!(g.is_ok());
let g = g.unwrap();
assert_eq!(g.year(), 2024);
assert_eq!(g.target_count(), 52);
}
#[test]
fn new_goal_rejects_year_before_2020() {
assert!(make_goal(2019, 10).is_err());
}
#[test]
fn new_goal_rejects_zero_target() {
assert!(make_goal(2024, 0).is_err());
}
#[test]
fn update_target_valid() {
let mut g = make_goal(2024, 10).unwrap();
assert!(g.update_target(50).is_ok());
assert_eq!(g.target_count(), 50);
}
#[test]
fn update_target_rejects_zero() {
let mut g = make_goal(2024, 10).unwrap();
assert!(g.update_target(0).is_err());
}
#[test]
fn from_persistence_preserves_fields() {
let id = GoalId::generate();
let uid = UserId::generate();
let ts = chrono::Utc::now().naive_utc();
let g = Goal::from_persistence(id.clone(), uid.clone(), 2025, 42, GoalType::Movies, ts);
assert_eq!(*g.id(), id);
assert_eq!(*g.user_id(), uid);
assert_eq!(g.year(), 2025);
assert_eq!(g.target_count(), 42);
assert_eq!(g.created_at(), &ts);
}
#[test]
fn percentage_calculation() {
let g = make_goal(2024, 100).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 50,
};
assert!((wp.percentage() - 50.0).abs() < f64::EPSILON);
}
#[test]
fn percentage_caps_at_100() {
let g = make_goal(2024, 10).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 20,
};
assert!((wp.percentage() - 100.0).abs() < f64::EPSILON);
}
#[test]
fn is_complete() {
let g = make_goal(2024, 10).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 10,
};
assert!(wp.is_complete());
}
#[test]
fn is_not_complete() {
let g = make_goal(2024, 10).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 9,
};
assert!(!wp.is_complete());
}
}
#[path = "tests/goal.rs"]
mod tests;

View File

@@ -92,5 +92,5 @@ pub enum ExportFormat {
}
#[cfg(test)]
#[path = "tests.rs"]
#[path = "tests/mod.rs"]
mod tests;

View File

@@ -113,60 +113,5 @@ pub struct CrewCredit {
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn person_new() {
let ext = ExternalPersonId::new("tmdb:12345");
let pid = PersonId::from_external(&ext);
let p = Person::new(
pid,
ext,
"Keanu Reeves".into(),
Some("Acting".into()),
Some("/profiles/keanu.jpg".into()),
);
assert_eq!(p.name(), "Keanu Reeves");
assert_eq!(p.known_for_department(), Some("Acting"));
assert_eq!(p.profile_path(), Some("/profiles/keanu.jpg"));
assert_eq!(p.external_id().value(), "tmdb:12345");
assert_eq!(p.external_id().tmdb_id(), Some(12345));
}
#[test]
fn person_id_from_external() {
let ext = ExternalPersonId::new("tmdb:99999");
let pid = PersonId::from_external(&ext);
// UUIDv5 is deterministic — just ensure it's a valid uuid
let _ = pid.value();
}
#[test]
fn person_id_deterministic() {
let ext = ExternalPersonId::new("tmdb:42");
let a = PersonId::from_external(&ext);
let b = PersonId::from_external(&ext);
assert_eq!(a, b);
}
#[test]
fn person_credits_default_empty() {
let ext = ExternalPersonId::new("tmdb:1");
let pid = PersonId::from_external(&ext);
let p = Person::new(pid, ext, "Test".into(), None, None);
let credits = PersonCredits {
person: p,
cast: vec![],
crew: vec![],
};
assert!(credits.cast.is_empty());
assert!(credits.crew.is_empty());
}
#[test]
fn external_person_id_tmdb_id_none_for_other() {
let ext = ExternalPersonId::new("imdb:nm0000206");
assert_eq!(ext.tmdb_id(), None);
}
}
#[path = "tests/person.rs"]
mod tests;

View File

@@ -0,0 +1,91 @@
use super::*;
use crate::value_objects::UserId;
fn make_goal(year: u16, target: u32) -> Result<Goal, DomainError> {
Goal::new(UserId::generate(), year, target, GoalType::Movies)
}
#[test]
fn new_goal_valid() {
let g = make_goal(2024, 52);
assert!(g.is_ok());
let g = g.unwrap();
assert_eq!(g.year(), 2024);
assert_eq!(g.target_count(), 52);
}
#[test]
fn new_goal_rejects_year_before_2020() {
assert!(make_goal(2019, 10).is_err());
}
#[test]
fn new_goal_rejects_zero_target() {
assert!(make_goal(2024, 0).is_err());
}
#[test]
fn update_target_valid() {
let mut g = make_goal(2024, 10).unwrap();
assert!(g.update_target(50).is_ok());
assert_eq!(g.target_count(), 50);
}
#[test]
fn update_target_rejects_zero() {
let mut g = make_goal(2024, 10).unwrap();
assert!(g.update_target(0).is_err());
}
#[test]
fn from_persistence_preserves_fields() {
let id = GoalId::generate();
let uid = UserId::generate();
let ts = chrono::Utc::now().naive_utc();
let g = Goal::from_persistence(id.clone(), uid.clone(), 2025, 42, GoalType::Movies, ts);
assert_eq!(*g.id(), id);
assert_eq!(*g.user_id(), uid);
assert_eq!(g.year(), 2025);
assert_eq!(g.target_count(), 42);
assert_eq!(g.created_at(), &ts);
}
#[test]
fn percentage_calculation() {
let g = make_goal(2024, 100).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 50,
};
assert!((wp.percentage() - 50.0).abs() < f64::EPSILON);
}
#[test]
fn percentage_caps_at_100() {
let g = make_goal(2024, 10).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 20,
};
assert!((wp.percentage() - 100.0).abs() < f64::EPSILON);
}
#[test]
fn is_complete() {
let g = make_goal(2024, 10).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 10,
};
assert!(wp.is_complete());
}
#[test]
fn is_not_complete() {
let g = make_goal(2024, 10).unwrap();
let wp = GoalWithProgress {
goal: g,
current_count: 9,
};
assert!(!wp.is_complete());
}

View File

@@ -0,0 +1,55 @@
use super::*;
#[test]
fn person_new() {
let ext = ExternalPersonId::new("tmdb:12345");
let pid = PersonId::from_external(&ext);
let p = Person::new(
pid,
ext,
"Keanu Reeves".into(),
Some("Acting".into()),
Some("/profiles/keanu.jpg".into()),
);
assert_eq!(p.name(), "Keanu Reeves");
assert_eq!(p.known_for_department(), Some("Acting"));
assert_eq!(p.profile_path(), Some("/profiles/keanu.jpg"));
assert_eq!(p.external_id().value(), "tmdb:12345");
assert_eq!(p.external_id().tmdb_id(), Some(12345));
}
#[test]
fn person_id_from_external() {
let ext = ExternalPersonId::new("tmdb:99999");
let pid = PersonId::from_external(&ext);
// UUIDv5 is deterministic — just ensure it's a valid uuid
let _ = pid.value();
}
#[test]
fn person_id_deterministic() {
let ext = ExternalPersonId::new("tmdb:42");
let a = PersonId::from_external(&ext);
let b = PersonId::from_external(&ext);
assert_eq!(a, b);
}
#[test]
fn person_credits_default_empty() {
let ext = ExternalPersonId::new("tmdb:1");
let pid = PersonId::from_external(&ext);
let p = Person::new(pid, ext, "Test".into(), None, None);
let credits = PersonCredits {
person: p,
cast: vec![],
crew: vec![],
};
assert!(credits.cast.is_empty());
assert!(credits.crew.is_empty());
}
#[test]
fn external_person_id_tmdb_id_none_for_other() {
let ext = ExternalPersonId::new("imdb:nm0000206");
assert_eq!(ext.tmdb_id(), None);
}

View File

@@ -0,0 +1,136 @@
use super::*;
fn ts() -> NaiveDateTime {
chrono::NaiveDate::from_ymd_opt(2024, 6, 1)
.unwrap()
.and_hms_opt(12, 0, 0)
.unwrap()
}
#[test]
fn watch_event_new_has_pending_status() {
let e = WatchEvent::new(
UserId::generate(),
"Dune".into(),
Some(2021),
None,
WatchEventSource::Jellyfin,
ts(),
None,
);
assert_eq!(*e.status(), WatchEventStatus::Pending);
}
#[test]
fn watch_event_getters() {
let uid = UserId::generate();
let mid = MovieId::generate();
let e = WatchEvent::new(
uid.clone(),
"Arrival".into(),
Some(2016),
Some("ext123".into()),
WatchEventSource::Plex,
ts(),
Some(mid.clone()),
);
assert_eq!(*e.user_id(), uid);
assert_eq!(e.title(), "Arrival");
assert_eq!(e.year(), Some(2016));
assert_eq!(e.external_metadata_id(), Some("ext123"));
assert_eq!(*e.source(), WatchEventSource::Plex);
assert_eq!(e.watched_at(), &ts());
assert_eq!(*e.movie_id().unwrap(), mid);
}
#[test]
fn webhook_token_new() {
let uid = UserId::generate();
let t = WebhookToken::new(
uid.clone(),
"hash123".into(),
WatchEventSource::Jellyfin,
Some("my server".into()),
);
assert_eq!(*t.user_id(), uid);
assert_eq!(t.token_hash(), "hash123");
assert_eq!(*t.provider(), WatchEventSource::Jellyfin);
assert_eq!(t.label(), Some("my server"));
assert!(t.last_used_at().is_none());
}
#[test]
fn webhook_token_from_persistence() {
let id = WebhookTokenId::generate();
let uid = UserId::generate();
let created = ts();
let used = chrono::NaiveDate::from_ymd_opt(2024, 7, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
let t = WebhookToken::from_persistence(
id.clone(),
uid.clone(),
"h".into(),
WatchEventSource::Plex,
None,
created,
Some(used),
);
assert_eq!(*t.id(), id);
assert_eq!(*t.user_id(), uid);
assert_eq!(t.token_hash(), "h");
assert_eq!(*t.provider(), WatchEventSource::Plex);
assert_eq!(t.label(), None);
assert_eq!(t.created_at(), &created);
assert_eq!(t.last_used_at(), Some(&used));
}
#[test]
fn watch_event_source_display() {
assert_eq!(WatchEventSource::Jellyfin.to_string(), "jellyfin");
assert_eq!(WatchEventSource::Plex.to_string(), "plex");
}
#[test]
fn watch_event_source_from_str() {
assert_eq!(
"jellyfin".parse::<WatchEventSource>().unwrap(),
WatchEventSource::Jellyfin
);
assert_eq!(
"plex".parse::<WatchEventSource>().unwrap(),
WatchEventSource::Plex
);
assert!("unknown".parse::<WatchEventSource>().is_err());
}
#[test]
fn watch_event_status_display() {
assert_eq!(WatchEventStatus::Pending.to_string(), "pending");
assert_eq!(WatchEventStatus::Confirmed.to_string(), "confirmed");
assert_eq!(WatchEventStatus::Dismissed.to_string(), "dismissed");
}
#[test]
fn watch_event_status_from_str() {
for s in ["pending", "confirmed", "dismissed"] {
let parsed: WatchEventStatus = s.parse().unwrap();
assert_eq!(parsed.to_string(), s);
}
assert!("bogus".parse::<WatchEventStatus>().is_err());
}
#[test]
fn parsed_playback_event_fields() {
let p = ParsedPlaybackEvent {
title: "Matrix".into(),
year: Some(1999),
tmdb_id: Some("603".into()),
imdb_id: Some("tt0133093".into()),
};
assert_eq!(p.title, "Matrix");
assert_eq!(p.year, Some(1999));
assert_eq!(p.tmdb_id.as_deref(), Some("603"));
assert_eq!(p.imdb_id.as_deref(), Some("tt0133093"));
}

View File

@@ -236,141 +236,6 @@ pub struct ParsedPlaybackEvent {
}
#[cfg(test)]
mod tests {
use super::*;
#[path = "tests/watch_event.rs"]
mod tests;
fn ts() -> NaiveDateTime {
chrono::NaiveDate::from_ymd_opt(2024, 6, 1)
.unwrap()
.and_hms_opt(12, 0, 0)
.unwrap()
}
#[test]
fn watch_event_new_has_pending_status() {
let e = WatchEvent::new(
UserId::generate(),
"Dune".into(),
Some(2021),
None,
WatchEventSource::Jellyfin,
ts(),
None,
);
assert_eq!(*e.status(), WatchEventStatus::Pending);
}
#[test]
fn watch_event_getters() {
let uid = UserId::generate();
let mid = MovieId::generate();
let e = WatchEvent::new(
uid.clone(),
"Arrival".into(),
Some(2016),
Some("ext123".into()),
WatchEventSource::Plex,
ts(),
Some(mid.clone()),
);
assert_eq!(*e.user_id(), uid);
assert_eq!(e.title(), "Arrival");
assert_eq!(e.year(), Some(2016));
assert_eq!(e.external_metadata_id(), Some("ext123"));
assert_eq!(*e.source(), WatchEventSource::Plex);
assert_eq!(e.watched_at(), &ts());
assert_eq!(*e.movie_id().unwrap(), mid);
}
#[test]
fn webhook_token_new() {
let uid = UserId::generate();
let t = WebhookToken::new(
uid.clone(),
"hash123".into(),
WatchEventSource::Jellyfin,
Some("my server".into()),
);
assert_eq!(*t.user_id(), uid);
assert_eq!(t.token_hash(), "hash123");
assert_eq!(*t.provider(), WatchEventSource::Jellyfin);
assert_eq!(t.label(), Some("my server"));
assert!(t.last_used_at().is_none());
}
#[test]
fn webhook_token_from_persistence() {
let id = WebhookTokenId::generate();
let uid = UserId::generate();
let created = ts();
let used = chrono::NaiveDate::from_ymd_opt(2024, 7, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap();
let t = WebhookToken::from_persistence(
id.clone(),
uid.clone(),
"h".into(),
WatchEventSource::Plex,
None,
created,
Some(used),
);
assert_eq!(*t.id(), id);
assert_eq!(*t.user_id(), uid);
assert_eq!(t.token_hash(), "h");
assert_eq!(*t.provider(), WatchEventSource::Plex);
assert_eq!(t.label(), None);
assert_eq!(t.created_at(), &created);
assert_eq!(t.last_used_at(), Some(&used));
}
#[test]
fn watch_event_source_display() {
assert_eq!(WatchEventSource::Jellyfin.to_string(), "jellyfin");
assert_eq!(WatchEventSource::Plex.to_string(), "plex");
}
#[test]
fn watch_event_source_from_str() {
assert_eq!(
"jellyfin".parse::<WatchEventSource>().unwrap(),
WatchEventSource::Jellyfin
);
assert_eq!(
"plex".parse::<WatchEventSource>().unwrap(),
WatchEventSource::Plex
);
assert!("unknown".parse::<WatchEventSource>().is_err());
}
#[test]
fn watch_event_status_display() {
assert_eq!(WatchEventStatus::Pending.to_string(), "pending");
assert_eq!(WatchEventStatus::Confirmed.to_string(), "confirmed");
assert_eq!(WatchEventStatus::Dismissed.to_string(), "dismissed");
}
#[test]
fn watch_event_status_from_str() {
for s in ["pending", "confirmed", "dismissed"] {
let parsed: WatchEventStatus = s.parse().unwrap();
assert_eq!(parsed.to_string(), s);
}
assert!("bogus".parse::<WatchEventStatus>().is_err());
}
#[test]
fn parsed_playback_event_fields() {
let p = ParsedPlaybackEvent {
title: "Matrix".into(),
year: Some(1999),
tmdb_id: Some("603".into()),
imdb_id: Some("tt0133093".into()),
};
assert_eq!(p.title, "Matrix");
assert_eq!(p.year, Some(1999));
assert_eq!(p.tmdb_id.as_deref(), Some("603"));
assert_eq!(p.imdb_id.as_deref(), Some("tt0133093"));
}
}