test: shared factories and command builders
This commit is contained in:
@@ -13,5 +13,6 @@ bytes = { workspace = true }
|
||||
async-stream = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
domain = { workspace = true, features = ["test-helpers"] }
|
||||
uuid = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
use super::ExportAdapter;
|
||||
use domain::{
|
||||
models::{DiaryEntry, ExportFormat, Movie, Review},
|
||||
models::{DiaryEntry, ExportFormat, Movie, ReviewEdit},
|
||||
ports::DiaryExporter,
|
||||
value_objects::{ExternalMetadataId, MovieTitle, Rating, ReleaseYear},
|
||||
testing::{
|
||||
test_movie_with_director, test_movie_with_external_id, test_movie_with_year, test_review,
|
||||
},
|
||||
value_objects::{Comment, ExternalMetadataId, UserId},
|
||||
};
|
||||
|
||||
async fn collect_stream(
|
||||
@@ -26,55 +29,19 @@ fn entry_stream(
|
||||
Box::pin(futures::stream::iter(entries.into_iter().map(Ok)))
|
||||
}
|
||||
|
||||
fn make_entry(
|
||||
title: &str,
|
||||
year: u16,
|
||||
director: Option<&str>,
|
||||
rating: u8,
|
||||
comment: Option<&str>,
|
||||
) -> DiaryEntry {
|
||||
make_entry_full(title, year, director, rating, comment, None)
|
||||
}
|
||||
|
||||
fn make_entry_full(
|
||||
title: &str,
|
||||
year: u16,
|
||||
director: Option<&str>,
|
||||
rating: u8,
|
||||
comment: Option<&str>,
|
||||
external_id: Option<&str>,
|
||||
) -> DiaryEntry {
|
||||
let movie = Movie::new(
|
||||
external_id.map(|id| ExternalMetadataId::new(id.to_string()).unwrap()),
|
||||
MovieTitle::new(title.to_string()).unwrap(),
|
||||
ReleaseYear::new(year).unwrap(),
|
||||
director.map(str::to_string),
|
||||
None,
|
||||
);
|
||||
let user_id = domain::value_objects::UserId::from_uuid(uuid::Uuid::new_v4());
|
||||
let review = Review::new(
|
||||
movie.id().clone(),
|
||||
user_id,
|
||||
Rating::new(rating).unwrap(),
|
||||
comment.map(|c| domain::value_objects::Comment::new(c.to_string()).unwrap()),
|
||||
chrono::NaiveDate::from_ymd_opt(2024, 3, 15)
|
||||
.unwrap()
|
||||
.and_hms_opt(0, 0, 0)
|
||||
.unwrap(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
fn entry_for(movie: Movie, rating: u8, comment: Option<&str>) -> DiaryEntry {
|
||||
let review = test_review(movie.id(), &UserId::generate(), rating).apply_edit(ReviewEdit {
|
||||
comment: Some(comment.map(|c| Comment::new(c.to_string()).unwrap())),
|
||||
..Default::default()
|
||||
});
|
||||
DiaryEntry::new(movie, review)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn csv_has_header_and_one_row() {
|
||||
let adapter = ExportAdapter;
|
||||
let entry = make_entry(
|
||||
"Inception",
|
||||
2010,
|
||||
Some("Christopher Nolan"),
|
||||
let entry = entry_for(
|
||||
test_movie_with_director("Inception", 2010, "Christopher Nolan"),
|
||||
5,
|
||||
Some("great"),
|
||||
);
|
||||
@@ -95,7 +62,7 @@ async fn csv_has_header_and_one_row() {
|
||||
#[tokio::test]
|
||||
async fn csv_escapes_commas_in_title() {
|
||||
let adapter = ExportAdapter;
|
||||
let entry = make_entry("Tár, A Film", 2022, None, 4, None);
|
||||
let entry = entry_for(test_movie_with_year("Tár, A Film", 2022), 4, None);
|
||||
let bytes =
|
||||
collect_stream(adapter.stream_entries(entry_stream(vec![entry]), ExportFormat::Csv)).await;
|
||||
let text = String::from_utf8(bytes).unwrap();
|
||||
@@ -105,7 +72,11 @@ async fn csv_escapes_commas_in_title() {
|
||||
#[tokio::test]
|
||||
async fn json_is_valid_array() {
|
||||
let adapter = ExportAdapter;
|
||||
let entry = make_entry("Dune", 2021, Some("Denis Villeneuve"), 5, None);
|
||||
let entry = entry_for(
|
||||
test_movie_with_director("Dune", 2021, "Denis Villeneuve"),
|
||||
5,
|
||||
None,
|
||||
);
|
||||
let bytes =
|
||||
collect_stream(adapter.stream_entries(entry_stream(vec![entry]), ExportFormat::Json)).await;
|
||||
let arr: Vec<serde_json::Value> = serde_json::from_slice(&bytes).unwrap();
|
||||
@@ -120,20 +91,29 @@ async fn json_is_valid_array() {
|
||||
#[tokio::test]
|
||||
async fn external_metadata_id_included_when_present() {
|
||||
let adapter = ExportAdapter;
|
||||
let entry = make_entry_full("Alien", 1979, None, 5, None, Some("tt0078748"));
|
||||
let entry = entry_for(
|
||||
test_movie_with_external_id(
|
||||
"Alien",
|
||||
1979,
|
||||
ExternalMetadataId::new("tt0078748".to_string()).unwrap(),
|
||||
),
|
||||
5,
|
||||
None,
|
||||
);
|
||||
let bytes =
|
||||
collect_stream(adapter.stream_entries(entry_stream(vec![entry]), ExportFormat::Json)).await;
|
||||
let arr: Vec<serde_json::Value> = serde_json::from_slice(&bytes).unwrap();
|
||||
assert_eq!(arr[0]["external_metadata_id"], "tt0078748");
|
||||
|
||||
let bytes = collect_stream(adapter.stream_entries(
|
||||
entry_stream(vec![make_entry_full(
|
||||
"Alien",
|
||||
1979,
|
||||
None,
|
||||
entry_stream(vec![entry_for(
|
||||
test_movie_with_external_id(
|
||||
"Alien",
|
||||
1979,
|
||||
ExternalMetadataId::new("tt0078748".to_string()).unwrap(),
|
||||
),
|
||||
5,
|
||||
None,
|
||||
Some("tt0078748"),
|
||||
)]),
|
||||
ExportFormat::Csv,
|
||||
))
|
||||
|
||||
@@ -966,26 +966,6 @@ impl UserRepository for ErroringUserRepo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds a bare `User` with the given role, for the admin/non-admin extractor
|
||||
/// tests — only `id()` and `role()` are read by `authorize_admin::execute`.
|
||||
fn make_user_with_role(id: UserId, role: domain::models::UserRole) -> domain::models::User {
|
||||
domain::models::User::from_persistence(
|
||||
id,
|
||||
domain::value_objects::Email::new("extractor-test@example.com".into()).unwrap(),
|
||||
domain::value_objects::Username::new("extractor_test_user".into()).unwrap(),
|
||||
domain::value_objects::PasswordHash::new("hashed".into()).unwrap(),
|
||||
role,
|
||||
domain::models::UserProfile {
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_path: None,
|
||||
banner_path: None,
|
||||
also_known_as: None,
|
||||
profile_fields: vec![],
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Auth stub that accepts any bearer token and resolves it to a fixed user id,
|
||||
/// for tests that need to authenticate as a specific user rather than merely
|
||||
/// exercise the unauthenticated-request path.
|
||||
@@ -1154,7 +1134,7 @@ async fn admin_api_user_allows_an_admin_user() {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
user_repo.store.lock().unwrap().insert(
|
||||
id.value(),
|
||||
make_user_with_role(id.clone(), domain::models::UserRole::Admin),
|
||||
domain::testing::test_user_with_role(id.clone(), domain::models::UserRole::Admin),
|
||||
);
|
||||
let app = router_admin_api(test_app_state_with_user(
|
||||
user_repo as _,
|
||||
@@ -1179,7 +1159,7 @@ async fn admin_api_user_rejects_a_non_admin_user() {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
user_repo.store.lock().unwrap().insert(
|
||||
id.value(),
|
||||
make_user_with_role(id.clone(), domain::models::UserRole::Standard),
|
||||
domain::testing::test_user_with_role(id.clone(), domain::models::UserRole::Standard),
|
||||
);
|
||||
let app = router_admin_api(test_app_state_with_user(
|
||||
user_repo as _,
|
||||
@@ -1245,7 +1225,7 @@ async fn admin_user_allows_an_admin_user() {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
user_repo.store.lock().unwrap().insert(
|
||||
id.value(),
|
||||
make_user_with_role(id.clone(), domain::models::UserRole::Admin),
|
||||
domain::testing::test_user_with_role(id.clone(), domain::models::UserRole::Admin),
|
||||
);
|
||||
let app = router_admin_cookie(test_app_state_with_user(
|
||||
user_repo as _,
|
||||
@@ -1270,7 +1250,7 @@ async fn admin_user_rejects_a_non_admin_user() {
|
||||
let user_repo = InMemoryUserRepository::new();
|
||||
user_repo.store.lock().unwrap().insert(
|
||||
id.value(),
|
||||
make_user_with_role(id.clone(), domain::models::UserRole::Standard),
|
||||
domain::testing::test_user_with_role(id.clone(), domain::models::UserRole::Standard),
|
||||
);
|
||||
let app = router_admin_cookie(test_app_state_with_user(
|
||||
user_repo as _,
|
||||
|
||||
@@ -8,4 +8,5 @@ rss-feed = { package = "rss", version = "2", features = ["atom"] }
|
||||
domain = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
domain = { workspace = true, features = ["test-helpers"] }
|
||||
chrono = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use domain::models::{Movie, Review};
|
||||
use domain::value_objects::{Comment, MovieTitle, Rating, ReleaseYear, UserId};
|
||||
use domain::models::ReviewEdit;
|
||||
use domain::testing::{test_movie_with_year, test_review};
|
||||
use domain::value_objects::{Comment, UserId};
|
||||
|
||||
fn channel<'a>(title: &'a str) -> FeedChannel<'a> {
|
||||
FeedChannel {
|
||||
@@ -11,23 +12,12 @@ fn channel<'a>(title: &'a str) -> FeedChannel<'a> {
|
||||
}
|
||||
|
||||
fn entry(spoilers: bool) -> DiaryEntry {
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Blade Runner".into()).unwrap(),
|
||||
ReleaseYear::new(1982).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let review = Review::new(
|
||||
movie.id().clone(),
|
||||
UserId::generate(),
|
||||
Rating::new(4).unwrap(),
|
||||
Some(Comment::new("replicants".into()).unwrap()),
|
||||
chrono::Utc::now().naive_utc(),
|
||||
None,
|
||||
spoilers,
|
||||
)
|
||||
.unwrap();
|
||||
let movie = test_movie_with_year("Blade Runner", 1982);
|
||||
let review = test_review(movie.id(), &UserId::generate(), 4).apply_edit(ReviewEdit {
|
||||
comment: Some(Some(Comment::new("replicants".into()).unwrap())),
|
||||
spoilers: Some(spoilers),
|
||||
..Default::default()
|
||||
});
|
||||
DiaryEntry::new(movie, review)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,4 +22,5 @@ bytes = { workspace = true }
|
||||
async-stream = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
domain = { workspace = true, features = ["test-helpers"] }
|
||||
tempfile = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::SqlitePersonAdapter;
|
||||
use domain::testing::test_person_with_tmdb;
|
||||
use domain::{
|
||||
models::{ExternalPersonId, Person, PersonId},
|
||||
models::{ExternalPersonId, PersonId},
|
||||
ports::{PersonCommand, PersonQuery},
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
@@ -47,25 +48,14 @@ async fn pool_with_schema() -> SqlitePool {
|
||||
pool
|
||||
}
|
||||
|
||||
fn make_person(tmdb_id: i64, name: &str, dept: Option<&str>) -> Person {
|
||||
let ext = ExternalPersonId::new(format!("tmdb:{tmdb_id}"));
|
||||
Person::basic(
|
||||
PersonId::from_external(&ext),
|
||||
ext,
|
||||
name.to_string(),
|
||||
dept.map(str::to_string),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn upsert_batch_inserts_persons() {
|
||||
let pool = pool_with_schema().await;
|
||||
let adapter = SqlitePersonAdapter::new(pool.clone());
|
||||
|
||||
let persons = vec![
|
||||
make_person(1, "Alice", Some("Acting")),
|
||||
make_person(2, "Bob", Some("Directing")),
|
||||
test_person_with_tmdb(1, "Alice", Some("Acting")),
|
||||
test_person_with_tmdb(2, "Bob", Some("Directing")),
|
||||
];
|
||||
adapter.upsert_batch(&persons).await.unwrap();
|
||||
|
||||
@@ -81,7 +71,7 @@ async fn upsert_batch_is_idempotent() {
|
||||
let pool = pool_with_schema().await;
|
||||
let adapter = SqlitePersonAdapter::new(pool.clone());
|
||||
|
||||
let persons = vec![make_person(1, "Alice", Some("Acting"))];
|
||||
let persons = vec![test_person_with_tmdb(1, "Alice", Some("Acting"))];
|
||||
adapter.upsert_batch(&persons).await.unwrap();
|
||||
adapter.upsert_batch(&persons).await.unwrap();
|
||||
|
||||
@@ -97,7 +87,7 @@ async fn get_by_id_returns_person() {
|
||||
let pool = pool_with_schema().await;
|
||||
let adapter = SqlitePersonAdapter::new(pool.clone());
|
||||
|
||||
let p = make_person(42, "Charlie", Some("Acting"));
|
||||
let p = test_person_with_tmdb(42, "Charlie", Some("Acting"));
|
||||
adapter
|
||||
.upsert_batch(std::slice::from_ref(&p))
|
||||
.await
|
||||
@@ -122,7 +112,7 @@ async fn get_credits_returns_cast_and_crew() {
|
||||
let pool = pool_with_schema().await;
|
||||
let adapter = SqlitePersonAdapter::new(pool.clone());
|
||||
|
||||
let p = make_person(7, "Diana", Some("Acting"));
|
||||
let p = test_person_with_tmdb(7, "Diana", Some("Acting"));
|
||||
adapter
|
||||
.upsert_batch(std::slice::from_ref(&p))
|
||||
.await
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::*;
|
||||
use domain::models::UserRole;
|
||||
use domain::value_objects::{Email, PasswordHash, Username};
|
||||
use domain::testing::test_user;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
async fn setup() -> (SqlitePool, SqliteUserRepository) {
|
||||
@@ -55,12 +54,7 @@ async fn find_by_id_returns_user_when_found() {
|
||||
#[tokio::test]
|
||||
async fn update_profile_persists_bio_and_avatar() {
|
||||
let (_, repo) = setup().await;
|
||||
let user = domain::models::User::new(
|
||||
Email::new("test@example.com".to_string()).unwrap(),
|
||||
Username::new("testuser".to_string()).unwrap(),
|
||||
PasswordHash::new("hash".to_string()).unwrap(),
|
||||
UserRole::Standard,
|
||||
);
|
||||
let user = test_user("testuser");
|
||||
repo.save(&user).await.unwrap();
|
||||
|
||||
repo.update_profile(
|
||||
@@ -82,12 +76,7 @@ async fn update_profile_persists_bio_and_avatar() {
|
||||
#[tokio::test]
|
||||
async fn update_profile_clears_fields_with_none() {
|
||||
let (_, repo) = setup().await;
|
||||
let user = domain::models::User::new(
|
||||
Email::new("test2@example.com".to_string()).unwrap(),
|
||||
Username::new("testuser2".to_string()).unwrap(),
|
||||
PasswordHash::new("hash".to_string()).unwrap(),
|
||||
UserRole::Standard,
|
||||
);
|
||||
let user = test_user("testuser2");
|
||||
repo.save(&user).await.unwrap();
|
||||
repo.update_profile(
|
||||
user.id(),
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
use crate::test_support::{login_command, register_command};
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::UserRole;
|
||||
use domain::testing::InMemoryUserRepository;
|
||||
|
||||
use crate::{
|
||||
auth::{
|
||||
commands::RegisterCommand,
|
||||
deps::{LoginDeps, RegisterDeps},
|
||||
login,
|
||||
queries::LoginCommand,
|
||||
register,
|
||||
login, register,
|
||||
},
|
||||
test_helpers::TestContextBuilder,
|
||||
};
|
||||
@@ -23,10 +21,8 @@ async fn setup_user(b: &TestContextBuilder, email: &str, password: &str) {
|
||||
register::execute(
|
||||
&deps,
|
||||
RegisterCommand {
|
||||
email: email.to_string(),
|
||||
username: "testuser".to_string(),
|
||||
password: password.to_string(),
|
||||
role: UserRole::Standard,
|
||||
..register_command(email, "testuser")
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -46,15 +42,9 @@ async fn test_login_valid_credentials_returns_token() {
|
||||
refresh_session: b.refresh_session_repo.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
let result = login::execute(
|
||||
&deps,
|
||||
LoginCommand {
|
||||
email: "carol@example.com".into(),
|
||||
password: "secret123".into(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = login::execute(&deps, login_command("carol@example.com", "secret123"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(!result.token.is_empty());
|
||||
assert!(!result.refresh_token.is_empty());
|
||||
@@ -74,14 +64,7 @@ async fn test_login_wrong_password_fails() {
|
||||
refresh_session: b.refresh_session_repo.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
let result = login::execute(
|
||||
&deps,
|
||||
LoginCommand {
|
||||
email: "dave@example.com".into(),
|
||||
password: "wrong_password".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = login::execute(&deps, login_command("dave@example.com", "wrong_password")).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -96,14 +79,7 @@ async fn test_login_unknown_email_fails() {
|
||||
refresh_session: b.refresh_session_repo.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
let result = login::execute(
|
||||
&deps,
|
||||
LoginCommand {
|
||||
email: "nobody@example.com".into(),
|
||||
password: "anything".into(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = login::execute(&deps, login_command("nobody@example.com", "anything")).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use crate::test_support::{login_command, register_command};
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::UserRole;
|
||||
use domain::testing::InMemoryUserRepository;
|
||||
|
||||
use crate::{
|
||||
auth::{
|
||||
commands::RegisterCommand,
|
||||
deps::{LoginDeps, LogoutDeps, RefreshDeps, RegisterDeps},
|
||||
login, logout,
|
||||
queries::LoginCommand,
|
||||
refresh, register,
|
||||
login, logout, refresh, register,
|
||||
},
|
||||
test_helpers::TestContextBuilder,
|
||||
};
|
||||
@@ -24,17 +21,9 @@ async fn logout_revokes_refresh_token() {
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
®_deps,
|
||||
RegisterCommand {
|
||||
email: "bob@example.com".to_string(),
|
||||
username: "bob".to_string(),
|
||||
password: "password123".to_string(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(®_deps, register_command("bob@example.com", "bob"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let login_deps = LoginDeps {
|
||||
user: b.user_repo.clone(),
|
||||
@@ -43,15 +32,9 @@ async fn logout_revokes_refresh_token() {
|
||||
refresh_session: b.refresh_session_repo.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
let login_result = login::execute(
|
||||
&login_deps,
|
||||
LoginCommand {
|
||||
email: "bob@example.com".into(),
|
||||
password: "password123".into(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let login_result = login::execute(&login_deps, login_command("bob@example.com", "password123"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let logout_deps = LogoutDeps {
|
||||
refresh_session: b.refresh_session_repo.clone(),
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use crate::test_support::{login_command, register_command};
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::UserRole;
|
||||
use domain::testing::InMemoryUserRepository;
|
||||
|
||||
use crate::{
|
||||
auth::{
|
||||
commands::RegisterCommand,
|
||||
deps::{LoginDeps, RefreshDeps, RegisterDeps},
|
||||
login,
|
||||
queries::LoginCommand,
|
||||
refresh, register,
|
||||
login, refresh, register,
|
||||
},
|
||||
test_helpers::TestContextBuilder,
|
||||
};
|
||||
@@ -20,17 +17,9 @@ async fn login_user(b: &TestContextBuilder) -> login::LoginResult {
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
®_deps,
|
||||
RegisterCommand {
|
||||
email: "alice@example.com".to_string(),
|
||||
username: "alice".to_string(),
|
||||
password: "password123".to_string(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(®_deps, register_command("alice@example.com", "alice"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let login_deps = LoginDeps {
|
||||
user: b.user_repo.clone(),
|
||||
@@ -41,10 +30,7 @@ async fn login_user(b: &TestContextBuilder) -> login::LoginResult {
|
||||
};
|
||||
login::execute(
|
||||
&login_deps,
|
||||
LoginCommand {
|
||||
email: "alice@example.com".into(),
|
||||
password: "password123".into(),
|
||||
},
|
||||
login_command("alice@example.com", "password123"),
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::test_support::register_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::UserRole;
|
||||
use domain::ports::UserRepository;
|
||||
use domain::testing::InMemoryUserRepository;
|
||||
use domain::value_objects::Email;
|
||||
@@ -11,12 +11,7 @@ use crate::{
|
||||
};
|
||||
|
||||
fn cmd(email: &str) -> RegisterCommand {
|
||||
RegisterCommand {
|
||||
email: email.to_string(),
|
||||
username: "alice".to_string(),
|
||||
password: "password123".to_string(),
|
||||
role: UserRole::Standard,
|
||||
}
|
||||
register_command(email, "alice")
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -67,10 +62,8 @@ async fn test_register_short_password_fails() {
|
||||
let result = register::execute(
|
||||
&deps,
|
||||
RegisterCommand {
|
||||
email: "x@y.com".to_string(),
|
||||
username: "testuser".to_string(),
|
||||
password: "short".to_string(),
|
||||
role: UserRole::Standard,
|
||||
..register_command("x@y.com", "testuser")
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,43 +1,18 @@
|
||||
use domain::testing::{test_movie_with_year, test_review};
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use domain::{
|
||||
models::{Movie, Review},
|
||||
ports::{MovieCommand, MovieQuery, ReviewRepository},
|
||||
testing::{
|
||||
FakeDiaryQuery, InMemoryMovieRepository, InMemoryReviewRepository, NoopEventPublisher,
|
||||
},
|
||||
value_objects::{MovieId, MovieTitle, Rating, ReleaseYear, UserId},
|
||||
value_objects::{MovieId, UserId},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
diary::commands::DeleteReviewCommand, diary::delete_review, diary::deps::DeleteReviewDeps,
|
||||
};
|
||||
|
||||
fn make_movie() -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Terminator".into()).unwrap(),
|
||||
ReleaseYear::new(1984).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_review(movie_id: MovieId, user_id: UserId) -> Review {
|
||||
Review::new(
|
||||
movie_id,
|
||||
user_id,
|
||||
Rating::new(4).unwrap(),
|
||||
None,
|
||||
Utc::now().naive_utc(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_delete_review_removes_it() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
@@ -45,9 +20,9 @@ async fn test_delete_review_removes_it() {
|
||||
let diary = FakeDiaryQuery::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Terminator", 1984);
|
||||
let user_id = UserId::from_uuid(uuid::Uuid::new_v4());
|
||||
let review = make_review(movie.id().clone(), user_id.clone());
|
||||
let review = test_review(movie.id(), &user_id, 4);
|
||||
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
reviews.save_review(&review).await.unwrap();
|
||||
@@ -87,7 +62,7 @@ async fn test_delete_review_wrong_user_is_unauthorized() {
|
||||
let movie_id = MovieId::from_uuid(uuid::Uuid::new_v4());
|
||||
let owner_id = UserId::from_uuid(uuid::Uuid::new_v4());
|
||||
let other_id = uuid::Uuid::new_v4();
|
||||
let review = make_review(movie_id, owner_id);
|
||||
let review = test_review(&movie_id, &owner_id, 4);
|
||||
|
||||
reviews.save_review(&review).await.unwrap();
|
||||
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
use crate::test_support::edit_review_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::{
|
||||
models::Review,
|
||||
models::ReviewEdit,
|
||||
ports::ReviewRepository,
|
||||
testing::{InMemoryReviewRepository, NoopEventPublisher},
|
||||
value_objects::{Comment, MovieId, Rating, ReviewId, UserId, WatchMedium},
|
||||
testing::{InMemoryReviewRepository, NoopEventPublisher, test_review},
|
||||
value_objects::{Comment, MovieId, ReviewId, UserId, WatchMedium},
|
||||
};
|
||||
|
||||
use crate::diary::{commands::EditReviewCommand, deps::EditReviewDeps, edit_review};
|
||||
|
||||
fn make_review(user_id: UserId) -> Review {
|
||||
Review::new(
|
||||
MovieId::generate(),
|
||||
user_id,
|
||||
Rating::new(3).unwrap(),
|
||||
Some(Comment::new("original comment".into()).unwrap()),
|
||||
Utc::now().naive_utc(),
|
||||
Some(WatchMedium::Streaming),
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn setup() -> (
|
||||
Arc<InMemoryReviewRepository>,
|
||||
Arc<NoopEventPublisher>,
|
||||
@@ -34,7 +19,11 @@ async fn setup() -> (
|
||||
let reviews = InMemoryReviewRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let user_id = UserId::generate();
|
||||
let review = make_review(user_id.clone());
|
||||
let review = test_review(&MovieId::generate(), &user_id, 3).apply_edit(ReviewEdit {
|
||||
comment: Some(Some(Comment::new("original comment".into()).unwrap())),
|
||||
watch_medium: Some(Some(WatchMedium::Streaming)),
|
||||
..Default::default()
|
||||
});
|
||||
let review_id = review.id().clone();
|
||||
reviews.save_review(&review).await.unwrap();
|
||||
(reviews, events, review_id, user_id)
|
||||
@@ -59,11 +48,7 @@ async fn edit_own_review_updates_rating() {
|
||||
EditReviewCommand {
|
||||
review_id: review_id.value(),
|
||||
requesting_user_id: user_id.value(),
|
||||
rating: Some(5),
|
||||
comment: None,
|
||||
watched_at: None,
|
||||
watch_medium: None,
|
||||
spoilers: None,
|
||||
..edit_review_command(5)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -80,20 +65,9 @@ async fn edit_nonexistent_review_returns_not_found() {
|
||||
let reviews = InMemoryReviewRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
|
||||
let err = edit_review::execute(
|
||||
&deps(&reviews, &events),
|
||||
EditReviewCommand {
|
||||
review_id: Uuid::new_v4(),
|
||||
requesting_user_id: Uuid::new_v4(),
|
||||
rating: Some(5),
|
||||
comment: None,
|
||||
watched_at: None,
|
||||
watch_medium: None,
|
||||
spoilers: None,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let err = edit_review::execute(&deps(&reviews, &events), edit_review_command(5))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(matches!(err, domain::errors::DomainError::NotFound(_)));
|
||||
}
|
||||
@@ -106,12 +80,7 @@ async fn edit_other_users_review_returns_forbidden() {
|
||||
&deps(&reviews, &events),
|
||||
EditReviewCommand {
|
||||
review_id: review_id.value(),
|
||||
requesting_user_id: Uuid::new_v4(),
|
||||
rating: Some(5),
|
||||
comment: None,
|
||||
watched_at: None,
|
||||
watch_medium: None,
|
||||
spoilers: None,
|
||||
..edit_review_command(5)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -129,11 +98,7 @@ async fn edit_publishes_review_updated_event() {
|
||||
EditReviewCommand {
|
||||
review_id: review_id.value(),
|
||||
requesting_user_id: user_id.value(),
|
||||
rating: Some(1),
|
||||
comment: None,
|
||||
watched_at: None,
|
||||
watch_medium: None,
|
||||
spoilers: None,
|
||||
..edit_review_command(1)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -154,13 +119,11 @@ async fn edit_sets_watch_medium() {
|
||||
edit_review::execute(
|
||||
&deps(&reviews, &events),
|
||||
EditReviewCommand {
|
||||
rating: None,
|
||||
review_id: review_id.value(),
|
||||
requesting_user_id: user_id.value(),
|
||||
rating: None,
|
||||
comment: None,
|
||||
watched_at: None,
|
||||
watch_medium: Some(Some(WatchMedium::Cinema)),
|
||||
spoilers: None,
|
||||
..edit_review_command(1)
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use domain::testing::test_movie_with_year;
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::{
|
||||
models::Movie,
|
||||
ports::MovieCommand,
|
||||
testing::{FakeDiaryQuery, InMemoryMovieProfileRepository, InMemoryMovieRepository},
|
||||
value_objects::{MovieTitle, ReleaseYear},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -39,13 +38,7 @@ async fn fails_when_movie_not_found() {
|
||||
async fn returns_movie_social_page() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Social Movie".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Social Movie", 2024);
|
||||
let movie_uuid = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use domain::testing::test_movie_with_year;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
models::Movie,
|
||||
ports::DiaryQuery,
|
||||
services::review_history::Trend,
|
||||
value_objects::{MovieTitle, ReleaseYear},
|
||||
};
|
||||
use domain::{ports::DiaryQuery, services::review_history::Trend};
|
||||
|
||||
use crate::{
|
||||
diary::deps::GetReviewHistoryDeps, diary::get_review_history,
|
||||
@@ -14,13 +10,7 @@ use crate::{
|
||||
|
||||
#[tokio::test]
|
||||
async fn returns_empty_history() {
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Test".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let movie_id = movie.id().value();
|
||||
|
||||
let diary = domain::testing::FakeDiaryQuery::new();
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
use crate::test_support::register_command;
|
||||
use domain::testing::{test_movie_with_year, test_review};
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::errors::DomainError;
|
||||
use domain::models::{DiaryEntry, Movie, Review, UserRole, collections::Paginated};
|
||||
use domain::models::{DiaryEntry, collections::Paginated};
|
||||
use domain::testing::FakeDiaryQuery;
|
||||
use domain::value_objects::{Email, MovieTitle, Rating, ReleaseYear, UserId};
|
||||
use domain::value_objects::{Email, UserId};
|
||||
|
||||
use crate::auth::commands::RegisterCommand;
|
||||
use crate::auth::deps::RegisterDeps;
|
||||
use crate::auth::register;
|
||||
use crate::diary::deps::GetUserFeedDeps;
|
||||
@@ -20,17 +21,9 @@ async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) {
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
&deps,
|
||||
RegisterCommand {
|
||||
email: email.into(),
|
||||
username: username.into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(&deps, register_command(email, username))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -43,23 +36,8 @@ async fn user_feed_carries_author_and_entries() {
|
||||
let uid = user.id().value();
|
||||
|
||||
let diary = FakeDiaryQuery::new();
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Feed Movie".into()).unwrap(),
|
||||
ReleaseYear::new(2020).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let review = Review::new(
|
||||
movie.id().clone(),
|
||||
UserId::from_uuid(uid),
|
||||
Rating::new(5).unwrap(),
|
||||
None,
|
||||
chrono::Utc::now().naive_utc(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
let movie = test_movie_with_year("Feed Movie", 2020);
|
||||
let review = test_review(movie.id(), &UserId::from_uuid(uid), 5);
|
||||
diary.set_diary_page(Paginated {
|
||||
items: vec![DiaryEntry::new(movie, review)],
|
||||
total_count: 1,
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
use crate::test_support::{log_review_command, log_review_command_manual};
|
||||
use domain::testing::test_movie_with_year;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
|
||||
use domain::{
|
||||
models::Movie,
|
||||
ports::MovieCommand,
|
||||
value_objects::{MovieTitle, ReleaseYear},
|
||||
};
|
||||
use domain::ports::MovieCommand;
|
||||
|
||||
use domain::testing::{InMemoryMovieRepository, InMemoryReviewRepository, NoopEventPublisher};
|
||||
|
||||
use crate::{
|
||||
diary::commands::{LogReviewCommand, MovieInput},
|
||||
diary::log_review,
|
||||
diary::review_logger::DefaultReviewLogger,
|
||||
test_helpers::TestContextBuilder,
|
||||
diary::commands::LogReviewCommand, diary::log_review,
|
||||
diary::review_logger::DefaultReviewLogger, test_helpers::TestContextBuilder,
|
||||
};
|
||||
|
||||
fn build_logger(
|
||||
@@ -32,26 +26,6 @@ fn build_logger(
|
||||
))
|
||||
}
|
||||
|
||||
fn movie_input_manual(title: &str, year: u16) -> MovieInput {
|
||||
MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some(title.to_string()),
|
||||
manual_release_year: Some(year),
|
||||
manual_director: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn movie_input_by_id(id: uuid::Uuid) -> MovieInput {
|
||||
MovieInput {
|
||||
movie_id: Some(id),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_log_review_creates_movie_and_review() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
@@ -60,15 +34,7 @@ async fn test_log_review_creates_movie_and_review() {
|
||||
let logger = build_logger(&movies, &reviews, &events);
|
||||
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
let cmd = LogReviewCommand {
|
||||
user_id,
|
||||
input: movie_input_manual("Blade Runner", 1982),
|
||||
rating: 4,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
};
|
||||
let cmd = log_review_command_manual(user_id, "Blade Runner", 1982);
|
||||
|
||||
log_review::execute(&logger, cmd).await.unwrap();
|
||||
|
||||
@@ -81,13 +47,7 @@ async fn test_log_review_reuses_existing_movie() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
let reviews = InMemoryReviewRepository::new();
|
||||
|
||||
let existing_movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Alien".into()).unwrap(),
|
||||
ReleaseYear::new(1979).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let existing_movie = test_movie_with_year("Alien", 1979);
|
||||
let movie_uuid = existing_movie.id().value();
|
||||
movies.upsert_movie(&existing_movie).await.unwrap();
|
||||
|
||||
@@ -95,13 +55,8 @@ async fn test_log_review_reuses_existing_movie() {
|
||||
let logger = build_logger(&movies, &reviews, &events);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: uuid::Uuid::new_v4(),
|
||||
input: movie_input_by_id(movie_uuid),
|
||||
rating: 5,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
..log_review_command(uuid::Uuid::new_v4(), movie_uuid)
|
||||
};
|
||||
|
||||
log_review::execute(&logger, cmd).await.unwrap();
|
||||
@@ -118,13 +73,8 @@ async fn test_log_review_with_invalid_rating_fails() {
|
||||
let logger = build_logger(&movies, &reviews, &events);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: uuid::Uuid::new_v4(),
|
||||
input: movie_input_manual("Some Film", 2000),
|
||||
rating: 6,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
..log_review_command_manual(uuid::Uuid::new_v4(), "Some Film", 2000)
|
||||
};
|
||||
let result = log_review::execute(&logger, cmd).await;
|
||||
assert!(result.is_err(), "rating > 5 should fail");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::diary::commands::MovieInput;
|
||||
use domain::testing::test_movie_with_year;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{MetadataSearchCriteria, Movie},
|
||||
@@ -17,16 +18,6 @@ fn make_input(ext_id: Option<&str>, title: Option<&str>, year: Option<u16>) -> M
|
||||
}
|
||||
}
|
||||
|
||||
fn make_movie() -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Inception".to_string()).unwrap(),
|
||||
ReleaseYear::new(2010).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
struct RepoWithExternalMovie(Movie);
|
||||
struct RepoEmpty;
|
||||
struct RepoWithTitleMatch(Movie);
|
||||
@@ -213,7 +204,7 @@ fn external_id_strategy_cannot_handle_cmd_without_id() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_id_strategy_returns_cached_movie() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Inception", 2010);
|
||||
let repo = RepoWithExternalMovie(movie.clone());
|
||||
let meta = MetaErrors;
|
||||
let deps = MovieResolverDeps {
|
||||
@@ -227,7 +218,7 @@ async fn external_id_strategy_returns_cached_movie() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_id_strategy_fetches_from_metadata_when_not_cached() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Inception", 2010);
|
||||
let repo = RepoEmpty;
|
||||
let meta = MetaReturnsMovie(movie);
|
||||
let deps = MovieResolverDeps {
|
||||
@@ -268,7 +259,7 @@ fn title_strategy_cannot_handle_cmd_without_title() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn title_strategy_fetches_from_metadata() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Inception", 2010);
|
||||
let repo = RepoEmpty;
|
||||
let meta = MetaReturnsMovie(movie);
|
||||
let deps = MovieResolverDeps {
|
||||
@@ -309,7 +300,7 @@ fn manual_strategy_cannot_handle_cmd_without_title() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn manual_strategy_returns_existing_movie() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Inception", 2010);
|
||||
let repo = RepoWithTitleMatch(movie.clone());
|
||||
let meta = MetaErrors;
|
||||
let deps = MovieResolverDeps {
|
||||
@@ -365,7 +356,7 @@ async fn resolver_returns_error_when_no_strategy_matches() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn resolver_uses_cached_movie_when_external_id_matches() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Inception", 2010);
|
||||
let repo = RepoWithExternalMovie(movie.clone());
|
||||
let meta = MetaErrors;
|
||||
let deps = MovieResolverDeps {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::test_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 chrono::Utc;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::WatchlistEntry,
|
||||
@@ -11,11 +12,11 @@ use domain::{
|
||||
FakeMetadataClient, InMemoryMovieRepository, InMemoryReviewRepository,
|
||||
InMemoryWatchlistRepository, NoopEventPublisher,
|
||||
},
|
||||
value_objects::{ExternalMetadataId, MovieId, MovieTitle, PosterUrl, ReleaseYear, UserId},
|
||||
value_objects::{ExternalMetadataId, MovieId, PosterUrl, UserId},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::diary::commands::{LogReviewCommand, MovieInput};
|
||||
use crate::diary::commands::LogReviewCommand;
|
||||
use crate::diary::review_logger::DefaultReviewLogger;
|
||||
use crate::ports::ReviewLogger;
|
||||
|
||||
@@ -44,21 +45,7 @@ async fn logs_review_with_manual_movie() {
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let uid = Uuid::new_v4();
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: uid,
|
||||
input: MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some("Test Film".into()),
|
||||
manual_release_year: Some(2024),
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 4,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
};
|
||||
let cmd = log_review_command_manual(uid, "Test Film", 2024);
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
@@ -78,13 +65,7 @@ async fn removes_from_watchlist_on_review() {
|
||||
let user_id = UserId::from_uuid(uid);
|
||||
|
||||
// Create and store movie
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Watchlisted Film".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Watchlisted Film", 2024);
|
||||
let movie_id = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
@@ -95,19 +76,8 @@ async fn removes_from_watchlist_on_review() {
|
||||
|
||||
// Log review for same movie
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: uid,
|
||||
input: MovieInput {
|
||||
movie_id: Some(movie_id),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 5,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
..log_review_command(uid, movie_id)
|
||||
};
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
@@ -123,30 +93,13 @@ async fn logs_review_with_existing_movie_by_id() {
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Existing Film".into()).unwrap(),
|
||||
ReleaseYear::new(2020).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Existing Film", 2020);
|
||||
let movie_uuid = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: Some(movie_uuid),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 3,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
..log_review_command(Uuid::new_v4(), movie_uuid)
|
||||
};
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
@@ -163,21 +116,7 @@ async fn existing_movie_not_found_returns_error() {
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: Some(Uuid::new_v4()),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 4,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
};
|
||||
let cmd = log_review_command(Uuid::new_v4(), Uuid::new_v4());
|
||||
|
||||
assert!(logger.log_review(cmd).await.is_err());
|
||||
}
|
||||
@@ -191,19 +130,8 @@ async fn invalid_rating_returns_error() {
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some("Film".into()),
|
||||
manual_release_year: Some(2024),
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 6,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
..log_review_command_manual(Uuid::new_v4(), "Film", 2024)
|
||||
};
|
||||
|
||||
let result = logger.log_review(cmd).await;
|
||||
@@ -221,21 +149,7 @@ async fn watchlist_not_present_does_not_publish_removed() {
|
||||
let events = NoopEventPublisher::new();
|
||||
let logger = make_logger(&movies, &reviews, &watchlist, &events);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some("No Watchlist Film".into()),
|
||||
manual_release_year: Some(2024),
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 4,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
};
|
||||
let cmd = log_review_command_manual(Uuid::new_v4(), "No Watchlist Film", 2024);
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
@@ -256,12 +170,10 @@ impl MetadataClient for MetadataClientWithExternalId {
|
||||
&self,
|
||||
_criteria: &MetadataSearchCriteria,
|
||||
) -> Result<Movie, DomainError> {
|
||||
Ok(Movie::new(
|
||||
Some(ExternalMetadataId::new("tmdb:99999".into()).unwrap()),
|
||||
MovieTitle::new("Discovered Film".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
Ok(test_movie_with_external_id(
|
||||
"Discovered Film",
|
||||
2024,
|
||||
ExternalMetadataId::new("tmdb:99999".into()).unwrap(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -290,19 +202,8 @@ async fn publishes_movie_discovered_for_new_movie_with_external_id() {
|
||||
);
|
||||
|
||||
let cmd = LogReviewCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some("Discovered Film".into()),
|
||||
manual_release_year: Some(2024),
|
||||
manual_director: None,
|
||||
},
|
||||
rating: 5,
|
||||
comment: None,
|
||||
watched_at: Utc::now().naive_utc(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
..log_review_command_manual(Uuid::new_v4(), "Discovered Film", 2024)
|
||||
};
|
||||
|
||||
logger.log_review(cmd).await.unwrap();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::test_support::create_goal_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::events::DomainEvent;
|
||||
use domain::testing::{FakeStatsRepository, InMemoryGoalRepository, NoopEventPublisher};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::goals::create;
|
||||
use crate::goals::deps::GoalCommandDeps;
|
||||
use crate::goals::{commands::CreateGoalCommand, create};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -20,16 +21,9 @@ async fn creates_goal_and_returns_progress() {
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
|
||||
let result = create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 50,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = create::execute(&deps, create_goal_command(2025, 50))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.goal.year(), 2025);
|
||||
assert_eq!(result.goal.target_count(), 50);
|
||||
@@ -50,16 +44,9 @@ async fn creates_goal_with_review_count() {
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
|
||||
let result = create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 50,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = create::execute(&deps, create_goal_command(2025, 50))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.current_count, 5);
|
||||
assert_eq!(goals.count(), 1);
|
||||
@@ -76,16 +63,9 @@ async fn emits_goal_created_event() {
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
|
||||
create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
create::execute(&deps, create_goal_command(2025, 10))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let published = events.published();
|
||||
assert!(
|
||||
@@ -104,23 +84,11 @@ async fn rejects_duplicate_year() {
|
||||
stats: b.stats_repo.clone(),
|
||||
event_publisher: b.event_publisher.clone(),
|
||||
};
|
||||
let cmd = CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 10,
|
||||
};
|
||||
let cmd = create_goal_command(2025, 10);
|
||||
|
||||
create::execute(&deps, cmd).await.unwrap();
|
||||
|
||||
let result = create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 20,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = create::execute(&deps, create_goal_command(2025, 20)).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -134,15 +102,7 @@ async fn rejects_year_before_2020() {
|
||||
stats: b.stats_repo.clone(),
|
||||
event_publisher: b.event_publisher.clone(),
|
||||
};
|
||||
let result = create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2019,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = create::execute(&deps, create_goal_command(2019, 10)).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -156,15 +116,7 @@ async fn rejects_zero_target() {
|
||||
stats: b.stats_repo.clone(),
|
||||
event_publisher: b.event_publisher.clone(),
|
||||
};
|
||||
let result = create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = create::execute(&deps, create_goal_command(2025, 0)).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
use crate::test_support::create_goal_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::testing::{FakeStatsRepository, InMemoryGoalRepository, NoopEventPublisher};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::goals::deps::GoalCommandDeps;
|
||||
use crate::goals::{
|
||||
commands::{CreateGoalCommand, DeleteGoalCommand},
|
||||
create, delete,
|
||||
};
|
||||
use crate::goals::{commands::DeleteGoalCommand, create, delete};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -22,16 +20,9 @@ async fn deletes_existing_goal() {
|
||||
event_publisher: Arc::clone(&events) as _,
|
||||
};
|
||||
|
||||
create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
create::execute(&deps, create_goal_command(2025, 10))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(goals.count(), 1);
|
||||
|
||||
delete::execute(
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::test_support::create_goal_command;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::goals::deps::{GoalCommandDeps, GoalQueryDeps};
|
||||
use crate::goals::{commands::CreateGoalCommand, create, get, queries::GetGoalQuery};
|
||||
use crate::goals::{create, get, queries::GetGoalQuery};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -18,16 +19,9 @@ async fn returns_goal_when_exists() {
|
||||
stats: b.stats_repo.clone(),
|
||||
};
|
||||
|
||||
create::execute(
|
||||
&cmd_deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 50,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
create::execute(&cmd_deps, create_goal_command(2025, 50))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = get::execute(
|
||||
&query_deps,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::test_support::create_goal_command;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::goals::deps::{GoalCommandDeps, GoalQueryDeps};
|
||||
use crate::goals::{commands::CreateGoalCommand, create, list, queries::ListGoalsQuery};
|
||||
use crate::goals::{create, list, queries::ListGoalsQuery};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -38,16 +39,9 @@ async fn returns_all_goals_for_user() {
|
||||
};
|
||||
|
||||
for year in [2023, 2024, 2025] {
|
||||
create::execute(
|
||||
&cmd_deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
create::execute(&cmd_deps, create_goal_command(year, 10))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let result = list::execute(
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
use uuid::Uuid;
|
||||
use crate::test_support::{create_goal_command, update_goal_command};
|
||||
|
||||
use crate::goals::deps::GoalCommandDeps;
|
||||
use crate::goals::{
|
||||
commands::{CreateGoalCommand, UpdateGoalCommand},
|
||||
create, update,
|
||||
};
|
||||
use crate::goals::{create, update};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -17,27 +14,13 @@ async fn updates_target_count() {
|
||||
event_publisher: b.event_publisher.clone(),
|
||||
};
|
||||
|
||||
create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
create::execute(&deps, create_goal_command(2025, 10))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = update::execute(
|
||||
&deps,
|
||||
UpdateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 100,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = update::execute(&deps, update_goal_command(2025, 100))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.goal.target_count(), 100);
|
||||
}
|
||||
@@ -51,15 +34,7 @@ async fn fails_when_goal_not_found() {
|
||||
stats: b.stats_repo.clone(),
|
||||
event_publisher: b.event_publisher.clone(),
|
||||
};
|
||||
let result = update::execute(
|
||||
&deps,
|
||||
UpdateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = update::execute(&deps, update_goal_command(2025, 10)).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -74,26 +49,11 @@ async fn rejects_zero_target() {
|
||||
event_publisher: b.event_publisher.clone(),
|
||||
};
|
||||
|
||||
create::execute(
|
||||
&deps,
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 10,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
create::execute(&deps, create_goal_command(2025, 10))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = update::execute(
|
||||
&deps,
|
||||
UpdateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year: 2025,
|
||||
target_count: 0,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = update::execute(&deps, update_goal_command(2025, 0)).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
use crate::test_support::{apply_import_mapping_command, create_import_session_command};
|
||||
use domain::testing::{test_movie_with_external_id, test_movie_with_year};
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::{
|
||||
models::{
|
||||
AnnotatedRow, Movie,
|
||||
AnnotatedRow,
|
||||
import::{ImportRow, ParsedFile, RowResult},
|
||||
},
|
||||
ports::{DocumentParser, MovieCommand},
|
||||
testing::{InMemoryImportSessionRepository, InMemoryMovieRepository},
|
||||
value_objects::{ExternalMetadataId, MovieTitle, ReleaseYear},
|
||||
value_objects::ExternalMetadataId,
|
||||
};
|
||||
|
||||
use crate::import::deps::{ApplyMappingDeps, CreateSessionDeps};
|
||||
use crate::import::{
|
||||
apply_mapping,
|
||||
commands::{ApplyImportMappingCommand, CreateImportSessionCommand},
|
||||
create_session,
|
||||
};
|
||||
use crate::import::{apply_mapping, create_session};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -33,11 +31,7 @@ async fn applies_mapping_to_session() {
|
||||
|
||||
let session = create_session::execute(
|
||||
&create_deps,
|
||||
CreateImportSessionCommand {
|
||||
user_id,
|
||||
bytes: b"title\nTest".to_vec(),
|
||||
format: domain::models::FileFormat::Csv,
|
||||
},
|
||||
create_import_session_command(user_id, b"title\nTest"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -50,11 +44,7 @@ async fn applies_mapping_to_session() {
|
||||
|
||||
let rows = apply_mapping::execute(
|
||||
&mapping_deps,
|
||||
ApplyImportMappingCommand {
|
||||
user_id,
|
||||
session_id: session.session_id.value(),
|
||||
mappings: vec![],
|
||||
},
|
||||
apply_import_mapping_command(user_id, session.session_id.value()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -75,11 +65,7 @@ async fn fails_when_session_not_found() {
|
||||
|
||||
let result = apply_mapping::execute(
|
||||
&deps,
|
||||
ApplyImportMappingCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
session_id: Uuid::new_v4(),
|
||||
mappings: vec![],
|
||||
},
|
||||
apply_import_mapping_command(Uuid::new_v4(), Uuid::new_v4()),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -123,13 +109,7 @@ async fn marks_duplicate_by_external_id() {
|
||||
let sessions = InMemoryImportSessionRepository::new();
|
||||
|
||||
let ext_id = ExternalMetadataId::new("tt1234567".into()).unwrap();
|
||||
let movie = Movie::new(
|
||||
Some(ext_id),
|
||||
MovieTitle::new("Known Movie".into()).unwrap(),
|
||||
ReleaseYear::new(2020).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_external_id("Known Movie", 2020, ext_id);
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
let parser = Arc::new(DuplicateTestParser {
|
||||
@@ -150,11 +130,7 @@ async fn marks_duplicate_by_external_id() {
|
||||
|
||||
let session = create_session::execute(
|
||||
&create_deps,
|
||||
CreateImportSessionCommand {
|
||||
user_id,
|
||||
bytes: b"title\nKnown Movie".to_vec(),
|
||||
format: domain::models::FileFormat::Csv,
|
||||
},
|
||||
create_import_session_command(user_id, b"title\nKnown Movie"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -167,11 +143,7 @@ async fn marks_duplicate_by_external_id() {
|
||||
|
||||
let rows = apply_mapping::execute(
|
||||
&mapping_deps,
|
||||
ApplyImportMappingCommand {
|
||||
user_id,
|
||||
session_id: session.session_id.value(),
|
||||
mappings: vec![],
|
||||
},
|
||||
apply_import_mapping_command(user_id, session.session_id.value()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -185,13 +157,7 @@ async fn marks_duplicate_by_title_and_year() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
let sessions = InMemoryImportSessionRepository::new();
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Duplicate Film".into()).unwrap(),
|
||||
ReleaseYear::new(2022).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Duplicate Film", 2022);
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
let parser = Arc::new(DuplicateTestParser {
|
||||
@@ -211,11 +177,7 @@ async fn marks_duplicate_by_title_and_year() {
|
||||
|
||||
let session = create_session::execute(
|
||||
&create_deps,
|
||||
CreateImportSessionCommand {
|
||||
user_id,
|
||||
bytes: b"title\nDuplicate Film".to_vec(),
|
||||
format: domain::models::FileFormat::Csv,
|
||||
},
|
||||
create_import_session_command(user_id, b"title\nDuplicate Film"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -228,11 +190,7 @@ async fn marks_duplicate_by_title_and_year() {
|
||||
|
||||
let rows = apply_mapping::execute(
|
||||
&mapping_deps,
|
||||
ApplyImportMappingCommand {
|
||||
user_id,
|
||||
session_id: session.session_id.value(),
|
||||
mappings: vec![],
|
||||
},
|
||||
apply_import_mapping_command(user_id, session.session_id.value()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::apply_import_profile_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
@@ -7,8 +8,8 @@ use domain::testing::{InMemoryImportProfileRepository, InMemoryImportSessionRepo
|
||||
use domain::value_objects::{ImportProfileId, UserId};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::import::apply_profile;
|
||||
use crate::import::deps::ApplyProfileDeps;
|
||||
use crate::import::{apply_profile, commands::ApplyImportProfileCommand};
|
||||
|
||||
#[tokio::test]
|
||||
async fn fails_when_profile_not_found() {
|
||||
@@ -22,11 +23,7 @@ async fn fails_when_profile_not_found() {
|
||||
|
||||
let result = apply_profile::execute(
|
||||
&deps,
|
||||
ApplyImportProfileCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
session_id: Uuid::new_v4(),
|
||||
profile_id: Uuid::new_v4(),
|
||||
},
|
||||
apply_import_profile_command(Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -56,11 +53,7 @@ async fn fails_when_session_not_found() {
|
||||
|
||||
let result = apply_profile::execute(
|
||||
&deps,
|
||||
ApplyImportProfileCommand {
|
||||
user_id,
|
||||
session_id: Uuid::new_v4(),
|
||||
profile_id: profile_id.value(),
|
||||
},
|
||||
apply_import_profile_command(user_id, Uuid::new_v4(), profile_id.value()),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -98,11 +91,7 @@ async fn applies_profile_mappings_to_session() {
|
||||
|
||||
apply_profile::execute(
|
||||
&deps,
|
||||
ApplyImportProfileCommand {
|
||||
user_id,
|
||||
session_id: session_id.value(),
|
||||
profile_id: profile_id.value(),
|
||||
},
|
||||
apply_import_profile_command(user_id, session_id.value(), profile_id.value()),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
use crate::test_support::create_import_session_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::models::import::{DomainField, Transform};
|
||||
use domain::models::{FieldMapping, FileFormat, ImportProfile};
|
||||
use domain::models::{FieldMapping, ImportProfile};
|
||||
use domain::ports::{ImportProfileRepository, ImportSessionRepository};
|
||||
use domain::testing::{InMemoryImportProfileRepository, InMemoryImportSessionRepository};
|
||||
use domain::value_objects::{ImportProfileId, UserId};
|
||||
|
||||
use crate::import::deps::{ApplyProfileAndMapDeps, CreateSessionDeps};
|
||||
use crate::import::{
|
||||
apply_profile_and_map, commands::ApplyProfileAndMapCommand,
|
||||
commands::CreateImportSessionCommand, create_session,
|
||||
};
|
||||
use crate::import::{apply_profile_and_map, commands::ApplyProfileAndMapCommand, create_session};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -69,11 +67,7 @@ async fn applies_profile_then_regenerates_mapping() {
|
||||
};
|
||||
let created = create_session::execute(
|
||||
&create_deps,
|
||||
CreateImportSessionCommand {
|
||||
user_id,
|
||||
bytes: b"title\nTest".to_vec(),
|
||||
format: FileFormat::Csv,
|
||||
},
|
||||
create_import_session_command(user_id, b"title\nTest"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::test_support::create_import_session_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::testing::InMemoryImportSessionRepository;
|
||||
|
||||
use crate::import::create_session;
|
||||
use crate::import::deps::CreateSessionDeps;
|
||||
use crate::import::{commands::CreateImportSessionCommand, create_session};
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
|
||||
#[tokio::test]
|
||||
@@ -20,11 +21,7 @@ async fn creates_session_with_parsed_file() {
|
||||
|
||||
let result = create_session::execute(
|
||||
&deps,
|
||||
CreateImportSessionCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
bytes: b"col1\nval1".to_vec(),
|
||||
format: domain::models::FileFormat::Csv,
|
||||
},
|
||||
create_import_session_command(Uuid::new_v4(), b"col1\nval1"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::execute_import_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::{AnnotatedRow, ImportSession, import::RowResult};
|
||||
@@ -59,9 +60,8 @@ async fn imports_confirmed_rows() {
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0, 1],
|
||||
..execute_import_command(uid, sid.value())
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -86,16 +86,9 @@ async fn skips_unconfirmed_rows() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 1);
|
||||
assert_eq!(result.skipped_duplicates, 1);
|
||||
@@ -113,9 +106,8 @@ async fn fails_when_session_not_found() {
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
session_id: Uuid::new_v4(),
|
||||
confirmed_indices: vec![],
|
||||
..execute_import_command(Uuid::new_v4(), Uuid::new_v4())
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -149,16 +141,9 @@ async fn handles_datetime_format() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 1);
|
||||
assert!(result.failed.is_empty());
|
||||
@@ -190,16 +175,9 @@ async fn fails_on_invalid_rating() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 0);
|
||||
assert_eq!(result.failed.len(), 1);
|
||||
@@ -231,16 +209,9 @@ async fn fails_on_missing_watched_at() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 0);
|
||||
assert_eq!(result.failed.len(), 1);
|
||||
@@ -272,16 +243,9 @@ async fn imports_row_with_external_metadata_id() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 1);
|
||||
assert!(result.failed.is_empty());
|
||||
@@ -313,16 +277,9 @@ async fn imports_row_with_director_and_comment() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 1);
|
||||
assert!(result.failed.is_empty());
|
||||
@@ -354,16 +311,9 @@ async fn handles_space_separated_datetime_format() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 1);
|
||||
assert!(result.failed.is_empty());
|
||||
@@ -390,16 +340,9 @@ async fn reports_invalid_row_result_errors() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 0);
|
||||
assert_eq!(result.failed.len(), 1);
|
||||
@@ -433,16 +376,9 @@ async fn fails_on_missing_rating() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 0);
|
||||
assert_eq!(result.failed.len(), 1);
|
||||
@@ -475,16 +411,9 @@ async fn fails_on_unparseable_date() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 0);
|
||||
assert_eq!(result.failed.len(), 1);
|
||||
@@ -517,16 +446,9 @@ async fn imports_row_without_release_year() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result = execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.imported, 1);
|
||||
assert!(result.failed.is_empty());
|
||||
@@ -547,16 +469,9 @@ async fn deletes_session_after_import() {
|
||||
review_logger: Arc::new(NoopReviewLogger),
|
||||
};
|
||||
|
||||
execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices: vec![0],
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
execute::execute(&deps, execute_import_command(uid, sid.value()))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
sessions.count(),
|
||||
@@ -599,9 +514,8 @@ async fn imports_more_rows_than_concurrency_limit() {
|
||||
let result = execute::execute(
|
||||
&deps,
|
||||
ExecuteImportCommand {
|
||||
user_id: uid,
|
||||
session_id: sid.value(),
|
||||
confirmed_indices,
|
||||
..execute_import_command(uid, sid.value())
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::confirm_watch_events_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::{WatchEvent, WatchEventSource};
|
||||
@@ -45,14 +46,7 @@ async fn confirms_watch_event_via_review_logger() {
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id: uid,
|
||||
confirmations: vec![WatchEventConfirmation {
|
||||
watch_event_id: event_id,
|
||||
rating: 4,
|
||||
comment: None,
|
||||
}],
|
||||
},
|
||||
confirm_watch_events_command(uid, event_id, 4),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -131,14 +125,7 @@ async fn rejects_other_users_event() {
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id: intruder,
|
||||
confirmations: vec![WatchEventConfirmation {
|
||||
watch_event_id: event_id,
|
||||
rating: 3,
|
||||
comment: None,
|
||||
}],
|
||||
},
|
||||
confirm_watch_events_command(intruder, event_id, 3),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -151,14 +138,7 @@ async fn fails_when_event_not_found() {
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
confirmations: vec![WatchEventConfirmation {
|
||||
watch_event_id: Uuid::new_v4(),
|
||||
rating: 4,
|
||||
comment: None,
|
||||
}],
|
||||
},
|
||||
confirm_watch_events_command(Uuid::new_v4(), Uuid::new_v4(), 4),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -211,14 +191,7 @@ async fn confirms_event_with_movie_id() {
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, review_logger),
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id: uid,
|
||||
confirmations: vec![WatchEventConfirmation {
|
||||
watch_event_id: event_id,
|
||||
rating: 4,
|
||||
comment: None,
|
||||
}],
|
||||
},
|
||||
confirm_watch_events_command(uid, event_id, 4),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -293,7 +266,6 @@ async fn confirms_multiple_events() {
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id: uid,
|
||||
confirmations: vec![
|
||||
WatchEventConfirmation {
|
||||
watch_event_id: id1,
|
||||
@@ -306,6 +278,7 @@ async fn confirms_multiple_events() {
|
||||
comment: None,
|
||||
},
|
||||
],
|
||||
..confirm_watch_events_command(uid, id1, 3)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -333,14 +306,7 @@ async fn confirms_event_without_year() {
|
||||
|
||||
let result = confirm::execute(
|
||||
&deps(&watch_events, noop_logger()),
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id: uid,
|
||||
confirmations: vec![WatchEventConfirmation {
|
||||
watch_event_id: event_id,
|
||||
rating: 3,
|
||||
comment: None,
|
||||
}],
|
||||
},
|
||||
confirm_watch_events_command(uid, event_id, 3),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::dismiss_watch_events_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::{WatchEvent, WatchEventSource};
|
||||
@@ -7,7 +8,7 @@ use domain::value_objects::UserId;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::integrations::deps::DismissWatchEventsDeps;
|
||||
use crate::integrations::{commands::DismissWatchEventsCommand, dismiss};
|
||||
use crate::integrations::dismiss;
|
||||
|
||||
fn deps(watch_events: &Arc<InMemoryWatchEventRepository>) -> DismissWatchEventsDeps {
|
||||
DismissWatchEventsDeps {
|
||||
@@ -22,10 +23,7 @@ async fn dismisses_empty_list_returns_zero() {
|
||||
|
||||
let result = dismiss::execute(
|
||||
&deps(&events),
|
||||
DismissWatchEventsCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
event_ids: vec![],
|
||||
},
|
||||
dismiss_watch_events_command(Uuid::new_v4(), vec![]),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -39,10 +37,7 @@ async fn fails_when_event_not_found() {
|
||||
|
||||
let result = dismiss::execute(
|
||||
&deps(&events),
|
||||
DismissWatchEventsCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
event_ids: vec![Uuid::new_v4()],
|
||||
},
|
||||
dismiss_watch_events_command(Uuid::new_v4(), vec![Uuid::new_v4()]),
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -80,10 +75,7 @@ async fn dismisses_existing_events() {
|
||||
|
||||
let result = dismiss::execute(
|
||||
&deps(&watch_events),
|
||||
DismissWatchEventsCommand {
|
||||
user_id: uid,
|
||||
event_ids: vec![id1, id2],
|
||||
},
|
||||
dismiss_watch_events_command(uid, vec![id1, id2]),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::test_support::generate_webhook_token_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::WatchEventSource;
|
||||
use domain::ports::WebhookTokenRepository;
|
||||
use domain::testing::InMemoryWebhookTokenRepository;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::integrations::deps::GenerateWebhookTokenDeps;
|
||||
use crate::integrations::{commands::GenerateWebhookTokenCommand, generate_token};
|
||||
use crate::integrations::generate_token;
|
||||
|
||||
#[tokio::test]
|
||||
async fn generates_token_and_saves() {
|
||||
@@ -17,11 +17,7 @@ async fn generates_token_and_saves() {
|
||||
&GenerateWebhookTokenDeps {
|
||||
webhook_token: Arc::clone(&tokens),
|
||||
},
|
||||
GenerateWebhookTokenCommand {
|
||||
user_id,
|
||||
provider: WatchEventSource::Jellyfin,
|
||||
label: None,
|
||||
},
|
||||
generate_webhook_token_command(user_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::generate_webhook_token_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::WatchEventSource;
|
||||
@@ -47,11 +48,7 @@ async fn returns_tokens_after_generate() {
|
||||
|
||||
generate_token::execute(
|
||||
&generate_deps(&tokens),
|
||||
GenerateWebhookTokenCommand {
|
||||
user_id,
|
||||
provider: WatchEventSource::Jellyfin,
|
||||
label: None,
|
||||
},
|
||||
generate_webhook_token_command(user_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -59,9 +56,9 @@ async fn returns_tokens_after_generate() {
|
||||
generate_token::execute(
|
||||
&generate_deps(&tokens),
|
||||
GenerateWebhookTokenCommand {
|
||||
user_id,
|
||||
provider: WatchEventSource::Plex,
|
||||
label: Some("living room".into()),
|
||||
..generate_webhook_token_command(user_id)
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::generate_webhook_token_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::WatchEventSource;
|
||||
@@ -7,7 +8,7 @@ use domain::testing::{
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::integrations::commands::{GenerateWebhookTokenCommand, IngestWatchEventCommand};
|
||||
use crate::integrations::commands::IngestWatchEventCommand;
|
||||
use crate::integrations::deps::{GenerateWebhookTokenDeps, IngestWatchEventDeps};
|
||||
use crate::integrations::{generate_token, ingest};
|
||||
|
||||
@@ -38,11 +39,7 @@ async fn ingests_watch_event() {
|
||||
&GenerateWebhookTokenDeps {
|
||||
webhook_token: Arc::clone(&tokens),
|
||||
},
|
||||
GenerateWebhookTokenCommand {
|
||||
user_id,
|
||||
provider: WatchEventSource::Jellyfin,
|
||||
label: None,
|
||||
},
|
||||
generate_webhook_token_command(user_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::test_support::generate_webhook_token_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::WatchEventSource;
|
||||
use domain::ports::WebhookTokenRepository;
|
||||
use domain::testing::InMemoryWebhookTokenRepository;
|
||||
use uuid::Uuid;
|
||||
@@ -9,10 +9,8 @@ use crate::integrations::deps::{
|
||||
GenerateWebhookTokenDeps, GetWebhookTokensDeps, RevokeWebhookTokenDeps,
|
||||
};
|
||||
use crate::integrations::{
|
||||
commands::{GenerateWebhookTokenCommand, RevokeWebhookTokenCommand},
|
||||
generate_token, get_tokens,
|
||||
queries::GetWebhookTokensQuery,
|
||||
revoke_token,
|
||||
commands::RevokeWebhookTokenCommand, generate_token, get_tokens,
|
||||
queries::GetWebhookTokensQuery, revoke_token,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
@@ -25,11 +23,7 @@ async fn revokes_existing_token() {
|
||||
&GenerateWebhookTokenDeps {
|
||||
webhook_token: Arc::clone(&tokens),
|
||||
},
|
||||
GenerateWebhookTokenCommand {
|
||||
user_id,
|
||||
provider: WatchEventSource::Jellyfin,
|
||||
label: None,
|
||||
},
|
||||
generate_webhook_token_command(user_id),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -22,6 +22,9 @@ pub mod wrapup;
|
||||
#[cfg(test)]
|
||||
pub mod test_helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_support;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/services.rs"]
|
||||
mod services_tests;
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use domain::testing::test_movie_with_year;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::Utc;
|
||||
use domain::{
|
||||
models::{Movie, MovieProfile},
|
||||
models::MovieProfile,
|
||||
ports::MovieCommand,
|
||||
testing::{
|
||||
FakeSearchCommand, InMemoryMovieProfileRepository, InMemoryMovieRepository,
|
||||
PanicPersonCommand,
|
||||
},
|
||||
value_objects::{MovieId, MovieTitle, ReleaseYear},
|
||||
value_objects::MovieId,
|
||||
};
|
||||
|
||||
use crate::movies::{commands::EnrichMovieCommand, deps::EnrichMovieDeps, enrich_movie};
|
||||
@@ -19,13 +20,7 @@ async fn stores_profile_and_indexes() {
|
||||
let profile_repo = InMemoryMovieProfileRepository::new();
|
||||
// PanicPersonCommand is safe here — empty cast/crew means upsert_batch is never called
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Test".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let movie_id = MovieId::from_uuid(movie.id().value());
|
||||
movie_repo.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
@@ -99,13 +94,7 @@ async fn extracts_and_indexes_persons() {
|
||||
let movie_repo = InMemoryMovieRepository::new();
|
||||
let profile_repo = InMemoryMovieProfileRepository::new();
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Cast Movie".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Cast Movie", 2024);
|
||||
let movie_id = MovieId::from_uuid(movie.id().value());
|
||||
movie_repo.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::test_support::sync_poster_command;
|
||||
use domain::testing::{test_movie_with_external_id, test_movie_with_year};
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
@@ -10,13 +12,10 @@ use domain::{
|
||||
FakeSearchCommand, InMemoryMovieProfileRepository, InMemoryMovieRepository,
|
||||
NoopEventPublisher, NoopObjectStorage,
|
||||
},
|
||||
value_objects::{ExternalMetadataId, MovieTitle, PosterUrl, ReleaseYear},
|
||||
value_objects::{ExternalMetadataId, PosterUrl},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
diary::commands::SyncPosterCommand,
|
||||
movies::{deps::SyncPosterDeps, sync_poster},
|
||||
};
|
||||
use crate::movies::{deps::SyncPosterDeps, sync_poster};
|
||||
|
||||
fn default_deps() -> SyncPosterDeps {
|
||||
SyncPosterDeps {
|
||||
@@ -35,13 +34,7 @@ fn default_deps() -> SyncPosterDeps {
|
||||
async fn fails_when_movie_not_found() {
|
||||
let deps = default_deps();
|
||||
|
||||
let result = sync_poster::execute(
|
||||
&deps,
|
||||
SyncPosterCommand {
|
||||
movie_id: Uuid::new_v4(),
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let result = sync_poster::execute(&deps, sync_poster_command(Uuid::new_v4())).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -49,13 +42,7 @@ async fn fails_when_movie_not_found() {
|
||||
#[tokio::test]
|
||||
async fn fails_when_no_external_id() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Test".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let movie_id = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
@@ -65,7 +52,7 @@ async fn fails_when_no_external_id() {
|
||||
..default_deps()
|
||||
};
|
||||
|
||||
let result = sync_poster::execute(&deps, SyncPosterCommand { movie_id }).await;
|
||||
let result = sync_poster::execute(&deps, sync_poster_command(movie_id)).await;
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
@@ -94,13 +81,7 @@ impl MetadataClient for FakeMetaWithPoster {
|
||||
async fn syncs_poster_for_movie_with_external_id() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
let ext_id = ExternalMetadataId::new("tmdb:999".into()).unwrap();
|
||||
let movie = Movie::new(
|
||||
Some(ext_id),
|
||||
MovieTitle::new("Poster Movie".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_external_id("Poster Movie", 2024, ext_id);
|
||||
let movie_id = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
@@ -111,7 +92,7 @@ async fn syncs_poster_for_movie_with_external_id() {
|
||||
..default_deps()
|
||||
};
|
||||
|
||||
sync_poster::execute(&deps, SyncPosterCommand { movie_id })
|
||||
sync_poster::execute(&deps, sync_poster_command(movie_id))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use domain::testing::test_user;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{User, UserRole},
|
||||
ports::{
|
||||
BlockQuery, FollowCommand, FollowGraphQuery, FollowQuery, SocialCommand, UserRepository,
|
||||
},
|
||||
testing::InMemoryUserRepository,
|
||||
value_objects::{
|
||||
Email, FollowRelation, FollowStatus, FollowTarget, InstanceIdentity, PasswordHash,
|
||||
SocialActor, SocialIdentity, UserId, Username,
|
||||
FollowRelation, FollowStatus, FollowTarget, InstanceIdentity, SocialActor, SocialIdentity,
|
||||
UserId,
|
||||
},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
@@ -247,12 +247,7 @@ fn instance() -> InstanceIdentity {
|
||||
}
|
||||
|
||||
async fn register_user(repo: &Arc<InMemoryUserRepository>, username: &str) -> UserId {
|
||||
let user = User::new(
|
||||
Email::new(format!("{username}@example.com")).unwrap(),
|
||||
Username::new(username.to_string()).unwrap(),
|
||||
PasswordHash::new("hashed-password".to_string()).unwrap(),
|
||||
UserRole::Standard,
|
||||
);
|
||||
let user = test_user(username);
|
||||
let id = user.id().clone();
|
||||
repo.save(&user).await.unwrap();
|
||||
id
|
||||
|
||||
240
crates/application/src/test_support.rs
Normal file
240
crates/application/src/test_support.rs
Normal file
@@ -0,0 +1,240 @@
|
||||
//! Command builders for tests. Each builder returns a command with sensible
|
||||
//! defaults; tests override individual fields with struct-update syntax:
|
||||
//! `LogReviewCommand { rating: 6, ..log_review_command(user, movie) }`.
|
||||
//! Moves to `tests/support/mod.rs` when application tests become integration tests.
|
||||
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::models::{FileFormat, UserRole, WatchEventSource};
|
||||
|
||||
use crate::auth::commands::RegisterCommand;
|
||||
use crate::auth::queries::LoginCommand;
|
||||
use crate::diary::commands::{EditReviewCommand, LogReviewCommand, MovieInput, SyncPosterCommand};
|
||||
use crate::goals::commands::{CreateGoalCommand, UpdateGoalCommand};
|
||||
use crate::import::commands::{
|
||||
ApplyImportMappingCommand, ApplyImportProfileCommand, CreateImportSessionCommand,
|
||||
ExecuteImportCommand,
|
||||
};
|
||||
use crate::integrations::commands::{
|
||||
ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand,
|
||||
WatchEventConfirmation,
|
||||
};
|
||||
use crate::users::commands::UpdateProfileCommand;
|
||||
use crate::users::update_settings::UpdateUserSettingsCommand;
|
||||
use crate::watchlist::commands::AddToWatchlistCommand;
|
||||
use crate::wrapup::commands::RequestWrapUpCommand;
|
||||
|
||||
fn watched_at() -> NaiveDateTime {
|
||||
NaiveDate::from_ymd_opt(2024, 6, 15)
|
||||
.unwrap()
|
||||
.and_hms_opt(20, 0, 0)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn movie_input_by_id(movie_id: Uuid) -> MovieInput {
|
||||
MovieInput {
|
||||
movie_id: Some(movie_id),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn movie_input_manual(title: &str, year: u16) -> MovieInput {
|
||||
MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some(title.to_string()),
|
||||
manual_release_year: Some(year),
|
||||
manual_director: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_review_command(user_id: Uuid, movie_id: Uuid) -> LogReviewCommand {
|
||||
LogReviewCommand {
|
||||
user_id,
|
||||
input: movie_input_by_id(movie_id),
|
||||
rating: 4,
|
||||
comment: None,
|
||||
watched_at: watched_at(),
|
||||
watch_medium: None,
|
||||
spoilers: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn log_review_command_manual(user_id: Uuid, title: &str, year: u16) -> LogReviewCommand {
|
||||
LogReviewCommand {
|
||||
input: movie_input_manual(title, year),
|
||||
..log_review_command(user_id, Uuid::nil())
|
||||
}
|
||||
}
|
||||
|
||||
/// Edits only the rating; `review_id` and `requesting_user_id` are random.
|
||||
pub(crate) fn edit_review_command(rating: u8) -> EditReviewCommand {
|
||||
EditReviewCommand {
|
||||
review_id: Uuid::new_v4(),
|
||||
requesting_user_id: Uuid::new_v4(),
|
||||
rating: Some(rating),
|
||||
comment: None,
|
||||
watched_at: None,
|
||||
watch_medium: None,
|
||||
spoilers: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn sync_poster_command(movie_id: Uuid) -> SyncPosterCommand {
|
||||
SyncPosterCommand { movie_id }
|
||||
}
|
||||
|
||||
pub(crate) fn register_command(
|
||||
email: impl Into<String>,
|
||||
username: impl Into<String>,
|
||||
) -> RegisterCommand {
|
||||
RegisterCommand {
|
||||
email: email.into(),
|
||||
username: username.into(),
|
||||
password: "password123".to_string(),
|
||||
role: UserRole::Standard,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn login_command(email: impl Into<String>, password: impl Into<String>) -> LoginCommand {
|
||||
LoginCommand {
|
||||
email: email.into(),
|
||||
password: password.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_goal_command(year: u16, target_count: u32) -> CreateGoalCommand {
|
||||
CreateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year,
|
||||
target_count,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_goal_command(year: u16, target_count: u32) -> UpdateGoalCommand {
|
||||
UpdateGoalCommand {
|
||||
user_id: Uuid::nil(),
|
||||
year,
|
||||
target_count,
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirms indices `[0]`.
|
||||
pub(crate) fn execute_import_command(user_id: Uuid, session_id: Uuid) -> ExecuteImportCommand {
|
||||
ExecuteImportCommand {
|
||||
user_id,
|
||||
session_id,
|
||||
confirmed_indices: vec![0],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn create_import_session_command(
|
||||
user_id: Uuid,
|
||||
csv: &[u8],
|
||||
) -> CreateImportSessionCommand {
|
||||
CreateImportSessionCommand {
|
||||
user_id,
|
||||
bytes: csv.to_vec(),
|
||||
format: FileFormat::Csv,
|
||||
}
|
||||
}
|
||||
|
||||
/// No field mappings.
|
||||
pub(crate) fn apply_import_mapping_command(
|
||||
user_id: Uuid,
|
||||
session_id: Uuid,
|
||||
) -> ApplyImportMappingCommand {
|
||||
ApplyImportMappingCommand {
|
||||
user_id,
|
||||
session_id,
|
||||
mappings: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn apply_import_profile_command(
|
||||
user_id: Uuid,
|
||||
session_id: Uuid,
|
||||
profile_id: Uuid,
|
||||
) -> ApplyImportProfileCommand {
|
||||
ApplyImportProfileCommand {
|
||||
user_id,
|
||||
session_id,
|
||||
profile_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// One confirmation, rating as given, no comment.
|
||||
pub(crate) fn confirm_watch_events_command(
|
||||
user_id: Uuid,
|
||||
watch_event_id: Uuid,
|
||||
rating: u8,
|
||||
) -> ConfirmWatchEventsCommand {
|
||||
ConfirmWatchEventsCommand {
|
||||
user_id,
|
||||
confirmations: vec![WatchEventConfirmation {
|
||||
watch_event_id,
|
||||
rating,
|
||||
comment: None,
|
||||
}],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn dismiss_watch_events_command(
|
||||
user_id: Uuid,
|
||||
event_ids: Vec<Uuid>,
|
||||
) -> DismissWatchEventsCommand {
|
||||
DismissWatchEventsCommand { user_id, event_ids }
|
||||
}
|
||||
|
||||
/// Jellyfin provider, no label.
|
||||
pub(crate) fn generate_webhook_token_command(user_id: Uuid) -> GenerateWebhookTokenCommand {
|
||||
GenerateWebhookTokenCommand {
|
||||
user_id,
|
||||
provider: WatchEventSource::Jellyfin,
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Every field unset; override what the test cares about.
|
||||
pub(crate) fn update_profile_command(user_id: Uuid) -> UpdateProfileCommand {
|
||||
UpdateProfileCommand {
|
||||
user_id,
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// All federation toggles on.
|
||||
pub(crate) fn update_user_settings_command(user_id: Uuid) -> UpdateUserSettingsCommand {
|
||||
UpdateUserSettingsCommand {
|
||||
user_id,
|
||||
federate_goals: true,
|
||||
federate_reviews: true,
|
||||
federate_watchlist: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn add_to_watchlist_command(user_id: Uuid, movie_id: Uuid) -> AddToWatchlistCommand {
|
||||
AddToWatchlistCommand {
|
||||
user_id,
|
||||
input: movie_input_by_id(movie_id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Window is 1 Jan of `year` to 1 Jan of `year + 1`.
|
||||
pub(crate) fn request_wrap_up_command(user_id: Option<Uuid>, year: i32) -> RequestWrapUpCommand {
|
||||
RequestWrapUpCommand {
|
||||
user_id,
|
||||
start_date: NaiveDate::from_ymd_opt(year, 1, 1).unwrap(),
|
||||
end_date: NaiveDate::from_ymd_opt(year + 1, 1, 1).unwrap(),
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::register_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::UserRole;
|
||||
@@ -23,10 +24,8 @@ async fn register_user(
|
||||
register::execute(
|
||||
®_deps,
|
||||
RegisterCommand {
|
||||
email: email.into(),
|
||||
username: username.into(),
|
||||
password: "password123".into(),
|
||||
role,
|
||||
..register_command(email, username)
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::register_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::models::{ProfileField, User, UserProfile, UserRole};
|
||||
@@ -7,7 +8,7 @@ use domain::value_objects::{Email, PasswordHash, UserId, Username};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{commands::RegisterCommand, deps::RegisterDeps, register},
|
||||
auth::{deps::RegisterDeps, register},
|
||||
test_helpers::TestContextBuilder,
|
||||
users::{deps::GetCurrentProfileDeps, get_current_profile, queries::GetCurrentProfileQuery},
|
||||
};
|
||||
@@ -23,17 +24,9 @@ async fn returns_profile_for_existing_user() {
|
||||
config: b.config.clone(),
|
||||
};
|
||||
|
||||
register::execute(
|
||||
®_deps,
|
||||
RegisterCommand {
|
||||
email: "alice@example.com".into(),
|
||||
username: "alice".into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(®_deps, register_command("alice@example.com", "alice"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user = users
|
||||
.find_by_email(&domain::value_objects::Email::new("alice@example.com".into()).unwrap())
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::test_support::{register_command, update_profile_command};
|
||||
use domain::errors::DomainError;
|
||||
use domain::models::UserRole;
|
||||
use domain::value_objects::Email;
|
||||
|
||||
use crate::auth::commands::RegisterCommand;
|
||||
use crate::auth::deps::RegisterDeps;
|
||||
use crate::auth::register;
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
@@ -18,17 +17,9 @@ async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) {
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
&deps,
|
||||
RegisterCommand {
|
||||
email: email.into(),
|
||||
username: username.into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(&deps, register_command(email, username))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn deps(b: &TestContextBuilder) -> GetLocalProfileDeps {
|
||||
@@ -276,14 +267,9 @@ async fn populates_handle_and_actor_url_for_a_local_user() {
|
||||
update_profile::execute(
|
||||
&update_deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: Some("Gabriel K".into()),
|
||||
bio: Some("Movies and diaries.".into()),
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use domain::models::UserRole;
|
||||
use crate::test_support::register_command;
|
||||
use domain::value_objects::{Email, FollowTarget, SocialIdentity, UserId};
|
||||
|
||||
use crate::auth::commands::RegisterCommand;
|
||||
use crate::auth::deps::RegisterDeps;
|
||||
use crate::auth::register;
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
@@ -14,17 +13,9 @@ async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) -> uuid
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
&deps,
|
||||
RegisterCommand {
|
||||
email: email.into(),
|
||||
username: username.into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(&deps, register_command(email, username))
|
||||
.await
|
||||
.unwrap();
|
||||
let user = b
|
||||
.user_repo
|
||||
.find_by_email(&Email::new(email.into()).unwrap())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use domain::models::{ProfileField, UserRole};
|
||||
use crate::test_support::register_command;
|
||||
use domain::models::ProfileField;
|
||||
use domain::value_objects::{Email, UserId};
|
||||
|
||||
use crate::auth::commands::RegisterCommand;
|
||||
use crate::auth::deps::RegisterDeps;
|
||||
use crate::auth::register;
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
@@ -18,12 +18,7 @@ async fn get_profile_settings_includes_profile_fields() {
|
||||
};
|
||||
register::execute(
|
||||
®ister_deps,
|
||||
RegisterCommand {
|
||||
email: "settings@test.com".into(),
|
||||
username: "settingsuser".into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
register_command("settings@test.com", "settingsuser"),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use crate::test_support::{register_command, update_profile_command};
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::events::DomainEvent;
|
||||
use domain::models::UserRole;
|
||||
use domain::ports::UserRepository;
|
||||
use domain::testing::{InMemoryUserRepository, NoopEventPublisher};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
auth::{commands::RegisterCommand, deps::RegisterDeps, register},
|
||||
auth::{deps::RegisterDeps, register},
|
||||
test_helpers::TestContextBuilder,
|
||||
users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps, update_profile},
|
||||
};
|
||||
@@ -18,17 +18,9 @@ async fn register_user(b: &TestContextBuilder, users: &Arc<InMemoryUserRepositor
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
®_deps,
|
||||
RegisterCommand {
|
||||
email: "alice@example.com".into(),
|
||||
username: "alice".into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(®_deps, register_command("alice@example.com", "alice"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let user = users
|
||||
.find_by_email(&domain::value_objects::Email::new("alice@example.com".into()).unwrap())
|
||||
@@ -55,14 +47,8 @@ async fn updates_display_name() {
|
||||
update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: Some("Alice W.".into()),
|
||||
bio: None,
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -90,14 +76,9 @@ async fn rejects_invalid_avatar_content_type() {
|
||||
let result = update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_bytes: Some(vec![0u8; 10]),
|
||||
avatar_content_type: Some("image/gif".into()),
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -122,14 +103,9 @@ async fn uploads_avatar() {
|
||||
update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_bytes: Some(vec![0xFFu8, 0xD8, 0xFF]),
|
||||
avatar_content_type: Some("image/jpeg".into()),
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -165,14 +141,9 @@ async fn uploads_banner() {
|
||||
update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: Some(vec![0x89, 0x50, 0x4E]),
|
||||
banner_content_type: Some("image/png".into()),
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -203,14 +174,8 @@ async fn fails_for_nonexistent_user() {
|
||||
let result = update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: Uuid::new_v4(),
|
||||
display_name: Some("Ghost".into()),
|
||||
bio: None,
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
..update_profile_command(Uuid::new_v4())
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -232,14 +197,9 @@ async fn rejects_invalid_banner_content_type() {
|
||||
let result = update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: None,
|
||||
bio: None,
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: Some(vec![0u8; 10]),
|
||||
banner_content_type: Some("text/plain".into()),
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
@@ -264,14 +224,9 @@ async fn text_only_update_emits_user_updated_no_image_stored() {
|
||||
update_profile::execute(
|
||||
&deps,
|
||||
UpdateProfileCommand {
|
||||
user_id: uid,
|
||||
display_name: Some("Alice Updated".into()),
|
||||
bio: Some("Hello world".into()),
|
||||
avatar_bytes: None,
|
||||
avatar_content_type: None,
|
||||
banner_bytes: None,
|
||||
banner_content_type: None,
|
||||
also_known_as: None,
|
||||
..update_profile_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::update_user_settings_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::testing::InMemoryUserSettingsRepository;
|
||||
@@ -24,10 +25,8 @@ async fn updates_federate_goals() {
|
||||
user_settings: user_settings.clone(),
|
||||
},
|
||||
UpdateUserSettingsCommand {
|
||||
user_id: uid,
|
||||
federate_goals: false,
|
||||
federate_reviews: true,
|
||||
federate_watchlist: true,
|
||||
..update_user_settings_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -53,10 +52,8 @@ async fn updates_federate_reviews() {
|
||||
user_settings: user_settings.clone(),
|
||||
},
|
||||
UpdateUserSettingsCommand {
|
||||
user_id: uid,
|
||||
federate_goals: true,
|
||||
federate_reviews: false,
|
||||
federate_watchlist: true,
|
||||
..update_user_settings_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
@@ -82,10 +79,8 @@ async fn updates_federate_watchlist() {
|
||||
user_settings: user_settings.clone(),
|
||||
},
|
||||
UpdateUserSettingsCommand {
|
||||
user_id: uid,
|
||||
federate_goals: true,
|
||||
federate_reviews: true,
|
||||
federate_watchlist: false,
|
||||
..update_user_settings_command(uid)
|
||||
},
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
use crate::test_support::{add_to_watchlist_command, movie_input_manual};
|
||||
use domain::testing::test_movie_with_year;
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::{
|
||||
models::Movie,
|
||||
ports::MovieCommand,
|
||||
testing::{InMemoryMovieRepository, InMemoryWatchlistRepository, NoopEventPublisher},
|
||||
value_objects::{MovieTitle, ReleaseYear},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
diary::commands::MovieInput,
|
||||
watchlist::{add, commands::AddToWatchlistCommand, deps::WatchlistAddDeps},
|
||||
};
|
||||
use crate::watchlist::{add, commands::AddToWatchlistCommand, deps::WatchlistAddDeps};
|
||||
|
||||
fn make_deps(
|
||||
movies: Arc<InMemoryMovieRepository>,
|
||||
@@ -30,28 +27,13 @@ async fn test_add_to_watchlist_resolves_and_saves() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
let watchlist = InMemoryWatchlistRepository::new();
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("The Thing".into()).unwrap(),
|
||||
ReleaseYear::new(1982).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("The Thing", 1982);
|
||||
let movie_uuid = movie.id().value();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
let deps = make_deps(Arc::clone(&movies), Arc::clone(&watchlist));
|
||||
|
||||
let cmd = AddToWatchlistCommand {
|
||||
user_id: uuid::Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: Some(movie_uuid),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
},
|
||||
};
|
||||
let cmd = add_to_watchlist_command(uuid::Uuid::new_v4(), movie_uuid);
|
||||
|
||||
add::execute(&deps, cmd).await.unwrap();
|
||||
|
||||
@@ -63,29 +45,14 @@ async fn test_add_to_watchlist_already_present_is_idempotent() {
|
||||
let movies = InMemoryMovieRepository::new();
|
||||
let watchlist = InMemoryWatchlistRepository::new();
|
||||
|
||||
let movie = Movie::new(
|
||||
None,
|
||||
MovieTitle::new("RoboCop".into()).unwrap(),
|
||||
ReleaseYear::new(1987).unwrap(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let movie = test_movie_with_year("RoboCop", 1987);
|
||||
let movie_uuid = movie.id().value();
|
||||
let user_id = uuid::Uuid::new_v4();
|
||||
movies.upsert_movie(&movie).await.unwrap();
|
||||
|
||||
let deps = make_deps(Arc::clone(&movies), Arc::clone(&watchlist));
|
||||
|
||||
let make_cmd = || AddToWatchlistCommand {
|
||||
user_id,
|
||||
input: MovieInput {
|
||||
movie_id: Some(movie_uuid),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
},
|
||||
};
|
||||
let make_cmd = || add_to_watchlist_command(user_id, movie_uuid);
|
||||
|
||||
add::execute(&deps, make_cmd()).await.unwrap();
|
||||
add::execute(&deps, make_cmd()).await.unwrap();
|
||||
@@ -102,13 +69,7 @@ async fn test_add_to_watchlist_with_manual_movie() {
|
||||
|
||||
let cmd = AddToWatchlistCommand {
|
||||
user_id: uuid::Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: None,
|
||||
external_metadata_id: None,
|
||||
manual_title: Some("New Manual Movie".into()),
|
||||
manual_release_year: Some(2024),
|
||||
manual_director: None,
|
||||
},
|
||||
input: movie_input_manual("New Manual Movie", 2024),
|
||||
};
|
||||
|
||||
add::execute(&deps, cmd).await.unwrap();
|
||||
@@ -124,16 +85,7 @@ async fn test_add_to_watchlist_movie_not_found_by_id() {
|
||||
|
||||
let deps = make_deps(Arc::clone(&movies), Arc::clone(&watchlist));
|
||||
|
||||
let cmd = AddToWatchlistCommand {
|
||||
user_id: uuid::Uuid::new_v4(),
|
||||
input: MovieInput {
|
||||
movie_id: Some(uuid::Uuid::new_v4()),
|
||||
external_metadata_id: None,
|
||||
manual_title: None,
|
||||
manual_release_year: None,
|
||||
manual_director: None,
|
||||
},
|
||||
};
|
||||
let cmd = add_to_watchlist_command(uuid::Uuid::new_v4(), uuid::Uuid::new_v4());
|
||||
|
||||
assert!(add::execute(&deps, cmd).await.is_err());
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use crate::test_support::register_command;
|
||||
use std::sync::Arc;
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use domain::models::UserRole;
|
||||
use domain::testing::{
|
||||
InMemoryRemoteWatchlistRepository, InMemoryUserRepository, InMemoryWatchlistRepository,
|
||||
PanicRemoteWatchlistRepository, PanicWatchlistRepository,
|
||||
};
|
||||
use domain::value_objects::Email;
|
||||
|
||||
use crate::auth::commands::RegisterCommand;
|
||||
use crate::auth::deps::RegisterDeps;
|
||||
use crate::auth::register;
|
||||
use crate::test_helpers::TestContextBuilder;
|
||||
@@ -22,17 +21,9 @@ async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) {
|
||||
password_hasher: b.password_hasher.clone(),
|
||||
config: b.config.clone(),
|
||||
};
|
||||
register::execute(
|
||||
&deps,
|
||||
RegisterCommand {
|
||||
email: email.into(),
|
||||
username: username.into(),
|
||||
password: "password123".into(),
|
||||
role: UserRole::Standard,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
register::execute(&deps, register_command(email, username))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::test_support::request_wrap_up_command;
|
||||
use chrono::NaiveDate;
|
||||
use domain::events::DomainEvent;
|
||||
use domain::models::wrapup::{WrapUpRecord, WrapUpStatus};
|
||||
@@ -21,11 +22,7 @@ fn deps(
|
||||
}
|
||||
|
||||
fn past_cmd() -> RequestWrapUpCommand {
|
||||
RequestWrapUpCommand {
|
||||
user_id: Some(Uuid::nil()),
|
||||
start_date: NaiveDate::from_ymd_opt(2024, 1, 1).unwrap(),
|
||||
end_date: NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
}
|
||||
request_wrap_up_command(Some(Uuid::nil()), 2024)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -105,16 +102,9 @@ async fn replaces_failed_wrapup() {
|
||||
async fn rejects_future_end_date() {
|
||||
let repo = InMemoryWrapUpRepository::new();
|
||||
let events = NoopEventPublisher::new();
|
||||
let err = generate::execute(
|
||||
&deps(&repo, &events),
|
||||
RequestWrapUpCommand {
|
||||
user_id: None,
|
||||
start_date: NaiveDate::from_ymd_opt(2030, 1, 1).unwrap(),
|
||||
end_date: NaiveDate::from_ymd_opt(2031, 1, 1).unwrap(),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let err = generate::execute(&deps(&repo, &events), request_wrap_up_command(None, 2030))
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert!(err.to_string().contains("future"));
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ pub mod ports;
|
||||
pub mod services;
|
||||
pub mod value_objects;
|
||||
|
||||
#[cfg(feature = "test-helpers")]
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
pub mod testing;
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
use super::*;
|
||||
use crate::testing::test_goal;
|
||||
use crate::value_objects::UserId;
|
||||
|
||||
fn make_goal(year: u16, target: u32) -> Result<Goal, DomainError> {
|
||||
Goal::new(UserId::generate(), year, target, GoalType::Movies)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_goal_valid() {
|
||||
let g = make_goal(2024, 52);
|
||||
assert!(g.is_ok());
|
||||
let g = g.unwrap();
|
||||
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!(make_goal(2019, 10).is_err());
|
||||
assert!(Goal::new(UserId::generate(), 2019, 10, GoalType::Movies).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_goal_rejects_zero_target() {
|
||||
assert!(make_goal(2024, 0).is_err());
|
||||
assert!(Goal::new(UserId::generate(), 2024, 0, GoalType::Movies).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_target_valid() {
|
||||
let mut g = make_goal(2024, 10).unwrap();
|
||||
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 = make_goal(2024, 10).unwrap();
|
||||
let mut g = test_goal(&UserId::generate(), 2024, 10);
|
||||
assert!(g.update_target(0).is_err());
|
||||
}
|
||||
|
||||
@@ -52,7 +47,7 @@ fn from_persistence_preserves_fields() {
|
||||
|
||||
#[test]
|
||||
fn percentage_calculation() {
|
||||
let g = make_goal(2024, 100).unwrap();
|
||||
let g = test_goal(&UserId::generate(), 2024, 100);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 50,
|
||||
@@ -62,7 +57,7 @@ fn percentage_calculation() {
|
||||
|
||||
#[test]
|
||||
fn percentage_caps_at_100() {
|
||||
let g = make_goal(2024, 10).unwrap();
|
||||
let g = test_goal(&UserId::generate(), 2024, 10);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 20,
|
||||
@@ -72,7 +67,7 @@ fn percentage_caps_at_100() {
|
||||
|
||||
#[test]
|
||||
fn is_complete() {
|
||||
let g = make_goal(2024, 10).unwrap();
|
||||
let g = test_goal(&UserId::generate(), 2024, 10);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 10,
|
||||
@@ -82,7 +77,7 @@ fn is_complete() {
|
||||
|
||||
#[test]
|
||||
fn is_not_complete() {
|
||||
let g = make_goal(2024, 10).unwrap();
|
||||
let g = test_goal(&UserId::generate(), 2024, 10);
|
||||
let wp = GoalWithProgress {
|
||||
goal: g,
|
||||
current_count: 9,
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
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};
|
||||
|
||||
fn make_user() -> User {
|
||||
User::from_persistence(
|
||||
UserId::generate(),
|
||||
Email::new("a@b.com".to_string()).unwrap(),
|
||||
Username::new("alice".to_string()).unwrap(),
|
||||
PasswordHash::new("hash".to_string()).unwrap(),
|
||||
UserRole::Standard,
|
||||
UserProfile::default(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_profile_sets_fields() {
|
||||
let mut user = make_user();
|
||||
let mut user = test_user("alice");
|
||||
user.update_profile(UserProfile {
|
||||
bio: Some("My bio".to_string()),
|
||||
avatar_path: Some("avatars/abc".to_string()),
|
||||
@@ -26,7 +16,7 @@ fn update_profile_sets_fields() {
|
||||
|
||||
#[test]
|
||||
fn update_profile_clears_with_none() {
|
||||
let mut user = make_user();
|
||||
let mut user = test_user("alice");
|
||||
user.update_profile(UserProfile {
|
||||
bio: Some("bio".to_string()),
|
||||
avatar_path: Some("path".to_string()),
|
||||
@@ -39,19 +29,15 @@ fn update_profile_clears_with_none() {
|
||||
|
||||
// ── Movie ────────────────────────────────────────────────────────────────────
|
||||
|
||||
fn make_movie() -> Movie {
|
||||
Movie::new(
|
||||
#[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("Ridley Scott".into()),
|
||||
Some(crate::value_objects::PosterPath::new("/poster.jpg".into()).unwrap()),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn movie_new_sets_fields() {
|
||||
let m = make_movie();
|
||||
);
|
||||
assert_eq!(m.title().value(), "Blade Runner");
|
||||
assert_eq!(m.release_year().value(), 1982);
|
||||
assert_eq!(m.director(), Some("Ridley Scott"));
|
||||
@@ -61,7 +47,7 @@ fn movie_new_sets_fields() {
|
||||
|
||||
#[test]
|
||||
fn movie_update_poster() {
|
||||
let mut m = make_movie();
|
||||
let mut m = test_movie_with_year("Blade Runner", 1982);
|
||||
let new_poster = crate::value_objects::PosterPath::new("/new.jpg".into()).unwrap();
|
||||
m.update_poster(new_poster);
|
||||
assert_eq!(m.poster_path().unwrap().value(), "/new.jpg");
|
||||
@@ -69,31 +55,18 @@ fn movie_update_poster() {
|
||||
|
||||
// ── Review ───────────────────────────────────────────────────────────────────
|
||||
|
||||
use crate::value_objects::{Comment, MovieId, Rating, ReviewId};
|
||||
|
||||
fn make_review() -> Review {
|
||||
Review::new(
|
||||
MovieId::generate(),
|
||||
UserId::generate(),
|
||||
Rating::new(4).unwrap(),
|
||||
Some(Comment::new("great".into()).unwrap()),
|
||||
chrono::Utc::now().naive_utc(),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
use crate::value_objects::{MovieId, Rating, ReviewId};
|
||||
|
||||
#[test]
|
||||
fn review_new_sets_local_source() {
|
||||
let r = make_review();
|
||||
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 = make_review(); // rating=4
|
||||
let r = test_review(&MovieId::generate(), &UserId::generate(), 4);
|
||||
assert_eq!(r.stars(), [true, true, true, true, false]);
|
||||
}
|
||||
|
||||
@@ -141,7 +114,7 @@ fn user_new() {
|
||||
|
||||
#[test]
|
||||
fn user_update_password() {
|
||||
let mut u = make_user();
|
||||
let mut u = test_user("alice");
|
||||
u.update_password(PasswordHash::new("new_hash".into()).unwrap());
|
||||
assert_eq!(u.password_hash().value(), "new_hash");
|
||||
}
|
||||
@@ -197,7 +170,10 @@ fn movie_stats_construction() {
|
||||
|
||||
#[test]
|
||||
fn feed_entry_display_name_from_email() {
|
||||
let entry = DiaryEntry::new(make_movie(), make_review());
|
||||
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");
|
||||
@@ -222,7 +198,7 @@ fn month_activity_construction() {
|
||||
|
||||
#[test]
|
||||
fn movie_is_manual_match_same_title_year() {
|
||||
let m = make_movie();
|
||||
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();
|
||||
assert!(m.is_manual_match(&title, &year, Some("ridley scott")));
|
||||
@@ -230,7 +206,7 @@ fn movie_is_manual_match_same_title_year() {
|
||||
|
||||
#[test]
|
||||
fn movie_is_manual_match_different_director_fails() {
|
||||
let m = make_movie();
|
||||
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();
|
||||
assert!(!m.is_manual_match(&title, &year, Some("Denis Villeneuve")));
|
||||
|
||||
@@ -1,37 +1,31 @@
|
||||
use chrono::NaiveDateTime;
|
||||
|
||||
use crate::testing::test_review;
|
||||
use crate::value_objects::{Comment, MovieId, Rating, UserId, WatchMedium};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn make_review(watch_medium: Option<WatchMedium>) -> Review {
|
||||
Review::new(
|
||||
MovieId::generate(),
|
||||
UserId::generate(),
|
||||
Rating::new(4).unwrap(),
|
||||
Some(Comment::new("great film".into()).unwrap()),
|
||||
NaiveDateTime::parse_from_str("2024-06-15 20:00:00", "%Y-%m-%d %H:%M:%S").unwrap(),
|
||||
watch_medium,
|
||||
false,
|
||||
)
|
||||
.unwrap()
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_review_stores_watch_medium() {
|
||||
let review = make_review(Some(WatchMedium::Cinema));
|
||||
let review = review_with_medium(Some(WatchMedium::Cinema));
|
||||
assert_eq!(review.watch_medium(), Some(&WatchMedium::Cinema));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_review_without_medium() {
|
||||
let review = make_review(None);
|
||||
let review = review_with_medium(None);
|
||||
assert_eq!(review.watch_medium(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_edit_updates_rating_only() {
|
||||
let original = make_review(Some(WatchMedium::Streaming));
|
||||
let original = review_with_medium(Some(WatchMedium::Streaming));
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
rating: Some(Rating::new(2).unwrap()),
|
||||
..Default::default()
|
||||
@@ -43,7 +37,7 @@ fn apply_edit_updates_rating_only() {
|
||||
|
||||
#[test]
|
||||
fn apply_edit_clears_comment() {
|
||||
let original = make_review(None);
|
||||
let original = review_with_medium(None);
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
comment: Some(None),
|
||||
..Default::default()
|
||||
@@ -54,7 +48,7 @@ fn apply_edit_clears_comment() {
|
||||
|
||||
#[test]
|
||||
fn apply_edit_sets_watch_medium() {
|
||||
let original = make_review(None);
|
||||
let original = review_with_medium(None);
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
watch_medium: Some(Some(WatchMedium::Cinema)),
|
||||
..Default::default()
|
||||
@@ -64,7 +58,7 @@ fn apply_edit_sets_watch_medium() {
|
||||
|
||||
#[test]
|
||||
fn apply_edit_clears_watch_medium() {
|
||||
let original = make_review(Some(WatchMedium::Cinema));
|
||||
let original = review_with_medium(Some(WatchMedium::Cinema));
|
||||
let edited = original.apply_edit(ReviewEdit {
|
||||
watch_medium: Some(None),
|
||||
..Default::default()
|
||||
@@ -74,7 +68,7 @@ fn apply_edit_clears_watch_medium() {
|
||||
|
||||
#[test]
|
||||
fn apply_edit_toggles_spoilers() {
|
||||
let original = make_review(None);
|
||||
let original = review_with_medium(None);
|
||||
let flag_on = original.apply_edit(ReviewEdit {
|
||||
spoilers: Some(true),
|
||||
..Default::default()
|
||||
@@ -91,7 +85,7 @@ fn apply_edit_toggles_spoilers() {
|
||||
|
||||
#[test]
|
||||
fn apply_edit_no_changes_preserves_all() {
|
||||
let original = make_review(Some(WatchMedium::TV));
|
||||
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");
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
use super::*;
|
||||
use crate::models::{Movie, Review, ReviewHistory};
|
||||
use crate::value_objects::{MovieId, MovieTitle, Rating, ReleaseYear, UserId};
|
||||
use crate::models::{Review, ReviewHistory};
|
||||
use crate::testing::test_movie_with_year;
|
||||
use crate::value_objects::{MovieId, Rating, UserId};
|
||||
use chrono::{NaiveDate, NaiveDateTime, NaiveTime};
|
||||
|
||||
fn make_movie() -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new("Test".into()).unwrap(),
|
||||
ReleaseYear::new(2024).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
fn dt(year: i32, month: u32, day: u32) -> NaiveDateTime {
|
||||
NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(year, month, day).unwrap(),
|
||||
@@ -36,7 +27,7 @@ fn review_with_rating(movie_id: &MovieId, rating: u8, watched_at: NaiveDateTime)
|
||||
|
||||
#[test]
|
||||
fn neutral_when_empty() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let history = ReviewHistory::new(movie, vec![]);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
assert_eq!(trend, Trend::Neutral);
|
||||
@@ -44,7 +35,7 @@ fn neutral_when_empty() {
|
||||
|
||||
#[test]
|
||||
fn neutral_when_single_review() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let r = review_with_rating(movie.id(), 4, dt(2024, 1, 1));
|
||||
let history = ReviewHistory::new(movie, vec![r]);
|
||||
let trend = ReviewHistoryAnalyzer::rating_trend(&history).unwrap();
|
||||
@@ -53,7 +44,7 @@ fn neutral_when_single_review() {
|
||||
|
||||
#[test]
|
||||
fn improved_when_latest_above_average() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let viewings = vec![
|
||||
review_with_rating(movie.id(), 2, dt(2024, 1, 1)),
|
||||
review_with_rating(movie.id(), 3, dt(2024, 2, 1)),
|
||||
@@ -66,7 +57,7 @@ fn improved_when_latest_above_average() {
|
||||
|
||||
#[test]
|
||||
fn declined_when_latest_below_average() {
|
||||
let movie = make_movie();
|
||||
let movie = test_movie_with_year("Test", 2024);
|
||||
let viewings = vec![
|
||||
review_with_rating(movie.id(), 5, dt(2024, 1, 1)),
|
||||
review_with_rating(movie.id(), 4, dt(2024, 2, 1)),
|
||||
|
||||
131
crates/domain/src/testing/factories.rs
Normal file
131
crates/domain/src/testing/factories.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
use chrono::{NaiveDate, NaiveDateTime};
|
||||
|
||||
use crate::models::{
|
||||
DiaryEntry, ExternalPersonId, Goal, GoalType, Movie, Person, Review, User, UserProfile,
|
||||
UserRole,
|
||||
};
|
||||
use crate::value_objects::{
|
||||
Email, ExternalMetadataId, MovieId, MovieTitle, PasswordHash, PersonId, Rating, ReleaseYear,
|
||||
UserId, Username,
|
||||
};
|
||||
|
||||
const DEFAULT_YEAR: u16 = 2000;
|
||||
|
||||
/// Fixed watch timestamp used by review factories (2024-03-15 20:00:00).
|
||||
pub fn test_watched_at() -> NaiveDateTime {
|
||||
NaiveDate::from_ymd_opt(2024, 3, 15)
|
||||
.unwrap()
|
||||
.and_hms_opt(20, 0, 0)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn test_movie(title: &str) -> Movie {
|
||||
test_movie_with_year(title, DEFAULT_YEAR)
|
||||
}
|
||||
|
||||
pub fn test_movie_with_year(title: &str, year: u16) -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new(title.to_string()).unwrap(),
|
||||
ReleaseYear::new(year).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_movie_with_director(title: &str, year: u16, director: &str) -> Movie {
|
||||
Movie::new(
|
||||
None,
|
||||
MovieTitle::new(title.to_string()).unwrap(),
|
||||
ReleaseYear::new(year).unwrap(),
|
||||
Some(director.to_string()),
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_movie_with_external_id(
|
||||
title: &str,
|
||||
year: u16,
|
||||
external_id: ExternalMetadataId,
|
||||
) -> Movie {
|
||||
Movie::new(
|
||||
Some(external_id),
|
||||
MovieTitle::new(title.to_string()).unwrap(),
|
||||
ReleaseYear::new(year).unwrap(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_review(movie_id: &MovieId, user_id: &UserId, rating: u8) -> Review {
|
||||
build_review(movie_id, user_id, rating, false)
|
||||
}
|
||||
|
||||
pub fn test_spoiler_review(movie_id: &MovieId, user_id: &UserId, rating: u8) -> Review {
|
||||
build_review(movie_id, user_id, rating, true)
|
||||
}
|
||||
|
||||
fn build_review(movie_id: &MovieId, user_id: &UserId, rating: u8, spoilers: bool) -> Review {
|
||||
Review::new(
|
||||
movie_id.clone(),
|
||||
user_id.clone(),
|
||||
Rating::new(rating).unwrap(),
|
||||
None,
|
||||
test_watched_at(),
|
||||
None,
|
||||
spoilers,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn test_user(username: &str) -> User {
|
||||
User::new(
|
||||
Email::new(format!("{username}@example.com")).unwrap(),
|
||||
Username::new(username.to_string()).unwrap(),
|
||||
PasswordHash::new("hashed-password".to_string()).unwrap(),
|
||||
UserRole::Standard,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_diary_entry(title: &str, rating: u8) -> DiaryEntry {
|
||||
let movie = test_movie(title);
|
||||
let review = test_review(movie.id(), &UserId::generate(), rating);
|
||||
DiaryEntry::new(movie, review)
|
||||
}
|
||||
|
||||
pub fn test_goal(user_id: &UserId, year: u16, target_count: u32) -> Goal {
|
||||
Goal::new(user_id.clone(), year, target_count, GoalType::Movies).unwrap()
|
||||
}
|
||||
|
||||
pub fn test_user_with_role(id: UserId, role: UserRole) -> User {
|
||||
User::from_persistence(
|
||||
id,
|
||||
Email::new("role-test@example.com".to_string()).unwrap(),
|
||||
Username::new("role_test_user".to_string()).unwrap(),
|
||||
PasswordHash::new("hashed-password".to_string()).unwrap(),
|
||||
role,
|
||||
UserProfile::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_person(name: &str) -> Person {
|
||||
let ext = ExternalPersonId::new(format!("test:{name}"));
|
||||
Person::basic(
|
||||
PersonId::from_external(&ext),
|
||||
ext,
|
||||
name.to_string(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn test_person_with_tmdb(tmdb_id: i64, name: &str, department: Option<&str>) -> Person {
|
||||
let ext = ExternalPersonId::new(format!("tmdb:{tmdb_id}"));
|
||||
Person::basic(
|
||||
PersonId::from_external(&ext),
|
||||
ext,
|
||||
name.to_string(),
|
||||
department.map(str::to_string),
|
||||
None,
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod factories;
|
||||
mod fakes;
|
||||
mod in_memory;
|
||||
mod insights;
|
||||
@@ -5,6 +6,7 @@ mod noops;
|
||||
mod panics;
|
||||
mod wrapup;
|
||||
|
||||
pub use factories::*;
|
||||
pub use fakes::*;
|
||||
pub use in_memory::*;
|
||||
pub use insights::*;
|
||||
|
||||
Reference in New Issue
Block a user