test: move application tests to tests/

test_support/test_helpers -> tests/support; watched_at unified w/ domain test_watched_at (2024-03-15).
Kept inline: users::diary_filter tests (feed_sort_to_direction_all_variants, paged_user_filter_builds_correctly) - pub(super) helpers in private module.
Dropped stale never-compiled users/tests/get_users.rs (old execute signature).
This commit is contained in:
2026-10-25 22:36:52 +01:00
parent a2c713c1fa
commit 8526b77f18
175 changed files with 541 additions and 695 deletions

View File

@@ -13,7 +13,3 @@ pub async fn execute(
let found = deps.user.find_by_id(&id).await?;
Ok(found.map(|user| matches!(user.role(), UserRole::Admin)))
}
#[cfg(test)]
#[path = "tests/authorize_admin.rs"]
mod tests;

View File

@@ -38,5 +38,43 @@ pub(super) fn paged_user_filter(
}
#[cfg(test)]
#[path = "tests/diary_filter.rs"]
mod tests;
mod tests {
use super::*;
#[test]
fn feed_sort_to_direction_all_variants() {
assert!(matches!(
feed_sort_to_direction(FeedSortBy::Date),
ReviewSortBy::Descending
));
assert!(matches!(
feed_sort_to_direction(FeedSortBy::DateAsc),
ReviewSortBy::Ascending
));
assert!(matches!(
feed_sort_to_direction(FeedSortBy::Rating),
ReviewSortBy::ByRatingDesc
));
assert!(matches!(
feed_sort_to_direction(FeedSortBy::RatingAsc),
ReviewSortBy::ByRatingAsc
));
}
#[test]
fn paged_user_filter_builds_correctly() {
let uid = UserId::from_uuid(uuid::Uuid::new_v4());
let filter = paged_user_filter(
uid.clone(),
ReviewSortBy::Descending,
Some(20),
Some(5),
Some("blade".into()),
false,
)
.unwrap();
assert_eq!(filter.user_id.unwrap().value(), uid.value());
assert_eq!(filter.search.as_deref(), Some("blade"));
}
}

View File

@@ -50,7 +50,3 @@ pub async fn execute(
role: found.role().clone(),
})
}
#[cfg(test)]
#[path = "tests/get_current_profile.rs"]
mod tests;

View File

@@ -13,7 +13,3 @@ pub async fn execute(
None => Ok(None),
}
}
#[cfg(test)]
#[path = "tests/get_federated_profile.rs"]
mod tests;

View File

@@ -83,7 +83,3 @@ pub async fn execute(
}
}
}
#[cfg(test)]
#[path = "tests/get_federated_profile_stats.rs"]
mod tests;

View File

@@ -144,7 +144,3 @@ async fn load_social_counts(
.collect();
(following, followers, pending)
}
#[cfg(test)]
#[path = "tests/get_local_profile.rs"]
mod tests;

View File

@@ -38,7 +38,3 @@ pub async fn execute(deps: &GetPageViewerDeps, user_id: Uuid) -> Result<PageView
pending_follow_count,
})
}
#[cfg(test)]
#[path = "tests/get_page_viewer.rs"]
mod tests;

View File

@@ -45,7 +45,3 @@ pub async fn execute(
fields,
})
}
#[cfg(test)]
#[path = "tests/get_profile_settings.rs"]
mod tests;

View File

@@ -9,7 +9,3 @@ pub async fn execute(
let uid = UserId::from_uuid(user_id);
deps.user_settings.get(&uid).await
}
#[cfg(test)]
#[path = "tests/get_settings.rs"]
mod tests;

View File

@@ -17,7 +17,3 @@ pub async fn execute(
let user = deps.user.find_by_username(username).await?;
Ok(user.map(|u| u.id().clone()))
}
#[cfg(test)]
#[path = "tests/resolve_username_to_id.rs"]
mod tests;

View File

@@ -1,91 +0,0 @@
use crate::test_support::register_command;
use std::sync::Arc;
use domain::models::UserRole;
use domain::testing::InMemoryStore;
use crate::{
auth::{commands::RegisterCommand, deps::RegisterDeps, register},
test_helpers::TestContextBuilder,
users::{authorize_admin, deps::AuthorizeAdminDeps},
};
async fn register_user(
b: &TestContextBuilder,
email: &str,
username: &str,
role: UserRole,
) -> domain::models::User {
let reg_deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(
&reg_deps,
RegisterCommand {
role,
..register_command(email, username)
},
)
.await
.unwrap();
b.user_repo
.find_by_email(&domain::value_objects::Email::new(email.into()).unwrap())
.await
.unwrap()
.unwrap()
}
#[tokio::test]
async fn authorize_admin_is_true_for_an_admin_user() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let user = register_user(&b, "admin@example.com", "admin_user", UserRole::Admin).await;
let deps = AuthorizeAdminDeps {
user: b.user_repo.clone(),
};
let result = authorize_admin::execute(&deps, user.id().value())
.await
.unwrap();
assert_eq!(result, Some(true));
}
#[tokio::test]
async fn authorize_admin_is_false_for_a_regular_user() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let user = register_user(
&b,
"standard@example.com",
"standard_user",
UserRole::Standard,
)
.await;
let deps = AuthorizeAdminDeps {
user: b.user_repo.clone(),
};
let result = authorize_admin::execute(&deps, user.id().value())
.await
.unwrap();
assert_eq!(result, Some(false));
}
#[tokio::test]
async fn authorize_admin_is_not_found_for_unknown_user() {
let b = TestContextBuilder::new();
let deps = AuthorizeAdminDeps {
user: b.user_repo.clone(),
};
let result = authorize_admin::execute(&deps, uuid::Uuid::new_v4())
.await
.unwrap();
assert_eq!(result, None);
}

View File

@@ -1,38 +0,0 @@
use super::*;
#[test]
fn feed_sort_to_direction_all_variants() {
assert!(matches!(
feed_sort_to_direction(FeedSortBy::Date),
ReviewSortBy::Descending
));
assert!(matches!(
feed_sort_to_direction(FeedSortBy::DateAsc),
ReviewSortBy::Ascending
));
assert!(matches!(
feed_sort_to_direction(FeedSortBy::Rating),
ReviewSortBy::ByRatingDesc
));
assert!(matches!(
feed_sort_to_direction(FeedSortBy::RatingAsc),
ReviewSortBy::ByRatingAsc
));
}
#[test]
fn paged_user_filter_builds_correctly() {
let uid = UserId::from_uuid(uuid::Uuid::new_v4());
let filter = paged_user_filter(
uid.clone(),
ReviewSortBy::Descending,
Some(20),
Some(5),
Some("blade".into()),
false,
)
.unwrap();
assert_eq!(filter.user_id.unwrap().value(), uid.value());
assert_eq!(filter.search.as_deref(), Some("blade"));
}

View File

@@ -1,113 +0,0 @@
use crate::test_support::register_command;
use std::sync::Arc;
use domain::models::{ProfileField, User, UserProfile, UserRole};
use domain::ports::UserRepository;
use domain::testing::InMemoryStore;
use domain::value_objects::{Email, PasswordHash, UserId, Username};
use uuid::Uuid;
use crate::{
auth::{deps::RegisterDeps, register},
test_helpers::TestContextBuilder,
users::{deps::GetCurrentProfileDeps, get_current_profile, queries::GetCurrentProfileQuery},
};
#[tokio::test]
async fn returns_profile_for_existing_user() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let user_repo = b.user_repo.clone();
let reg_deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(&reg_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())
.await
.unwrap()
.unwrap();
let deps = GetCurrentProfileDeps { user: user_repo };
let profile = get_current_profile::execute(
&deps,
GetCurrentProfileQuery {
user_id: user.id().value(),
},
)
.await
.unwrap();
assert_eq!(profile.username, "alice");
}
#[tokio::test]
async fn fails_for_nonexistent_user() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let deps = GetCurrentProfileDeps { user: user_repo };
let result = get_current_profile::execute(
&deps,
GetCurrentProfileQuery {
user_id: Uuid::new_v4(),
},
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn returns_profile_with_avatar_banner_and_fields() {
let users = InMemoryStore::new();
let uid = UserId::generate();
let user = User::from_persistence(
uid.clone(),
Email::new("full@example.com".into()).unwrap(),
Username::new("fulluser".into()).unwrap(),
PasswordHash::new("hashed".into()).unwrap(),
UserRole::Standard,
UserProfile {
display_name: Some("Full Name".into()),
bio: Some("My bio".into()),
avatar_path: Some("avatars/abc123".into()),
banner_path: Some("banners/def456".into()),
also_known_as: None,
profile_fields: vec![ProfileField {
name: "Website".into(),
value: "https://example.com".into(),
}],
},
);
users.seed_user(user);
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let user_repo = b.user_repo.clone();
let deps = GetCurrentProfileDeps { user: user_repo };
let profile = get_current_profile::execute(
&deps,
GetCurrentProfileQuery {
user_id: uid.value(),
},
)
.await
.unwrap();
assert_eq!(profile.username, "fulluser");
assert_eq!(profile.display_name.as_deref(), Some("Full Name"));
assert_eq!(profile.bio.as_deref(), Some("My bio"));
assert_eq!(profile.avatar_path.as_deref(), Some("avatars/abc123"));
assert_eq!(profile.banner_path.as_deref(), Some("banners/def456"));
assert_eq!(profile.fields.len(), 1);
assert_eq!(profile.fields[0].name, "Website");
assert_eq!(profile.fields[0].value, "https://example.com");
}

View File

@@ -1,115 +0,0 @@
use std::sync::Arc;
use async_trait::async_trait;
use domain::{errors::DomainError, models::FederatedProfile, ports::FederatedProfileQuery};
use crate::users::deps::GetFederatedProfileDeps;
use crate::users::get_federated_profile;
fn a_profile() -> FederatedProfile {
FederatedProfile {
actor_url: "https://remote.example/users/alice".into(),
handle: "alice@remote.example".into(),
display_name: Some("Alice".into()),
bio: None,
avatar_url: None,
banner_url: None,
}
}
struct FoundFederatedProfileQuery;
#[async_trait]
impl FederatedProfileQuery for FoundFederatedProfileQuery {
async fn get_federated_profile(
&self,
_synthetic_user_id: uuid::Uuid,
) -> Result<Option<FederatedProfile>, DomainError> {
Ok(Some(a_profile()))
}
}
struct EmptyFederatedProfileQuery;
#[async_trait]
impl FederatedProfileQuery for EmptyFederatedProfileQuery {
async fn get_federated_profile(
&self,
_synthetic_user_id: uuid::Uuid,
) -> Result<Option<FederatedProfile>, DomainError> {
Ok(None)
}
}
struct FailingFederatedProfileQuery;
#[async_trait]
impl FederatedProfileQuery for FailingFederatedProfileQuery {
async fn get_federated_profile(
&self,
_synthetic_user_id: uuid::Uuid,
) -> Result<Option<FederatedProfile>, DomainError> {
Err(DomainError::InfrastructureError(
"remote lookup failed".into(),
))
}
}
#[tokio::test]
async fn returns_the_profile_when_the_port_finds_one() {
let deps = GetFederatedProfileDeps {
federated_profile: Some(Arc::new(FoundFederatedProfileQuery)),
};
let result = get_federated_profile::execute(&deps, uuid::Uuid::new_v4())
.await
.unwrap();
assert_eq!(
result.map(|p| p.handle),
Some("alice@remote.example".to_string())
);
}
#[tokio::test]
async fn returns_ok_none_when_the_port_finds_nothing() {
let deps = GetFederatedProfileDeps {
federated_profile: Some(Arc::new(EmptyFederatedProfileQuery)),
};
let result = get_federated_profile::execute(&deps, uuid::Uuid::new_v4())
.await
.unwrap();
assert!(result.is_none());
}
#[tokio::test]
async fn returns_ok_none_when_federation_is_disabled() {
let deps = GetFederatedProfileDeps {
federated_profile: None,
};
let result = get_federated_profile::execute(&deps, uuid::Uuid::new_v4())
.await
.unwrap();
assert!(
result.is_none(),
"an absent port must collapse to Ok(None), the same as an empty lookup — \
the handler cannot and must not tell the two apart"
);
}
#[tokio::test]
async fn propagates_an_error_from_the_port_unchanged() {
let deps = GetFederatedProfileDeps {
federated_profile: Some(Arc::new(FailingFederatedProfileQuery)),
};
let err = get_federated_profile::execute(&deps, uuid::Uuid::new_v4())
.await
.expect_err("a repository error must not be swallowed inside the use case");
assert!(matches!(err, DomainError::InfrastructureError(_)));
}

View File

@@ -1,37 +0,0 @@
use crate::test_helpers::TestContextBuilder;
use crate::users::deps::GetFederatedProfileStatsDeps;
use crate::users::get_federated_profile_stats;
use crate::users::queries::{GetUserProfileQuery, ProfileView};
#[tokio::test]
async fn succeeds_for_a_user_id_with_no_local_row() {
let b = TestContextBuilder::new();
let deps = GetFederatedProfileStatsDeps {
stats: b.stats_repo.clone(),
diary: b.diary_repo.clone(),
social_query: b.social_query_unified.clone(),
};
let synthetic_user_id = uuid::Uuid::new_v4();
let result = get_federated_profile_stats::execute(
&deps,
GetUserProfileQuery {
user_id: synthetic_user_id,
view: ProfileView::Recent,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: false,
include_remote: true,
},
)
.await;
assert!(
result.is_ok(),
"execute must not hard-fail for a user_id absent from the local `users` table \
(the federated-profile call shape) — got Err"
);
}

View File

@@ -1,333 +0,0 @@
use crate::test_support::{register_command, update_profile_command};
use domain::errors::DomainError;
use domain::value_objects::Email;
use crate::auth::deps::RegisterDeps;
use crate::auth::register;
use crate::test_helpers::TestContextBuilder;
use crate::users::commands::UpdateProfileCommand;
use crate::users::deps::{GetLocalProfileDeps, UpdateProfileDeps};
use crate::users::get_local_profile;
use crate::users::queries::{GetUserProfileQuery, ProfileView};
use crate::users::update_profile;
async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) {
let deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(&deps, register_command(email, username))
.await
.unwrap();
}
fn deps(b: &TestContextBuilder) -> GetLocalProfileDeps {
GetLocalProfileDeps {
stats: b.stats_repo.clone(),
diary: b.diary_repo.clone(),
social_query: b.social_query_unified.clone(),
user: b.user_repo.clone(),
instance: domain::value_objects::InstanceIdentity::new(b.config.base_url.clone()),
}
}
fn unknown_query(user_id: uuid::Uuid) -> GetUserProfileQuery {
GetUserProfileQuery {
user_id,
view: ProfileView::Recent,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: false,
include_remote: false,
}
}
#[tokio::test]
async fn get_local_profile_is_not_found_for_unknown_user() {
let b = TestContextBuilder::new();
let d = deps(&b);
let unknown_id = uuid::Uuid::new_v4();
let err = match get_local_profile::execute(&d, unknown_query(unknown_id)).await {
Err(e) => e,
Ok(_) => panic!("expected Err(NotFound) for an unknown user id, got Ok"),
};
assert!(matches!(err, DomainError::NotFound(_)), "got {err:?}");
}
#[tokio::test]
async fn returns_profile_with_empty_stats() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "profile@test.com", "profuser").await;
let email = Email::new("profile@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Recent,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert!(result.entries.is_some());
}
#[tokio::test]
async fn returns_history_view() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "hist@test.com", "histuser").await;
let email = Email::new("hist@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::History,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert!(result.history.is_some());
assert!(result.entries.is_none());
assert!(result.trends.is_none());
}
#[tokio::test]
async fn returns_trends_view() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "trends@test.com", "trendsuser").await;
let email = Email::new("trends@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Trends,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert!(result.trends.is_some());
assert!(result.entries.is_none());
assert!(result.history.is_none());
}
#[tokio::test]
async fn returns_ratings_view() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "ratings@test.com", "ratingsuser").await;
let email = Email::new("ratings@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Ratings,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Rating,
search: None,
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert!(result.entries.is_some());
}
#[tokio::test]
async fn returns_recent_with_search() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "search@test.com", "searchuser").await;
let email = Email::new("search@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Recent,
limit: Some(10),
offset: Some(0),
sort_by: domain::models::FeedSortBy::Date,
search: Some("blade".into()),
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert!(result.entries.is_some());
}
#[tokio::test]
async fn non_own_profile_skips_pending_followers() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "other@test.com", "otheruser").await;
let email = Email::new("other@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Recent,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: false,
include_remote: false,
},
)
.await
.unwrap();
assert!(result.pending_followers.is_empty());
}
#[tokio::test]
async fn populates_handle_and_actor_url_for_a_local_user() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "handle@test.com", "gabriel").await;
let email = Email::new("handle@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let update_deps = UpdateProfileDeps {
user: user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
update_profile::execute(
&update_deps,
UpdateProfileCommand {
display_name: Some("Gabriel K".into()),
bio: Some("Movies and diaries.".into()),
..update_profile_command(uid)
},
)
.await
.unwrap();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Recent,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert_eq!(result.identity.username, "gabriel");
assert_eq!(result.identity.handle, "@gabriel@localhost:3000");
assert_eq!(
result.identity.actor_url,
format!("http://localhost:3000/users/{}", uid)
);
assert_eq!(result.identity.display_name, Some("Gabriel K".to_string()));
assert_eq!(result.identity.bio, Some("Movies and diaries.".to_string()));
}
#[tokio::test]
async fn identity_display_name_and_bio_are_none_when_unset() {
let b = TestContextBuilder::new();
let user_repo = b.user_repo.clone();
let d = deps(&b);
setup_user(&b, "unset@test.com", "unsetuser").await;
let email = Email::new("unset@test.com".into()).unwrap();
let user = user_repo.find_by_email(&email).await.unwrap().unwrap();
let uid = user.id().value();
let result = get_local_profile::execute(
&d,
GetUserProfileQuery {
user_id: uid,
view: ProfileView::Recent,
limit: None,
offset: None,
sort_by: domain::models::FeedSortBy::Date,
search: None,
is_own_profile: true,
include_remote: false,
},
)
.await
.unwrap();
assert_eq!(result.identity.display_name, None);
assert_eq!(result.identity.bio, None);
}

View File

@@ -1,54 +0,0 @@
use crate::test_support::register_command;
use domain::value_objects::{Email, FollowTarget, SocialIdentity, UserId};
use crate::auth::deps::RegisterDeps;
use crate::auth::register;
use crate::test_helpers::TestContextBuilder;
use crate::users::deps::GetPageViewerDeps;
use crate::users::get_page_viewer;
async fn setup_user(b: &TestContextBuilder, email: &str, username: &str) -> uuid::Uuid {
let deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(&deps, register_command(email, username))
.await
.unwrap();
let user = b
.user_repo
.find_by_email(&Email::new(email.into()).unwrap())
.await
.unwrap()
.unwrap();
user.id().value()
}
#[tokio::test]
async fn get_page_viewer_reports_pending_follower_count() {
let b = TestContextBuilder::new();
let owner_uuid = setup_user(&b, "owner@test.com", "pageowner").await;
let follower1 = setup_user(&b, "follower1@test.com", "pagefollower1").await;
let follower2 = setup_user(&b, "follower2@test.com", "pagefollower2").await;
let owner_id = UserId::from_uuid(owner_uuid);
let target = FollowTarget::Identity(SocialIdentity::Local(owner_id.clone()));
b.social_command
.follow(&UserId::from_uuid(follower1), &target)
.await
.unwrap();
b.social_command
.follow(&UserId::from_uuid(follower2), &target)
.await
.unwrap();
let deps = GetPageViewerDeps {
user: b.user_repo.clone(),
follow_graph: b.social_query_unified.clone(),
};
let v = get_page_viewer::execute(&deps, owner_uuid).await.unwrap();
assert_eq!(v.pending_follow_count, 2);
assert!(!v.is_admin);
}

View File

@@ -1,55 +0,0 @@
use crate::test_support::register_command;
use domain::models::ProfileField;
use domain::value_objects::{Email, UserId};
use crate::auth::deps::RegisterDeps;
use crate::auth::register;
use crate::test_helpers::TestContextBuilder;
use crate::users::deps::GetProfileSettingsDeps;
use crate::users::get_profile_settings;
#[tokio::test]
async fn get_profile_settings_includes_profile_fields() {
let b = TestContextBuilder::new();
let register_deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(
&register_deps,
register_command("settings@test.com", "settingsuser"),
)
.await
.unwrap();
let user = b
.user_repo
.find_by_email(&Email::new("settings@test.com".into()).unwrap())
.await
.unwrap()
.unwrap();
let uid = user.id().value();
b.profile_fields_repo
.set_fields(
&UserId::from_uuid(uid),
vec![ProfileField {
name: "pronouns".into(),
value: "they/them".into(),
}],
)
.await
.unwrap();
let deps = GetProfileSettingsDeps {
user: b.user_repo.clone(),
profile_fields: b.profile_fields_repo.clone(),
instance: domain::value_objects::InstanceIdentity::new(b.config.base_url.clone()),
};
let s = get_profile_settings::execute(&deps, uid).await.unwrap();
assert_eq!(
s.fields,
vec![("pronouns".to_string(), "they/them".to_string())]
);
}

View File

@@ -1,20 +0,0 @@
use uuid::Uuid;
use crate::{
test_helpers::TestContextBuilder,
users::{deps::GetSettingsDeps, get_settings},
};
#[tokio::test]
async fn returns_default_settings() {
let b = TestContextBuilder::new();
let deps = GetSettingsDeps {
user_settings: b.user_settings_repo.clone(),
};
let settings = get_settings::execute(&deps, Uuid::nil()).await.unwrap();
assert!(settings.federate_goals());
assert!(settings.federate_reviews());
assert!(settings.federate_watchlist());
}

View File

@@ -1,17 +0,0 @@
use crate::test_helpers::TestContextBuilder;
use crate::users::get_users;
use crate::users::queries::GetUsersQuery;
#[tokio::test]
async fn returns_empty_when_no_users() {
let b = TestContextBuilder::new();
let user = b.user_repo.clone();
let social_query = b.social_query.clone();
let result = get_users::execute(user, social_query, GetUsersQuery)
.await
.unwrap();
assert!(result.users.is_empty());
assert!(result.remote_actors.is_empty());
}

View File

@@ -1,20 +0,0 @@
use domain::value_objects::Username;
use crate::test_helpers::TestContextBuilder;
use crate::users::deps::ResolveUsernameDeps;
use crate::users::resolve_username_to_id;
#[tokio::test]
async fn resolve_username_to_id_returns_none_for_unknown() {
let b = TestContextBuilder::new();
let deps = ResolveUsernameDeps {
user: b.user_repo.clone(),
};
let unknown = Username::new("nosuchuser".into()).unwrap();
assert!(
resolve_username_to_id::execute(&deps, &unknown)
.await
.unwrap()
.is_none()
);
}

View File

@@ -1,247 +0,0 @@
use crate::test_support::{register_command, update_profile_command};
use std::sync::Arc;
use domain::events::DomainEvent;
use domain::ports::UserRepository;
use domain::testing::{InMemoryStore, NoopEventPublisher};
use uuid::Uuid;
use crate::{
auth::{deps::RegisterDeps, register},
test_helpers::TestContextBuilder,
users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps, update_profile},
};
async fn register_user(b: &TestContextBuilder, users: &Arc<InMemoryStore>) -> Uuid {
let reg_deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(&reg_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())
.await
.unwrap()
.unwrap();
user.id().value()
}
#[tokio::test]
async fn updates_display_name() {
let users = InMemoryStore::new();
let events = NoopEventPublisher::new();
let b = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.with_event_publisher(Arc::clone(&events) as _);
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let uid = register_user(&b, &users).await;
update_profile::execute(
&deps,
UpdateProfileCommand {
display_name: Some("Alice W.".into()),
..update_profile_command(uid)
},
)
.await
.unwrap();
let published = events.published();
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::UserUpdated { .. }))
);
}
#[tokio::test]
async fn rejects_invalid_avatar_content_type() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let uid = register_user(&b, &users).await;
let result = update_profile::execute(
&deps,
UpdateProfileCommand {
avatar_bytes: Some(vec![0u8; 10]),
avatar_content_type: Some("image/gif".into()),
..update_profile_command(uid)
},
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn uploads_avatar() {
let users = InMemoryStore::new();
let events = NoopEventPublisher::new();
let b = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.with_event_publisher(Arc::clone(&events) as _);
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let uid = register_user(&b, &users).await;
update_profile::execute(
&deps,
UpdateProfileCommand {
avatar_bytes: Some(vec![0xFFu8, 0xD8, 0xFF]),
avatar_content_type: Some("image/jpeg".into()),
..update_profile_command(uid)
},
)
.await
.unwrap();
let published = events.published();
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::UserUpdated { .. }))
);
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::ImageStored { .. }))
);
}
#[tokio::test]
async fn uploads_banner() {
let users = InMemoryStore::new();
let events = NoopEventPublisher::new();
let b = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.with_event_publisher(Arc::clone(&events) as _);
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let uid = register_user(&b, &users).await;
update_profile::execute(
&deps,
UpdateProfileCommand {
banner_bytes: Some(vec![0x89, 0x50, 0x4E]),
banner_content_type: Some("image/png".into()),
..update_profile_command(uid)
},
)
.await
.unwrap();
let published = events.published();
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::UserUpdated { .. }))
);
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::ImageStored { .. }))
);
}
#[tokio::test]
async fn fails_for_nonexistent_user() {
let b = TestContextBuilder::new();
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let result = update_profile::execute(
&deps,
UpdateProfileCommand {
display_name: Some("Ghost".into()),
..update_profile_command(Uuid::new_v4())
},
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn rejects_invalid_banner_content_type() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let uid = register_user(&b, &users).await;
let result = update_profile::execute(
&deps,
UpdateProfileCommand {
banner_bytes: Some(vec![0u8; 10]),
banner_content_type: Some("text/plain".into()),
..update_profile_command(uid)
},
)
.await;
assert!(result.is_err());
}
#[tokio::test]
async fn text_only_update_emits_user_updated_no_image_stored() {
let users = InMemoryStore::new();
let events = NoopEventPublisher::new();
let b = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.with_event_publisher(Arc::clone(&events) as _);
let deps = UpdateProfileDeps {
user: b.user_repo.clone(),
object_storage: b.object_storage.clone(),
event_publisher: b.event_publisher.clone(),
};
let uid = register_user(&b, &users).await;
update_profile::execute(
&deps,
UpdateProfileCommand {
display_name: Some("Alice Updated".into()),
bio: Some("Hello world".into()),
..update_profile_command(uid)
},
)
.await
.unwrap();
let published = events.published();
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::UserUpdated { .. }))
);
assert!(
!published
.iter()
.any(|e| matches!(e, DomainEvent::ImageStored { .. })),
"text-only update should not emit ImageStored"
);
}

View File

@@ -1,79 +0,0 @@
use std::sync::Arc;
use domain::events::DomainEvent;
use domain::models::ProfileField;
use domain::testing::{InMemoryStore, NoopEventPublisher};
use uuid::Uuid;
use crate::{
test_helpers::TestContextBuilder,
users::{
commands::UpdateProfileFieldsCommand, deps::UpdateProfileFieldsDeps, update_profile_fields,
},
};
#[tokio::test]
async fn saves_profile_fields() {
let fields_repo = InMemoryStore::new();
let events = NoopEventPublisher::new();
let b = TestContextBuilder::new()
.with_profile_fields(Arc::clone(&fields_repo) as _)
.with_event_publisher(Arc::clone(&events) as _);
let deps = UpdateProfileFieldsDeps {
profile_fields: b.profile_fields_repo.clone(),
event_publisher: b.event_publisher.clone(),
};
update_profile_fields::execute(
&deps,
UpdateProfileFieldsCommand {
user_id: Uuid::nil(),
fields: vec![
ProfileField {
name: "Website".into(),
value: "https://example.com".into(),
},
ProfileField {
name: "Location".into(),
value: "Berlin".into(),
},
],
},
)
.await
.unwrap();
let published = events.published();
assert!(
published
.iter()
.any(|e| matches!(e, DomainEvent::UserUpdated { .. }))
);
}
#[tokio::test]
async fn rejects_more_than_four_fields() {
let b = TestContextBuilder::new();
let deps = UpdateProfileFieldsDeps {
profile_fields: b.profile_fields_repo.clone(),
event_publisher: b.event_publisher.clone(),
};
let fields: Vec<ProfileField> = (0..5)
.map(|i| ProfileField {
name: format!("field{i}"),
value: format!("val{i}"),
})
.collect();
let result = update_profile_fields::execute(
&deps,
UpdateProfileFieldsCommand {
user_id: Uuid::nil(),
fields,
},
)
.await;
assert!(result.is_err());
}

View File

@@ -1,95 +0,0 @@
use crate::test_support::update_user_settings_command;
use std::sync::Arc;
use domain::testing::InMemoryStore;
use uuid::Uuid;
use crate::{
test_helpers::TestContextBuilder,
users::{
deps::{GetSettingsDeps, UpdateSettingsDeps},
get_settings,
update_settings::UpdateUserSettingsCommand,
},
};
#[tokio::test]
async fn updates_federate_goals() {
let settings_repo = InMemoryStore::new();
let b = TestContextBuilder::new().with_user_settings(Arc::clone(&settings_repo) as _);
let user_settings = b.user_settings_repo.clone();
let uid = Uuid::nil();
crate::users::update_settings::execute(
&UpdateSettingsDeps {
user_settings: user_settings.clone(),
},
UpdateUserSettingsCommand {
federate_goals: false,
..update_user_settings_command(uid)
},
)
.await
.unwrap();
let settings = get_settings::execute(&GetSettingsDeps { user_settings }, uid)
.await
.unwrap();
assert!(!settings.federate_goals());
assert!(settings.federate_reviews());
assert!(settings.federate_watchlist());
}
#[tokio::test]
async fn updates_federate_reviews() {
let settings_repo = InMemoryStore::new();
let b = TestContextBuilder::new().with_user_settings(Arc::clone(&settings_repo) as _);
let user_settings = b.user_settings_repo.clone();
let uid = Uuid::nil();
crate::users::update_settings::execute(
&UpdateSettingsDeps {
user_settings: user_settings.clone(),
},
UpdateUserSettingsCommand {
federate_reviews: false,
..update_user_settings_command(uid)
},
)
.await
.unwrap();
let settings = get_settings::execute(&GetSettingsDeps { user_settings }, uid)
.await
.unwrap();
assert!(settings.federate_goals());
assert!(!settings.federate_reviews());
assert!(settings.federate_watchlist());
}
#[tokio::test]
async fn updates_federate_watchlist() {
let settings_repo = InMemoryStore::new();
let b = TestContextBuilder::new().with_user_settings(Arc::clone(&settings_repo) as _);
let user_settings = b.user_settings_repo.clone();
let uid = Uuid::nil();
crate::users::update_settings::execute(
&UpdateSettingsDeps {
user_settings: user_settings.clone(),
},
UpdateUserSettingsCommand {
federate_watchlist: false,
..update_user_settings_command(uid)
},
)
.await
.unwrap();
let settings = get_settings::execute(&GetSettingsDeps { user_settings }, uid)
.await
.unwrap();
assert!(settings.federate_goals());
assert!(settings.federate_reviews());
assert!(!settings.federate_watchlist());
}

View File

@@ -123,7 +123,3 @@ pub async fn execute(
Ok(())
}
#[cfg(test)]
#[path = "tests/update_profile.rs"]
mod tests;

View File

@@ -17,7 +17,3 @@ pub async fn execute(
.await?;
Ok(())
}
#[cfg(test)]
#[path = "tests/update_profile_fields.rs"]
mod tests;

View File

@@ -20,7 +20,3 @@ pub async fn execute(
settings.set_federate_watchlist(cmd.federate_watchlist);
deps.user_settings.save(&settings).await
}
#[cfg(test)]
#[path = "tests/update_settings.rs"]
mod tests;