Reviewed-on: #26
This commit was merged in pull request #26.
This commit is contained in:
242
crates/domain/tests/models/constructors_test.rs
Normal file
242
crates/domain/tests/models/constructors_test.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use domain::models::*;
|
||||
use domain::testing::{test_movie_with_director, test_movie_with_year, test_review, test_user};
|
||||
use domain::value_objects::{Email, PasswordHash, UserId, Username};
|
||||
|
||||
#[test]
|
||||
fn update_profile_sets_fields() {
|
||||
let mut user = test_user("alice");
|
||||
user.update_profile(UserProfile {
|
||||
bio: Some("My bio".to_string()),
|
||||
avatar_path: Some("avatars/abc".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(user.bio(), Some("My bio"));
|
||||
assert_eq!(user.avatar_path(), Some("avatars/abc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_profile_clears_with_none() {
|
||||
let mut user = test_user("alice");
|
||||
user.update_profile(UserProfile {
|
||||
bio: Some("bio".to_string()),
|
||||
avatar_path: Some("path".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
user.update_profile(UserProfile::default());
|
||||
assert_eq!(user.bio(), None);
|
||||
assert_eq!(user.avatar_path(), None);
|
||||
}
|
||||
|
||||
// ── Movie ────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn movie_new_sets_fields() {
|
||||
let m = Movie::new(
|
||||
Some(domain::value_objects::ExternalMetadataId::new("tt1234567".into()).unwrap()),
|
||||
domain::value_objects::MovieTitle::new("Blade Runner".into()).unwrap(),
|
||||
domain::value_objects::ReleaseYear::new(1982).unwrap(),
|
||||
Some("Ridley Scott".into()),
|
||||
Some(domain::value_objects::PosterPath::new("/poster.jpg".into()).unwrap()),
|
||||
);
|
||||
assert_eq!(m.title().value(), "Blade Runner");
|
||||
assert_eq!(m.release_year().value(), 1982);
|
||||
assert_eq!(m.director(), Some("Ridley Scott"));
|
||||
assert!(m.poster_path().is_some());
|
||||
assert!(m.external_metadata_id().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_update_poster() {
|
||||
let mut m = test_movie_with_year("Blade Runner", 1982);
|
||||
let new_poster = domain::value_objects::PosterPath::new("/new.jpg".into()).unwrap();
|
||||
m.update_poster(new_poster);
|
||||
assert_eq!(m.poster_path().unwrap().value(), "/new.jpg");
|
||||
}
|
||||
|
||||
// ── Review ───────────────────────────────────────────────────────────────────
|
||||
|
||||
use domain::value_objects::{MovieId, Rating, ReviewId};
|
||||
|
||||
#[test]
|
||||
fn review_new_sets_local_source() {
|
||||
let r = test_review(&MovieId::generate(), &UserId::generate(), 4);
|
||||
assert_eq!(*r.source(), ReviewSource::Local);
|
||||
assert!(!r.is_remote());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn review_stars() {
|
||||
let r = test_review(&MovieId::generate(), &UserId::generate(), 4);
|
||||
assert_eq!(r.stars(), [true, true, true, true, false]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn review_from_persistence() {
|
||||
let id = ReviewId::generate();
|
||||
let mid = MovieId::generate();
|
||||
let uid = UserId::generate();
|
||||
let ts = chrono::Utc::now().naive_utc();
|
||||
let r = Review::from_persistence(PersistedReview {
|
||||
id: id.clone(),
|
||||
movie_id: mid.clone(),
|
||||
user_id: uid.clone(),
|
||||
rating: Rating::new(2).unwrap(),
|
||||
comment: None,
|
||||
watched_at: ts,
|
||||
created_at: ts,
|
||||
source: ReviewSource::Remote {
|
||||
actor_url: "https://example.com/actor".into(),
|
||||
},
|
||||
watch_medium: None,
|
||||
spoilers: true,
|
||||
});
|
||||
assert_eq!(*r.id(), id);
|
||||
assert!(r.is_remote());
|
||||
assert_eq!(r.comment(), None);
|
||||
assert!(r.spoilers());
|
||||
}
|
||||
|
||||
// ── User ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn user_new() {
|
||||
let u = User::new(
|
||||
Email::new("x@y.com".into()).unwrap(),
|
||||
Username::new("bob".into()).unwrap(),
|
||||
PasswordHash::new("hashed".into()).unwrap(),
|
||||
UserRole::Admin,
|
||||
);
|
||||
assert_eq!(u.email().value(), "x@y.com");
|
||||
assert_eq!(u.username().value(), "bob");
|
||||
assert_eq!(u.role().as_str(), "admin");
|
||||
assert_eq!(u.bio(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_update_password() {
|
||||
let mut u = test_user("alice");
|
||||
u.update_password(PasswordHash::new("new_hash".into()).unwrap());
|
||||
assert_eq!(u.password_hash().value(), "new_hash");
|
||||
}
|
||||
|
||||
// ── GoalType ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn goal_type_as_str() {
|
||||
assert_eq!(GoalType::Movies.as_str(), "movies");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn goal_type_from_str() {
|
||||
assert_eq!("movies".parse::<GoalType>().unwrap(), GoalType::Movies);
|
||||
assert!("invalid".parse::<GoalType>().is_err());
|
||||
}
|
||||
|
||||
// ── UserRole ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn user_role_as_str() {
|
||||
assert_eq!(UserRole::Standard.as_str(), "standard");
|
||||
assert_eq!(UserRole::Admin.as_str(), "admin");
|
||||
}
|
||||
|
||||
// ── ProfileField ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn profile_field_construction() {
|
||||
let f = ProfileField {
|
||||
name: "Website".into(),
|
||||
value: "https://example.com".into(),
|
||||
};
|
||||
assert_eq!(f.name, "Website");
|
||||
assert_eq!(f.value, "https://example.com");
|
||||
}
|
||||
|
||||
// ── MovieStats ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn movie_stats_construction() {
|
||||
let s = MovieStats {
|
||||
total_count: 100,
|
||||
avg_rating: Some(3.5),
|
||||
federated_count: 10,
|
||||
rating_histogram: [5, 10, 30, 40, 15],
|
||||
};
|
||||
assert_eq!(s.total_count, 100);
|
||||
assert_eq!(s.rating_histogram[4], 15);
|
||||
}
|
||||
|
||||
// ── FeedEntry ────────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn feed_entry_display_name_from_email() {
|
||||
let entry = DiaryEntry::new(
|
||||
test_movie_with_year("Test", 2000),
|
||||
test_review(&MovieId::generate(), &UserId::generate(), 4),
|
||||
);
|
||||
let fe = FeedEntry::new(entry, "alice@example.com".into());
|
||||
assert_eq!(fe.user_display_name(), "alice");
|
||||
assert_eq!(fe.user_email(), "alice@example.com");
|
||||
}
|
||||
|
||||
// ── MonthActivity ────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn month_activity_construction() {
|
||||
let ma = MonthActivity {
|
||||
year_month: "2024-06".into(),
|
||||
month_label: "June".into(),
|
||||
count: 5,
|
||||
entries: vec![],
|
||||
};
|
||||
assert_eq!(ma.year_month, "2024-06");
|
||||
assert_eq!(ma.count, 5);
|
||||
assert!(ma.entries.is_empty());
|
||||
}
|
||||
|
||||
// ── Movie::is_manual_match ───────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn movie_is_manual_match_same_title_year() {
|
||||
let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott");
|
||||
let title = domain::value_objects::MovieTitle::new("Blade Runner".into()).unwrap();
|
||||
let year = domain::value_objects::ReleaseYear::new(1982).unwrap();
|
||||
assert!(m.is_manual_match(&title, &year, Some("ridley scott")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_is_manual_match_different_director_fails() {
|
||||
let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott");
|
||||
let title = domain::value_objects::MovieTitle::new("Blade Runner".into()).unwrap();
|
||||
let year = domain::value_objects::ReleaseYear::new(1982).unwrap();
|
||||
assert!(!m.is_manual_match(&title, &year, Some("Denis Villeneuve")));
|
||||
}
|
||||
|
||||
// ── UserProfile field validation ─────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn profile_fields_validates_max_count() {
|
||||
let fields: Vec<ProfileField> = (0..5)
|
||||
.map(|i| ProfileField {
|
||||
name: format!("f{i}"),
|
||||
value: format!("v{i}"),
|
||||
})
|
||||
.collect();
|
||||
assert!(UserProfile::validate_custom_fields(&fields).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_fields_allows_four() {
|
||||
let fields: Vec<ProfileField> = (0..4)
|
||||
.map(|i| ProfileField {
|
||||
name: format!("f{i}"),
|
||||
value: format!("v{i}"),
|
||||
})
|
||||
.collect();
|
||||
assert!(UserProfile::validate_custom_fields(&fields).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_fields_allows_zero() {
|
||||
assert!(UserProfile::validate_custom_fields(&[]).is_ok());
|
||||
}
|
||||
86
crates/domain/tests/models/goal_test.rs
Normal file
86
crates/domain/tests/models/goal_test.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use domain::models::*;
|
||||
use domain::testing::test_goal;
|
||||
use domain::value_objects::{GoalId, UserId};
|
||||
|
||||
#[test]
|
||||
fn new_goal_valid() {
|
||||
let g = test_goal(&UserId::generate(), 2024, 52);
|
||||
assert_eq!(g.year(), 2024);
|
||||
assert_eq!(g.target_count(), 52);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_goal_rejects_year_before_2020() {
|
||||
assert!(Goal::new(UserId::generate(), 2019, 10, GoalType::Movies).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_goal_rejects_zero_target() {
|
||||
assert!(Goal::new(UserId::generate(), 2024, 0, GoalType::Movies).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_target_valid() {
|
||||
let mut g = test_goal(&UserId::generate(), 2024, 10);
|
||||
assert!(g.update_target(50).is_ok());
|
||||
assert_eq!(g.target_count(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_target_rejects_zero() {
|
||||
let mut g = test_goal(&UserId::generate(), 2024, 10);
|
||||
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 = test_goal(&UserId::generate(), 2024, 100);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 50,
|
||||
};
|
||||
assert!((wp.percentage() - 50.0).abs() < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn percentage_caps_at_100() {
|
||||
let g = test_goal(&UserId::generate(), 2024, 10);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 20,
|
||||
};
|
||||
assert!((wp.percentage() - 100.0).abs() < f64::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_complete() {
|
||||
let g = test_goal(&UserId::generate(), 2024, 10);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 10,
|
||||
};
|
||||
assert!(wp.is_complete());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_not_complete() {
|
||||
let g = test_goal(&UserId::generate(), 2024, 10);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 9,
|
||||
};
|
||||
assert!(!wp.is_complete());
|
||||
}
|
||||
55
crates/domain/tests/models/person_test.rs
Normal file
55
crates/domain/tests/models/person_test.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use domain::models::*;
|
||||
|
||||
#[test]
|
||||
fn person_new() {
|
||||
let ext = ExternalPersonId::new("tmdb:12345");
|
||||
let pid = PersonId::from_external(&ext);
|
||||
let p = Person::basic(
|
||||
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::basic(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);
|
||||
}
|
||||
106
crates/domain/tests/models/review_test.rs
Normal file
106
crates/domain/tests/models/review_test.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use domain::testing::{test_review, test_watched_at};
|
||||
use domain::value_objects::{Comment, MovieId, Rating, UserId, WatchMedium};
|
||||
|
||||
use domain::models::*;
|
||||
|
||||
fn review_with_medium(watch_medium: Option<WatchMedium>) -> Review {
|
||||
test_review(&MovieId::generate(), &UserId::generate(), 4).apply_edit(ReviewEdit {
|
||||
comment: Some(Some(Comment::new("great film".into()).unwrap())),
|
||||
watch_medium: Some(watch_medium),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn new_review_with_medium(watch_medium: Option<WatchMedium>) -> Review {
|
||||
Review::new(
|
||||
MovieId::generate(),
|
||||
UserId::generate(),
|
||||
Rating::new(4).unwrap(),
|
||||
Some(Comment::new("great film".into()).unwrap()),
|
||||
test_watched_at(),
|
||||
watch_medium,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_review_stores_watch_medium() {
|
||||
let review = new_review_with_medium(Some(WatchMedium::Cinema));
|
||||
assert_eq!(review.watch_medium(), Some(&WatchMedium::Cinema));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_review_without_medium() {
|
||||
let review = new_review_with_medium(None);
|
||||
assert_eq!(review.watch_medium(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_updates_rating_only() {
|
||||
let original = review_with_medium(Some(WatchMedium::Streaming));
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
rating: Some(Rating::new(2).unwrap()),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(edited.rating().value(), 2);
|
||||
assert_eq!(edited.comment().unwrap().value(), "great film");
|
||||
assert_eq!(edited.watch_medium(), Some(&WatchMedium::Streaming));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_clears_comment() {
|
||||
let original = review_with_medium(None);
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
comment: Some(None),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(edited.comment().is_none());
|
||||
assert_eq!(edited.rating().value(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_sets_watch_medium() {
|
||||
let original = review_with_medium(None);
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
watch_medium: Some(Some(WatchMedium::Cinema)),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(edited.watch_medium(), Some(&WatchMedium::Cinema));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_clears_watch_medium() {
|
||||
let original = review_with_medium(Some(WatchMedium::Cinema));
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
watch_medium: Some(None),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(edited.watch_medium(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_toggles_spoilers() {
|
||||
let original = review_with_medium(None);
|
||||
let flag_on = original.apply_edit(ReviewEdit {
|
||||
spoilers: Some(true),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(flag_on.spoilers());
|
||||
let flag_off = flag_on.apply_edit(ReviewEdit {
|
||||
spoilers: Some(false),
|
||||
..Default::default()
|
||||
});
|
||||
assert!(!flag_off.spoilers());
|
||||
let untouched = original.apply_edit(ReviewEdit::default());
|
||||
assert!(!untouched.spoilers());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_no_changes_preserves_all() {
|
||||
let original = review_with_medium(Some(WatchMedium::TV));
|
||||
let edited = original.apply_edit(ReviewEdit::default());
|
||||
assert_eq!(edited.rating().value(), 4);
|
||||
assert_eq!(edited.comment().unwrap().value(), "great film");
|
||||
assert_eq!(edited.watch_medium(), Some(&WatchMedium::TV));
|
||||
}
|
||||
71
crates/domain/tests/models/stats_test.rs
Normal file
71
crates/domain/tests/models/stats_test.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use domain::models::*;
|
||||
|
||||
fn appearance(person_id: u64, name: &str, billing: u32, movie: &str) -> ActorAppearance {
|
||||
ActorAppearance {
|
||||
tmdb_person_id: person_id,
|
||||
name: name.to_string(),
|
||||
profile_path: Some(format!("/profile_{person_id}.jpg")),
|
||||
billing_order: billing,
|
||||
movie_id: movie.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_appearances_returns_empty() {
|
||||
assert!(compute_top_actors(vec![]).is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lead_scores_higher_than_minor_with_same_movie_count() {
|
||||
let appearances = vec![
|
||||
appearance(1, "Lead Actor", 0, "movie_a"),
|
||||
appearance(1, "Lead Actor", 1, "movie_b"),
|
||||
appearance(2, "Minor Actor", 15, "movie_a"),
|
||||
appearance(2, "Minor Actor", 12, "movie_b"),
|
||||
];
|
||||
let result = compute_top_actors(appearances);
|
||||
assert_eq!(result.len(), 2);
|
||||
assert_eq!(result[0].name, "Lead Actor");
|
||||
assert_eq!(result[0].movie_count, 2);
|
||||
assert!(result[0].score > result[1].score);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn same_movie_counted_once_per_actor() {
|
||||
let appearances = vec![
|
||||
appearance(1, "Actor A", 0, "movie_a"),
|
||||
appearance(1, "Actor A", 0, "movie_a"),
|
||||
];
|
||||
let result = compute_top_actors(appearances);
|
||||
assert_eq!(result[0].movie_count, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn returns_at_most_five() {
|
||||
let appearances: Vec<ActorAppearance> = (0..10)
|
||||
.flat_map(|i| {
|
||||
(0..=i)
|
||||
.map(move |m| appearance(i as u64, &format!("Actor {i}"), 0, &format!("movie_{m}")))
|
||||
})
|
||||
.collect();
|
||||
let result = compute_top_actors(appearances);
|
||||
assert_eq!(result.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tier_weights_applied_correctly() {
|
||||
let appearances = vec![
|
||||
appearance(1, "Lead", 0, "m1"),
|
||||
appearance(2, "Support", 5, "m1"),
|
||||
appearance(2, "Support", 3, "m2"),
|
||||
appearance(3, "Minor", 10, "m1"),
|
||||
];
|
||||
let result = compute_top_actors(appearances);
|
||||
assert_eq!(result[0].name, "Support");
|
||||
assert!((result[0].score - 1.4).abs() < 0.001);
|
||||
assert_eq!(result[0].movie_count, 2);
|
||||
assert_eq!(result[1].name, "Lead");
|
||||
assert!((result[1].score - 1.0).abs() < 0.001);
|
||||
assert_eq!(result[2].name, "Minor");
|
||||
assert!((result[2].score - 0.2).abs() < 0.001);
|
||||
}
|
||||
138
crates/domain/tests/models/watch_event_test.rs
Normal file
138
crates/domain/tests/models/watch_event_test.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use domain::models::*;
|
||||
use domain::value_objects::{MovieId, UserId, WebhookTokenId};
|
||||
|
||||
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"));
|
||||
}
|
||||
Reference in New Issue
Block a user