refactor: move domain inline tests to separate files under tests/
Some checks failed
CI / Check / Test (push) Failing after 44s
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:
@@ -135,21 +135,5 @@ impl EventEnvelope {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::value_objects::UserId;
|
||||
|
||||
#[test]
|
||||
fn follow_accepted_matches() {
|
||||
let uid = UserId::from_uuid(uuid::Uuid::new_v4());
|
||||
let event = DomainEvent::FollowAccepted {
|
||||
local_user_id: uid.clone(),
|
||||
remote_actor_url: "https://remote.example/users/alice".to_string(),
|
||||
outbox_url: "https://remote.example/users/alice/outbox".to_string(),
|
||||
};
|
||||
let DomainEvent::FollowAccepted { outbox_url, .. } = event else {
|
||||
panic!("wrong variant");
|
||||
};
|
||||
assert_eq!(outbox_url, "https://remote.example/users/alice/outbox");
|
||||
}
|
||||
}
|
||||
#[path = "tests/events.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -92,5 +92,5 @@ pub enum ExportFormat {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests.rs"]
|
||||
#[path = "tests/mod.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -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;
|
||||
|
||||
91
crates/domain/src/models/tests/goal.rs
Normal file
91
crates/domain/src/models/tests/goal.rs
Normal 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());
|
||||
}
|
||||
55
crates/domain/src/models/tests/person.rs
Normal file
55
crates/domain/src/models/tests/person.rs
Normal 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);
|
||||
}
|
||||
136
crates/domain/src/models/tests/watch_event.rs
Normal file
136
crates/domain/src/models/tests/watch_event.rs
Normal 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"));
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,81 +53,5 @@ impl ReviewHistoryAnalyzer {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::models::{Movie, Review, ReviewHistory};
|
||||
use crate::value_objects::{MovieId, MovieTitle, Rating, ReleaseYear, UserId};
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
|
||||
fn make_movie() -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Test".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn dt(year: i32, month: u32, day: u32) -> NaiveDateTime {
|
||||
NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(year, month, day).unwrap(),
|
||||
NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn review_with_rating(movie_id: &MovieId, rating: u8, watched_at: NaiveDateTime) -> Review {
|
||||
let user_id = UserId::generate();
|
||||
Review::new(
|
||||
movie_id.clone(),
|
||||
user_id,
|
||||
Rating::new(rating).unwrap(),
|
||||
None,
|
||||
watched_at,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neutral_when_empty() {
|
||||
let movie = make_movie();
|
||||
let history = ReviewHistory::new(movie, vec![]);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Neutral);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neutral_when_single_review() {
|
||||
let movie = make_movie();
|
||||
let r = review_with_rating(movie.id(), 4, dt(2024, 1, 1));
|
||||
let history = ReviewHistory::new(movie, vec![r]);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Neutral);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn improved_when_latest_above_average() {
|
||||
let movie = make_movie();
|
||||
let viewings = vec![
|
||||
review_with_rating(movie.id(), 2, dt(2024, 1, 1)),
|
||||
review_with_rating(movie.id(), 3, dt(2024, 2, 1)),
|
||||
review_with_rating(movie.id(), 5, dt(2024, 3, 1)),
|
||||
];
|
||||
let history = ReviewHistory::new(movie, viewings);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Improved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declined_when_latest_below_average() {
|
||||
let movie = make_movie();
|
||||
let viewings = vec![
|
||||
review_with_rating(movie.id(), 5, dt(2024, 1, 1)),
|
||||
review_with_rating(movie.id(), 4, dt(2024, 2, 1)),
|
||||
review_with_rating(movie.id(), 2, dt(2024, 3, 1)),
|
||||
];
|
||||
let history = ReviewHistory::new(movie, viewings);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Declined);
|
||||
}
|
||||
}
|
||||
#[path = "tests/review_history.rs"]
|
||||
mod tests;
|
||||
|
||||
76
crates/domain/src/services/tests/review_history.rs
Normal file
76
crates/domain/src/services/tests/review_history.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
use super::*;
|
||||
use crate::models::{Movie, Review, ReviewHistory};
|
||||
use crate::value_objects::{MovieId, MovieTitle, Rating, ReleaseYear, UserId};
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
|
||||
fn make_movie() -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Test".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn dt(year: i32, month: u32, day: u32) -> NaiveDateTime {
|
||||
NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(year, month, day).unwrap(),
|
||||
NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn review_with_rating(movie_id: &MovieId, rating: u8, watched_at: NaiveDateTime) -> Review {
|
||||
let user_id = UserId::generate();
|
||||
Review::new(
|
||||
movie_id.clone(),
|
||||
user_id,
|
||||
Rating::new(rating).unwrap(),
|
||||
None,
|
||||
watched_at,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neutral_when_empty() {
|
||||
let movie = make_movie();
|
||||
let history = ReviewHistory::new(movie, vec![]);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Neutral);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neutral_when_single_review() {
|
||||
let movie = make_movie();
|
||||
let r = review_with_rating(movie.id(), 4, dt(2024, 1, 1));
|
||||
let history = ReviewHistory::new(movie, vec![r]);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Neutral);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn improved_when_latest_above_average() {
|
||||
let movie = make_movie();
|
||||
let viewings = vec![
|
||||
review_with_rating(movie.id(), 2, dt(2024, 1, 1)),
|
||||
review_with_rating(movie.id(), 3, dt(2024, 2, 1)),
|
||||
review_with_rating(movie.id(), 5, dt(2024, 3, 1)),
|
||||
];
|
||||
let history = ReviewHistory::new(movie, viewings);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Improved);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declined_when_latest_below_average() {
|
||||
let movie = make_movie();
|
||||
let viewings = vec![
|
||||
review_with_rating(movie.id(), 5, dt(2024, 1, 1)),
|
||||
review_with_rating(movie.id(), 4, dt(2024, 2, 1)),
|
||||
review_with_rating(movie.id(), 2, dt(2024, 3, 1)),
|
||||
];
|
||||
let history = ReviewHistory::new(movie, viewings);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Declined);
|
||||
}
|
||||
16
crates/domain/src/tests/events.rs
Normal file
16
crates/domain/src/tests/events.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use super::*;
|
||||
use crate::value_objects::UserId;
|
||||
|
||||
#[test]
|
||||
fn follow_accepted_matches() {
|
||||
let uid = UserId::from_uuid(uuid::Uuid::new_v4());
|
||||
let event = DomainEvent::FollowAccepted {
|
||||
local_user_id: uid.clone(),
|
||||
remote_actor_url: "https://remote.example/users/alice".to_string(),
|
||||
outbox_url: "https://remote.example/users/alice/outbox".to_string(),
|
||||
};
|
||||
let DomainEvent::FollowAccepted { outbox_url, .. } = event else {
|
||||
panic!("wrong variant");
|
||||
};
|
||||
assert_eq!(outbox_url, "https://remote.example/users/alice/outbox");
|
||||
}
|
||||
141
crates/domain/src/tests/value_objects.rs
Normal file
141
crates/domain/src/tests/value_objects.rs
Normal file
@@ -0,0 +1,141 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn movie_id_generate_unique() {
|
||||
let a = MovieId::generate();
|
||||
let b = MovieId::generate();
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rating_valid_range() {
|
||||
assert!(Rating::new(0).is_ok());
|
||||
assert!(Rating::new(5).is_ok());
|
||||
assert_eq!(Rating::new(3).unwrap().value(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rating_invalid() {
|
||||
assert!(Rating::new(6).is_err());
|
||||
assert!(Rating::new(255).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_title_valid() {
|
||||
let t = MovieTitle::new("Test".into());
|
||||
assert!(t.is_ok());
|
||||
assert_eq!(t.unwrap().value(), "Test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_title_empty_rejected() {
|
||||
assert!(MovieTitle::new("".into()).is_err());
|
||||
assert!(MovieTitle::new(" ".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_year_valid() {
|
||||
assert!(ReleaseYear::new(2024).is_ok());
|
||||
assert_eq!(ReleaseYear::new(1888).unwrap().value(), 1888);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_year_too_early() {
|
||||
assert!(ReleaseYear::new(1887).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_valid() {
|
||||
let e = Email::new("a@b.com".into());
|
||||
assert!(e.is_ok());
|
||||
assert_eq!(e.unwrap().value(), "a@b.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_invalid() {
|
||||
assert!(Email::new("invalid".into()).is_err());
|
||||
assert!(Email::new("".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_valid() {
|
||||
let u = Username::new("test".into());
|
||||
assert!(u.is_ok());
|
||||
assert_eq!(u.unwrap().value(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_lowercases() {
|
||||
assert_eq!(Username::new("Alice".into()).unwrap().value(), "alice");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_rejects_too_short() {
|
||||
assert!(Username::new("a".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_rejects_special_chars() {
|
||||
assert!(Username::new("no spaces".into()).is_err());
|
||||
assert!(Username::new("no@at".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_path_valid() {
|
||||
let p = PosterPath::new("path/to/poster".into());
|
||||
assert!(p.is_ok());
|
||||
assert_eq!(p.unwrap().value(), "path/to/poster");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_path_empty_rejected() {
|
||||
assert!(PosterPath::new("".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_valid() {
|
||||
let c = Comment::new("nice movie".into());
|
||||
assert!(c.is_ok());
|
||||
assert_eq!(c.unwrap().value(), "nice movie");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_empty_is_ok() {
|
||||
// empty comment allowed — only max-length checked
|
||||
assert!(Comment::new("".into()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_metadata_id_valid() {
|
||||
let e = ExternalMetadataId::new("tt1234567".into());
|
||||
assert!(e.is_ok());
|
||||
assert_eq!(e.unwrap().value(), "tt1234567");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_metadata_id_empty_rejected() {
|
||||
assert!(ExternalMetadataId::new("".into()).is_err());
|
||||
assert!(ExternalMetadataId::new(" ".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_hash_valid() {
|
||||
assert!(PasswordHash::new("hash".into()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_hash_empty_rejected() {
|
||||
assert!(PasswordHash::new("".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_url_valid() {
|
||||
let u = PosterUrl::new("https://img.com/poster.jpg".into());
|
||||
assert!(u.is_ok());
|
||||
assert_eq!(u.unwrap().value(), "https://img.com/poster.jpg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_url_empty_rejected() {
|
||||
assert!(PosterUrl::new("".into()).is_err());
|
||||
}
|
||||
@@ -253,146 +253,6 @@ impl PosterUrl {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[path = "tests/value_objects.rs"]
|
||||
mod tests;
|
||||
|
||||
#[test]
|
||||
fn movie_id_generate_unique() {
|
||||
let a = MovieId::generate();
|
||||
let b = MovieId::generate();
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rating_valid_range() {
|
||||
assert!(Rating::new(0).is_ok());
|
||||
assert!(Rating::new(5).is_ok());
|
||||
assert_eq!(Rating::new(3).unwrap().value(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rating_invalid() {
|
||||
assert!(Rating::new(6).is_err());
|
||||
assert!(Rating::new(255).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_title_valid() {
|
||||
let t = MovieTitle::new("Test".into());
|
||||
assert!(t.is_ok());
|
||||
assert_eq!(t.unwrap().value(), "Test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_title_empty_rejected() {
|
||||
assert!(MovieTitle::new("".into()).is_err());
|
||||
assert!(MovieTitle::new(" ".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_year_valid() {
|
||||
assert!(ReleaseYear::new(2024).is_ok());
|
||||
assert_eq!(ReleaseYear::new(1888).unwrap().value(), 1888);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_year_too_early() {
|
||||
assert!(ReleaseYear::new(1887).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_valid() {
|
||||
let e = Email::new("a@b.com".into());
|
||||
assert!(e.is_ok());
|
||||
assert_eq!(e.unwrap().value(), "a@b.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn email_invalid() {
|
||||
assert!(Email::new("invalid".into()).is_err());
|
||||
assert!(Email::new("".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_valid() {
|
||||
let u = Username::new("test".into());
|
||||
assert!(u.is_ok());
|
||||
assert_eq!(u.unwrap().value(), "test");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_lowercases() {
|
||||
assert_eq!(Username::new("Alice".into()).unwrap().value(), "alice");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_rejects_too_short() {
|
||||
assert!(Username::new("a".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn username_rejects_special_chars() {
|
||||
assert!(Username::new("no spaces".into()).is_err());
|
||||
assert!(Username::new("no@at".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_path_valid() {
|
||||
let p = PosterPath::new("path/to/poster".into());
|
||||
assert!(p.is_ok());
|
||||
assert_eq!(p.unwrap().value(), "path/to/poster");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_path_empty_rejected() {
|
||||
assert!(PosterPath::new("".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_valid() {
|
||||
let c = Comment::new("nice movie".into());
|
||||
assert!(c.is_ok());
|
||||
assert_eq!(c.unwrap().value(), "nice movie");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment_empty_is_ok() {
|
||||
// empty comment allowed — only max-length checked
|
||||
assert!(Comment::new("".into()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_metadata_id_valid() {
|
||||
let e = ExternalMetadataId::new("tt1234567".into());
|
||||
assert!(e.is_ok());
|
||||
assert_eq!(e.unwrap().value(), "tt1234567");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn external_metadata_id_empty_rejected() {
|
||||
assert!(ExternalMetadataId::new("".into()).is_err());
|
||||
assert!(ExternalMetadataId::new(" ".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_hash_valid() {
|
||||
assert!(PasswordHash::new("hash".into()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn password_hash_empty_rejected() {
|
||||
assert!(PasswordHash::new("".into()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_url_valid() {
|
||||
let u = PosterUrl::new("https://img.com/poster.jpg".into());
|
||||
assert!(u.is_ok());
|
||||
assert_eq!(u.unwrap().value(), "https://img.com/poster.jpg");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn poster_url_empty_rejected() {
|
||||
assert!(PosterUrl::new("".into()).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user