From d813e59b5cce75496a158113ebfb4cacd66172fa Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 17 May 2026 12:04:51 +0200 Subject: [PATCH] fmt --- .../adapters/activitypub-base/src/ap_ports.rs | 4 +- crates/adapters/activitypub-base/src/lib.rs | 2 +- .../adapters/activitypub-base/src/service.rs | 5 +- crates/adapters/activitypub/src/handler.rs | 3 +- .../auth/src/api_key_service/tests.rs | 8 +- crates/adapters/event-payload/src/lib.rs | 1 - crates/adapters/event-transport/src/lib.rs | 1 - crates/adapters/nats/src/lib.rs | 1 - .../adapters/postgres/src/activitypub/mod.rs | 11 +- .../postgres/src/activitypub/tests.rs | 115 ++++++------ crates/adapters/postgres/src/api_key/tests.rs | 93 +++++----- crates/adapters/postgres/src/block/tests.rs | 65 +++---- crates/adapters/postgres/src/boost/tests.rs | 67 +++---- crates/adapters/postgres/src/feed/tests.rs | 139 +++++++------- crates/adapters/postgres/src/follow/tests.rs | 111 +++++------ crates/adapters/postgres/src/lib.rs | 4 +- crates/adapters/postgres/src/like/tests.rs | 67 +++---- .../postgres/src/notification/tests.rs | 127 ++++++------- crates/adapters/postgres/src/tag/tests.rs | 93 +++++----- crates/adapters/postgres/src/thought/tests.rs | 173 +++++++++--------- .../adapters/postgres/src/top_friend/tests.rs | 89 ++++----- crates/adapters/postgres/src/user/mod.rs | 35 ++-- crates/adapters/postgres/src/user/tests.rs | 129 ++++++------- .../src/services/federation_event/tests.rs | 21 +-- crates/application/src/testing.rs | 6 +- crates/application/src/use_cases/auth/mod.rs | 25 +-- .../application/src/use_cases/auth/tests.rs | 53 ++++-- .../use_cases/federation_management/mod.rs | 14 +- crates/application/src/use_cases/feed.rs | 3 +- .../application/src/use_cases/thoughts/mod.rs | 41 ++++- .../src/use_cases/thoughts/tests.rs | 74 ++++++-- crates/bootstrap/src/factory.rs | 12 +- crates/domain/src/ports.rs | 38 +++- crates/domain/src/testing/mod.rs | 62 ++++++- crates/presentation/src/handlers/api_keys.rs | 6 +- crates/presentation/src/handlers/auth.rs | 6 +- .../src/handlers/federation_actors/mod.rs | 2 +- crates/presentation/src/handlers/feed.rs | 20 +- .../src/handlers/notifications/mod.rs | 6 +- .../presentation/src/handlers/social/mod.rs | 8 +- crates/presentation/src/handlers/thoughts.rs | 38 ++-- crates/presentation/src/handlers/users/mod.rs | 4 +- crates/presentation/src/testing.rs | 6 +- crates/worker/src/factory.rs | 2 +- crates/worker/src/main.rs | 6 +- crates/worker/src/outbox_relay.rs | 17 +- 46 files changed, 1003 insertions(+), 810 deletions(-) diff --git a/crates/adapters/activitypub-base/src/ap_ports.rs b/crates/adapters/activitypub-base/src/ap_ports.rs index 15190c0..471814f 100644 --- a/crates/adapters/activitypub-base/src/ap_ports.rs +++ b/crates/adapters/activitypub-base/src/ap_ports.rs @@ -44,7 +44,7 @@ pub trait ActivityPubRepository: Send + Sync { /// Find the local UserId for a remote actor by its AP URL. async fn find_remote_actor_id(&self, actor_ap_url: &str) - -> Result, DomainError>; + -> Result, DomainError>; /// Ensure a remote actor placeholder exists; create one if absent. /// Idempotent — safe to call multiple times with the same URL. @@ -99,7 +99,7 @@ pub trait ActivityPubRepository: Send + Sync { /// Return the AP actor URL and inbox URL for a user, if stored. /// Returns None for users that have not been federated. async fn get_actor_ap_urls(&self, user_id: &UserId) - -> Result, DomainError>; + -> Result, DomainError>; } #[async_trait] diff --git a/crates/adapters/activitypub-base/src/lib.rs b/crates/adapters/activitypub-base/src/lib.rs index aceb0d5..75bd509 100644 --- a/crates/adapters/activitypub-base/src/lib.rs +++ b/crates/adapters/activitypub-base/src/lib.rs @@ -18,7 +18,7 @@ pub mod user; pub mod webfinger; pub use activitypub_federation::kinds::object::NoteType; -pub use ap_ports::{ActorApUrls, ActivityPubRepository, OutboxEntry, OutboundFederationPort}; +pub use ap_ports::{ActivityPubRepository, ActorApUrls, OutboundFederationPort, OutboxEntry}; pub use content::ApObjectHandler; pub use data::FederationData; pub use error::Error; diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index eb77f1d..e3d27d7 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -1666,10 +1666,7 @@ impl domain::ports::FederationSchedulerPort for ActivityPubService { let empty = vec![]; let items = val["orderedItems"].as_array().unwrap_or(&empty); for item in items { - let actor_url = item - .as_str() - .or_else(|| item["id"].as_str()) - .unwrap_or(""); + let actor_url = item.as_str().or_else(|| item["id"].as_str()).unwrap_or(""); if !actor_url.is_empty() { all_urls.push(actor_url.to_string()); } diff --git a/crates/adapters/activitypub/src/handler.rs b/crates/adapters/activitypub/src/handler.rs index a2635bb..7267b74 100644 --- a/crates/adapters/activitypub/src/handler.rs +++ b/crates/adapters/activitypub/src/handler.rs @@ -141,7 +141,8 @@ impl ApObjectHandler for ThoughtsObjectHandler { "direct" }; - let thought_id = self.repo + let thought_id = self + .repo .accept_note( ap_id.as_str(), &author_id, diff --git a/crates/adapters/auth/src/api_key_service/tests.rs b/crates/adapters/auth/src/api_key_service/tests.rs index 03fa9c9..86e795b 100644 --- a/crates/adapters/auth/src/api_key_service/tests.rs +++ b/crates/adapters/auth/src/api_key_service/tests.rs @@ -18,7 +18,13 @@ impl ApiKeyRepository for FakeApiKeyRepo { Ok(()) } async fn find_by_hash(&self, hash: &str) -> Result, DomainError> { - Ok(self.0.lock().unwrap().iter().find(|k| k.key_hash == hash).cloned()) + Ok(self + .0 + .lock() + .unwrap() + .iter() + .find(|k| k.key_hash == hash) + .cloned()) } async fn list_for_user(&self, _uid: &UserId) -> Result, DomainError> { Ok(vec![]) diff --git a/crates/adapters/event-payload/src/lib.rs b/crates/adapters/event-payload/src/lib.rs index 54a7825..53fa98b 100644 --- a/crates/adapters/event-payload/src/lib.rs +++ b/crates/adapters/event-payload/src/lib.rs @@ -356,6 +356,5 @@ impl TryFrom for DomainEvent { } } - #[cfg(test)] mod tests; diff --git a/crates/adapters/event-transport/src/lib.rs b/crates/adapters/event-transport/src/lib.rs index 6a44373..4205726 100644 --- a/crates/adapters/event-transport/src/lib.rs +++ b/crates/adapters/event-transport/src/lib.rs @@ -109,6 +109,5 @@ impl EventConsumer for EventConsumerAdapter { } } - #[cfg(test)] mod tests; diff --git a/crates/adapters/nats/src/lib.rs b/crates/adapters/nats/src/lib.rs index 6d11b72..025a6f3 100644 --- a/crates/adapters/nats/src/lib.rs +++ b/crates/adapters/nats/src/lib.rs @@ -239,6 +239,5 @@ impl MessageSource for NatsMessageSource { } } - #[cfg(test)] mod tests; diff --git a/crates/adapters/postgres/src/activitypub/mod.rs b/crates/adapters/postgres/src/activitypub/mod.rs index fb74e64..af31713 100644 --- a/crates/adapters/postgres/src/activitypub/mod.rs +++ b/crates/adapters/postgres/src/activitypub/mod.rs @@ -254,12 +254,11 @@ impl ActivityPubRepository for PgActivityPubRepository { .into_domain()?; // SELECT the id — works whether the INSERT was a no-op or not (idempotent). - let row: (uuid::Uuid,) = - sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1") - .bind(ap_id) - .fetch_one(&self.pool) - .await - .into_domain()?; + let row: (uuid::Uuid,) = sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1") + .bind(ap_id) + .fetch_one(&self.pool) + .await + .into_domain()?; Ok(ThoughtId::from_uuid(row.0)) } diff --git a/crates/adapters/postgres/src/activitypub/tests.rs b/crates/adapters/postgres/src/activitypub/tests.rs index 9da0b57..258e7fa 100644 --- a/crates/adapters/postgres/src/activitypub/tests.rs +++ b/crates/adapters/postgres/src/activitypub/tests.rs @@ -1,25 +1,56 @@ - use super::*; - use activitypub_base::ActivityPubRepository; - #[sqlx::test(migrations = "./migrations")] - async fn intern_remote_actor_is_idempotent(pool: sqlx::PgPool) { - let repo = PgActivityPubRepository::new(pool); - let url = "https://mastodon.social/users/alice"; - let id1 = repo.intern_remote_actor(url).await.unwrap(); - let id2 = repo.intern_remote_actor(url).await.unwrap(); - assert_eq!(id1, id2); - } +use super::*; +use activitypub_base::ActivityPubRepository; - #[sqlx::test(migrations = "./migrations")] - async fn accept_and_retract_note(pool: sqlx::PgPool) { - let repo = PgActivityPubRepository::new(pool); - let actor_url = "https://remote.example/users/bob"; - let ap_id = "https://remote.example/notes/1"; - let author = repo.intern_remote_actor(actor_url).await.unwrap(); - repo.accept_note( - ap_id, - &author, - "hello from remote", +#[sqlx::test(migrations = "./migrations")] +async fn intern_remote_actor_is_idempotent(pool: sqlx::PgPool) { + let repo = PgActivityPubRepository::new(pool); + let url = "https://mastodon.social/users/alice"; + let id1 = repo.intern_remote_actor(url).await.unwrap(); + let id2 = repo.intern_remote_actor(url).await.unwrap(); + assert_eq!(id1, id2); +} + +#[sqlx::test(migrations = "./migrations")] +async fn accept_and_retract_note(pool: sqlx::PgPool) { + let repo = PgActivityPubRepository::new(pool); + let actor_url = "https://remote.example/users/bob"; + let ap_id = "https://remote.example/notes/1"; + let author = repo.intern_remote_actor(actor_url).await.unwrap(); + repo.accept_note( + ap_id, + &author, + "hello from remote", + chrono::Utc::now(), + false, + None, + "public", + None, + ) + .await + .unwrap(); + repo.retract_note(ap_id).await.unwrap(); +} + +#[sqlx::test(migrations = "./migrations")] +async fn count_local_notes_excludes_remote(pool: sqlx::PgPool) { + let repo = PgActivityPubRepository::new(pool); + assert_eq!(repo.count_local_notes().await.unwrap(), 0); +} + +#[sqlx::test(migrations = "./migrations")] +async fn accept_note_returns_thought_id(pool: sqlx::PgPool) { + let repo = PgActivityPubRepository::new(pool.clone()); + let actor_user_id = repo + .intern_remote_actor("https://remote.example/users/alice") + .await + .unwrap(); + + let thought_id = repo + .accept_note( + "https://remote.example/notes/1", + &actor_user_id, + "Hello #rust world", chrono::Utc::now(), false, None, @@ -28,41 +59,11 @@ ) .await .unwrap(); - repo.retract_note(ap_id).await.unwrap(); - } - #[sqlx::test(migrations = "./migrations")] - async fn count_local_notes_excludes_remote(pool: sqlx::PgPool) { - let repo = PgActivityPubRepository::new(pool); - assert_eq!(repo.count_local_notes().await.unwrap(), 0); - } - - #[sqlx::test(migrations = "./migrations")] - async fn accept_note_returns_thought_id(pool: sqlx::PgPool) { - let repo = PgActivityPubRepository::new(pool.clone()); - let actor_user_id = repo - .intern_remote_actor("https://remote.example/users/alice") - .await - .unwrap(); - - let thought_id = repo - .accept_note( - "https://remote.example/notes/1", - &actor_user_id, - "Hello #rust world", - chrono::Utc::now(), - false, - None, - "public", - None, - ) - .await - .unwrap(); - - let row: (uuid::Uuid,) = sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1") - .bind("https://remote.example/notes/1") - .fetch_one(&pool) - .await - .unwrap(); - assert_eq!(thought_id.as_uuid(), row.0); - } + let row: (uuid::Uuid,) = sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1") + .bind("https://remote.example/notes/1") + .fetch_one(&pool) + .await + .unwrap(); + assert_eq!(thought_id.as_uuid(), row.0); +} diff --git a/crates/adapters/postgres/src/api_key/tests.rs b/crates/adapters/postgres/src/api_key/tests.rs index 703a710..fa1725f 100644 --- a/crates/adapters/postgres/src/api_key/tests.rs +++ b/crates/adapters/postgres/src/api_key/tests.rs @@ -1,49 +1,50 @@ - use super::*; - use crate::user::PgUserRepository; - use chrono::Utc; - use domain::ports::UserWriter; - use domain::{models::user::User, value_objects::*}; - async fn seed_user(pool: &sqlx::PgPool) -> User { - let repo = PgUserRepository::new(pool.clone()); - let u = User::new_local( - UserId::new(), - Username::new("alice").unwrap(), - Email::new("alice@ex.com").unwrap(), - PasswordHash("h".into()), - ); - repo.save(&u).await.unwrap(); - u - } +use super::*; +use crate::user::PgUserRepository; +use chrono::Utc; +use domain::ports::UserWriter; +use domain::{models::user::User, value_objects::*}; - #[sqlx::test(migrations = "./migrations")] - async fn save_and_find_by_hash(pool: sqlx::PgPool) { - let user = seed_user(&pool).await; - let repo = PgApiKeyRepository::new(pool); - let key = ApiKey { - id: ApiKeyId::new(), - user_id: user.id.clone(), - key_hash: "abc123".into(), - name: "test".into(), - created_at: Utc::now(), - }; - repo.save(&key).await.unwrap(); - let found = repo.find_by_hash("abc123").await.unwrap().unwrap(); - assert_eq!(found.name, "test"); - } +async fn seed_user(pool: &sqlx::PgPool) -> User { + let repo = PgUserRepository::new(pool.clone()); + let u = User::new_local( + UserId::new(), + Username::new("alice").unwrap(), + Email::new("alice@ex.com").unwrap(), + PasswordHash("h".into()), + ); + repo.save(&u).await.unwrap(); + u +} - #[sqlx::test(migrations = "./migrations")] - async fn delete_key(pool: sqlx::PgPool) { - let user = seed_user(&pool).await; - let repo = PgApiKeyRepository::new(pool); - let key = ApiKey { - id: ApiKeyId::new(), - user_id: user.id.clone(), - key_hash: "def456".into(), - name: "key2".into(), - created_at: Utc::now(), - }; - repo.save(&key).await.unwrap(); - repo.delete(&key.id, &user.id).await.unwrap(); - assert!(repo.find_by_hash("def456").await.unwrap().is_none()); - } +#[sqlx::test(migrations = "./migrations")] +async fn save_and_find_by_hash(pool: sqlx::PgPool) { + let user = seed_user(&pool).await; + let repo = PgApiKeyRepository::new(pool); + let key = ApiKey { + id: ApiKeyId::new(), + user_id: user.id.clone(), + key_hash: "abc123".into(), + name: "test".into(), + created_at: Utc::now(), + }; + repo.save(&key).await.unwrap(); + let found = repo.find_by_hash("abc123").await.unwrap().unwrap(); + assert_eq!(found.name, "test"); +} + +#[sqlx::test(migrations = "./migrations")] +async fn delete_key(pool: sqlx::PgPool) { + let user = seed_user(&pool).await; + let repo = PgApiKeyRepository::new(pool); + let key = ApiKey { + id: ApiKeyId::new(), + user_id: user.id.clone(), + key_hash: "def456".into(), + name: "key2".into(), + created_at: Utc::now(), + }; + repo.save(&key).await.unwrap(); + repo.delete(&key.id, &user.id).await.unwrap(); + assert!(repo.find_by_hash("def456").await.unwrap().is_none()); +} diff --git a/crates/adapters/postgres/src/block/tests.rs b/crates/adapters/postgres/src/block/tests.rs index 9473974..f13401e 100644 --- a/crates/adapters/postgres/src/block/tests.rs +++ b/crates/adapters/postgres/src/block/tests.rs @@ -1,34 +1,35 @@ - use super::*; - use crate::test_helpers::seed_user; - use chrono::Utc; - use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn block_exists(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgBlockRepository::new(pool); - let block = Block { - blocker_id: alice.id.clone(), - blocked_id: bob.id.clone(), - created_at: Utc::now(), - }; - repo.save(&block).await.unwrap(); - assert!(repo.exists(&alice.id, &bob.id).await.unwrap()); - assert!(!repo.exists(&bob.id, &alice.id).await.unwrap()); - } +use super::*; +use crate::test_helpers::seed_user; +use chrono::Utc; +use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn unblock(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgBlockRepository::new(pool); - let block = Block { - blocker_id: alice.id.clone(), - blocked_id: bob.id.clone(), - created_at: Utc::now(), - }; - repo.save(&block).await.unwrap(); - repo.delete(&alice.id, &bob.id).await.unwrap(); - assert!(!repo.exists(&alice.id, &bob.id).await.unwrap()); - } +#[sqlx::test(migrations = "./migrations")] +async fn block_exists(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgBlockRepository::new(pool); + let block = Block { + blocker_id: alice.id.clone(), + blocked_id: bob.id.clone(), + created_at: Utc::now(), + }; + repo.save(&block).await.unwrap(); + assert!(repo.exists(&alice.id, &bob.id).await.unwrap()); + assert!(!repo.exists(&bob.id, &alice.id).await.unwrap()); +} + +#[sqlx::test(migrations = "./migrations")] +async fn unblock(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgBlockRepository::new(pool); + let block = Block { + blocker_id: alice.id.clone(), + blocked_id: bob.id.clone(), + created_at: Utc::now(), + }; + repo.save(&block).await.unwrap(); + repo.delete(&alice.id, &bob.id).await.unwrap(); + assert!(!repo.exists(&alice.id, &bob.id).await.unwrap()); +} diff --git a/crates/adapters/postgres/src/boost/tests.rs b/crates/adapters/postgres/src/boost/tests.rs index f93291a..a4401c0 100644 --- a/crates/adapters/postgres/src/boost/tests.rs +++ b/crates/adapters/postgres/src/boost/tests.rs @@ -1,35 +1,36 @@ - use super::*; - use crate::test_helpers::seed_user_and_thought; - use chrono::Utc; - use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn boost_and_count(pool: sqlx::PgPool) { - let (user, thought) = seed_user_and_thought(&pool).await; - let repo = PgBoostRepository::new(pool); - let boost = Boost { - id: BoostId::new(), - user_id: user.id.clone(), - thought_id: thought.id.clone(), - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&boost).await.unwrap(); - assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1); - } +use super::*; +use crate::test_helpers::seed_user_and_thought; +use chrono::Utc; +use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn unboost(pool: sqlx::PgPool) { - let (user, thought) = seed_user_and_thought(&pool).await; - let repo = PgBoostRepository::new(pool); - let boost = Boost { - id: BoostId::new(), - user_id: user.id.clone(), - thought_id: thought.id.clone(), - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&boost).await.unwrap(); - repo.delete(&user.id, &thought.id).await.unwrap(); - assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0); - } +#[sqlx::test(migrations = "./migrations")] +async fn boost_and_count(pool: sqlx::PgPool) { + let (user, thought) = seed_user_and_thought(&pool).await; + let repo = PgBoostRepository::new(pool); + let boost = Boost { + id: BoostId::new(), + user_id: user.id.clone(), + thought_id: thought.id.clone(), + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&boost).await.unwrap(); + assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1); +} + +#[sqlx::test(migrations = "./migrations")] +async fn unboost(pool: sqlx::PgPool) { + let (user, thought) = seed_user_and_thought(&pool).await; + let repo = PgBoostRepository::new(pool); + let boost = Boost { + id: BoostId::new(), + user_id: user.id.clone(), + thought_id: thought.id.clone(), + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&boost).await.unwrap(); + repo.delete(&user.id, &thought.id).await.unwrap(); + assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0); +} diff --git a/crates/adapters/postgres/src/feed/tests.rs b/crates/adapters/postgres/src/feed/tests.rs index 9193601..c6dc143 100644 --- a/crates/adapters/postgres/src/feed/tests.rs +++ b/crates/adapters/postgres/src/feed/tests.rs @@ -1,69 +1,76 @@ - use super::*; - use crate::{thought::PgThoughtRepository, user::PgUserRepository}; - use domain::{ - models::{ - feed::PageParams, - thought::{Thought, Visibility}, - user::User, - }, - ports::{FeedQuery, ThoughtRepository, UserWriter}, - value_objects::*, - }; - async fn seed(pool: &sqlx::PgPool, username: &str, content: &str) -> (User, Thought) { - let urepo = PgUserRepository::new(pool.clone()); - let trepo = PgThoughtRepository::new(pool.clone()); - let u = User::new_local( - UserId::new(), - Username::new(username).unwrap(), - Email::new(format!("{username}@ex.com")).unwrap(), - PasswordHash("h".into()), - ); - urepo.save(&u).await.unwrap(); - let t = Thought::new_local( - ThoughtId::new(), - u.id.clone(), - Content::new_local(content).unwrap(), +use super::*; +use crate::{thought::PgThoughtRepository, user::PgUserRepository}; +use domain::{ + models::{ + feed::PageParams, + thought::{Thought, Visibility}, + user::User, + }, + ports::{FeedQuery, ThoughtRepository, UserWriter}, + value_objects::*, +}; + +async fn seed(pool: &sqlx::PgPool, username: &str, content: &str) -> (User, Thought) { + let urepo = PgUserRepository::new(pool.clone()); + let trepo = PgThoughtRepository::new(pool.clone()); + let u = User::new_local( + UserId::new(), + Username::new(username).unwrap(), + Email::new(format!("{username}@ex.com")).unwrap(), + PasswordHash("h".into()), + ); + urepo.save(&u).await.unwrap(); + let t = Thought::new_local( + ThoughtId::new(), + u.id.clone(), + Content::new_local(content).unwrap(), + None, + Visibility::Public, + None, + false, + ); + trepo.save(&t).await.unwrap(); + (u, t) +} + +#[sqlx::test(migrations = "./migrations")] +async fn public_feed_returns_local_thoughts(pool: sqlx::PgPool) { + let (_, _) = seed(&pool, "alice", "hello").await; + let repo = PgFeedRepository::new(pool); + let result = repo + .query(&FeedQuery::public( + PageParams { + page: 1, + per_page: 20, + }, None, - Visibility::Public, + )) + .await + .unwrap(); + assert_eq!(result.total, 1); + assert_eq!(result.items[0].thought.content.as_str(), "hello"); +} + +#[sqlx::test(migrations = "./migrations")] +async fn search_returns_matching_thoughts(pool: sqlx::PgPool) { + let (_, _) = seed(&pool, "alice", "hello world").await; + let (_, _) = seed(&pool, "bob", "goodbye world").await; + let repo = PgFeedRepository::new(pool); + let result = repo + .query(&FeedQuery::search( + "hello world", + PageParams { + page: 1, + per_page: 20, + }, None, - false, - ); - trepo.save(&t).await.unwrap(); - (u, t) - } - - #[sqlx::test(migrations = "./migrations")] - async fn public_feed_returns_local_thoughts(pool: sqlx::PgPool) { - let (_, _) = seed(&pool, "alice", "hello").await; - let repo = PgFeedRepository::new(pool); - let result = repo - .query(&FeedQuery::public( - PageParams { page: 1, per_page: 20 }, - None, - )) - .await - .unwrap(); - assert_eq!(result.total, 1); - assert_eq!(result.items[0].thought.content.as_str(), "hello"); - } - - #[sqlx::test(migrations = "./migrations")] - async fn search_returns_matching_thoughts(pool: sqlx::PgPool) { - let (_, _) = seed(&pool, "alice", "hello world").await; - let (_, _) = seed(&pool, "bob", "goodbye world").await; - let repo = PgFeedRepository::new(pool); - let result = repo - .query(&FeedQuery::search( - "hello world", - PageParams { page: 1, per_page: 20 }, - None, - )) - .await - .unwrap(); - assert!(result.total >= 1); - assert!(result - .items - .iter() - .any(|e| e.thought.content.as_str() == "hello world")); - } + )) + .await + .unwrap(); + assert!(result.total >= 1); + assert!(result + .items + .iter() + .any(|e| e.thought.content.as_str() == "hello world")); +} diff --git a/crates/adapters/postgres/src/follow/tests.rs b/crates/adapters/postgres/src/follow/tests.rs index 05ba499..e3379d8 100644 --- a/crates/adapters/postgres/src/follow/tests.rs +++ b/crates/adapters/postgres/src/follow/tests.rs @@ -1,58 +1,59 @@ - use super::*; - use crate::test_helpers::seed_user; - use chrono::Utc; - use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn save_and_find_follow(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgFollowRepository::new(pool); - let follow = Follow { - follower_id: alice.id.clone(), - following_id: bob.id.clone(), - state: FollowState::Accepted, - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&follow).await.unwrap(); - let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap(); - assert_eq!(found.state, FollowState::Accepted); - } +use super::*; +use crate::test_helpers::seed_user; +use chrono::Utc; +use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn update_state(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgFollowRepository::new(pool); - let follow = Follow { - follower_id: alice.id.clone(), - following_id: bob.id.clone(), - state: FollowState::Pending, - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&follow).await.unwrap(); - repo.update_state(&alice.id, &bob.id, &FollowState::Accepted) - .await - .unwrap(); - let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap(); - assert_eq!(found.state, FollowState::Accepted); - } +#[sqlx::test(migrations = "./migrations")] +async fn save_and_find_follow(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgFollowRepository::new(pool); + let follow = Follow { + follower_id: alice.id.clone(), + following_id: bob.id.clone(), + state: FollowState::Accepted, + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&follow).await.unwrap(); + let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap(); + assert_eq!(found.state, FollowState::Accepted); +} - #[sqlx::test(migrations = "./migrations")] - async fn get_accepted_following_ids(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgFollowRepository::new(pool); - let follow = Follow { - follower_id: alice.id.clone(), - following_id: bob.id.clone(), - state: FollowState::Accepted, - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&follow).await.unwrap(); - let ids = repo.get_accepted_following_ids(&alice.id).await.unwrap(); - assert_eq!(ids, vec![bob.id]); - } +#[sqlx::test(migrations = "./migrations")] +async fn update_state(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgFollowRepository::new(pool); + let follow = Follow { + follower_id: alice.id.clone(), + following_id: bob.id.clone(), + state: FollowState::Pending, + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&follow).await.unwrap(); + repo.update_state(&alice.id, &bob.id, &FollowState::Accepted) + .await + .unwrap(); + let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap(); + assert_eq!(found.state, FollowState::Accepted); +} + +#[sqlx::test(migrations = "./migrations")] +async fn get_accepted_following_ids(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgFollowRepository::new(pool); + let follow = Follow { + follower_id: alice.id.clone(), + following_id: bob.id.clone(), + state: FollowState::Accepted, + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&follow).await.unwrap(); + let ids = repo.get_accepted_following_ids(&alice.id).await.unwrap(); + assert_eq!(ids, vec![bob.id]); +} diff --git a/crates/adapters/postgres/src/lib.rs b/crates/adapters/postgres/src/lib.rs index 7694f24..2d86fdb 100644 --- a/crates/adapters/postgres/src/lib.rs +++ b/crates/adapters/postgres/src/lib.rs @@ -1,15 +1,15 @@ pub mod activitypub; -pub mod engagement; pub mod api_key; pub mod block; pub mod boost; mod db_error; +pub mod engagement; pub mod failed_event; -pub mod outbox; pub mod feed; pub mod follow; pub mod like; pub mod notification; +pub mod outbox; pub mod remote_actor; pub mod remote_actor_connections; pub mod tag; diff --git a/crates/adapters/postgres/src/like/tests.rs b/crates/adapters/postgres/src/like/tests.rs index 1106d67..3c2f14e 100644 --- a/crates/adapters/postgres/src/like/tests.rs +++ b/crates/adapters/postgres/src/like/tests.rs @@ -1,35 +1,36 @@ - use super::*; - use crate::test_helpers::seed_user_and_thought; - use chrono::Utc; - use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn like_and_count(pool: sqlx::PgPool) { - let (user, thought) = seed_user_and_thought(&pool).await; - let repo = PgLikeRepository::new(pool); - let like = Like { - id: LikeId::new(), - user_id: user.id.clone(), - thought_id: thought.id.clone(), - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&like).await.unwrap(); - assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1); - } +use super::*; +use crate::test_helpers::seed_user_and_thought; +use chrono::Utc; +use domain::value_objects::*; - #[sqlx::test(migrations = "./migrations")] - async fn unlike(pool: sqlx::PgPool) { - let (user, thought) = seed_user_and_thought(&pool).await; - let repo = PgLikeRepository::new(pool); - let like = Like { - id: LikeId::new(), - user_id: user.id.clone(), - thought_id: thought.id.clone(), - ap_id: None, - created_at: Utc::now(), - }; - repo.save(&like).await.unwrap(); - repo.delete(&user.id, &thought.id).await.unwrap(); - assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0); - } +#[sqlx::test(migrations = "./migrations")] +async fn like_and_count(pool: sqlx::PgPool) { + let (user, thought) = seed_user_and_thought(&pool).await; + let repo = PgLikeRepository::new(pool); + let like = Like { + id: LikeId::new(), + user_id: user.id.clone(), + thought_id: thought.id.clone(), + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&like).await.unwrap(); + assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1); +} + +#[sqlx::test(migrations = "./migrations")] +async fn unlike(pool: sqlx::PgPool) { + let (user, thought) = seed_user_and_thought(&pool).await; + let repo = PgLikeRepository::new(pool); + let like = Like { + id: LikeId::new(), + user_id: user.id.clone(), + thought_id: thought.id.clone(), + ap_id: None, + created_at: Utc::now(), + }; + repo.save(&like).await.unwrap(); + repo.delete(&user.id, &thought.id).await.unwrap(); + assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0); +} diff --git a/crates/adapters/postgres/src/notification/tests.rs b/crates/adapters/postgres/src/notification/tests.rs index 9a9caf8..876a23f 100644 --- a/crates/adapters/postgres/src/notification/tests.rs +++ b/crates/adapters/postgres/src/notification/tests.rs @@ -1,67 +1,68 @@ - use super::*; - use crate::test_helpers; - use chrono::Utc; - use domain::{ - models::{notification::NotificationKind, user::User}, - value_objects::*, + +use super::*; +use crate::test_helpers; +use chrono::Utc; +use domain::{ + models::{notification::NotificationKind, user::User}, + value_objects::*, +}; + +#[sqlx::test(migrations = "./migrations")] +async fn save_and_list(pool: sqlx::PgPool) { + let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await; + let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgNotificationRepository::new(pool); + use domain::models::feed::PageParams; + let n = Notification { + id: NotificationId::new(), + user_id: user.id.clone(), + kind: NotificationKind::Follow { + from_user_id: from_user.id.clone(), + }, + read: false, + created_at: Utc::now(), }; - - #[sqlx::test(migrations = "./migrations")] - async fn save_and_list(pool: sqlx::PgPool) { - let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await; - let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgNotificationRepository::new(pool); - use domain::models::feed::PageParams; - let n = Notification { - id: NotificationId::new(), - user_id: user.id.clone(), - kind: NotificationKind::Follow { - from_user_id: from_user.id.clone(), + repo.save(&n).await.unwrap(); + let page = repo + .list_for_user( + &user.id, + &PageParams { + page: 1, + per_page: 20, }, - read: false, - created_at: Utc::now(), - }; - repo.save(&n).await.unwrap(); - let page = repo - .list_for_user( - &user.id, - &PageParams { - page: 1, - per_page: 20, - }, - ) - .await - .unwrap(); - assert_eq!(page.total, 1); - assert!(!page.items[0].read); - } + ) + .await + .unwrap(); + assert_eq!(page.total, 1); + assert!(!page.items[0].read); +} - #[sqlx::test(migrations = "./migrations")] - async fn mark_all_read(pool: sqlx::PgPool) { - let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await; - let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgNotificationRepository::new(pool); - use domain::models::feed::PageParams; - let n = Notification { - id: NotificationId::new(), - user_id: user.id.clone(), - kind: NotificationKind::Follow { - from_user_id: from_user.id.clone(), +#[sqlx::test(migrations = "./migrations")] +async fn mark_all_read(pool: sqlx::PgPool) { + let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await; + let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgNotificationRepository::new(pool); + use domain::models::feed::PageParams; + let n = Notification { + id: NotificationId::new(), + user_id: user.id.clone(), + kind: NotificationKind::Follow { + from_user_id: from_user.id.clone(), + }, + read: false, + created_at: Utc::now(), + }; + repo.save(&n).await.unwrap(); + repo.mark_all_read(&user.id).await.unwrap(); + let page = repo + .list_for_user( + &user.id, + &PageParams { + page: 1, + per_page: 20, }, - read: false, - created_at: Utc::now(), - }; - repo.save(&n).await.unwrap(); - repo.mark_all_read(&user.id).await.unwrap(); - let page = repo - .list_for_user( - &user.id, - &PageParams { - page: 1, - per_page: 20, - }, - ) - .await - .unwrap(); - assert!(page.items[0].read); - } + ) + .await + .unwrap(); + assert!(page.items[0].read); +} diff --git a/crates/adapters/postgres/src/tag/tests.rs b/crates/adapters/postgres/src/tag/tests.rs index 0cb5512..92550dd 100644 --- a/crates/adapters/postgres/src/tag/tests.rs +++ b/crates/adapters/postgres/src/tag/tests.rs @@ -1,48 +1,49 @@ - use super::*; - use crate::{thought::PgThoughtRepository, user::PgUserRepository}; - use domain::ports::{ThoughtRepository, UserWriter}; - use domain::{ - models::{ - thought::{Thought, Visibility}, - user::User, - }, - value_objects::*, - }; - #[sqlx::test(migrations = "./migrations")] - async fn find_or_create_tag(pool: sqlx::PgPool) { - let repo = PgTagRepository::new(pool); - let t1 = repo.find_or_create("rust").await.unwrap(); - let t2 = repo.find_or_create("rust").await.unwrap(); - assert_eq!(t1.id, t2.id); - assert_eq!(t1.name, "rust"); - } +use super::*; +use crate::{thought::PgThoughtRepository, user::PgUserRepository}; +use domain::ports::{ThoughtRepository, UserWriter}; +use domain::{ + models::{ + thought::{Thought, Visibility}, + user::User, + }, + value_objects::*, +}; - #[sqlx::test(migrations = "./migrations")] - async fn attach_and_list(pool: sqlx::PgPool) { - let urepo = PgUserRepository::new(pool.clone()); - let trepo = PgThoughtRepository::new(pool.clone()); - let u = User::new_local( - UserId::new(), - Username::new("alice").unwrap(), - Email::new("alice@ex.com").unwrap(), - PasswordHash("h".into()), - ); - urepo.save(&u).await.unwrap(); - let t = Thought::new_local( - ThoughtId::new(), - u.id.clone(), - Content::new_local("hi").unwrap(), - None, - Visibility::Public, - None, - false, - ); - trepo.save(&t).await.unwrap(); - let repo = PgTagRepository::new(pool); - let tag = repo.find_or_create("greetings").await.unwrap(); - repo.attach_to_thought(&t.id, tag.id).await.unwrap(); - let tags = repo.list_for_thought(&t.id).await.unwrap(); - assert_eq!(tags.len(), 1); - assert_eq!(tags[0].name, "greetings"); - } +#[sqlx::test(migrations = "./migrations")] +async fn find_or_create_tag(pool: sqlx::PgPool) { + let repo = PgTagRepository::new(pool); + let t1 = repo.find_or_create("rust").await.unwrap(); + let t2 = repo.find_or_create("rust").await.unwrap(); + assert_eq!(t1.id, t2.id); + assert_eq!(t1.name, "rust"); +} + +#[sqlx::test(migrations = "./migrations")] +async fn attach_and_list(pool: sqlx::PgPool) { + let urepo = PgUserRepository::new(pool.clone()); + let trepo = PgThoughtRepository::new(pool.clone()); + let u = User::new_local( + UserId::new(), + Username::new("alice").unwrap(), + Email::new("alice@ex.com").unwrap(), + PasswordHash("h".into()), + ); + urepo.save(&u).await.unwrap(); + let t = Thought::new_local( + ThoughtId::new(), + u.id.clone(), + Content::new_local("hi").unwrap(), + None, + Visibility::Public, + None, + false, + ); + trepo.save(&t).await.unwrap(); + let repo = PgTagRepository::new(pool); + let tag = repo.find_or_create("greetings").await.unwrap(); + repo.attach_to_thought(&t.id, tag.id).await.unwrap(); + let tags = repo.list_for_thought(&t.id).await.unwrap(); + assert_eq!(tags.len(), 1); + assert_eq!(tags[0].name, "greetings"); +} diff --git a/crates/adapters/postgres/src/thought/tests.rs b/crates/adapters/postgres/src/thought/tests.rs index c227c4d..82426e6 100644 --- a/crates/adapters/postgres/src/thought/tests.rs +++ b/crates/adapters/postgres/src/thought/tests.rs @@ -1,90 +1,91 @@ - use super::*; - use crate::test_helpers::seed_user; - use domain::{ - models::thought::{Thought, Visibility}, - value_objects::*, - }; - #[sqlx::test(migrations = "./migrations")] - async fn save_and_find_thought(pool: sqlx::PgPool) { - let user = seed_user(&pool, "alice", "alice@ex.com").await; - let repo = PgThoughtRepository::new(pool); - let t = Thought::new_local( - ThoughtId::new(), - user.id.clone(), - Content::new_local("hello world").unwrap(), - None, - Visibility::Public, - None, - false, - ); - repo.save(&t).await.unwrap(); - let found = repo.find_by_id(&t.id).await.unwrap().unwrap(); - assert_eq!(found.content.as_str(), "hello world"); - assert!(found.local); - } +use super::*; +use crate::test_helpers::seed_user; +use domain::{ + models::thought::{Thought, Visibility}, + value_objects::*, +}; - #[sqlx::test(migrations = "./migrations")] - async fn delete_thought(pool: sqlx::PgPool) { - let user = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgThoughtRepository::new(pool); - let t = Thought::new_local( - ThoughtId::new(), - user.id.clone(), - Content::new_local("bye").unwrap(), - None, - Visibility::Public, - None, - false, - ); - repo.save(&t).await.unwrap(); - repo.delete(&t.id, &user.id).await.unwrap(); - assert!(repo.find_by_id(&t.id).await.unwrap().is_none()); - } +#[sqlx::test(migrations = "./migrations")] +async fn save_and_find_thought(pool: sqlx::PgPool) { + let user = seed_user(&pool, "alice", "alice@ex.com").await; + let repo = PgThoughtRepository::new(pool); + let t = Thought::new_local( + ThoughtId::new(), + user.id.clone(), + Content::new_local("hello world").unwrap(), + None, + Visibility::Public, + None, + false, + ); + repo.save(&t).await.unwrap(); + let found = repo.find_by_id(&t.id).await.unwrap().unwrap(); + assert_eq!(found.content.as_str(), "hello world"); + assert!(found.local); +} - #[sqlx::test(migrations = "./migrations")] - async fn delete_wrong_owner_returns_not_found(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgThoughtRepository::new(pool); - let t = Thought::new_local( - ThoughtId::new(), - alice.id.clone(), - Content::new_local("secret").unwrap(), - None, - Visibility::Public, - None, - false, - ); - repo.save(&t).await.unwrap(); - let err = repo.delete(&t.id, &bob.id).await.unwrap_err(); - assert!(matches!(err, DomainError::NotFound)); - } +#[sqlx::test(migrations = "./migrations")] +async fn delete_thought(pool: sqlx::PgPool) { + let user = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgThoughtRepository::new(pool); + let t = Thought::new_local( + ThoughtId::new(), + user.id.clone(), + Content::new_local("bye").unwrap(), + None, + Visibility::Public, + None, + false, + ); + repo.save(&t).await.unwrap(); + repo.delete(&t.id, &user.id).await.unwrap(); + assert!(repo.find_by_id(&t.id).await.unwrap().is_none()); +} - #[sqlx::test(migrations = "./migrations")] - async fn get_thread_returns_root_and_replies(pool: sqlx::PgPool) { - let user = seed_user(&pool, "charlie", "charlie@ex.com").await; - let repo = PgThoughtRepository::new(pool); - let root = Thought::new_local( - ThoughtId::new(), - user.id.clone(), - Content::new_local("root").unwrap(), - None, - Visibility::Public, - None, - false, - ); - let reply = Thought::new_local( - ThoughtId::new(), - user.id.clone(), - Content::new_local("reply").unwrap(), - Some(root.id.clone()), - Visibility::Public, - None, - false, - ); - repo.save(&root).await.unwrap(); - repo.save(&reply).await.unwrap(); - let thread = repo.get_thread(&root.id).await.unwrap(); - assert_eq!(thread.len(), 2); - } +#[sqlx::test(migrations = "./migrations")] +async fn delete_wrong_owner_returns_not_found(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgThoughtRepository::new(pool); + let t = Thought::new_local( + ThoughtId::new(), + alice.id.clone(), + Content::new_local("secret").unwrap(), + None, + Visibility::Public, + None, + false, + ); + repo.save(&t).await.unwrap(); + let err = repo.delete(&t.id, &bob.id).await.unwrap_err(); + assert!(matches!(err, DomainError::NotFound)); +} + +#[sqlx::test(migrations = "./migrations")] +async fn get_thread_returns_root_and_replies(pool: sqlx::PgPool) { + let user = seed_user(&pool, "charlie", "charlie@ex.com").await; + let repo = PgThoughtRepository::new(pool); + let root = Thought::new_local( + ThoughtId::new(), + user.id.clone(), + Content::new_local("root").unwrap(), + None, + Visibility::Public, + None, + false, + ); + let reply = Thought::new_local( + ThoughtId::new(), + user.id.clone(), + Content::new_local("reply").unwrap(), + Some(root.id.clone()), + Visibility::Public, + None, + false, + ); + repo.save(&root).await.unwrap(); + repo.save(&reply).await.unwrap(); + let thread = repo.get_thread(&root.id).await.unwrap(); + assert_eq!(thread.len(), 2); +} diff --git a/crates/adapters/postgres/src/top_friend/tests.rs b/crates/adapters/postgres/src/top_friend/tests.rs index 8e14acc..1042c93 100644 --- a/crates/adapters/postgres/src/top_friend/tests.rs +++ b/crates/adapters/postgres/src/top_friend/tests.rs @@ -1,47 +1,48 @@ - use super::*; - use crate::user::PgUserRepository; - use domain::ports::UserWriter; - use domain::{models::user::User, value_objects::*}; - async fn seed_user(pool: &sqlx::PgPool, username: &str, email: &str) -> User { - let repo = PgUserRepository::new(pool.clone()); - let u = User::new_local( - UserId::new(), - Username::new(username).unwrap(), - Email::new(email).unwrap(), - PasswordHash("h".into()), - ); - repo.save(&u).await.unwrap(); - u - } +use super::*; +use crate::user::PgUserRepository; +use domain::ports::UserWriter; +use domain::{models::user::User, value_objects::*}; - #[sqlx::test(migrations = "./migrations")] - async fn set_and_list_top_friends(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let repo = PgTopFriendRepository::new(pool); - repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)]) - .await - .unwrap(); - let friends = repo.list_for_user(&alice.id).await.unwrap(); - assert_eq!(friends.len(), 1); - assert_eq!(friends[0].0.position, 1); - assert_eq!(friends[0].1.username.as_str(), "bob"); - } +async fn seed_user(pool: &sqlx::PgPool, username: &str, email: &str) -> User { + let repo = PgUserRepository::new(pool.clone()); + let u = User::new_local( + UserId::new(), + Username::new(username).unwrap(), + Email::new(email).unwrap(), + PasswordHash("h".into()), + ); + repo.save(&u).await.unwrap(); + u +} - #[sqlx::test(migrations = "./migrations")] - async fn replace_top_friends(pool: sqlx::PgPool) { - let alice = seed_user(&pool, "alice", "alice@ex.com").await; - let bob = seed_user(&pool, "bob", "bob@ex.com").await; - let carol = seed_user(&pool, "carol", "carol@ex.com").await; - let repo = PgTopFriendRepository::new(pool); - repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)]) - .await - .unwrap(); - repo.set_top_friends(&alice.id, vec![(carol.id.clone(), 1)]) - .await - .unwrap(); - let friends = repo.list_for_user(&alice.id).await.unwrap(); - assert_eq!(friends.len(), 1); - assert_eq!(friends[0].1.username.as_str(), "carol"); - } +#[sqlx::test(migrations = "./migrations")] +async fn set_and_list_top_friends(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let repo = PgTopFriendRepository::new(pool); + repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)]) + .await + .unwrap(); + let friends = repo.list_for_user(&alice.id).await.unwrap(); + assert_eq!(friends.len(), 1); + assert_eq!(friends[0].0.position, 1); + assert_eq!(friends[0].1.username.as_str(), "bob"); +} + +#[sqlx::test(migrations = "./migrations")] +async fn replace_top_friends(pool: sqlx::PgPool) { + let alice = seed_user(&pool, "alice", "alice@ex.com").await; + let bob = seed_user(&pool, "bob", "bob@ex.com").await; + let carol = seed_user(&pool, "carol", "carol@ex.com").await; + let repo = PgTopFriendRepository::new(pool); + repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)]) + .await + .unwrap(); + repo.set_top_friends(&alice.id, vec![(carol.id.clone(), 1)]) + .await + .unwrap(); + let friends = repo.list_for_user(&alice.id).await.unwrap(); + assert_eq!(friends.len(), 1); + assert_eq!(friends[0].1.username.as_str(), "carol"); +} diff --git a/crates/adapters/postgres/src/user/mod.rs b/crates/adapters/postgres/src/user/mod.rs index 1f68bd7..9e30ad8 100644 --- a/crates/adapters/postgres/src/user/mod.rs +++ b/crates/adapters/postgres/src/user/mod.rs @@ -139,7 +139,10 @@ impl UserReader for PgUserRepository { .into_domain() } - async fn list_paginated(&self, page: PageParams) -> Result, DomainError> { + async fn list_paginated( + &self, + page: PageParams, + ) -> Result, DomainError> { #[derive(sqlx::FromRow)] struct Row { id: uuid::Uuid, @@ -187,7 +190,12 @@ impl UserReader for PgUserRepository { following_count: r.following_count, }) .collect(); - Ok(Paginated { items, total, page: page.page, per_page: page.per_page }) + Ok(Paginated { + items, + total, + page: page.page, + per_page: page.per_page, + }) } async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> { @@ -195,18 +203,19 @@ impl UserReader for PgUserRepository { return Ok(HashMap::new()); } let uuids: Vec = ids.iter().map(|id| id.as_uuid()).collect(); - let rows = sqlx::query_as::<_, UserRow>( - &format!("{USER_SELECT} WHERE id = ANY($1)") - ) - .bind(&uuids[..]) - .fetch_all(&self.pool) - .await - .into_domain()?; + let rows = sqlx::query_as::<_, UserRow>(&format!("{USER_SELECT} WHERE id = ANY($1)")) + .bind(&uuids[..]) + .fetch_all(&self.pool) + .await + .into_domain()?; - Ok(rows.into_iter().map(|r| { - let user = User::from(r); - (user.id.clone(), user) - }).collect()) + Ok(rows + .into_iter() + .map(|r| { + let user = User::from(r); + (user.id.clone(), user) + }) + .collect()) } } diff --git a/crates/adapters/postgres/src/user/tests.rs b/crates/adapters/postgres/src/user/tests.rs index b7cf915..5f5aa92 100644 --- a/crates/adapters/postgres/src/user/tests.rs +++ b/crates/adapters/postgres/src/user/tests.rs @@ -1,69 +1,70 @@ - use super::*; - use domain::{models::user::User, value_objects::*}; - #[sqlx::test(migrations = "./migrations")] - async fn save_and_find_by_id(pool: sqlx::PgPool) { - let repo = PgUserRepository::new(pool); - let user = User::new_local( - UserId::new(), - Username::new("alice").unwrap(), - Email::new("alice@ex.com").unwrap(), - PasswordHash("hash".into()), - ); - repo.save(&user).await.unwrap(); - let found = repo.find_by_id(&user.id).await.unwrap().unwrap(); - assert_eq!(found.username.as_str(), "alice"); - assert_eq!(found.email.as_str(), "alice@ex.com"); - } +use super::*; +use domain::{models::user::User, value_objects::*}; - #[sqlx::test(migrations = "./migrations")] - async fn find_by_username_returns_none_when_missing(pool: sqlx::PgPool) { - let repo = PgUserRepository::new(pool); - let result = repo - .find_by_username(&Username::new("ghost").unwrap()) - .await - .unwrap(); - assert!(result.is_none()); - } +#[sqlx::test(migrations = "./migrations")] +async fn save_and_find_by_id(pool: sqlx::PgPool) { + let repo = PgUserRepository::new(pool); + let user = User::new_local( + UserId::new(), + Username::new("alice").unwrap(), + Email::new("alice@ex.com").unwrap(), + PasswordHash("hash".into()), + ); + repo.save(&user).await.unwrap(); + let found = repo.find_by_id(&user.id).await.unwrap().unwrap(); + assert_eq!(found.username.as_str(), "alice"); + assert_eq!(found.email.as_str(), "alice@ex.com"); +} - #[sqlx::test(migrations = "./migrations")] - async fn find_by_email(pool: sqlx::PgPool) { - let repo = PgUserRepository::new(pool); - let user = User::new_local( - UserId::new(), - Username::new("bob").unwrap(), - Email::new("bob@ex.com").unwrap(), - PasswordHash("hash".into()), - ); - repo.save(&user).await.unwrap(); - let found = repo - .find_by_email(&Email::new("bob@ex.com").unwrap()) - .await - .unwrap(); - assert!(found.is_some()); - } - - #[sqlx::test(migrations = "./migrations")] - async fn update_profile_changes_fields(pool: sqlx::PgPool) { - let repo = PgUserRepository::new(pool); - let user = User::new_local( - UserId::new(), - Username::new("charlie").unwrap(), - Email::new("charlie@ex.com").unwrap(), - PasswordHash("hash".into()), - ); - repo.save(&user).await.unwrap(); - repo.update_profile( - &user.id, - Some("Charlie".into()), - Some("bio".into()), - None, - None, - None, - ) +#[sqlx::test(migrations = "./migrations")] +async fn find_by_username_returns_none_when_missing(pool: sqlx::PgPool) { + let repo = PgUserRepository::new(pool); + let result = repo + .find_by_username(&Username::new("ghost").unwrap()) .await .unwrap(); - let found = repo.find_by_id(&user.id).await.unwrap().unwrap(); - assert_eq!(found.display_name.as_deref(), Some("Charlie")); - assert_eq!(found.bio.as_deref(), Some("bio")); - } + assert!(result.is_none()); +} + +#[sqlx::test(migrations = "./migrations")] +async fn find_by_email(pool: sqlx::PgPool) { + let repo = PgUserRepository::new(pool); + let user = User::new_local( + UserId::new(), + Username::new("bob").unwrap(), + Email::new("bob@ex.com").unwrap(), + PasswordHash("hash".into()), + ); + repo.save(&user).await.unwrap(); + let found = repo + .find_by_email(&Email::new("bob@ex.com").unwrap()) + .await + .unwrap(); + assert!(found.is_some()); +} + +#[sqlx::test(migrations = "./migrations")] +async fn update_profile_changes_fields(pool: sqlx::PgPool) { + let repo = PgUserRepository::new(pool); + let user = User::new_local( + UserId::new(), + Username::new("charlie").unwrap(), + Email::new("charlie@ex.com").unwrap(), + PasswordHash("hash".into()), + ); + repo.save(&user).await.unwrap(); + repo.update_profile( + &user.id, + Some("Charlie".into()), + Some("bio".into()), + None, + None, + None, + ) + .await + .unwrap(); + let found = repo.find_by_id(&user.id).await.unwrap().unwrap(); + assert_eq!(found.display_name.as_deref(), Some("Charlie")); + assert_eq!(found.bio.as_deref(), Some("bio")); +} diff --git a/crates/application/src/services/federation_event/tests.rs b/crates/application/src/services/federation_event/tests.rs index 9a407d8..b6ac54e 100644 --- a/crates/application/src/services/federation_event/tests.rs +++ b/crates/application/src/services/federation_event/tests.rs @@ -1,7 +1,7 @@ use super::*; +use crate::testing::TestApRepo; use activitypub_base::{ActorApUrls, OutboundFederationPort}; use async_trait::async_trait; -use crate::testing::TestApRepo; use domain::{ errors::DomainError, events::DomainEvent, @@ -56,21 +56,12 @@ impl OutboundFederationPort for SpyPort { self.announced.lock().unwrap().push(ap_id.to_string()); Ok(()) } - async fn broadcast_undo_announce( - &self, - _: &UserId, - ap_id: &str, - ) -> Result<(), DomainError> { + async fn broadcast_undo_announce(&self, _: &UserId, ap_id: &str) -> Result<(), DomainError> { self.undo_announced.lock().unwrap().push(ap_id.to_string()); Ok(()) } - async fn broadcast_like( - &self, - _: &UserId, - ap_id: &str, - _: &str, - ) -> Result<(), DomainError> { + async fn broadcast_like(&self, _: &UserId, ap_id: &str, _: &str) -> Result<(), DomainError> { self.liked.lock().unwrap().push(ap_id.to_string()); Ok(()) } @@ -123,7 +114,11 @@ fn svc(store: &TestStore, spy: Arc) -> FederationEventService { } } -fn svc_with_ap(store: &TestStore, ap_repo: TestApRepo, spy: Arc) -> FederationEventService { +fn svc_with_ap( + store: &TestStore, + ap_repo: TestApRepo, + spy: Arc, +) -> FederationEventService { FederationEventService { thoughts: Arc::new(store.clone()), users: Arc::new(store.clone()), diff --git a/crates/application/src/testing.rs b/crates/application/src/testing.rs index b35a40c..64e2653 100644 --- a/crates/application/src/testing.rs +++ b/crates/application/src/testing.rs @@ -106,11 +106,7 @@ impl ActivityPubRepository for TestApRepo { ) -> Result { Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4())) } - async fn apply_note_update( - &self, - _ap_id: &str, - _new_content: &str, - ) -> Result<(), DomainError> { + async fn apply_note_update(&self, _ap_id: &str, _new_content: &str) -> Result<(), DomainError> { Ok(()) } async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> { diff --git a/crates/application/src/use_cases/auth/mod.rs b/crates/application/src/use_cases/auth/mod.rs index be41385..9359536 100644 --- a/crates/application/src/use_cases/auth/mod.rs +++ b/crates/application/src/use_cases/auth/mod.rs @@ -34,21 +34,16 @@ pub async fn register( } let hash = hasher.hash(&input.password).await?; let user = User::new_local(UserId::new(), username, email, hash); - users - .save(&user) - .await - .map_err(|e| match e { - DomainError::UniqueViolation { field: "username" } => { - DomainError::Conflict("username taken".into()) - } - DomainError::UniqueViolation { field: "email" } => { - DomainError::Conflict("email taken".into()) - } - DomainError::UniqueViolation { .. } => { - DomainError::Conflict("already exists".into()) - } - other => other, - })?; + users.save(&user).await.map_err(|e| match e { + DomainError::UniqueViolation { field: "username" } => { + DomainError::Conflict("username taken".into()) + } + DomainError::UniqueViolation { field: "email" } => { + DomainError::Conflict("email taken".into()) + } + DomainError::UniqueViolation { .. } => DomainError::Conflict("already exists".into()), + other => other, + })?; events .publish(&DomainEvent::UserRegistered { user_id: user.id.clone(), diff --git a/crates/application/src/use_cases/auth/tests.rs b/crates/application/src/use_cases/auth/tests.rs index 86e4d1b..d5985eb 100644 --- a/crates/application/src/use_cases/auth/tests.rs +++ b/crates/application/src/use_cases/auth/tests.rs @@ -3,7 +3,10 @@ use async_trait::async_trait; use domain::{ errors::DomainError, events::DomainEvent, - models::{feed::{PageParams, Paginated, UserSummary}, user::User}, + models::{ + feed::{PageParams, Paginated, UserSummary}, + user::User, + }, ports::{AuthService, GeneratedToken, PasswordHasher, UserReader, UserWriter}, testing::{NoOpEventPublisher, TestStore}, value_objects::{Email, PasswordHash, UserId, Username}, @@ -19,10 +22,7 @@ impl UserReader for ConflictOnSaveStore { async fn find_by_id(&self, id: &UserId) -> Result, DomainError> { self.0.find_by_id(id).await } - async fn find_by_username( - &self, - username: &Username, - ) -> Result, DomainError> { + async fn find_by_username(&self, username: &Username) -> Result, DomainError> { self.0.find_by_username(username).await } async fn find_by_email(&self, email: &Email) -> Result, DomainError> { @@ -34,10 +34,16 @@ impl UserReader for ConflictOnSaveStore { async fn count(&self) -> Result { self.0.count().await } - async fn list_paginated(&self, page: PageParams) -> Result, DomainError> { + async fn list_paginated( + &self, + page: PageParams, + ) -> Result, DomainError> { self.0.list_paginated(page).await } - async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> { + async fn find_by_ids( + &self, + ids: &[UserId], + ) -> Result, DomainError> { self.0.find_by_ids(ids).await } } @@ -57,7 +63,14 @@ impl UserWriter for ConflictOnSaveStore { custom_css: Option, ) -> Result<(), DomainError> { self.0 - .update_profile(user_id, display_name, bio, avatar_url, header_url, custom_css) + .update_profile( + user_id, + display_name, + bio, + avatar_url, + header_url, + custom_css, + ) .await } } @@ -67,10 +80,7 @@ impl UserReader for EmailConflictOnSaveStore { async fn find_by_id(&self, id: &UserId) -> Result, DomainError> { self.0.find_by_id(id).await } - async fn find_by_username( - &self, - username: &Username, - ) -> Result, DomainError> { + async fn find_by_username(&self, username: &Username) -> Result, DomainError> { self.0.find_by_username(username).await } async fn find_by_email(&self, email: &Email) -> Result, DomainError> { @@ -82,10 +92,16 @@ impl UserReader for EmailConflictOnSaveStore { async fn count(&self) -> Result { self.0.count().await } - async fn list_paginated(&self, page: PageParams) -> Result, DomainError> { + async fn list_paginated( + &self, + page: PageParams, + ) -> Result, DomainError> { self.0.list_paginated(page).await } - async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> { + async fn find_by_ids( + &self, + ids: &[UserId], + ) -> Result, DomainError> { self.0.find_by_ids(ids).await } } @@ -105,7 +121,14 @@ impl UserWriter for EmailConflictOnSaveStore { custom_css: Option, ) -> Result<(), DomainError> { self.0 - .update_profile(user_id, display_name, bio, avatar_url, header_url, custom_css) + .update_profile( + user_id, + display_name, + bio, + avatar_url, + header_url, + custom_css, + ) .await } } diff --git a/crates/application/src/use_cases/federation_management/mod.rs b/crates/application/src/use_cases/federation_management/mod.rs index a9f2771..f4712e1 100644 --- a/crates/application/src/use_cases/federation_management/mod.rs +++ b/crates/application/src/use_cases/federation_management/mod.rs @@ -7,9 +7,9 @@ use domain::{ remote_actor::RemoteActor, }, ports::{ - EventPublisher, FederationActionPort, FederationFollowPort, - FederationFollowRequestPort, FederationSchedulerPort, FeedQuery, FeedRepository, - FollowRepository, RemoteActorConnectionRepository, UserReader, + EventPublisher, FederationActionPort, FederationFollowPort, FederationFollowRequestPort, + FederationSchedulerPort, FeedQuery, FeedRepository, FollowRepository, + RemoteActorConnectionRepository, UserReader, }, value_objects::UserId, }; @@ -86,7 +86,13 @@ pub async fn get_remote_actor_posts( Some(id) => id, None => ap_repo.intern_remote_actor(&actor.url).await?, }; - let result = feed.query(&FeedQuery::user(author_id, page.clone(), viewer_id.cloned())).await?; + let result = feed + .query(&FeedQuery::user( + author_id, + page.clone(), + viewer_id.cloned(), + )) + .await?; if let Some(outbox_url) = actor.outbox_url { let _ = scheduler .schedule_actor_posts_fetch(&actor.url, &outbox_url) diff --git a/crates/application/src/use_cases/feed.rs b/crates/application/src/use_cases/feed.rs index b384e3e..b16e057 100644 --- a/crates/application/src/use_cases/feed.rs +++ b/crates/application/src/use_cases/feed.rs @@ -13,5 +13,6 @@ pub async fn get_home_feed( ) -> Result, DomainError> { let mut following_ids = follows.get_accepted_following_ids(user_id).await?; following_ids.push(user_id.clone()); - feed.query(&FeedQuery::home(user_id.clone(), following_ids, page)).await + feed.query(&FeedQuery::home(user_id.clone(), following_ids, page)) + .await } diff --git a/crates/application/src/use_cases/thoughts/mod.rs b/crates/application/src/use_cases/thoughts/mod.rs index 6249131..f9627bd 100644 --- a/crates/application/src/use_cases/thoughts/mod.rs +++ b/crates/application/src/use_cases/thoughts/mod.rs @@ -5,7 +5,10 @@ use domain::{ feed::{EngagementStats, FeedEntry}, thought::{Thought, Visibility}, }, - ports::{EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, UserReader}, + ports::{ + EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, + UserReader, + }, value_objects::{Content, ThoughtId, UserId}, }; @@ -133,10 +136,20 @@ pub async fn get_thought_view( .await? .ok_or(DomainError::NotFound)?; let mut map = engagement.get_for_thoughts(&[id.clone()], viewer).await?; - let (stats, viewer_ctx) = map.remove(id).unwrap_or( - (EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 }, None) - ); - Ok(FeedEntry { thought, author, stats, viewer: viewer_ctx }) + let (stats, viewer_ctx) = map.remove(id).unwrap_or(( + EngagementStats { + like_count: 0, + boost_count: 0, + reply_count: 0, + }, + None, + )); + Ok(FeedEntry { + thought, + author, + stats, + viewer: viewer_ctx, + }) } /// Fetches a thread (root + replies) enriched with authors + real engagement stats. @@ -169,10 +182,20 @@ pub async fn get_thread_views( .get(&thought.user_id) .cloned() .ok_or(DomainError::NotFound)?; - let (stats, viewer_ctx) = engagement_map.remove(&thought.id).unwrap_or( - (EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 }, None) - ); - entries.push(FeedEntry { thought, author, stats, viewer: viewer_ctx }); + let (stats, viewer_ctx) = engagement_map.remove(&thought.id).unwrap_or(( + EngagementStats { + like_count: 0, + boost_count: 0, + reply_count: 0, + }, + None, + )); + entries.push(FeedEntry { + thought, + author, + stats, + viewer: viewer_ctx, + }); } Ok(entries) } diff --git a/crates/application/src/use_cases/thoughts/tests.rs b/crates/application/src/use_cases/thoughts/tests.rs index 6d69b04..c44d265 100644 --- a/crates/application/src/use_cases/thoughts/tests.rs +++ b/crates/application/src/use_cases/thoughts/tests.rs @@ -31,9 +31,16 @@ async fn create_thought_saves_and_stages_outbox_event() { let outbox = TestOutbox::default(); let u = user(); store.users.lock().unwrap().push(u.clone()); - let out = create_thought(&store, &store, &store, &NoOpEventPublisher, &outbox, input(u.id.clone())) - .await - .unwrap(); + let out = create_thought( + &store, + &store, + &store, + &NoOpEventPublisher, + &outbox, + input(u.id.clone()), + ) + .await + .unwrap(); assert_eq!(out.thought.content.as_str(), "hello"); let staged = outbox.staged(); assert_eq!(staged.len(), 1); @@ -64,7 +71,9 @@ async fn delete_thought_stages_outbox_event() { let staged = outbox.staged(); assert_eq!(staged.len(), 1); - assert!(matches!(&staged[0], DomainEvent::ThoughtDeleted { thought_id, .. } if *thought_id == tid)); + assert!( + matches!(&staged[0], DomainEvent::ThoughtDeleted { thought_id, .. } if *thought_id == tid) + ); } #[tokio::test] @@ -82,9 +91,15 @@ async fn delete_own_thought_succeeds() { ) .await .unwrap(); - delete_thought(&store, &NoOpEventPublisher, &NoOpOutboxWriter, &out.thought.id, &u.id) - .await - .unwrap(); + delete_thought( + &store, + &NoOpEventPublisher, + &NoOpOutboxWriter, + &out.thought.id, + &u.id, + ) + .await + .unwrap(); assert!(store.thoughts.lock().unwrap().is_empty()); } @@ -113,9 +128,15 @@ async fn delete_other_thought_returns_not_found() { ) .await .unwrap(); - let err = delete_thought(&store, &NoOpEventPublisher, &NoOpOutboxWriter, &out.thought.id, &bob.id) - .await - .unwrap_err(); + let err = delete_thought( + &store, + &NoOpEventPublisher, + &NoOpOutboxWriter, + &out.thought.id, + &bob.id, + ) + .await + .unwrap_err(); assert!(matches!(err, DomainError::NotFound)); } @@ -124,9 +145,16 @@ async fn edit_thought_changes_content_and_emits_event() { let store = TestStore::default(); let alice = user(); store.users.lock().unwrap().push(alice.clone()); - let out = create_thought(&store, &store, &store, &NoOpEventPublisher, &NoOpOutboxWriter, input(alice.id.clone())) - .await - .unwrap(); + let out = create_thought( + &store, + &store, + &store, + &NoOpEventPublisher, + &NoOpOutboxWriter, + input(alice.id.clone()), + ) + .await + .unwrap(); let tid = out.thought.id.clone(); edit_thought(&store, &store, &tid, &alice.id, "updated".to_string()) @@ -222,9 +250,13 @@ fn make_thought(user_id: UserId) -> Thought { async fn get_thought_view_returns_feed_entry() { let store = TestStore::default(); let user = make_user(); - ::save(&store, &user).await.unwrap(); + ::save(&store, &user) + .await + .unwrap(); let thought = make_thought(user.id.clone()); - ::save(&store, &thought).await.unwrap(); + ::save(&store, &thought) + .await + .unwrap(); let entry = get_thought_view(&store, &store, &store, &thought.id, None) .await @@ -248,9 +280,13 @@ async fn get_thought_view_returns_not_found_for_missing_thought() { async fn get_thread_views_batches_correctly() { let store = TestStore::default(); let user = make_user(); - ::save(&store, &user).await.unwrap(); + ::save(&store, &user) + .await + .unwrap(); let root = make_thought(user.id.clone()); - ::save(&store, &root).await.unwrap(); + ::save(&store, &root) + .await + .unwrap(); let reply = Thought::new_local( ThoughtId::new(), user.id.clone(), @@ -260,7 +296,9 @@ async fn get_thread_views_batches_correctly() { None, false, ); - ::save(&store, &reply).await.unwrap(); + ::save(&store, &reply) + .await + .unwrap(); let entries = get_thread_views(&store, &store, &store, &root.id, None) .await diff --git a/crates/bootstrap/src/factory.rs b/crates/bootstrap/src/factory.rs index 57041d9..73da3f0 100644 --- a/crates/bootstrap/src/factory.rs +++ b/crates/bootstrap/src/factory.rs @@ -8,7 +8,11 @@ use std::sync::Arc; use activitypub::ThoughtsObjectHandler; use activitypub_base::service::ActivityPubService; use auth::ApiKeyServiceImpl; -use domain::{errors::DomainError, events::DomainEvent, ports::{EventPublisher, OutboxWriter}}; +use domain::{ + errors::DomainError, + events::DomainEvent, + ports::{EventPublisher, OutboxWriter}, +}; use event_transport::EventPublisherAdapter; use nats::NatsTransport; use postgres::activitypub::PgActivityPubRepository; @@ -130,9 +134,9 @@ pub async fn build(cfg: &Config) -> Infrastructure { ap_repo: Arc::new(PgActivityPubRepository::new(pool.clone())), remote_actor_connections: Arc::new(PgRemoteActorConnectionRepository::new(pool.clone())), federation_scheduler: ap_service.clone() as Arc, - api_key_auth: Arc::new(ApiKeyServiceImpl::new( - Arc::new(postgres::api_key::PgApiKeyRepository::new(pool.clone())), - )), + api_key_auth: Arc::new(ApiKeyServiceImpl::new(Arc::new( + postgres::api_key::PgApiKeyRepository::new(pool.clone()), + ))), engagement: Arc::new(PgEngagementRepository::new(pool.clone())), }; diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index 8395906..6333cf1 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -58,7 +58,8 @@ pub trait UserReader: Send + Sync { async fn find_by_email(&self, email: &Email) -> Result, DomainError>; async fn list_with_stats(&self) -> Result, DomainError>; async fn count(&self) -> Result; - async fn list_paginated(&self, page: PageParams) -> Result, DomainError>; + async fn list_paginated(&self, page: PageParams) + -> Result, DomainError>; async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError>; } @@ -353,19 +354,43 @@ pub struct FeedQuery { impl FeedQuery { pub fn home(viewer_id: UserId, following_ids: Vec, page: PageParams) -> Self { - Self { scope: FeedScope::Home { following_ids }, page, viewer_id: Some(viewer_id) } + Self { + scope: FeedScope::Home { following_ids }, + page, + viewer_id: Some(viewer_id), + } } pub fn public(page: PageParams, viewer_id: Option) -> Self { - Self { scope: FeedScope::Public, page, viewer_id } + Self { + scope: FeedScope::Public, + page, + viewer_id, + } } pub fn tag(tag_name: impl Into, page: PageParams, viewer_id: Option) -> Self { - Self { scope: FeedScope::Tag { tag_name: tag_name.into() }, page, viewer_id } + Self { + scope: FeedScope::Tag { + tag_name: tag_name.into(), + }, + page, + viewer_id, + } } pub fn user(user_id: UserId, page: PageParams, viewer_id: Option) -> Self { - Self { scope: FeedScope::User { user_id }, page, viewer_id } + Self { + scope: FeedScope::User { user_id }, + page, + viewer_id, + } } pub fn search(query: impl Into, page: PageParams, viewer_id: Option) -> Self { - Self { scope: FeedScope::Search { query: query.into() }, page, viewer_id } + Self { + scope: FeedScope::Search { + query: query.into(), + }, + page, + viewer_id, + } } } @@ -392,7 +417,6 @@ pub trait SearchPort: Send + Sync { ) -> Result, DomainError>; } - #[async_trait] pub trait FederationSchedulerPort: Send + Sync { async fn schedule_actor_posts_fetch( diff --git a/crates/domain/src/testing/mod.rs b/crates/domain/src/testing/mod.rs index adc528c..a546a5f 100644 --- a/crates/domain/src/testing/mod.rs +++ b/crates/domain/src/testing/mod.rs @@ -83,17 +83,30 @@ impl UserReader for TestStore { .count() as i64) } - async fn list_paginated(&self, page: PageParams) -> Result, DomainError> { + async fn list_paginated( + &self, + page: PageParams, + ) -> Result, DomainError> { let all = self.list_with_stats().await?; let total = all.len() as i64; let start = page.offset() as usize; - let items: Vec = all.into_iter().skip(start).take(page.limit() as usize).collect(); - Ok(Paginated { items, total, page: page.page, per_page: page.per_page }) + let items: Vec = all + .into_iter() + .skip(start) + .take(page.limit() as usize) + .collect(); + Ok(Paginated { + items, + total, + page: page.page, + per_page: page.per_page, + }) } async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> { let g = self.users.lock().unwrap(); - let map = g.iter() + let map = g + .iter() .filter(|u| ids.contains(&u.id)) .map(|u| (u.id.clone(), u.clone())) .collect(); @@ -294,7 +307,16 @@ impl EngagementRepository for TestStore { &self, thought_ids: &[ThoughtId], viewer_id: Option<&UserId>, - ) -> Result)>, DomainError> { + ) -> Result< + HashMap< + ThoughtId, + ( + crate::models::feed::EngagementStats, + Option, + ), + >, + DomainError, + > { use crate::models::feed::{EngagementStats, ViewerContext}; let likes = self.likes.lock().unwrap(); let boosts = self.boosts.lock().unwrap(); @@ -304,12 +326,29 @@ impl EngagementRepository for TestStore { for tid in thought_ids { let like_count = likes.iter().filter(|l| &l.thought_id == tid).count() as i64; let boost_count = boosts.iter().filter(|b| &b.thought_id == tid).count() as i64; - let reply_count = thoughts.iter().filter(|t| t.in_reply_to_id.as_ref() == Some(tid)).count() as i64; + let reply_count = thoughts + .iter() + .filter(|t| t.in_reply_to_id.as_ref() == Some(tid)) + .count() as i64; let viewer = viewer_id.map(|vid| ViewerContext { - liked: likes.iter().any(|l| &l.thought_id == tid && &l.user_id == vid), - boosted: boosts.iter().any(|b| &b.thought_id == tid && &b.user_id == vid), + liked: likes + .iter() + .any(|l| &l.thought_id == tid && &l.user_id == vid), + boosted: boosts + .iter() + .any(|b| &b.thought_id == tid && &b.user_id == vid), }); - result.insert(tid.clone(), (EngagementStats { like_count, boost_count, reply_count }, viewer)); + result.insert( + tid.clone(), + ( + EngagementStats { + like_count, + boost_count, + reply_count, + }, + viewer, + ), + ); } Ok(result) } @@ -763,7 +802,10 @@ impl RemoteActorConnectionRepository for TestStore { #[async_trait] impl FeedRepository for TestStore { - async fn query(&self, _q: &crate::ports::FeedQuery) -> Result, DomainError> { + async fn query( + &self, + _q: &crate::ports::FeedQuery, + ) -> Result, DomainError> { Ok(Paginated { items: vec![], total: 0, diff --git a/crates/presentation/src/handlers/api_keys.rs b/crates/presentation/src/handlers/api_keys.rs index 983b97c..de14777 100644 --- a/crates/presentation/src/handlers/api_keys.rs +++ b/crates/presentation/src/handlers/api_keys.rs @@ -8,11 +8,7 @@ use api_types::{ responses::{ApiKeyResponse, CreatedApiKeyResponse}, }; use application::use_cases::api_keys::{create_api_key, delete_api_key, list_api_keys}; -use axum::{ - extract::Path, - http::StatusCode, - Json, -}; +use axum::{extract::Path, http::StatusCode, Json}; use domain::{ports::ApiKeyRepository, value_objects::ApiKeyId}; use uuid::Uuid; diff --git a/crates/presentation/src/handlers/auth.rs b/crates/presentation/src/handlers/auth.rs index ed66332..f61ee74 100644 --- a/crates/presentation/src/handlers/auth.rs +++ b/crates/presentation/src/handlers/auth.rs @@ -1,8 +1,4 @@ -use crate::{ - deps_struct, - errors::ApiError, - extractors::Deps, -}; +use crate::{deps_struct, errors::ApiError, extractors::Deps}; use api_types::{ requests::{LoginRequest, RegisterRequest}, responses::{AuthResponse, ErrorResponse, UserResponse}, diff --git a/crates/presentation/src/handlers/federation_actors/mod.rs b/crates/presentation/src/handlers/federation_actors/mod.rs index a84fe94..b44bb50 100644 --- a/crates/presentation/src/handlers/federation_actors/mod.rs +++ b/crates/presentation/src/handlers/federation_actors/mod.rs @@ -4,6 +4,7 @@ use crate::{ handlers::feed::to_thought_response, state::AppState, }; +use activitypub_base::ActivityPubRepository; use api_types::{ requests::PaginationQuery, responses::{ActorConnectionPageResponse, ActorConnectionResponse}, @@ -15,7 +16,6 @@ use axum::{ extract::{Path, Query}, Json, }; -use activitypub_base::ActivityPubRepository; use domain::{ models::feed::PageParams, ports::{ diff --git a/crates/presentation/src/handlers/feed.rs b/crates/presentation/src/handlers/feed.rs index 80d95f9..9917968 100644 --- a/crates/presentation/src/handlers/feed.rs +++ b/crates/presentation/src/handlers/feed.rs @@ -16,7 +16,10 @@ use axum::{ }; use domain::{ models::feed::PageParams, - ports::{FederationActionPort, FeedQuery, FeedRepository, FollowRepository, SearchPort, TagRepository, UserRepository}, + ports::{ + FederationActionPort, FeedQuery, FeedRepository, FollowRepository, SearchPort, + TagRepository, UserRepository, + }, }; deps_struct!(FeedDeps { @@ -224,7 +227,10 @@ pub async fn user_thoughts_handler( page: q.page(), per_page: q.per_page(), }; - let result = d.feed.query(&FeedQuery::user(user.id.clone(), page, viewer)).await?; + let result = d + .feed + .query(&FeedQuery::user(user.id.clone(), page, viewer)) + .await?; Ok(Json(serde_json::json!({ "total": result.total, "page": result.page, @@ -241,7 +247,10 @@ pub async fn get_popular_tags( .get("limit") .and_then(|v| v.parse().ok()) .unwrap_or(api_types::requests::DEFAULT_PER_PAGE as usize); - let tags = d.tags.popular_tags(limit.min(api_types::requests::MAX_PER_PAGE as usize)).await?; + let tags = d + .tags + .popular_tags(limit.min(api_types::requests::MAX_PER_PAGE as usize)) + .await?; Ok(Json(serde_json::json!({ "tags": tags.iter().map(|(name, count)| serde_json::json!({ "name": name, @@ -268,7 +277,10 @@ pub async fn tag_thoughts_handler( page: q.page(), per_page: q.per_page(), }; - let result = d.feed.query(&FeedQuery::tag(&tag_name, page, viewer)).await?; + let result = d + .feed + .query(&FeedQuery::tag(&tag_name, page, viewer)) + .await?; Ok(Json(serde_json::json!({ "tag": tag_name, "total": result.total, diff --git a/crates/presentation/src/handlers/notifications/mod.rs b/crates/presentation/src/handlers/notifications/mod.rs index 2374c7f..f5bc2ef 100644 --- a/crates/presentation/src/handlers/notifications/mod.rs +++ b/crates/presentation/src/handlers/notifications/mod.rs @@ -8,11 +8,7 @@ use application::use_cases::notifications::{ count_unread_notifications, list_notifications as uc_list_notifications, mark_all_notifications_read, mark_notification_read as uc_mark_notification_read, }; -use axum::{ - extract::Path, - http::StatusCode, - Json, -}; +use axum::{extract::Path, http::StatusCode, Json}; use domain::{ models::feed::PageParams, ports::NotificationRepository, value_objects::NotificationId, }; diff --git a/crates/presentation/src/handlers/social/mod.rs b/crates/presentation/src/handlers/social/mod.rs index 0958dde..b642c8f 100644 --- a/crates/presentation/src/handlers/social/mod.rs +++ b/crates/presentation/src/handlers/social/mod.rs @@ -1,3 +1,4 @@ +use crate::handlers::auth::to_user_response; use crate::{ deps_struct, errors::ApiError, @@ -5,14 +6,9 @@ use crate::{ }; use api_types::requests::SetTopFriendsRequest; use api_types::responses::TopFriendsResponse; -use crate::handlers::auth::to_user_response; use application::use_cases::profile::{get_top_friends, get_user_by_username, set_top_friends}; use application::use_cases::social::*; -use axum::{ - extract::Path, - http::StatusCode, - Json, -}; +use axum::{extract::Path, http::StatusCode, Json}; use domain::{ ports::{ BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository, diff --git a/crates/presentation/src/handlers/thoughts.rs b/crates/presentation/src/handlers/thoughts.rs index 1e804e8..8b8d19d 100644 --- a/crates/presentation/src/handlers/thoughts.rs +++ b/crates/presentation/src/handlers/thoughts.rs @@ -9,18 +9,16 @@ use api_types::{ responses::ErrorResponse, }; use application::use_cases::thoughts::{ - create_thought, delete_thought, edit_thought, get_thread_views, get_thought_view, + create_thought, delete_thought, edit_thought, get_thought_view, get_thread_views, CreateThoughtInput, }; -use axum::{ - extract::Path, - http::StatusCode, - response::IntoResponse, - Json, -}; +use axum::{extract::Path, http::StatusCode, response::IntoResponse, Json}; use domain::{ models::feed::{EngagementStats, FeedEntry, ViewerContext}, - ports::{EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, UserRepository}, + ports::{ + EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, + UserRepository, + }, value_objects::ThoughtId, }; use uuid::Uuid; @@ -74,8 +72,15 @@ pub async fn post_thought( let entry = FeedEntry { thought: out.thought, author, - stats: EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 }, - viewer: Some(ViewerContext { liked: false, boosted: false }), + stats: EngagementStats { + like_count: 0, + boost_count: 0, + reply_count: 0, + }, + viewer: Some(ViewerContext { + liked: false, + boosted: false, + }), }; Ok((StatusCode::CREATED, Json(to_thought_response(&entry)))) } @@ -101,7 +106,9 @@ pub async fn get_thought_handler( viewer.as_ref(), ) .await?; - Ok(Json(serde_json::to_value(to_thought_response(&entry)).unwrap())) + Ok(Json( + serde_json::to_value(to_thought_response(&entry)).unwrap(), + )) } #[utoipa::path( @@ -119,7 +126,14 @@ pub async fn delete_thought_handler( AuthUser(uid): AuthUser, Path(id): Path, ) -> Result { - delete_thought(&*d.thoughts, &*d.events, &*d.outbox, &ThoughtId::from_uuid(id), &uid).await?; + delete_thought( + &*d.thoughts, + &*d.events, + &*d.outbox, + &ThoughtId::from_uuid(id), + &uid, + ) + .await?; Ok(StatusCode::NO_CONTENT) } diff --git a/crates/presentation/src/handlers/users/mod.rs b/crates/presentation/src/handlers/users/mod.rs index 95560ce..5671f15 100644 --- a/crates/presentation/src/handlers/users/mod.rs +++ b/crates/presentation/src/handlers/users/mod.rs @@ -191,9 +191,7 @@ pub async fn get_users( }))) } -pub async fn get_user_count( - Deps(d): Deps, -) -> Result, ApiError> { +pub async fn get_user_count(Deps(d): Deps) -> Result, ApiError> { let count = d.users.count().await?; Ok(Json(serde_json::json!({ "count": count }))) } diff --git a/crates/presentation/src/testing.rs b/crates/presentation/src/testing.rs index 16f7506..a67dd8d 100644 --- a/crates/presentation/src/testing.rs +++ b/crates/presentation/src/testing.rs @@ -79,11 +79,7 @@ impl ActivityPubRepository for NoOpApRepo { ) -> Result { Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4())) } - async fn apply_note_update( - &self, - _ap_id: &str, - _new_content: &str, - ) -> Result<(), DomainError> { + async fn apply_note_update(&self, _ap_id: &str, _new_content: &str) -> Result<(), DomainError> { Ok(()) } async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> { diff --git a/crates/worker/src/factory.rs b/crates/worker/src/factory.rs index 8da2445..d3c9fbf 100644 --- a/crates/worker/src/factory.rs +++ b/crates/worker/src/factory.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use activitypub::ThoughtsObjectHandler; use activitypub_base::ActivityPubService; -use application::services::{FederationEventService, NotificationEventService}; use activitypub_base::{ActivityPubRepository, OutboundFederationPort}; +use application::services::{FederationEventService, NotificationEventService}; use domain::ports::EventPublisher; use postgres::activitypub::PgActivityPubRepository; use postgres_federation::{PostgresApUserRepository, PostgresFederationRepository}; diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs index 8d64b3f..953b395 100644 --- a/crates/worker/src/main.rs +++ b/crates/worker/src/main.rs @@ -44,7 +44,11 @@ async fn main() { Ok(envelope) => { let event = &envelope.event; let event_type = event_payload::EventPayload::from(event).subject(); - tracing::info!(event_type, delivery = envelope.delivery_count, "received event"); + tracing::info!( + event_type, + delivery = envelope.delivery_count, + "received event" + ); let n = infra.handlers.notification.handle(event).await; let f = infra.handlers.federation.handle(event).await; diff --git a/crates/worker/src/outbox_relay.rs b/crates/worker/src/outbox_relay.rs index 6631891..fa56cdd 100644 --- a/crates/worker/src/outbox_relay.rs +++ b/crates/worker/src/outbox_relay.rs @@ -57,7 +57,11 @@ impl OutboxRelay { let payload: EventPayload = match serde_json::from_value(row.payload.clone()) { Ok(p) => p, Err(e) => { - tracing::error!(seq = row.seq, event_type = row.event_type, "outbox: failed to deserialize payload: {e}"); + tracing::error!( + seq = row.seq, + event_type = row.event_type, + "outbox: failed to deserialize payload: {e}" + ); // Mark delivered to avoid blocking; investigate manually. sqlx::query( "UPDATE outbox_events \ @@ -75,7 +79,10 @@ impl OutboxRelay { let domain_event = match DomainEvent::try_from(payload) { Ok(ev) => ev, Err(e) => { - tracing::error!(seq = row.seq, "outbox: failed to convert to DomainEvent: {e}"); + tracing::error!( + seq = row.seq, + "outbox: failed to convert to DomainEvent: {e}" + ); sqlx::query( "UPDATE outbox_events \ SET delivered = true, delivered_at = now() \ @@ -100,7 +107,11 @@ impl OutboxRelay { .execute(&mut *tx) .await?; tx.commit().await?; - tracing::info!(seq = row.seq, event_type = row.event_type, "outbox: delivered"); + tracing::info!( + seq = row.seq, + event_type = row.event_type, + "outbox: delivered" + ); } Err(e) => { tracing::warn!(seq = row.seq, "outbox: publish failed (will retry): {e}");