test: move application tests to tests/
test_support/test_helpers -> tests/support; watched_at unified w/ domain test_watched_at (2024-03-15). Kept inline: users::diary_filter tests (feed_sort_to_direction_all_variants, paged_user_filter_builds_correctly) - pub(super) helpers in private module. Dropped stale never-compiled users/tests/get_users.rs (old execute signature).
This commit is contained in:
266
crates/application/tests/diary/review_logger_test.rs
Normal file
266
crates/application/tests/diary/review_logger_test.rs
Normal file
@@ -0,0 +1,266 @@
|
||||
use crate::support::{log_review_command, log_review_command_manual};
|
||||
use domain::testing::{test_movie_with_external_id, test_movie_with_year};
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::WatchlistEntry,
|
||||
models::{MetadataSearchCriteria, Movie},
|
||||
ports::{MetadataClient, MovieCommand, WatchlistRepository},
|
||||
testing::{FakeMetadataClient, InMemoryStore, NoopEventPublisher},
|
||||
value_objects::{ExternalMetadataId, MovieId, PosterUrl, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use application::diary::commands::LogReviewCommand;
|
||||
use application::diary::review_logger::DefaultReviewLogger;
|
||||
use application::ports::ReviewLogger;
|
||||
|
||||
fn make_logger(
|
||||
movies: &Arc<InMemoryStore>,
|
||||
reviews: &Arc<InMemoryStore>,
|
||||
watchlist: &Arc<InMemoryStore>,
|
||||
events: &Arc<NoopEventPublisher>,
|
||||
) -> DefaultReviewLogger {
|
||||
DefaultReviewLogger::new(
|
||||
Arc::clone(movies) as _,
|
||||
Arc::clone(movies) as _,
|
||||
Arc::clone(reviews) as _,
|
||||
Arc::clone(watchlist) as _,
|
||||
Arc::new(FakeMetadataClient) as _,
|
||||
Arc::clone(events) as _,
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn logs_review_with_manual_movie() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let uid = Uuid::new_v4();
|
||||
let cmd = log_review_command_manual(uid, "Test Film", 2024);
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
assert_eq!(movies.movie_count(), 1);
|
||||
assert_eq!(reviews.review_count(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn removes_from_watchlist_on_review() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let uid = Uuid::new_v4();
|
||||
let user_id = UserId::from_uuid(uid);
|
||||
|
||||
// Create and store movie
|
||||
let movie = test_movie_with_year("Watchlisted Film", 2024);
|
||||
let movie_id = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
// Add to watchlist
|
||||
let entry = WatchlistEntry::new(user_id.clone(), MovieId::from_uuid(movie_id));
|
||||
watchlist.add(&entry).await.unwrap();
|
||||
assert_eq!(watchlist.watchlist_count(), 1);
|
||||
|
||||
// Log review for same movie
|
||||
let cmd = LogReviewCommand {
|
||||
rating: 5,
|
||||
..log_review_command(uid, movie_id)
|
||||
};
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
assert_eq!(watchlist.watchlist_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn logs_review_with_existing_movie_by_id() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let movie = test_movie_with_year("Existing Film", 2020);
|
||||
let movie_uuid = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
rating: 3,
|
||||
..log_review_command(Uuid::new_v4(), movie_uuid)
|
||||
};
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
assert_eq!(movies.movie_count(), 1);
|
||||
assert_eq!(reviews.review_count(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn existing_movie_not_found_returns_error() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let cmd = log_review_command(Uuid::new_v4(), Uuid::new_v4());
|
||||
|
||||
assert!(logger.log_review(cmd).await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn invalid_rating_returns_error() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
rating: 6,
|
||||
..log_review_command_manual(Uuid::new_v4(), "Film", 2024)
|
||||
};
|
||||
|
||||
let result = logger.log_review(cmd).await;
|
||||
assert!(result.is_err());
|
||||
// No repo calls should have happened
|
||||
assert_eq!(movies.movie_count(), 0);
|
||||
assert_eq!(reviews.review_count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn watchlist_not_present_does_not_publish_removed() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let cmd = log_review_command_manual(Uuid::new_v4(), "No Watchlist Film", 2024);
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
!published
|
||||
.iter()
|
||||
.any(|e| matches!(e, domain::events::DomainEvent::WatchlistEntryRemoved { .. })),
|
||||
"should not publish WatchlistEntryRemoved when not on watchlist"
|
||||
);
|
||||
}
|
||||
|
||||
struct MetadataClientWithExternalId;
|
||||
|
||||
#[async_trait]
|
||||
impl MetadataClient for MetadataClientWithExternalId {
|
||||
async fn fetch_movie_metadata(
|
||||
&self,
|
||||
_criteria: &MetadataSearchCriteria,
|
||||
) -> Result<Movie, DomainError> {
|
||||
Ok(test_movie_with_external_id(
|
||||
"Discovered Film",
|
||||
2024,
|
||||
ExternalMetadataId::new("tmdb:99999".into()).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
async fn get_poster_url(
|
||||
&self,
|
||||
_external_metadata_id: &ExternalMetadataId,
|
||||
) -> Result<Option<PosterUrl>, DomainError> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn publishes_movie_discovered_for_new_movie_with_external_id() {
|
||||
let movies = InMemoryStore::new();
|
||||
let reviews = Arc::clone(&movies);
|
||||
let watchlist = Arc::clone(&movies);
|
||||
let events = NoopEventPublisher::new();
|
||||
|
||||
let logger = DefaultReviewLogger::new(
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::clone(&reviews) as _,
|
||||
Arc::clone(&watchlist) as _,
|
||||
Arc::new(MetadataClientWithExternalId) as _,
|
||||
Arc::clone(&events) as _,
|
||||
);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
rating: 5,
|
||||
..log_review_command_manual(Uuid::new_v4(), "Discovered Film", 2024)
|
||||
};
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
published
|
||||
.iter()
|
||||
.any(|e| matches!(e, domain::events::DomainEvent::MovieDiscovered { .. })),
|
||||
"should publish MovieDiscovered for new movie with external_metadata_id"
|
||||
);
|
||||
assert!(
|
||||
published.iter().any(|e| matches!(
|
||||
e,
|
||||
domain::events::DomainEvent::MovieEnrichmentRequested { .. }
|
||||
)),
|
||||
"should publish MovieEnrichmentRequested"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn logging_a_review_fails_when_the_review_store_refuses() {
|
||||
let movies = InMemoryStore::new();
|
||||
let logger = DefaultReviewLogger::new(
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::new(domain::testing::RefusingReviewRepository),
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::new(FakeMetadataClient),
|
||||
NoopEventPublisher::new(),
|
||||
);
|
||||
|
||||
let result = logger
|
||||
.log_review(log_review_command_manual(Uuid::new_v4(), "Test Film", 2024))
|
||||
.await;
|
||||
|
||||
assert!(matches!(result, Err(DomainError::InfrastructureError(m)) if m == "refused"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn logging_a_review_when_event_publishing_fails_returns_the_error_but_keeps_the_saved_review()
|
||||
{
|
||||
let movies = InMemoryStore::new();
|
||||
let logger = DefaultReviewLogger::new(
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::clone(&movies) as _,
|
||||
Arc::new(FakeMetadataClient),
|
||||
Arc::new(domain::testing::RefusingEventPublisher),
|
||||
);
|
||||
|
||||
let result = logger
|
||||
.log_review(log_review_command_manual(Uuid::new_v4(), "Test Film", 2024))
|
||||
.await;
|
||||
|
||||
assert!(matches!(result, Err(DomainError::InfrastructureError(m)) if m == "refused"));
|
||||
assert_eq!(
|
||||
movies.review_count(),
|
||||
1,
|
||||
"review is saved before publishing"
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user