use super::*; use chrono::Utc; use domain::ports::SocialQueryPort; use sqlx::SqlitePool; async fn test_pool() -> SqlitePool { let pool = SqlitePool::connect("sqlite::memory:").await.unwrap(); sqlx::query("CREATE TABLE ap_announces (id TEXT PRIMARY KEY, object_url TEXT NOT NULL, actor_url TEXT NOT NULL, announced_at TEXT NOT NULL)") .execute(&pool).await.unwrap(); pool } #[tokio::test] async fn add_announce_stores_and_counts() { let pool = test_pool().await; let repo = SqliteFederationRepository::new(pool); repo.add_announce( "https://remote/ann/1", "https://local/r/1", "https://remote/u/1", Utc::now(), ) .await .unwrap(); assert_eq!(repo.count_announces("https://local/r/1").await.unwrap(), 1); } #[tokio::test] async fn duplicate_announce_is_ignored() { let pool = test_pool().await; let repo = SqliteFederationRepository::new(pool); repo.add_announce( "https://remote/ann/1", "https://local/r/1", "https://remote/u/1", Utc::now(), ) .await .unwrap(); repo.add_announce( "https://remote/ann/1", "https://local/r/1", "https://remote/u/1", Utc::now(), ) .await .unwrap(); assert_eq!(repo.count_announces("https://local/r/1").await.unwrap(), 1); } async fn setup_db(pool: &SqlitePool) { sqlx::query( "CREATE TABLE IF NOT EXISTS ap_remote_actors ( url TEXT PRIMARY KEY, handle TEXT NOT NULL, inbox_url TEXT NOT NULL, shared_inbox_url TEXT, display_name TEXT, avatar_url TEXT, fetched_at TEXT NOT NULL )", ) .execute(pool) .await .unwrap(); sqlx::query( "CREATE TABLE IF NOT EXISTS ap_following ( local_user_id TEXT NOT NULL, remote_actor_url TEXT NOT NULL, follow_activity_id TEXT NOT NULL, status TEXT NOT NULL, PRIMARY KEY (local_user_id, remote_actor_url) )", ) .execute(pool) .await .unwrap(); } #[tokio::test] async fn test_get_accepted_following_urls_returns_only_accepted() { let pool = SqlitePool::connect(":memory:").await.unwrap(); setup_db(&pool).await; let repo = SqliteFederationRepository::new(pool.clone()); let user_id = uuid::Uuid::new_v4(); sqlx::query( "INSERT INTO ap_following (local_user_id, remote_actor_url, follow_activity_id, status) VALUES (?, 'https://other.social/users/alice', 'act1', 'accepted'), (?, 'https://other.social/users/bob', 'act2', 'pending')", ) .bind(user_id.to_string()) .bind(user_id.to_string()) .execute(&pool) .await .unwrap(); let urls = repo.get_accepted_following_urls(user_id).await.unwrap(); assert_eq!(urls.len(), 1); assert_eq!(urls[0], "https://other.social/users/alice"); } #[tokio::test] async fn test_list_all_followed_remote_actors_deduplicates() { let pool = SqlitePool::connect(":memory:").await.unwrap(); setup_db(&pool).await; let repo = SqliteFederationRepository::new(pool.clone()); let user1 = uuid::Uuid::new_v4(); let user2 = uuid::Uuid::new_v4(); sqlx::query( "INSERT INTO ap_remote_actors (url, handle, inbox_url, fetched_at, display_name) VALUES ('https://other.social/users/alice', 'alice@other.social', 'https://other.social/inbox', '2024-01-01', 'Alice')", ) .execute(&pool) .await .unwrap(); sqlx::query( "INSERT INTO ap_following (local_user_id, remote_actor_url, follow_activity_id, status) VALUES (?, 'https://other.social/users/alice', 'act1', 'accepted'), (?, 'https://other.social/users/alice', 'act2', 'accepted')", ) .bind(user1.to_string()) .bind(user2.to_string()) .execute(&pool) .await .unwrap(); let actors = repo.list_all_followed_remote_actors().await.unwrap(); assert_eq!(actors.len(), 1); assert_eq!(actors[0].handle, "alice@other.social"); }