test: move domain tests to tests/
Self dev-dep w/ test-helpers for domain::testing. Also moved social blanket-impl + InMemoryStore tests (public API only). No private-item exceptions.
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1663,6 +1663,7 @@ dependencies = [
|
|||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"domain",
|
||||||
"email_address",
|
"email_address",
|
||||||
"futures",
|
"futures",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ serde_json = { workspace = true }
|
|||||||
email_address = "0.2.9"
|
email_address = "0.2.9"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
domain = { path = ".", features = ["test-helpers"] }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -26,7 +26,3 @@ impl DomainError {
|
|||||||
matches!(self, DomainError::InfrastructureError(_))
|
matches!(self, DomainError::InfrastructureError(_))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -168,7 +168,3 @@ impl EventEnvelope {
|
|||||||
self.ack.nack().await
|
self.ack.nack().await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/events.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -109,7 +109,3 @@ impl GoalWithProgress {
|
|||||||
self.current_count >= self.goal.target_count
|
self.current_count >= self.goal.target_count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/goal.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -103,7 +103,3 @@ pub enum ExportFormat {
|
|||||||
Csv,
|
Csv,
|
||||||
Json,
|
Json,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/mod.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -181,7 +181,3 @@ pub struct CrewCredit {
|
|||||||
pub department: String,
|
pub department: String,
|
||||||
pub poster_path: Option<String>,
|
pub poster_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/person.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -202,7 +202,3 @@ impl ReviewHistory {
|
|||||||
self.viewings.sort_by_key(|r| *r.watched_at());
|
self.viewings.sort_by_key(|r| *r.watched_at());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/review.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -138,7 +138,3 @@ pub struct MovieStats {
|
|||||||
pub federated_count: u64,
|
pub federated_count: u64,
|
||||||
pub rating_histogram: [u64; 5], // index 0 = 1★, index 4 = 5★
|
pub rating_histogram: [u64; 5], // index 0 = 1★, index 4 = 5★
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/stats.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -234,7 +234,3 @@ pub struct ParsedPlaybackEvent {
|
|||||||
pub tmdb_id: Option<String>,
|
pub tmdb_id: Option<String>,
|
||||||
pub imdb_id: Option<String>,
|
pub imdb_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/watch_event.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -49,7 +49,3 @@ pub trait ApBackfillPort: Send + Sync {
|
|||||||
follower_inbox_url: String,
|
follower_inbox_url: String,
|
||||||
) -> Result<(), DomainError>;
|
) -> Result<(), DomainError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/federation.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -150,164 +150,3 @@ impl<T: SocialCommand + FollowGraphQuery + BlockQuery + FollowTargetResolver + R
|
|||||||
LocalSocial for T
|
LocalSocial for T
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod local_social_tests {
|
|
||||||
use super::*;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
struct Stub;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl SocialCommand for Stub {
|
|
||||||
async fn follow(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
_: &crate::value_objects::FollowTarget,
|
|
||||||
) -> 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<Vec<crate::value_objects::SocialActor>, DomainError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
async fn get_followers(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<Vec<crate::value_objects::SocialActor>, DomainError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
async fn get_pending_followers(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<Vec<crate::value_objects::SocialActor>, DomainError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
async fn get_pending_following(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<Vec<crate::value_objects::SocialActor>, DomainError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
async fn count_following(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<usize, DomainError> {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
async fn count_followers(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<usize, DomainError> {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
async fn count_pending_followers(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<usize, DomainError> {
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
async fn get_relation(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
_: &crate::value_objects::SocialIdentity,
|
|
||||||
) -> Result<crate::value_objects::FollowRelation, DomainError> {
|
|
||||||
Err(DomainError::NotFound("stub".into()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl BlockQuery for Stub {
|
|
||||||
async fn get_blocked(
|
|
||||||
&self,
|
|
||||||
_: &crate::value_objects::UserId,
|
|
||||||
) -> Result<Vec<crate::value_objects::SocialActor>, DomainError> {
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl FollowTargetResolver for Stub {
|
|
||||||
async fn resolve_target(
|
|
||||||
&self,
|
|
||||||
target: &crate::value_objects::FollowTarget,
|
|
||||||
) -> Result<SocialIdentity, DomainError> {
|
|
||||||
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<dyn LocalSocial> = 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -342,7 +342,3 @@ fn label_of(insight: &Insight) -> &str {
|
|||||||
fn round2(v: f64) -> f64 {
|
fn round2(v: f64) -> f64 {
|
||||||
(v * 100.0).round() / 100.0
|
(v * 100.0).round() / 100.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/insights_analyzer.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -51,7 +51,3 @@ impl ReviewHistoryAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/review_history.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -464,7 +464,3 @@ fn compute_global_stats(
|
|||||||
|
|
||||||
(most_active_user, most_watched, total_users_active)
|
(most_active_user, most_watched, total_users_active)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "tests/wrapup_analyzer.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ mod store_user;
|
|||||||
mod store_watch_event;
|
mod store_watch_event;
|
||||||
mod store_watchlist;
|
mod store_watchlist;
|
||||||
mod stubs;
|
mod stubs;
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
mod wrapup;
|
mod wrapup;
|
||||||
|
|
||||||
pub use insights::*;
|
pub use insights::*;
|
||||||
|
|||||||
@@ -11,7 +11,3 @@ pub use movie::*;
|
|||||||
pub use review::*;
|
pub use review::*;
|
||||||
pub use social::*;
|
pub use social::*;
|
||||||
pub use user::*;
|
pub use user::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[path = "../tests/value_objects.rs"]
|
|
||||||
mod tests;
|
|
||||||
|
|||||||
2
crates/domain/tests/errors.rs
Normal file
2
crates/domain/tests/errors.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#[path = "errors/errors_test.rs"]
|
||||||
|
mod errors_test;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::*;
|
use domain::errors::DomainError;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn infrastructure_error_is_transient() {
|
fn infrastructure_error_is_transient() {
|
||||||
2
crates/domain/tests/events.rs
Normal file
2
crates/domain/tests/events.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#[path = "events/events_test.rs"]
|
||||||
|
mod events_test;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use domain::events::DomainEvent;
|
||||||
use crate::value_objects::{FollowTarget, SocialIdentity, UserId};
|
use domain::value_objects::{FollowTarget, SocialIdentity, UserId};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn follow_accepted_matches() {
|
fn follow_accepted_matches() {
|
||||||
12
crates/domain/tests/models.rs
Normal file
12
crates/domain/tests/models.rs
Normal file
@@ -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;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use domain::models::*;
|
||||||
use crate::testing::{test_movie_with_director, test_movie_with_year, test_review, test_user};
|
use domain::testing::{test_movie_with_director, test_movie_with_year, test_review, test_user};
|
||||||
use crate::value_objects::{Email, PasswordHash, UserId, Username};
|
use domain::value_objects::{Email, PasswordHash, UserId, Username};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn update_profile_sets_fields() {
|
fn update_profile_sets_fields() {
|
||||||
@@ -32,11 +32,11 @@ fn update_profile_clears_with_none() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn movie_new_sets_fields() {
|
fn movie_new_sets_fields() {
|
||||||
let m = Movie::new(
|
let m = Movie::new(
|
||||||
Some(crate::value_objects::ExternalMetadataId::new("tt1234567".into()).unwrap()),
|
Some(domain::value_objects::ExternalMetadataId::new("tt1234567".into()).unwrap()),
|
||||||
crate::value_objects::MovieTitle::new("Blade Runner".into()).unwrap(),
|
domain::value_objects::MovieTitle::new("Blade Runner".into()).unwrap(),
|
||||||
crate::value_objects::ReleaseYear::new(1982).unwrap(),
|
domain::value_objects::ReleaseYear::new(1982).unwrap(),
|
||||||
Some("Ridley Scott".into()),
|
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.title().value(), "Blade Runner");
|
||||||
assert_eq!(m.release_year().value(), 1982);
|
assert_eq!(m.release_year().value(), 1982);
|
||||||
@@ -48,14 +48,14 @@ fn movie_new_sets_fields() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn movie_update_poster() {
|
fn movie_update_poster() {
|
||||||
let mut m = test_movie_with_year("Blade Runner", 1982);
|
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);
|
m.update_poster(new_poster);
|
||||||
assert_eq!(m.poster_path().unwrap().value(), "/new.jpg");
|
assert_eq!(m.poster_path().unwrap().value(), "/new.jpg");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Review ───────────────────────────────────────────────────────────────────
|
// ── Review ───────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
use crate::value_objects::{MovieId, Rating, ReviewId};
|
use domain::value_objects::{MovieId, Rating, ReviewId};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn review_new_sets_local_source() {
|
fn review_new_sets_local_source() {
|
||||||
@@ -199,16 +199,16 @@ fn month_activity_construction() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn movie_is_manual_match_same_title_year() {
|
fn movie_is_manual_match_same_title_year() {
|
||||||
let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott");
|
let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott");
|
||||||
let title = crate::value_objects::MovieTitle::new("Blade Runner".into()).unwrap();
|
let title = domain::value_objects::MovieTitle::new("Blade Runner".into()).unwrap();
|
||||||
let year = crate::value_objects::ReleaseYear::new(1982).unwrap();
|
let year = domain::value_objects::ReleaseYear::new(1982).unwrap();
|
||||||
assert!(m.is_manual_match(&title, &year, Some("ridley scott")));
|
assert!(m.is_manual_match(&title, &year, Some("ridley scott")));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn movie_is_manual_match_different_director_fails() {
|
fn movie_is_manual_match_different_director_fails() {
|
||||||
let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott");
|
let m = test_movie_with_director("Blade Runner", 1982, "Ridley Scott");
|
||||||
let title = crate::value_objects::MovieTitle::new("Blade Runner".into()).unwrap();
|
let title = domain::value_objects::MovieTitle::new("Blade Runner".into()).unwrap();
|
||||||
let year = crate::value_objects::ReleaseYear::new(1982).unwrap();
|
let year = domain::value_objects::ReleaseYear::new(1982).unwrap();
|
||||||
assert!(!m.is_manual_match(&title, &year, Some("Denis Villeneuve")));
|
assert!(!m.is_manual_match(&title, &year, Some("Denis Villeneuve")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::*;
|
use domain::models::*;
|
||||||
use crate::testing::test_goal;
|
use domain::testing::test_goal;
|
||||||
use crate::value_objects::UserId;
|
use domain::value_objects::{GoalId, UserId};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn new_goal_valid() {
|
fn new_goal_valid() {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::*;
|
use domain::models::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn person_new() {
|
fn person_new() {
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::testing::{test_review, test_watched_at};
|
use domain::testing::{test_review, test_watched_at};
|
||||||
use crate::value_objects::{Comment, MovieId, Rating, UserId, WatchMedium};
|
use domain::value_objects::{Comment, MovieId, Rating, UserId, WatchMedium};
|
||||||
|
|
||||||
use super::*;
|
use domain::models::*;
|
||||||
|
|
||||||
fn review_with_medium(watch_medium: Option<WatchMedium>) -> Review {
|
fn review_with_medium(watch_medium: Option<WatchMedium>) -> Review {
|
||||||
test_review(&MovieId::generate(), &UserId::generate(), 4).apply_edit(ReviewEdit {
|
test_review(&MovieId::generate(), &UserId::generate(), 4).apply_edit(ReviewEdit {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::*;
|
use domain::models::*;
|
||||||
|
|
||||||
fn appearance(person_id: u64, name: &str, billing: u32, movie: &str) -> ActorAppearance {
|
fn appearance(person_id: u64, name: &str, billing: u32, movie: &str) -> ActorAppearance {
|
||||||
ActorAppearance {
|
ActorAppearance {
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use super::*;
|
use chrono::NaiveDateTime;
|
||||||
|
use domain::models::*;
|
||||||
|
use domain::value_objects::{MovieId, UserId, WebhookTokenId};
|
||||||
|
|
||||||
fn ts() -> NaiveDateTime {
|
fn ts() -> NaiveDateTime {
|
||||||
chrono::NaiveDate::from_ymd_opt(2024, 6, 1)
|
chrono::NaiveDate::from_ymd_opt(2024, 6, 1)
|
||||||
4
crates/domain/tests/ports.rs
Normal file
4
crates/domain/tests/ports.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#[path = "ports/federation_test.rs"]
|
||||||
|
mod federation_test;
|
||||||
|
#[path = "ports/social_test.rs"]
|
||||||
|
mod social_test;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::*;
|
use domain::ports::noop::{NoopApBackfill, NoopApDocument, NoopInstanceBlocklist};
|
||||||
use crate::ports::noop::{NoopApBackfill, NoopApDocument, NoopInstanceBlocklist};
|
use domain::ports::{ApBackfillPort, ApDocumentPort, InstanceBlocklistPort};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn noop_document_returns_empty_strings() {
|
async fn noop_document_returns_empty_strings() {
|
||||||
162
crates/domain/tests/ports/social_test.rs
Normal file
162
crates/domain/tests/ports/social_test.rs
Normal file
@@ -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<Vec<domain::value_objects::SocialActor>, DomainError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
async fn get_followers(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<Vec<domain::value_objects::SocialActor>, DomainError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
async fn get_pending_followers(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<Vec<domain::value_objects::SocialActor>, DomainError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
async fn get_pending_following(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<Vec<domain::value_objects::SocialActor>, DomainError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
async fn count_following(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<usize, DomainError> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
async fn count_followers(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<usize, DomainError> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
async fn count_pending_followers(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<usize, DomainError> {
|
||||||
|
Ok(0)
|
||||||
|
}
|
||||||
|
async fn get_relation(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
_: &domain::value_objects::SocialIdentity,
|
||||||
|
) -> Result<domain::value_objects::FollowRelation, DomainError> {
|
||||||
|
Err(DomainError::NotFound("stub".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl BlockQuery for Stub {
|
||||||
|
async fn get_blocked(
|
||||||
|
&self,
|
||||||
|
_: &domain::value_objects::UserId,
|
||||||
|
) -> Result<Vec<domain::value_objects::SocialActor>, DomainError> {
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl FollowTargetResolver for Stub {
|
||||||
|
async fn resolve_target(
|
||||||
|
&self,
|
||||||
|
target: &domain::value_objects::FollowTarget,
|
||||||
|
) -> Result<SocialIdentity, DomainError> {
|
||||||
|
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<dyn LocalSocial> = 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();
|
||||||
|
}
|
||||||
6
crates/domain/tests/services.rs
Normal file
6
crates/domain/tests/services.rs
Normal file
@@ -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;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
use chrono::{Datelike, FixedOffset, NaiveDate, Utc, Weekday};
|
use chrono::{Datelike, FixedOffset, NaiveDate, Utc, Weekday};
|
||||||
use uuid::Uuid;
|
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 {
|
fn watch(genres: &[&str], rating: u8, date: NaiveDate, hour: u32) -> WatchRow {
|
||||||
WatchRow {
|
WatchRow {
|
||||||
@@ -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 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 {
|
fn dt(year: i32, month: u32, day: u32) -> NaiveDateTime {
|
||||||
NaiveDateTime::new(
|
NaiveDateTime::new(
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::models::WrapUpMovieRow;
|
use domain::models::WrapUpMovieRow;
|
||||||
use crate::models::wrapup::{DateRange, WrapUpScope};
|
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 {
|
fn row(title: &str, rating: u8, ym: &str) -> WrapUpMovieRow {
|
||||||
WrapUpMovieRow {
|
WrapUpMovieRow {
|
||||||
2
crates/domain/tests/testing.rs
Normal file
2
crates/domain/tests/testing.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#[path = "testing/in_memory_store_test.rs"]
|
||||||
|
mod in_memory_store_test;
|
||||||
@@ -3,21 +3,21 @@ use std::sync::Arc;
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{InMemoryStore, MOVIE_CASCADE_TABLES, USER_CASCADE_TABLES};
|
use domain::errors::DomainError;
|
||||||
use crate::errors::DomainError;
|
use domain::models::{
|
||||||
use crate::models::{
|
|
||||||
CastMember, CrewMember, Genre, Keyword, Movie, MovieProfile, ProfileField, RefreshSession,
|
CastMember, CrewMember, Genre, Keyword, Movie, MovieProfile, ProfileField, RefreshSession,
|
||||||
UserSettings, WatchEvent, WatchEventSource, WatchlistEntry, WebhookToken,
|
UserSettings, WatchEvent, WatchEventSource, WatchlistEntry, WebhookToken,
|
||||||
};
|
};
|
||||||
use crate::ports::{
|
use domain::ports::{
|
||||||
GoalCommand, MovieCommand, MovieProfileRepository, RefreshSessionRepository, ReviewRepository,
|
GoalCommand, MovieCommand, MovieProfileRepository, RefreshSessionRepository, ReviewRepository,
|
||||||
SocialCommand, UserProfileFieldsRepository, UserRepository, UserSettingsRepository,
|
SocialCommand, UserProfileFieldsRepository, UserRepository, UserSettingsRepository,
|
||||||
WatchEventCommand, WatchEventQuery, WatchlistRepository, WebhookTokenRepository,
|
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,
|
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";
|
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());
|
assert!(store.find_by_id(bystander.id()).await.unwrap().is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn seed_movie_with_dependents(store: &Arc<InMemoryStore>, with_review: bool) -> Movie {
|
async fn seed_movie_with_dependents(
|
||||||
|
store: &Arc<InMemoryStore>,
|
||||||
|
with_review: bool,
|
||||||
|
) -> (Movie, UserId) {
|
||||||
let user = test_user("alice");
|
let user = test_user("alice");
|
||||||
UserRepository::save(&**store, &user).await.unwrap();
|
UserRepository::save(&**store, &user).await.unwrap();
|
||||||
let movie = test_movie("Heat");
|
let movie = test_movie("Heat");
|
||||||
@@ -240,13 +243,13 @@ async fn seed_movie_with_dependents(store: &Arc<InMemoryStore>, with_review: boo
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
movie
|
(movie, user.id().clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn deleting_a_movie_removes_watchlist_entries_and_enrichment_profile() {
|
async fn deleting_a_movie_removes_watchlist_entries_and_enrichment_profile() {
|
||||||
let store = InMemoryStore::new();
|
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 {
|
for table in MOVIE_CASCADE_TABLES {
|
||||||
assert!(store.rows_in(table) > 0, "{table} should be seeded");
|
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]
|
#[tokio::test]
|
||||||
async fn deleting_a_movie_detaches_watch_events_instead_of_removing_them() {
|
async fn deleting_a_movie_detaches_watch_events_instead_of_removing_them() {
|
||||||
let store = InMemoryStore::new();
|
let store = InMemoryStore::new();
|
||||||
let movie = seed_movie_with_dependents(&store, false).await;
|
let (movie, user_id) = seed_movie_with_dependents(&store, false).await;
|
||||||
let user_id = store.watch_events.read().unwrap()[0].user_id().clone();
|
|
||||||
|
|
||||||
store.delete_movie(movie.id()).await.unwrap();
|
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]
|
#[tokio::test]
|
||||||
async fn deleting_a_movie_that_has_reviews_is_refused_because_reviews_do_not_cascade() {
|
async fn deleting_a_movie_that_has_reviews_is_refused_because_reviews_do_not_cascade() {
|
||||||
let store = InMemoryStore::new();
|
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;
|
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"))
|
UserRepository::save(&*store, &test_user("alice"))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let clash = crate::models::User::new(
|
let clash = domain::models::User::new(
|
||||||
crate::value_objects::Email::new("other@example.com".into()).unwrap(),
|
domain::value_objects::Email::new("other@example.com".into()).unwrap(),
|
||||||
crate::value_objects::Username::new("alice".into()).unwrap(),
|
domain::value_objects::Username::new("alice".into()).unwrap(),
|
||||||
crate::value_objects::PasswordHash::new("hashed".into()).unwrap(),
|
domain::value_objects::PasswordHash::new("hashed".into()).unwrap(),
|
||||||
crate::models::UserRole::Standard,
|
domain::models::UserRole::Standard,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = UserRepository::save(&*store, &clash).await;
|
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"))
|
UserRepository::save(&*store, &test_user("alice"))
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let clash = crate::models::User::new(
|
let clash = domain::models::User::new(
|
||||||
crate::value_objects::Email::new("alice@example.com".into()).unwrap(),
|
domain::value_objects::Email::new("alice@example.com".into()).unwrap(),
|
||||||
crate::value_objects::Username::new("alice2".into()).unwrap(),
|
domain::value_objects::Username::new("alice2".into()).unwrap(),
|
||||||
crate::value_objects::PasswordHash::new("hashed".into()).unwrap(),
|
domain::value_objects::PasswordHash::new("hashed".into()).unwrap(),
|
||||||
crate::models::UserRole::Standard,
|
domain::models::UserRole::Standard,
|
||||||
);
|
);
|
||||||
|
|
||||||
let result = UserRepository::save(&*store, &clash).await;
|
let result = UserRepository::save(&*store, &clash).await;
|
||||||
2
crates/domain/tests/value_objects.rs
Normal file
2
crates/domain/tests/value_objects.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#[path = "value_objects/value_objects_test.rs"]
|
||||||
|
mod value_objects_test;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::*;
|
use domain::value_objects::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn movie_id_generate_unique() {
|
fn movie_id_generate_unique() {
|
||||||
Reference in New Issue
Block a user