315 lines
9.3 KiB
Rust
315 lines
9.3 KiB
Rust
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"
|
|
);
|
|
}
|
|
|
|
#[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_saves_the_review_and_its_event_together() {
|
|
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),
|
|
);
|
|
|
|
logger
|
|
.log_review(log_review_command_manual(Uuid::new_v4(), "Test Film", 2024))
|
|
.await
|
|
.unwrap();
|
|
|
|
assert_eq!(movies.review_count(), 1);
|
|
let recorded = movies.recorded_events();
|
|
assert_eq!(recorded.len(), 1);
|
|
assert!(matches!(
|
|
recorded[0],
|
|
domain::events::DomainEvent::ReviewLogged { .. }
|
|
));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn a_refused_save_records_neither_the_review_nor_its_event() {
|
|
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!(result.is_err());
|
|
assert_eq!(movies.review_count(), 0);
|
|
assert!(movies.recorded_events().is_empty());
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn enrichment_request_rides_the_review_save() {
|
|
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(MetadataClientWithExternalId),
|
|
NoopEventPublisher::new(),
|
|
);
|
|
|
|
logger
|
|
.log_review(log_review_command_manual(
|
|
Uuid::new_v4(),
|
|
"Discovered Film",
|
|
2024,
|
|
))
|
|
.await
|
|
.unwrap();
|
|
|
|
let recorded = movies.recorded_events();
|
|
assert!(recorded.iter().any(|e| matches!(
|
|
e,
|
|
domain::events::DomainEvent::MovieEnrichmentRequested { .. }
|
|
)));
|
|
assert!(
|
|
recorded
|
|
.iter()
|
|
.any(|e| matches!(e, domain::events::DomainEvent::ReviewLogged { .. }))
|
|
);
|
|
}
|