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