use std::sync::Arc; use async_trait::async_trait; use domain::errors::DomainError; use domain::testing::InMemorySocialRepository; use domain::value_objects::{SocialActor, SocialIdentity, UserId}; use crate::{ config::AppConfig, diary::deps::GetActivityFeedDeps, diary::get_activity_feed, diary::queries::GetActivityFeedQuery, test_helpers::TestContextBuilder, }; fn default_deps() -> GetActivityFeedDeps { GetActivityFeedDeps { diary: domain::testing::FakeDiaryQuery::new() as _, social_query: InMemorySocialRepository::new() as _, config: TestContextBuilder::new().config, } } #[tokio::test] async fn returns_empty_feed() { let deps = default_deps(); let result = get_activity_feed::execute( &deps, GetActivityFeedQuery { limit: 10, offset: 0, sort_by: domain::models::FeedSortBy::Date, search: None, viewer_user_id: None, filter_following: false, }, ) .await .unwrap(); assert!(result.items.is_empty()); assert_eq!(result.total_count, 0); } #[tokio::test] async fn returns_feed_with_following_filter() { let deps = default_deps(); let viewer = uuid::Uuid::new_v4(); let result = get_activity_feed::execute( &deps, GetActivityFeedQuery { limit: 10, offset: 0, sort_by: domain::models::FeedSortBy::Date, search: None, viewer_user_id: Some(viewer), filter_following: true, }, ) .await .unwrap(); assert!(result.items.is_empty()); } struct FakeSocialWithFollowing(Vec); #[async_trait] impl domain::ports::SocialQuery for FakeSocialWithFollowing { async fn get_following( &self, _: &UserId, ) -> Result, DomainError> { Ok(self.0.clone()) } async fn get_followers(&self, _: &UserId) -> Result, DomainError> { Ok(vec![]) } async fn get_pending_followers(&self, _: &UserId) -> Result, DomainError> { Ok(vec![]) } async fn count_following(&self, _: &UserId) -> Result { Ok(0) } async fn count_followers(&self, _: &UserId) -> Result { Ok(0) } async fn get_blocked(&self, _: &UserId) -> Result, DomainError> { Ok(vec![]) } async fn is_following(&self, _: &UserId, _: &SocialIdentity) -> Result { Ok(false) } } #[tokio::test] async fn following_filter_separates_local_and_remote() { let viewer = uuid::Uuid::new_v4(); let local_friend = uuid::Uuid::new_v4(); let following = vec![ SocialActor { identity: SocialIdentity::Local(UserId::from_uuid(local_friend)), handle: "friend".into(), display_name: None, avatar_url: None, }, SocialActor { identity: SocialIdentity::Remote { actor_url: "https://remote.example/actor/1".into(), }, handle: "@alice@remote.example".into(), display_name: None, avatar_url: None, }, ]; let social = Arc::new(FakeSocialWithFollowing(following)); let deps = GetActivityFeedDeps { diary: domain::testing::FakeDiaryQuery::new() as _, social_query: social as _, config: AppConfig { allow_registration: true, base_url: "http://localhost:3000".into(), rate_limit: 20, refresh_ttl_seconds: 2_592_000, wrapup: crate::config::WrapUpConfig { font_path: None, logo_path: None, bg_dir: None, }, }, }; let result = get_activity_feed::execute( &deps, GetActivityFeedQuery { limit: 10, offset: 0, sort_by: domain::models::FeedSortBy::Date, search: None, viewer_user_id: Some(viewer), filter_following: true, }, ) .await .unwrap(); assert!(result.items.is_empty()); } #[tokio::test] async fn following_filter_without_viewer_returns_none() { let deps = default_deps(); let result = get_activity_feed::execute( &deps, GetActivityFeedQuery { limit: 10, offset: 0, sort_by: domain::models::FeedSortBy::Date, search: None, viewer_user_id: None, filter_following: true, }, ) .await .unwrap(); assert!(result.items.is_empty()); }