diff --git a/Cargo.lock b/Cargo.lock index 49d019f9..300197eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1663,6 +1663,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "domain", "email_address", "futures", "serde", diff --git a/crates/domain/Cargo.toml b/crates/domain/Cargo.toml index c16d2d0f..787c2e72 100644 --- a/crates/domain/Cargo.toml +++ b/crates/domain/Cargo.toml @@ -16,6 +16,7 @@ serde_json = { workspace = true } email_address = "0.2.9" [dev-dependencies] +domain = { path = ".", features = ["test-helpers"] } tokio = { workspace = true } [features] diff --git a/crates/domain/src/errors/mod.rs b/crates/domain/src/errors/mod.rs index 4e1dbf1d..b09e30c6 100644 --- a/crates/domain/src/errors/mod.rs +++ b/crates/domain/src/errors/mod.rs @@ -26,7 +26,3 @@ impl DomainError { matches!(self, DomainError::InfrastructureError(_)) } } - -#[cfg(test)] -#[path = "tests.rs"] -mod tests; diff --git a/crates/domain/src/events.rs b/crates/domain/src/events.rs index 073c6e3a..43bc964a 100644 --- a/crates/domain/src/events.rs +++ b/crates/domain/src/events.rs @@ -168,7 +168,3 @@ impl EventEnvelope { self.ack.nack().await } } - -#[cfg(test)] -#[path = "tests/events.rs"] -mod tests; diff --git a/crates/domain/src/models/goal.rs b/crates/domain/src/models/goal.rs index 534f0dc8..87c87fbc 100644 --- a/crates/domain/src/models/goal.rs +++ b/crates/domain/src/models/goal.rs @@ -109,7 +109,3 @@ impl GoalWithProgress { self.current_count >= self.goal.target_count } } - -#[cfg(test)] -#[path = "tests/goal.rs"] -mod tests; diff --git a/crates/domain/src/models/mod.rs b/crates/domain/src/models/mod.rs index eae18a79..eda2c9bb 100644 --- a/crates/domain/src/models/mod.rs +++ b/crates/domain/src/models/mod.rs @@ -103,7 +103,3 @@ pub enum ExportFormat { Csv, Json, } - -#[cfg(test)] -#[path = "tests/mod.rs"] -mod tests; diff --git a/crates/domain/src/models/person.rs b/crates/domain/src/models/person.rs index 34d589b9..8a8ddec2 100644 --- a/crates/domain/src/models/person.rs +++ b/crates/domain/src/models/person.rs @@ -181,7 +181,3 @@ pub struct CrewCredit { pub department: String, pub poster_path: Option, } - -#[cfg(test)] -#[path = "tests/person.rs"] -mod tests; diff --git a/crates/domain/src/models/review.rs b/crates/domain/src/models/review.rs index e78b4475..3dbd54f9 100644 --- a/crates/domain/src/models/review.rs +++ b/crates/domain/src/models/review.rs @@ -202,7 +202,3 @@ impl ReviewHistory { self.viewings.sort_by_key(|r| *r.watched_at()); } } - -#[cfg(test)] -#[path = "tests/review.rs"] -mod tests; diff --git a/crates/domain/src/models/stats.rs b/crates/domain/src/models/stats.rs index a6156b5c..adafdc90 100644 --- a/crates/domain/src/models/stats.rs +++ b/crates/domain/src/models/stats.rs @@ -138,7 +138,3 @@ pub struct MovieStats { pub federated_count: u64, pub rating_histogram: [u64; 5], // index 0 = 1★, index 4 = 5★ } - -#[cfg(test)] -#[path = "tests/stats.rs"] -mod tests; diff --git a/crates/domain/src/models/watch_event.rs b/crates/domain/src/models/watch_event.rs index 8c8b7c9e..de225eb5 100644 --- a/crates/domain/src/models/watch_event.rs +++ b/crates/domain/src/models/watch_event.rs @@ -234,7 +234,3 @@ pub struct ParsedPlaybackEvent { pub tmdb_id: Option, pub imdb_id: Option, } - -#[cfg(test)] -#[path = "tests/watch_event.rs"] -mod tests; diff --git a/crates/domain/src/ports/federation.rs b/crates/domain/src/ports/federation.rs index 9c944d8a..c71ff2ee 100644 --- a/crates/domain/src/ports/federation.rs +++ b/crates/domain/src/ports/federation.rs @@ -49,7 +49,3 @@ pub trait ApBackfillPort: Send + Sync { follower_inbox_url: String, ) -> Result<(), DomainError>; } - -#[cfg(test)] -#[path = "tests/federation.rs"] -mod tests; diff --git a/crates/domain/src/ports/social.rs b/crates/domain/src/ports/social.rs index 6680e9f6..067040aa 100644 --- a/crates/domain/src/ports/social.rs +++ b/crates/domain/src/ports/social.rs @@ -150,164 +150,3 @@ impl Result<(), DomainError> { - Ok(()) - } - async fn unfollow( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - async fn accept_follow( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - async fn reject_follow( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - async fn remove_follower( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - async fn block( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - async fn unblock( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - } - - #[async_trait] - impl FollowGraphQuery for Stub { - async fn get_following( - &self, - _: &crate::value_objects::UserId, - ) -> Result, DomainError> { - Ok(vec![]) - } - async fn get_followers( - &self, - _: &crate::value_objects::UserId, - ) -> Result, DomainError> { - Ok(vec![]) - } - async fn get_pending_followers( - &self, - _: &crate::value_objects::UserId, - ) -> Result, DomainError> { - Ok(vec![]) - } - async fn get_pending_following( - &self, - _: &crate::value_objects::UserId, - ) -> Result, DomainError> { - Ok(vec![]) - } - async fn count_following( - &self, - _: &crate::value_objects::UserId, - ) -> Result { - Ok(0) - } - async fn count_followers( - &self, - _: &crate::value_objects::UserId, - ) -> Result { - Ok(0) - } - async fn count_pending_followers( - &self, - _: &crate::value_objects::UserId, - ) -> Result { - Ok(0) - } - async fn get_relation( - &self, - _: &crate::value_objects::UserId, - _: &crate::value_objects::SocialIdentity, - ) -> Result { - Err(DomainError::NotFound("stub".into())) - } - } - - #[async_trait] - impl BlockQuery for Stub { - async fn get_blocked( - &self, - _: &crate::value_objects::UserId, - ) -> Result, DomainError> { - Ok(vec![]) - } - } - - #[async_trait] - impl FollowTargetResolver for Stub { - async fn resolve_target( - &self, - target: &crate::value_objects::FollowTarget, - ) -> Result { - match target { - crate::value_objects::FollowTarget::Identity(id) => Ok(id.clone()), - crate::value_objects::FollowTarget::Handle(handle) => Ok(SocialIdentity::Remote { - actor_url: handle.clone(), - }), - } - } - } - - #[async_trait] - impl ResolvedFollow for Stub { - async fn follow_resolved( - &self, - _: &crate::value_objects::UserId, - _: &SocialIdentity, - ) -> Result<(), DomainError> { - Ok(()) - } - } - - #[test] - fn blanket_impl_yields_a_trait_object() { - let local: Arc = Arc::new(Stub); - // Reachable through each supertrait without a separate Arc. - let _: &dyn SocialCommand = local.as_ref(); - let _: &dyn FollowGraphQuery = local.as_ref(); - let _: &dyn BlockQuery = local.as_ref(); - let _: &dyn FollowTargetResolver = local.as_ref(); - let _: &dyn ResolvedFollow = local.as_ref(); - } -} diff --git a/crates/domain/src/services/insights_analyzer.rs b/crates/domain/src/services/insights_analyzer.rs index 706e1ecd..73228f6f 100644 --- a/crates/domain/src/services/insights_analyzer.rs +++ b/crates/domain/src/services/insights_analyzer.rs @@ -342,7 +342,3 @@ fn label_of(insight: &Insight) -> &str { fn round2(v: f64) -> f64 { (v * 100.0).round() / 100.0 } - -#[cfg(test)] -#[path = "tests/insights_analyzer.rs"] -mod tests; diff --git a/crates/domain/src/services/review_history.rs b/crates/domain/src/services/review_history.rs index 38f2ba0b..e4efc31a 100644 --- a/crates/domain/src/services/review_history.rs +++ b/crates/domain/src/services/review_history.rs @@ -51,7 +51,3 @@ impl ReviewHistoryAnalyzer { } } } - -#[cfg(test)] -#[path = "tests/review_history.rs"] -mod tests; diff --git a/crates/domain/src/services/wrapup_analyzer.rs b/crates/domain/src/services/wrapup_analyzer.rs index 8d5c56c9..da3bb94d 100644 --- a/crates/domain/src/services/wrapup_analyzer.rs +++ b/crates/domain/src/services/wrapup_analyzer.rs @@ -464,7 +464,3 @@ fn compute_global_stats( (most_active_user, most_watched, total_users_active) } - -#[cfg(test)] -#[path = "tests/wrapup_analyzer.rs"] -mod tests; diff --git a/crates/domain/src/testing/fakes/mod.rs b/crates/domain/src/testing/fakes/mod.rs index 00f1fe69..3b0750fe 100644 --- a/crates/domain/src/testing/fakes/mod.rs +++ b/crates/domain/src/testing/fakes/mod.rs @@ -13,8 +13,6 @@ mod store_user; mod store_watch_event; mod store_watchlist; mod stubs; -#[cfg(test)] -mod tests; mod wrapup; pub use insights::*; diff --git a/crates/domain/src/value_objects/mod.rs b/crates/domain/src/value_objects/mod.rs index 1d41b86e..f0ec554e 100644 --- a/crates/domain/src/value_objects/mod.rs +++ b/crates/domain/src/value_objects/mod.rs @@ -11,7 +11,3 @@ pub use movie::*; pub use review::*; pub use social::*; pub use user::*; - -#[cfg(test)] -#[path = "../tests/value_objects.rs"] -mod tests; diff --git a/crates/domain/tests/errors.rs b/crates/domain/tests/errors.rs new file mode 100644 index 00000000..e8a917ab --- /dev/null +++ b/crates/domain/tests/errors.rs @@ -0,0 +1,2 @@ +#[path = "errors/errors_test.rs"] +mod errors_test; diff --git a/crates/domain/src/errors/tests.rs b/crates/domain/tests/errors/errors_test.rs similarity index 95% rename from crates/domain/src/errors/tests.rs rename to crates/domain/tests/errors/errors_test.rs index 4dc2e662..ebc69539 100644 --- a/crates/domain/src/errors/tests.rs +++ b/crates/domain/tests/errors/errors_test.rs @@ -1,4 +1,4 @@ -use super::*; +use domain::errors::DomainError; #[test] fn infrastructure_error_is_transient() { diff --git a/crates/domain/tests/events.rs b/crates/domain/tests/events.rs new file mode 100644 index 00000000..37b15d41 --- /dev/null +++ b/crates/domain/tests/events.rs @@ -0,0 +1,2 @@ +#[path = "events/events_test.rs"] +mod events_test; diff --git a/crates/domain/src/tests/events.rs b/crates/domain/tests/events/events_test.rs similarity index 93% rename from crates/domain/src/tests/events.rs rename to crates/domain/tests/events/events_test.rs index 57b618e3..bde7014d 100644 --- a/crates/domain/src/tests/events.rs +++ b/crates/domain/tests/events/events_test.rs @@ -1,5 +1,5 @@ -use super::*; -use crate::value_objects::{FollowTarget, SocialIdentity, UserId}; +use domain::events::DomainEvent; +use domain::value_objects::{FollowTarget, SocialIdentity, UserId}; #[test] fn follow_accepted_matches() { diff --git a/crates/domain/tests/models.rs b/crates/domain/tests/models.rs new file mode 100644 index 00000000..2cba8662 --- /dev/null +++ b/crates/domain/tests/models.rs @@ -0,0 +1,12 @@ +#[path = "models/constructors_test.rs"] +mod constructors_test; +#[path = "models/goal_test.rs"] +mod goal_test; +#[path = "models/person_test.rs"] +mod person_test; +#[path = "models/review_test.rs"] +mod review_test; +#[path = "models/stats_test.rs"] +mod stats_test; +#[path = "models/watch_event_test.rs"] +mod watch_event_test; diff --git a/crates/domain/src/models/tests/mod.rs b/crates/domain/tests/models/constructors_test.rs similarity index 89% rename from crates/domain/src/models/tests/mod.rs rename to crates/domain/tests/models/constructors_test.rs index f8eb4b7e..becb04bb 100644 --- a/crates/domain/src/models/tests/mod.rs +++ b/crates/domain/tests/models/constructors_test.rs @@ -1,6 +1,6 @@ -use super::*; -use crate::testing::{test_movie_with_director, test_movie_with_year, test_review, test_user}; -use crate::value_objects::{Email, PasswordHash, UserId, Username}; +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() { @@ -32,11 +32,11 @@ fn update_profile_clears_with_none() { #[test] fn movie_new_sets_fields() { let m = Movie::new( - Some(crate::value_objects::ExternalMetadataId::new("tt1234567".into()).unwrap()), - crate::value_objects::MovieTitle::new("Blade Runner".into()).unwrap(), - crate::value_objects::ReleaseYear::new(1982).unwrap(), + 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(crate::value_objects::PosterPath::new("/poster.jpg".into()).unwrap()), + Some(domain::value_objects::PosterPath::new("/poster.jpg".into()).unwrap()), ); assert_eq!(m.title().value(), "Blade Runner"); assert_eq!(m.release_year().value(), 1982); @@ -48,14 +48,14 @@ fn movie_new_sets_fields() { #[test] fn movie_update_poster() { let mut m = test_movie_with_year("Blade Runner", 1982); - let new_poster = crate::value_objects::PosterPath::new("/new.jpg".into()).unwrap(); + 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 crate::value_objects::{MovieId, Rating, ReviewId}; +use domain::value_objects::{MovieId, Rating, ReviewId}; #[test] fn review_new_sets_local_source() { @@ -199,16 +199,16 @@ fn month_activity_construction() { #[test] fn movie_is_manual_match_same_title_year() { let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott"); - let title = crate::value_objects::MovieTitle::new("Blade Runner".into()).unwrap(); - let year = crate::value_objects::ReleaseYear::new(1982).unwrap(); + 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 = crate::value_objects::MovieTitle::new("Blade Runner".into()).unwrap(); - let year = crate::value_objects::ReleaseYear::new(1982).unwrap(); + 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"))); } diff --git a/crates/domain/src/models/tests/goal.rs b/crates/domain/tests/models/goal_test.rs similarity index 95% rename from crates/domain/src/models/tests/goal.rs rename to crates/domain/tests/models/goal_test.rs index 41c2c2b9..7b7afd86 100644 --- a/crates/domain/src/models/tests/goal.rs +++ b/crates/domain/tests/models/goal_test.rs @@ -1,6 +1,6 @@ -use super::*; -use crate::testing::test_goal; -use crate::value_objects::UserId; +use domain::models::*; +use domain::testing::test_goal; +use domain::value_objects::{GoalId, UserId}; #[test] fn new_goal_valid() { diff --git a/crates/domain/src/models/tests/person.rs b/crates/domain/tests/models/person_test.rs similarity index 98% rename from crates/domain/src/models/tests/person.rs rename to crates/domain/tests/models/person_test.rs index 2e11467e..de485e79 100644 --- a/crates/domain/src/models/tests/person.rs +++ b/crates/domain/tests/models/person_test.rs @@ -1,4 +1,4 @@ -use super::*; +use domain::models::*; #[test] fn person_new() { diff --git a/crates/domain/src/models/tests/review.rs b/crates/domain/tests/models/review_test.rs similarity index 95% rename from crates/domain/src/models/tests/review.rs rename to crates/domain/tests/models/review_test.rs index 13eacffa..4d99db1d 100644 --- a/crates/domain/src/models/tests/review.rs +++ b/crates/domain/tests/models/review_test.rs @@ -1,7 +1,7 @@ -use crate::testing::{test_review, test_watched_at}; -use crate::value_objects::{Comment, MovieId, Rating, UserId, WatchMedium}; +use domain::testing::{test_review, test_watched_at}; +use domain::value_objects::{Comment, MovieId, Rating, UserId, WatchMedium}; -use super::*; +use domain::models::*; fn review_with_medium(watch_medium: Option) -> Review { test_review(&MovieId::generate(), &UserId::generate(), 4).apply_edit(ReviewEdit { diff --git a/crates/domain/src/models/tests/stats.rs b/crates/domain/tests/models/stats_test.rs similarity index 98% rename from crates/domain/src/models/tests/stats.rs rename to crates/domain/tests/models/stats_test.rs index 345eb3a5..dd9af144 100644 --- a/crates/domain/src/models/tests/stats.rs +++ b/crates/domain/tests/models/stats_test.rs @@ -1,4 +1,4 @@ -use super::*; +use domain::models::*; fn appearance(person_id: u64, name: &str, billing: u32, movie: &str) -> ActorAppearance { ActorAppearance { diff --git a/crates/domain/src/models/tests/watch_event.rs b/crates/domain/tests/models/watch_event_test.rs similarity index 97% rename from crates/domain/src/models/tests/watch_event.rs rename to crates/domain/tests/models/watch_event_test.rs index de495a81..d94ebc84 100644 --- a/crates/domain/src/models/tests/watch_event.rs +++ b/crates/domain/tests/models/watch_event_test.rs @@ -1,4 +1,6 @@ -use super::*; +use chrono::NaiveDateTime; +use domain::models::*; +use domain::value_objects::{MovieId, UserId, WebhookTokenId}; fn ts() -> NaiveDateTime { chrono::NaiveDate::from_ymd_opt(2024, 6, 1) diff --git a/crates/domain/tests/ports.rs b/crates/domain/tests/ports.rs new file mode 100644 index 00000000..eafd8bf0 --- /dev/null +++ b/crates/domain/tests/ports.rs @@ -0,0 +1,4 @@ +#[path = "ports/federation_test.rs"] +mod federation_test; +#[path = "ports/social_test.rs"] +mod social_test; diff --git a/crates/domain/src/ports/tests/federation.rs b/crates/domain/tests/ports/federation_test.rs similarity index 87% rename from crates/domain/src/ports/tests/federation.rs rename to crates/domain/tests/ports/federation_test.rs index 052cf14b..62728bb1 100644 --- a/crates/domain/src/ports/tests/federation.rs +++ b/crates/domain/tests/ports/federation_test.rs @@ -1,5 +1,5 @@ -use super::*; -use crate::ports::noop::{NoopApBackfill, NoopApDocument, NoopInstanceBlocklist}; +use domain::ports::noop::{NoopApBackfill, NoopApDocument, NoopInstanceBlocklist}; +use domain::ports::{ApBackfillPort, ApDocumentPort, InstanceBlocklistPort}; #[tokio::test] async fn noop_document_returns_empty_strings() { diff --git a/crates/domain/tests/ports/social_test.rs b/crates/domain/tests/ports/social_test.rs new file mode 100644 index 00000000..4a023a8b --- /dev/null +++ b/crates/domain/tests/ports/social_test.rs @@ -0,0 +1,162 @@ +use async_trait::async_trait; +use domain::errors::DomainError; +use domain::ports::{ + BlockQuery, FollowGraphQuery, FollowTargetResolver, LocalSocial, ResolvedFollow, SocialCommand, +}; +use domain::value_objects::SocialIdentity; +use std::sync::Arc; + +struct Stub; + +#[async_trait] +impl SocialCommand for Stub { + async fn follow( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::FollowTarget, + ) -> Result<(), DomainError> { + Ok(()) + } + async fn unfollow( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } + async fn accept_follow( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } + async fn reject_follow( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } + async fn remove_follower( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } + async fn block( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } + async fn unblock( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } +} + +#[async_trait] +impl FollowGraphQuery for Stub { + async fn get_following( + &self, + _: &domain::value_objects::UserId, + ) -> Result, DomainError> { + Ok(vec![]) + } + async fn get_followers( + &self, + _: &domain::value_objects::UserId, + ) -> Result, DomainError> { + Ok(vec![]) + } + async fn get_pending_followers( + &self, + _: &domain::value_objects::UserId, + ) -> Result, DomainError> { + Ok(vec![]) + } + async fn get_pending_following( + &self, + _: &domain::value_objects::UserId, + ) -> Result, DomainError> { + Ok(vec![]) + } + async fn count_following( + &self, + _: &domain::value_objects::UserId, + ) -> Result { + Ok(0) + } + async fn count_followers( + &self, + _: &domain::value_objects::UserId, + ) -> Result { + Ok(0) + } + async fn count_pending_followers( + &self, + _: &domain::value_objects::UserId, + ) -> Result { + Ok(0) + } + async fn get_relation( + &self, + _: &domain::value_objects::UserId, + _: &domain::value_objects::SocialIdentity, + ) -> Result { + Err(DomainError::NotFound("stub".into())) + } +} + +#[async_trait] +impl BlockQuery for Stub { + async fn get_blocked( + &self, + _: &domain::value_objects::UserId, + ) -> Result, DomainError> { + Ok(vec![]) + } +} + +#[async_trait] +impl FollowTargetResolver for Stub { + async fn resolve_target( + &self, + target: &domain::value_objects::FollowTarget, + ) -> Result { + match target { + domain::value_objects::FollowTarget::Identity(id) => Ok(id.clone()), + domain::value_objects::FollowTarget::Handle(handle) => Ok(SocialIdentity::Remote { + actor_url: handle.clone(), + }), + } + } +} + +#[async_trait] +impl ResolvedFollow for Stub { + async fn follow_resolved( + &self, + _: &domain::value_objects::UserId, + _: &SocialIdentity, + ) -> Result<(), DomainError> { + Ok(()) + } +} + +#[test] +fn blanket_impl_yields_a_trait_object() { + let local: Arc = Arc::new(Stub); + // Reachable through each supertrait without a separate Arc. + let _: &dyn SocialCommand = local.as_ref(); + let _: &dyn FollowGraphQuery = local.as_ref(); + let _: &dyn BlockQuery = local.as_ref(); + let _: &dyn FollowTargetResolver = local.as_ref(); + let _: &dyn ResolvedFollow = local.as_ref(); +} diff --git a/crates/domain/tests/services.rs b/crates/domain/tests/services.rs new file mode 100644 index 00000000..e43f552b --- /dev/null +++ b/crates/domain/tests/services.rs @@ -0,0 +1,6 @@ +#[path = "services/insights_analyzer_test.rs"] +mod insights_analyzer_test; +#[path = "services/review_history_test.rs"] +mod review_history_test; +#[path = "services/wrapup_analyzer_test.rs"] +mod wrapup_analyzer_test; diff --git a/crates/domain/src/services/tests/insights_analyzer.rs b/crates/domain/tests/services/insights_analyzer_test.rs similarity index 99% rename from crates/domain/src/services/tests/insights_analyzer.rs rename to crates/domain/tests/services/insights_analyzer_test.rs index 27597bab..a9b5c242 100644 --- a/crates/domain/src/services/tests/insights_analyzer.rs +++ b/crates/domain/tests/services/insights_analyzer_test.rs @@ -1,9 +1,9 @@ use chrono::{Datelike, FixedOffset, NaiveDate, Utc, Weekday}; use uuid::Uuid; -use crate::models::{Insight, WatchRow}; +use domain::models::{Insight, WatchRow}; -use super::super::insights_analyzer::build_insights; +use domain::services::insights_analyzer::build_insights; fn watch(genres: &[&str], rating: u8, date: NaiveDate, hour: u32) -> WatchRow { WatchRow { diff --git a/crates/domain/src/services/tests/review_history.rs b/crates/domain/tests/services/review_history_test.rs similarity index 90% rename from crates/domain/src/services/tests/review_history.rs rename to crates/domain/tests/services/review_history_test.rs index 85b4d122..312e1a86 100644 --- a/crates/domain/src/services/tests/review_history.rs +++ b/crates/domain/tests/services/review_history_test.rs @@ -1,8 +1,8 @@ -use super::*; -use crate::models::{Review, ReviewHistory}; -use crate::testing::test_movie_with_year; -use crate::value_objects::{MovieId, Rating, UserId}; use chrono::{NaiveDate, NaiveDateTime, NaiveTime}; +use domain::models::{Review, ReviewHistory}; +use domain::services::review_history::{ReviewHistoryAnalyzer, Trend}; +use domain::testing::test_movie_with_year; +use domain::value_objects::{MovieId, Rating, UserId}; fn dt(year: i32, month: u32, day: u32) -> NaiveDateTime { NaiveDateTime::new( diff --git a/crates/domain/src/services/tests/wrapup_analyzer.rs b/crates/domain/tests/services/wrapup_analyzer_test.rs similarity index 95% rename from crates/domain/src/services/tests/wrapup_analyzer.rs rename to crates/domain/tests/services/wrapup_analyzer_test.rs index b3d985c0..0c020d39 100644 --- a/crates/domain/src/services/tests/wrapup_analyzer.rs +++ b/crates/domain/tests/services/wrapup_analyzer_test.rs @@ -1,10 +1,10 @@ use chrono::NaiveDate; use uuid::Uuid; -use crate::models::WrapUpMovieRow; -use crate::models::wrapup::{DateRange, WrapUpScope}; +use domain::models::WrapUpMovieRow; +use domain::models::wrapup::{DateRange, WrapUpScope}; -use super::super::wrapup_analyzer::build_report; +use domain::services::wrapup_analyzer::build_report; fn row(title: &str, rating: u8, ym: &str) -> WrapUpMovieRow { WrapUpMovieRow { diff --git a/crates/domain/tests/testing.rs b/crates/domain/tests/testing.rs new file mode 100644 index 00000000..e9d0282a --- /dev/null +++ b/crates/domain/tests/testing.rs @@ -0,0 +1,2 @@ +#[path = "testing/in_memory_store_test.rs"] +mod in_memory_store_test; diff --git a/crates/domain/src/testing/fakes/tests.rs b/crates/domain/tests/testing/in_memory_store_test.rs similarity index 89% rename from crates/domain/src/testing/fakes/tests.rs rename to crates/domain/tests/testing/in_memory_store_test.rs index 34305ea3..ec8bb068 100644 --- a/crates/domain/src/testing/fakes/tests.rs +++ b/crates/domain/tests/testing/in_memory_store_test.rs @@ -3,21 +3,21 @@ use std::sync::Arc; use chrono::Utc; use uuid::Uuid; -use super::{InMemoryStore, MOVIE_CASCADE_TABLES, USER_CASCADE_TABLES}; -use crate::errors::DomainError; -use crate::models::{ +use domain::errors::DomainError; +use domain::models::{ CastMember, CrewMember, Genre, Keyword, Movie, MovieProfile, ProfileField, RefreshSession, UserSettings, WatchEvent, WatchEventSource, WatchlistEntry, WebhookToken, }; -use crate::ports::{ +use domain::ports::{ GoalCommand, MovieCommand, MovieProfileRepository, RefreshSessionRepository, ReviewRepository, SocialCommand, UserProfileFieldsRepository, UserRepository, UserSettingsRepository, WatchEventCommand, WatchEventQuery, WatchlistRepository, WebhookTokenRepository, }; -use crate::testing::{ +use domain::testing::{InMemoryStore, MOVIE_CASCADE_TABLES, USER_CASCADE_TABLES}; +use domain::testing::{ test_goal, test_movie, test_movie_with_external_id, test_review, test_user, test_watched_at, }; -use crate::value_objects::{ExternalMetadataId, FollowTarget, MovieId, SocialIdentity, UserId}; +use domain::value_objects::{ExternalMetadataId, FollowTarget, MovieId, SocialIdentity, UserId}; const REMOTE_ACTOR: &str = "https://remote.example/users/ann"; @@ -210,7 +210,10 @@ async fn deleting_a_user_leaves_other_users_rows_alone() { assert!(store.find_by_id(bystander.id()).await.unwrap().is_some()); } -async fn seed_movie_with_dependents(store: &Arc, with_review: bool) -> Movie { +async fn seed_movie_with_dependents( + store: &Arc, + with_review: bool, +) -> (Movie, UserId) { let user = test_user("alice"); UserRepository::save(&**store, &user).await.unwrap(); let movie = test_movie("Heat"); @@ -240,13 +243,13 @@ async fn seed_movie_with_dependents(store: &Arc, with_review: boo .await .unwrap(); } - movie + (movie, user.id().clone()) } #[tokio::test] async fn deleting_a_movie_removes_watchlist_entries_and_enrichment_profile() { let store = InMemoryStore::new(); - let movie = seed_movie_with_dependents(&store, false).await; + let (movie, _) = seed_movie_with_dependents(&store, false).await; for table in MOVIE_CASCADE_TABLES { assert!(store.rows_in(table) > 0, "{table} should be seeded"); } @@ -262,8 +265,7 @@ async fn deleting_a_movie_removes_watchlist_entries_and_enrichment_profile() { #[tokio::test] async fn deleting_a_movie_detaches_watch_events_instead_of_removing_them() { let store = InMemoryStore::new(); - let movie = seed_movie_with_dependents(&store, false).await; - let user_id = store.watch_events.read().unwrap()[0].user_id().clone(); + let (movie, user_id) = seed_movie_with_dependents(&store, false).await; store.delete_movie(movie.id()).await.unwrap(); @@ -275,7 +277,7 @@ async fn deleting_a_movie_detaches_watch_events_instead_of_removing_them() { #[tokio::test] async fn deleting_a_movie_that_has_reviews_is_refused_because_reviews_do_not_cascade() { let store = InMemoryStore::new(); - let movie = seed_movie_with_dependents(&store, true).await; + let (movie, _) = seed_movie_with_dependents(&store, true).await; let result = store.delete_movie(movie.id()).await; @@ -291,11 +293,11 @@ async fn saving_a_user_with_a_taken_username_is_a_validation_error() { UserRepository::save(&*store, &test_user("alice")) .await .unwrap(); - let clash = crate::models::User::new( - crate::value_objects::Email::new("other@example.com".into()).unwrap(), - crate::value_objects::Username::new("alice".into()).unwrap(), - crate::value_objects::PasswordHash::new("hashed".into()).unwrap(), - crate::models::UserRole::Standard, + let clash = domain::models::User::new( + domain::value_objects::Email::new("other@example.com".into()).unwrap(), + domain::value_objects::Username::new("alice".into()).unwrap(), + domain::value_objects::PasswordHash::new("hashed".into()).unwrap(), + domain::models::UserRole::Standard, ); let result = UserRepository::save(&*store, &clash).await; @@ -309,11 +311,11 @@ async fn saving_a_user_with_a_taken_email_is_a_validation_error() { UserRepository::save(&*store, &test_user("alice")) .await .unwrap(); - let clash = crate::models::User::new( - crate::value_objects::Email::new("alice@example.com".into()).unwrap(), - crate::value_objects::Username::new("alice2".into()).unwrap(), - crate::value_objects::PasswordHash::new("hashed".into()).unwrap(), - crate::models::UserRole::Standard, + let clash = domain::models::User::new( + domain::value_objects::Email::new("alice@example.com".into()).unwrap(), + domain::value_objects::Username::new("alice2".into()).unwrap(), + domain::value_objects::PasswordHash::new("hashed".into()).unwrap(), + domain::models::UserRole::Standard, ); let result = UserRepository::save(&*store, &clash).await; diff --git a/crates/domain/tests/value_objects.rs b/crates/domain/tests/value_objects.rs new file mode 100644 index 00000000..e94a056b --- /dev/null +++ b/crates/domain/tests/value_objects.rs @@ -0,0 +1,2 @@ +#[path = "value_objects/value_objects_test.rs"] +mod value_objects_test; diff --git a/crates/domain/src/tests/value_objects.rs b/crates/domain/tests/value_objects/value_objects_test.rs similarity index 99% rename from crates/domain/src/tests/value_objects.rs rename to crates/domain/tests/value_objects/value_objects_test.rs index beed6f0a..76c737bc 100644 --- a/crates/domain/src/tests/value_objects.rs +++ b/crates/domain/tests/value_objects/value_objects_test.rs @@ -1,4 +1,4 @@ -use super::*; +use domain::value_objects::*; #[test] fn movie_id_generate_unique() {