relay filter/appview bootstrap
at main 7.0 kB view raw
1mod common; 2 3use chrono::{Duration, Utc}; 4use reqwest::StatusCode; 5use serde_json::{json, Value}; 6use prism::records::{NewAccount, NewRecord, CHANNEL_COLLECTION}; 7use std::sync::atomic::{AtomicU64, Ordering}; 8 9static TEST_COUNTER: AtomicU64 = AtomicU64::new(0); 10 11fn unique_suffix() -> String { 12 let ts = std::time::SystemTime::now() 13 .duration_since(std::time::UNIX_EPOCH) 14 .unwrap() 15 .as_nanos(); 16 let count = TEST_COUNTER.fetch_add(1, Ordering::SeqCst); 17 format!("{}_{}", ts, count) 18} 19 20async fn insert_test_account(pool: &sqlx::PgPool, did: &str) { 21 prism::db::upsert_account( 22 pool, 23 &NewAccount { 24 did: did.to_string(), 25 handle: did.to_string(), 26 created_at: Utc::now(), 27 }, 28 ) 29 .await 30 .unwrap(); 31} 32 33async fn insert_channel_at_time( 34 pool: &sqlx::PgPool, 35 cid: &str, 36 creator_did: &str, 37 name: &str, 38 indexed_at: chrono::DateTime<Utc>, 39) { 40 let uri = format!("at://{}/systems.gmstn.development.channel/{}", creator_did, cid); 41 prism::db::insert_record( 42 pool, 43 &NewRecord { 44 uri, 45 cid: cid.to_string(), 46 collection: CHANNEL_COLLECTION.to_string(), 47 creator_did: creator_did.to_string(), 48 created_at: indexed_at, 49 indexed_at, 50 data: json!({"name": name, "topic": "test topic"}), 51 target_did: None, 52 ref_cids: vec![], 53 }, 54 ) 55 .await 56 .unwrap(); 57} 58 59#[tokio::test] 60async fn test_pagination_first_page_returns_latest() { 61 let base_url = common::base_url().await; 62 let pool = common::get_db_pool().await; 63 let client = common::client(); 64 65 let suffix = unique_suffix(); 66 let test_did = format!("did:plc:pagination_first_{}", suffix); 67 insert_test_account(&pool, &test_did).await; 68 69 let now = Utc::now(); 70 insert_channel_at_time(&pool, &format!("bafypagfirst001_{}", suffix), &test_did, "Oldest", now - Duration::hours(3)) 71 .await; 72 insert_channel_at_time(&pool, &format!("bafypagfirst002_{}", suffix), &test_did, "Middle", now - Duration::hours(2)) 73 .await; 74 insert_channel_at_time(&pool, &format!("bafypagfirst003_{}", suffix), &test_did, "Newest", now - Duration::hours(1)) 75 .await; 76 77 let res = client 78 .get(format!( 79 "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}&limit=2", 80 base_url, test_did 81 )) 82 .send() 83 .await 84 .unwrap(); 85 86 assert_eq!(res.status(), StatusCode::OK); 87 88 let body: Value = res.json().await.unwrap(); 89 let channels = body["channels"].as_array().unwrap(); 90 91 assert_eq!(channels.len(), 2); 92 assert_eq!(channels[0]["displayName"].as_str().unwrap(), "Newest"); 93 assert_eq!(channels[1]["displayName"].as_str().unwrap(), "Middle"); 94 assert!(body["cursor"].is_string()); 95} 96 97#[tokio::test] 98async fn test_pagination_with_cursor_excludes_newer() { 99 let base_url = common::base_url().await; 100 let pool = common::get_db_pool().await; 101 let client = common::client(); 102 103 let suffix = unique_suffix(); 104 let test_did = format!("did:plc:pagination_cursor_{}", suffix); 105 insert_test_account(&pool, &test_did).await; 106 107 let now = Utc::now(); 108 let oldest_time = now - Duration::hours(3); 109 let middle_time = now - Duration::hours(2); 110 let newest_time = now - Duration::hours(1); 111 112 insert_channel_at_time(&pool, &format!("bafypagcur001_{}", suffix), &test_did, "Oldest", oldest_time).await; 113 insert_channel_at_time(&pool, &format!("bafypagcur002_{}", suffix), &test_did, "Middle", middle_time).await; 114 insert_channel_at_time(&pool, &format!("bafypagcur003_{}", suffix), &test_did, "Newest", newest_time).await; 115 116 let cursor = middle_time.to_rfc3339(); 117 118 let res = client 119 .get(format!( 120 "{}/xrpc/systems.gmstn.development.channel.listChannels", 121 base_url 122 )) 123 .query(&[("author", test_did.as_str()), ("cursor", cursor.as_str()), ("limit", "10")]) 124 .send() 125 .await 126 .unwrap(); 127 128 assert_eq!(res.status(), StatusCode::OK); 129 130 let body: Value = res.json().await.unwrap(); 131 let channels = body["channels"].as_array().unwrap(); 132 133 assert_eq!(channels.len(), 1); 134 assert_eq!(channels[0]["displayName"].as_str().unwrap(), "Oldest"); 135} 136 137#[tokio::test] 138async fn test_pagination_returns_cursor_for_continuation() { 139 let base_url = common::base_url().await; 140 let pool = common::get_db_pool().await; 141 let client = common::client(); 142 143 let suffix = unique_suffix(); 144 let test_did = format!("did:plc:pagination_single_{}", suffix); 145 insert_test_account(&pool, &test_did).await; 146 147 let now = Utc::now(); 148 insert_channel_at_time(&pool, &format!("bafypagsingle001_{}", suffix), &test_did, "Only", now - Duration::hours(1)) 149 .await; 150 151 let res = client 152 .get(format!( 153 "{}/xrpc/systems.gmstn.development.channel.listChannels?author={}&limit=10", 154 base_url, test_did 155 )) 156 .send() 157 .await 158 .unwrap(); 159 160 assert_eq!(res.status(), StatusCode::OK); 161 162 let body: Value = res.json().await.unwrap(); 163 let channels = body["channels"].as_array().unwrap(); 164 165 assert_eq!(channels.len(), 1); 166 assert!(body["cursor"].is_string()); 167} 168 169#[tokio::test] 170async fn test_pagination_full_traversal() { 171 let base_url = common::base_url().await; 172 let pool = common::get_db_pool().await; 173 let client = common::client(); 174 175 let suffix = unique_suffix(); 176 let test_did = format!("did:plc:pagination_full_{}", suffix); 177 insert_test_account(&pool, &test_did).await; 178 179 let now = Utc::now(); 180 for i in 0..5 { 181 insert_channel_at_time( 182 &pool, 183 &format!("bafypagfull{:03}_{}", i, suffix), 184 &test_did, 185 &format!("Channel {}", i), 186 now - Duration::hours(5 - i as i64), 187 ) 188 .await; 189 } 190 191 let mut all_names: Vec<String> = Vec::new(); 192 let mut cursor: Option<String> = None; 193 194 loop { 195 let mut req = client 196 .get(format!( 197 "{}/xrpc/systems.gmstn.development.channel.listChannels", 198 base_url 199 )) 200 .query(&[("author", test_did.as_str()), ("limit", "2")]); 201 202 if let Some(c) = &cursor { 203 req = req.query(&[("cursor", c.as_str())]); 204 } 205 206 let res = req.send().await.unwrap(); 207 assert_eq!(res.status(), StatusCode::OK); 208 209 let body: Value = res.json().await.unwrap(); 210 let channels = body["channels"].as_array().unwrap(); 211 212 if channels.is_empty() { 213 break; 214 } 215 216 for channel in channels { 217 all_names.push(channel["displayName"].as_str().unwrap().to_string()); 218 } 219 220 cursor = body["cursor"].as_str().map(String::from); 221 222 if cursor.is_none() { 223 break; 224 } 225 } 226 227 assert_eq!(all_names.len(), 5); 228 assert_eq!(all_names[0], "Channel 4"); 229 assert_eq!(all_names[4], "Channel 0"); 230}