diff --git a/crates/adapters/postgres/src/follow/tests.rs b/crates/adapters/postgres/src/follow/tests.rs index 3ec4b1c..27a595f 100644 --- a/crates/adapters/postgres/src/follow/tests.rs +++ b/crates/adapters/postgres/src/follow/tests.rs @@ -56,3 +56,86 @@ async fn get_accepted_following_ids(pool: sqlx::PgPool) { let ids = repo.get_accepted_following_ids(&alice.id).await.unwrap(); assert_eq!(ids, vec![bob.id]); } + +#[sqlx::test(migrations = "./migrations")] +async fn list_mutual_returns_only_mutual_accepted_follows(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 = PgFollowRepository::new(pool); + let page = domain::models::feed::PageParams { + page: 1, + per_page: 20, + }; + + // alice → bob (accepted), bob → alice (accepted) = friends + repo.save(&Follow { + follower_id: alice.id.clone(), + following_id: bob.id.clone(), + state: FollowState::Accepted, + ap_id: None, + created_at: Utc::now(), + }) + .await + .unwrap(); + repo.save(&Follow { + follower_id: bob.id.clone(), + following_id: alice.id.clone(), + state: FollowState::Accepted, + ap_id: None, + created_at: Utc::now(), + }) + .await + .unwrap(); + + // alice → carol (accepted), carol does NOT follow back = not a friend + repo.save(&Follow { + follower_id: alice.id.clone(), + following_id: carol.id.clone(), + state: FollowState::Accepted, + ap_id: None, + created_at: Utc::now(), + }) + .await + .unwrap(); + + let result = repo.list_mutual(&alice.id, &page).await.unwrap(); + assert_eq!(result.total, 1); + assert_eq!(result.items.len(), 1); + assert_eq!(result.items[0].id, bob.id); +} + +#[sqlx::test(migrations = "./migrations")] +async fn list_mutual_excludes_pending_follows(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 page = domain::models::feed::PageParams { + page: 1, + per_page: 20, + }; + + // alice → bob (accepted), bob → alice (PENDING) = NOT a friend + repo.save(&Follow { + follower_id: alice.id.clone(), + following_id: bob.id.clone(), + state: FollowState::Accepted, + ap_id: None, + created_at: Utc::now(), + }) + .await + .unwrap(); + repo.save(&Follow { + follower_id: bob.id.clone(), + following_id: alice.id.clone(), + state: FollowState::Pending, + ap_id: None, + created_at: Utc::now(), + }) + .await + .unwrap(); + + let result = repo.list_mutual(&alice.id, &page).await.unwrap(); + assert_eq!(result.total, 0); + assert!(result.items.is_empty()); +}