mod common; use chrono::Utc; use reqwest::StatusCode; use serde_json::{json, Value}; use prism::records::{ NewAccount, NewRecord, CHANNEL_COLLECTION, INVITE_COLLECTION, LATTICE_COLLECTION, MEMBERSHIP_COLLECTION, SHARD_COLLECTION, }; async fn insert_test_account(pool: &sqlx::PgPool, did: &str) { prism::db::upsert_account( pool, &NewAccount { did: did.to_string(), handle: did.to_string(), created_at: Utc::now(), }, ) .await .unwrap(); } async fn insert_test_channel(pool: &sqlx::PgPool, cid: &str, creator_did: &str, name: &str) { let uri = format!("at://{}/systems.gmstn.development.channel/{}", creator_did, cid); let now = Utc::now(); prism::db::insert_record( pool, &NewRecord { uri, cid: cid.to_string(), collection: CHANNEL_COLLECTION.to_string(), creator_did: creator_did.to_string(), created_at: now, indexed_at: now, data: json!({"name": name, "topic": "test topic"}), target_did: None, ref_cids: vec![], }, ) .await .unwrap(); } async fn insert_test_invite( pool: &sqlx::PgPool, cid: &str, creator_did: &str, channel_cid: &str, recipient_did: &str, ) { let uri = format!( "at://{}/systems.gmstn.development.channel.invite/{}", creator_did, cid ); let now = Utc::now(); prism::db::insert_record( pool, &NewRecord { uri, cid: cid.to_string(), collection: INVITE_COLLECTION.to_string(), creator_did: creator_did.to_string(), created_at: now, indexed_at: now, data: json!({"recipient": recipient_did}), target_did: Some(recipient_did.to_string()), ref_cids: vec![channel_cid.to_string()], }, ) .await .unwrap(); } async fn insert_test_membership( pool: &sqlx::PgPool, cid: &str, recipient_did: &str, channel_cid: &str, invite_cid: &str, ) { let uri = format!( "at://{}/systems.gmstn.development.channel.membership/{}", recipient_did, cid ); let now = Utc::now(); prism::db::insert_record( pool, &NewRecord { uri, cid: cid.to_string(), collection: MEMBERSHIP_COLLECTION.to_string(), creator_did: recipient_did.to_string(), created_at: now, indexed_at: now, data: json!({"state": "accepted"}), target_did: Some(recipient_did.to_string()), ref_cids: vec![channel_cid.to_string(), invite_cid.to_string()], }, ) .await .unwrap(); } async fn insert_test_lattice(pool: &sqlx::PgPool, cid: &str, creator_did: &str) { let uri = format!("at://{}/systems.gmstn.development.lattice/{}", creator_did, cid); let now = Utc::now(); prism::db::insert_record( pool, &NewRecord { uri, cid: cid.to_string(), collection: LATTICE_COLLECTION.to_string(), creator_did: creator_did.to_string(), created_at: now, indexed_at: now, data: json!({"description": "test lattice"}), target_did: None, ref_cids: vec![], }, ) .await .unwrap(); } async fn insert_test_shard(pool: &sqlx::PgPool, cid: &str, creator_did: &str) { let uri = format!("at://{}/systems.gmstn.development.shard/{}", creator_did, cid); let now = Utc::now(); prism::db::insert_record( pool, &NewRecord { uri, cid: cid.to_string(), collection: SHARD_COLLECTION.to_string(), creator_did: creator_did.to_string(), created_at: now, indexed_at: now, data: json!({"description": "test shard"}), target_did: None, ref_cids: vec![], }, ) .await .unwrap(); } #[tokio::test] async fn test_list_channels_returns_correct_structure() { let base_url = common::base_url().await; let pool = common::get_db_pool().await; let client = common::client(); let test_did = "did:plc:test_channels_structure"; insert_test_account(&pool, test_did).await; insert_test_channel(&pool, "bafychannel001", test_did, "Test Channel").await; let res = client .get(format!( "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}", base_url, test_did )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); assert!(body["channels"].is_array()); let channels = body["channels"].as_array().unwrap(); assert!(!channels.is_empty()); let channel = &channels[0]; assert!(channel["uri"].is_string()); assert!(channel["cid"].is_string()); assert!(channel["displayName"].is_string()); assert!(channel["createdAt"].is_string()); assert!(channel["indexedAt"].is_string()); } #[tokio::test] async fn test_list_channels_empty_author() { let base_url = common::base_url().await; let client = common::client(); let res = client .get(format!( "{}/xrpc/systems.gmstn.development.channel.listChannels?author=did:plc:nonexistent", base_url )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); assert!(body["channels"].is_array()); assert!(body["channels"].as_array().unwrap().is_empty()); assert!(body["cursor"].is_null()); } #[tokio::test] async fn test_list_channels_limit_bounds() { let base_url = common::base_url().await; let pool = common::get_db_pool().await; let client = common::client(); let test_did = "did:plc:test_limit_bounds"; insert_test_account(&pool, test_did).await; for i in 0..5 { insert_test_channel(&pool, &format!("bafylimitchan{}", i), test_did, &format!("Channel {}", i)) .await; } let res = client .get(format!( "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}&limit=2", base_url, test_did )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); let channels = body["channels"].as_array().unwrap(); assert_eq!(channels.len(), 2); assert!(body["cursor"].is_string()); let res_over_limit = client .get(format!( "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}&limit=150", base_url, test_did )) .send() .await .unwrap(); assert_eq!(res_over_limit.status(), StatusCode::OK); let body_over: Value = res_over_limit.json().await.unwrap(); let channels_over = body_over["channels"].as_array().unwrap(); assert!(channels_over.len() <= 100); } #[tokio::test] async fn test_list_invites_by_recipient() { let base_url = common::base_url().await; let pool = common::get_db_pool().await; let client = common::client(); let creator_did = "did:plc:invite_creator"; let recipient_did = "did:plc:invite_recipient"; insert_test_account(&pool, creator_did).await; insert_test_account(&pool, recipient_did).await; insert_test_invite(&pool, "bafyinvite001", creator_did, "bafyinvitechan001", recipient_did).await; let res = client .get(format!( "{}/xrpc/systems.gmstn.development.channel.listInvites?recipient={}", base_url, recipient_did )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); assert!(body["invites"].is_array()); let invites = body["invites"].as_array().unwrap(); assert!(!invites.is_empty()); let invite = &invites[0]; assert!(invite["uri"].is_string()); assert!(invite["cid"].is_string()); assert!(invite["channel"].is_string()); assert!(invite["recipient"].is_string()); assert!(invite["createdAt"].is_string()); } #[tokio::test] async fn test_list_memberships_by_recipient() { let base_url = common::base_url().await; let pool = common::get_db_pool().await; let client = common::client(); let creator_did = "did:plc:membership_creator"; let recipient_did = "did:plc:membership_recipient"; insert_test_account(&pool, creator_did).await; insert_test_account(&pool, recipient_did).await; insert_test_membership(&pool, "bafymem001", recipient_did, "bafymemchan001", "bafymeminvite001").await; let res = client .get(format!( "{}/xrpc/systems.gmstn.development.channel.listMemberships?recipient={}", base_url, recipient_did )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); assert!(body["memberships"].is_array()); let memberships = body["memberships"].as_array().unwrap(); assert!(!memberships.is_empty()); let membership = &memberships[0]; assert!(membership["uri"].is_string()); assert!(membership["cid"].is_string()); assert!(membership["channel"].is_string()); assert!(membership["invite"].is_string()); assert!(membership["recipient"].is_string()); assert!(membership["state"].is_string()); assert!(membership["createdAt"].is_string()); } #[tokio::test] async fn test_list_lattices_by_author() { let base_url = common::base_url().await; let pool = common::get_db_pool().await; let client = common::client(); let test_did = "did:plc:lattice_author"; insert_test_account(&pool, test_did).await; insert_test_lattice(&pool, "bafylattice001", test_did).await; let res = client .get(format!( "{}/xrpc/systems.gmstn.development.lattice.listLattices?author={}", base_url, test_did )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); assert!(body["lattices"].is_array()); let lattices = body["lattices"].as_array().unwrap(); assert!(!lattices.is_empty()); let lattice = &lattices[0]; assert!(lattice["uri"].is_string()); assert!(lattice["cid"].is_string()); assert!(lattice["author"].is_string()); assert!(lattice["createdAt"].is_string()); assert!(lattice["indexedAt"].is_string()); } #[tokio::test] async fn test_list_shards_by_author() { let base_url = common::base_url().await; let pool = common::get_db_pool().await; let client = common::client(); let test_did = "did:plc:shard_author"; insert_test_account(&pool, test_did).await; insert_test_shard(&pool, "bafyshard001", test_did).await; let res = client .get(format!( "{}/xrpc/systems.gmstn.development.shard.listShards?author={}", base_url, test_did )) .send() .await .unwrap(); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.unwrap(); assert!(body["shards"].is_array()); let shards = body["shards"].as_array().unwrap(); assert!(!shards.is_empty()); let shard = &shards[0]; assert!(shard["uri"].is_string()); assert!(shard["cid"].is_string()); assert!(shard["author"].is_string()); assert!(shard["createdAt"].is_string()); assert!(shard["indexedAt"].is_string()); }