this repo has no description
1mod common; 2mod helpers; 3use common::*; 4use helpers::*; 5use reqwest::StatusCode; 6use serde_json::{Value, json}; 7use std::time::Duration; 8use chrono::Utc; 9#[tokio::test] 10async fn test_social_flow_lifecycle() { 11 let client = client(); 12 let (alice_did, alice_jwt) = setup_new_user("alice-social").await; 13 let (bob_did, bob_jwt) = setup_new_user("bob-social").await; 14 let (post1_uri, _) = create_post(&client, &alice_did, &alice_jwt, "Alice's first post!").await; 15 create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 16 tokio::time::sleep(Duration::from_secs(1)).await; 17 let timeline_res_1 = client 18 .get(format!( 19 "{}/xrpc/app.bsky.feed.getTimeline", 20 base_url().await 21 )) 22 .bearer_auth(&bob_jwt) 23 .send() 24 .await 25 .expect("Failed to get timeline (1)"); 26 assert_eq!( 27 timeline_res_1.status(), 28 reqwest::StatusCode::OK, 29 "Failed to get timeline (1)" 30 ); 31 let timeline_body_1: Value = timeline_res_1.json().await.expect("Timeline (1) not JSON"); 32 let feed_1 = timeline_body_1["feed"].as_array().unwrap(); 33 assert_eq!(feed_1.len(), 1, "Timeline should have 1 post"); 34 assert_eq!( 35 feed_1[0]["post"]["uri"], post1_uri, 36 "Post URI mismatch in timeline (1)" 37 ); 38 let (post2_uri, _) = create_post( 39 &client, 40 &alice_did, 41 &alice_jwt, 42 "Alice's second post, so exciting!", 43 ) 44 .await; 45 tokio::time::sleep(Duration::from_secs(1)).await; 46 let timeline_res_2 = client 47 .get(format!( 48 "{}/xrpc/app.bsky.feed.getTimeline", 49 base_url().await 50 )) 51 .bearer_auth(&bob_jwt) 52 .send() 53 .await 54 .expect("Failed to get timeline (2)"); 55 assert_eq!( 56 timeline_res_2.status(), 57 reqwest::StatusCode::OK, 58 "Failed to get timeline (2)" 59 ); 60 let timeline_body_2: Value = timeline_res_2.json().await.expect("Timeline (2) not JSON"); 61 let feed_2 = timeline_body_2["feed"].as_array().unwrap(); 62 assert_eq!(feed_2.len(), 2, "Timeline should have 2 posts"); 63 assert_eq!( 64 feed_2[0]["post"]["uri"], post2_uri, 65 "Post 2 should be first" 66 ); 67 assert_eq!( 68 feed_2[1]["post"]["uri"], post1_uri, 69 "Post 1 should be second" 70 ); 71 let delete_payload = json!({ 72 "repo": alice_did, 73 "collection": "app.bsky.feed.post", 74 "rkey": post1_uri.split('/').last().unwrap() 75 }); 76 let delete_res = client 77 .post(format!( 78 "{}/xrpc/com.atproto.repo.deleteRecord", 79 base_url().await 80 )) 81 .bearer_auth(&alice_jwt) 82 .json(&delete_payload) 83 .send() 84 .await 85 .expect("Failed to send delete request"); 86 assert_eq!( 87 delete_res.status(), 88 reqwest::StatusCode::OK, 89 "Failed to delete record" 90 ); 91 tokio::time::sleep(Duration::from_secs(1)).await; 92 let timeline_res_3 = client 93 .get(format!( 94 "{}/xrpc/app.bsky.feed.getTimeline", 95 base_url().await 96 )) 97 .bearer_auth(&bob_jwt) 98 .send() 99 .await 100 .expect("Failed to get timeline (3)"); 101 assert_eq!( 102 timeline_res_3.status(), 103 reqwest::StatusCode::OK, 104 "Failed to get timeline (3)" 105 ); 106 let timeline_body_3: Value = timeline_res_3.json().await.expect("Timeline (3) not JSON"); 107 let feed_3 = timeline_body_3["feed"].as_array().unwrap(); 108 assert_eq!(feed_3.len(), 1, "Timeline should have 1 post after delete"); 109 assert_eq!( 110 feed_3[0]["post"]["uri"], post2_uri, 111 "Only post 2 should remain" 112 ); 113} 114#[tokio::test] 115async fn test_like_lifecycle() { 116 let client = client(); 117 let (alice_did, alice_jwt) = setup_new_user("alice-like").await; 118 let (bob_did, bob_jwt) = setup_new_user("bob-like").await; 119 let (post_uri, post_cid) = create_post(&client, &alice_did, &alice_jwt, "Like this post!").await; 120 let (like_uri, _) = create_like(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await; 121 let like_rkey = like_uri.split('/').last().unwrap(); 122 let get_like_res = client 123 .get(format!( 124 "{}/xrpc/com.atproto.repo.getRecord", 125 base_url().await 126 )) 127 .query(&[ 128 ("repo", bob_did.as_str()), 129 ("collection", "app.bsky.feed.like"), 130 ("rkey", like_rkey), 131 ]) 132 .send() 133 .await 134 .expect("Failed to get like"); 135 assert_eq!(get_like_res.status(), StatusCode::OK); 136 let like_body: Value = get_like_res.json().await.unwrap(); 137 assert_eq!(like_body["value"]["subject"]["uri"], post_uri); 138 let delete_payload = json!({ 139 "repo": bob_did, 140 "collection": "app.bsky.feed.like", 141 "rkey": like_rkey 142 }); 143 let delete_res = client 144 .post(format!( 145 "{}/xrpc/com.atproto.repo.deleteRecord", 146 base_url().await 147 )) 148 .bearer_auth(&bob_jwt) 149 .json(&delete_payload) 150 .send() 151 .await 152 .expect("Failed to delete like"); 153 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete like"); 154 let get_deleted_res = client 155 .get(format!( 156 "{}/xrpc/com.atproto.repo.getRecord", 157 base_url().await 158 )) 159 .query(&[ 160 ("repo", bob_did.as_str()), 161 ("collection", "app.bsky.feed.like"), 162 ("rkey", like_rkey), 163 ]) 164 .send() 165 .await 166 .expect("Failed to check deleted like"); 167 assert_eq!(get_deleted_res.status(), StatusCode::NOT_FOUND, "Like should be deleted"); 168} 169#[tokio::test] 170async fn test_repost_lifecycle() { 171 let client = client(); 172 let (alice_did, alice_jwt) = setup_new_user("alice-repost").await; 173 let (bob_did, bob_jwt) = setup_new_user("bob-repost").await; 174 let (post_uri, post_cid) = create_post(&client, &alice_did, &alice_jwt, "Repost this!").await; 175 let (repost_uri, _) = create_repost(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await; 176 let repost_rkey = repost_uri.split('/').last().unwrap(); 177 let get_repost_res = client 178 .get(format!( 179 "{}/xrpc/com.atproto.repo.getRecord", 180 base_url().await 181 )) 182 .query(&[ 183 ("repo", bob_did.as_str()), 184 ("collection", "app.bsky.feed.repost"), 185 ("rkey", repost_rkey), 186 ]) 187 .send() 188 .await 189 .expect("Failed to get repost"); 190 assert_eq!(get_repost_res.status(), StatusCode::OK); 191 let repost_body: Value = get_repost_res.json().await.unwrap(); 192 assert_eq!(repost_body["value"]["subject"]["uri"], post_uri); 193 let delete_payload = json!({ 194 "repo": bob_did, 195 "collection": "app.bsky.feed.repost", 196 "rkey": repost_rkey 197 }); 198 let delete_res = client 199 .post(format!( 200 "{}/xrpc/com.atproto.repo.deleteRecord", 201 base_url().await 202 )) 203 .bearer_auth(&bob_jwt) 204 .json(&delete_payload) 205 .send() 206 .await 207 .expect("Failed to delete repost"); 208 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete repost"); 209} 210#[tokio::test] 211async fn test_unfollow_lifecycle() { 212 let client = client(); 213 let (alice_did, _alice_jwt) = setup_new_user("alice-unfollow").await; 214 let (bob_did, bob_jwt) = setup_new_user("bob-unfollow").await; 215 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 216 let follow_rkey = follow_uri.split('/').last().unwrap(); 217 let get_follow_res = client 218 .get(format!( 219 "{}/xrpc/com.atproto.repo.getRecord", 220 base_url().await 221 )) 222 .query(&[ 223 ("repo", bob_did.as_str()), 224 ("collection", "app.bsky.graph.follow"), 225 ("rkey", follow_rkey), 226 ]) 227 .send() 228 .await 229 .expect("Failed to get follow"); 230 assert_eq!(get_follow_res.status(), StatusCode::OK); 231 let unfollow_payload = json!({ 232 "repo": bob_did, 233 "collection": "app.bsky.graph.follow", 234 "rkey": follow_rkey 235 }); 236 let unfollow_res = client 237 .post(format!( 238 "{}/xrpc/com.atproto.repo.deleteRecord", 239 base_url().await 240 )) 241 .bearer_auth(&bob_jwt) 242 .json(&unfollow_payload) 243 .send() 244 .await 245 .expect("Failed to unfollow"); 246 assert_eq!(unfollow_res.status(), StatusCode::OK, "Failed to unfollow"); 247 let get_deleted_res = client 248 .get(format!( 249 "{}/xrpc/com.atproto.repo.getRecord", 250 base_url().await 251 )) 252 .query(&[ 253 ("repo", bob_did.as_str()), 254 ("collection", "app.bsky.graph.follow"), 255 ("rkey", follow_rkey), 256 ]) 257 .send() 258 .await 259 .expect("Failed to check deleted follow"); 260 assert_eq!(get_deleted_res.status(), StatusCode::NOT_FOUND, "Follow should be deleted"); 261} 262#[tokio::test] 263async fn test_timeline_after_unfollow() { 264 let client = client(); 265 let (alice_did, alice_jwt) = setup_new_user("alice-tl-unfollow").await; 266 let (bob_did, bob_jwt) = setup_new_user("bob-tl-unfollow").await; 267 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 268 create_post(&client, &alice_did, &alice_jwt, "Post while following").await; 269 tokio::time::sleep(Duration::from_secs(1)).await; 270 let timeline_res = client 271 .get(format!( 272 "{}/xrpc/app.bsky.feed.getTimeline", 273 base_url().await 274 )) 275 .bearer_auth(&bob_jwt) 276 .send() 277 .await 278 .expect("Failed to get timeline"); 279 assert_eq!(timeline_res.status(), StatusCode::OK); 280 let timeline_body: Value = timeline_res.json().await.unwrap(); 281 let feed = timeline_body["feed"].as_array().unwrap(); 282 assert_eq!(feed.len(), 1, "Should see 1 post from Alice"); 283 let follow_rkey = follow_uri.split('/').last().unwrap(); 284 let unfollow_payload = json!({ 285 "repo": bob_did, 286 "collection": "app.bsky.graph.follow", 287 "rkey": follow_rkey 288 }); 289 client 290 .post(format!( 291 "{}/xrpc/com.atproto.repo.deleteRecord", 292 base_url().await 293 )) 294 .bearer_auth(&bob_jwt) 295 .json(&unfollow_payload) 296 .send() 297 .await 298 .expect("Failed to unfollow"); 299 tokio::time::sleep(Duration::from_secs(1)).await; 300 let timeline_after_res = client 301 .get(format!( 302 "{}/xrpc/app.bsky.feed.getTimeline", 303 base_url().await 304 )) 305 .bearer_auth(&bob_jwt) 306 .send() 307 .await 308 .expect("Failed to get timeline after unfollow"); 309 assert_eq!(timeline_after_res.status(), StatusCode::OK); 310 let timeline_after: Value = timeline_after_res.json().await.unwrap(); 311 let feed_after = timeline_after["feed"].as_array().unwrap(); 312 assert_eq!(feed_after.len(), 0, "Should see 0 posts after unfollowing"); 313} 314#[tokio::test] 315async fn test_mutual_follow_lifecycle() { 316 let client = client(); 317 let (alice_did, alice_jwt) = setup_new_user("alice-mutual").await; 318 let (bob_did, bob_jwt) = setup_new_user("bob-mutual").await; 319 create_follow(&client, &alice_did, &alice_jwt, &bob_did).await; 320 create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 321 create_post(&client, &alice_did, &alice_jwt, "Alice's post for mutual").await; 322 create_post(&client, &bob_did, &bob_jwt, "Bob's post for mutual").await; 323 tokio::time::sleep(Duration::from_secs(1)).await; 324 let alice_timeline_res = client 325 .get(format!( 326 "{}/xrpc/app.bsky.feed.getTimeline", 327 base_url().await 328 )) 329 .bearer_auth(&alice_jwt) 330 .send() 331 .await 332 .expect("Failed to get Alice's timeline"); 333 assert_eq!(alice_timeline_res.status(), StatusCode::OK); 334 let alice_tl: Value = alice_timeline_res.json().await.unwrap(); 335 let alice_feed = alice_tl["feed"].as_array().unwrap(); 336 assert_eq!(alice_feed.len(), 1, "Alice should see Bob's 1 post"); 337 let bob_timeline_res = client 338 .get(format!( 339 "{}/xrpc/app.bsky.feed.getTimeline", 340 base_url().await 341 )) 342 .bearer_auth(&bob_jwt) 343 .send() 344 .await 345 .expect("Failed to get Bob's timeline"); 346 assert_eq!(bob_timeline_res.status(), StatusCode::OK); 347 let bob_tl: Value = bob_timeline_res.json().await.unwrap(); 348 let bob_feed = bob_tl["feed"].as_array().unwrap(); 349 assert_eq!(bob_feed.len(), 1, "Bob should see Alice's 1 post"); 350} 351#[tokio::test] 352async fn test_account_to_post_full_lifecycle() { 353 let client = client(); 354 let ts = Utc::now().timestamp_millis(); 355 let handle = format!("fullcycle-{}.test", ts); 356 let email = format!("fullcycle-{}@test.com", ts); 357 let password = "fullcycle-password"; 358 let create_account_res = client 359 .post(format!( 360 "{}/xrpc/com.atproto.server.createAccount", 361 base_url().await 362 )) 363 .json(&json!({ 364 "handle": handle, 365 "email": email, 366 "password": password 367 })) 368 .send() 369 .await 370 .expect("Failed to create account"); 371 assert_eq!(create_account_res.status(), StatusCode::OK); 372 let account_body: Value = create_account_res.json().await.unwrap(); 373 let did = account_body["did"].as_str().unwrap().to_string(); 374 let access_jwt = verify_new_account(&client, &did).await; 375 let get_session_res = client 376 .get(format!( 377 "{}/xrpc/com.atproto.server.getSession", 378 base_url().await 379 )) 380 .bearer_auth(&access_jwt) 381 .send() 382 .await 383 .expect("Failed to get session"); 384 assert_eq!(get_session_res.status(), StatusCode::OK); 385 let session_body: Value = get_session_res.json().await.unwrap(); 386 assert_eq!(session_body["did"], did); 387 assert_eq!(session_body["handle"], handle); 388 let profile_res = client 389 .post(format!( 390 "{}/xrpc/com.atproto.repo.putRecord", 391 base_url().await 392 )) 393 .bearer_auth(&access_jwt) 394 .json(&json!({ 395 "repo": did, 396 "collection": "app.bsky.actor.profile", 397 "rkey": "self", 398 "record": { 399 "$type": "app.bsky.actor.profile", 400 "displayName": "Full Cycle User" 401 } 402 })) 403 .send() 404 .await 405 .expect("Failed to create profile"); 406 assert_eq!(profile_res.status(), StatusCode::OK); 407 let (post_uri, post_cid) = create_post(&client, &did, &access_jwt, "My first post!").await; 408 let get_post_res = client 409 .get(format!( 410 "{}/xrpc/com.atproto.repo.getRecord", 411 base_url().await 412 )) 413 .query(&[ 414 ("repo", did.as_str()), 415 ("collection", "app.bsky.feed.post"), 416 ("rkey", post_uri.split('/').last().unwrap()), 417 ]) 418 .send() 419 .await 420 .expect("Failed to get post"); 421 assert_eq!(get_post_res.status(), StatusCode::OK); 422 create_like(&client, &did, &access_jwt, &post_uri, &post_cid).await; 423 let describe_res = client 424 .get(format!( 425 "{}/xrpc/com.atproto.repo.describeRepo", 426 base_url().await 427 )) 428 .query(&[("repo", did.as_str())]) 429 .send() 430 .await 431 .expect("Failed to describe repo"); 432 assert_eq!(describe_res.status(), StatusCode::OK); 433 let describe_body: Value = describe_res.json().await.unwrap(); 434 assert_eq!(describe_body["did"], did); 435 assert_eq!(describe_body["handle"], handle); 436}