this repo has no description
1mod common; 2mod helpers; 3use chrono::Utc; 4use common::*; 5use helpers::*; 6use reqwest::StatusCode; 7use serde_json::{Value, json}; 8use std::time::Duration; 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) = 122 create_post(&client, &alice_did, &alice_jwt, "Like this post!").await; 123 let (like_uri, _) = create_like(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await; 124 let like_rkey = like_uri.split('/').last().unwrap(); 125 let get_like_res = client 126 .get(format!( 127 "{}/xrpc/com.atproto.repo.getRecord", 128 base_url().await 129 )) 130 .query(&[ 131 ("repo", bob_did.as_str()), 132 ("collection", "app.bsky.feed.like"), 133 ("rkey", like_rkey), 134 ]) 135 .send() 136 .await 137 .expect("Failed to get like"); 138 assert_eq!(get_like_res.status(), StatusCode::OK); 139 let like_body: Value = get_like_res.json().await.unwrap(); 140 assert_eq!(like_body["value"]["subject"]["uri"], post_uri); 141 let delete_payload = json!({ 142 "repo": bob_did, 143 "collection": "app.bsky.feed.like", 144 "rkey": like_rkey 145 }); 146 let delete_res = client 147 .post(format!( 148 "{}/xrpc/com.atproto.repo.deleteRecord", 149 base_url().await 150 )) 151 .bearer_auth(&bob_jwt) 152 .json(&delete_payload) 153 .send() 154 .await 155 .expect("Failed to delete like"); 156 assert_eq!(delete_res.status(), StatusCode::OK, "Failed to delete like"); 157 let get_deleted_res = client 158 .get(format!( 159 "{}/xrpc/com.atproto.repo.getRecord", 160 base_url().await 161 )) 162 .query(&[ 163 ("repo", bob_did.as_str()), 164 ("collection", "app.bsky.feed.like"), 165 ("rkey", like_rkey), 166 ]) 167 .send() 168 .await 169 .expect("Failed to check deleted like"); 170 assert_eq!( 171 get_deleted_res.status(), 172 StatusCode::NOT_FOUND, 173 "Like should be deleted" 174 ); 175} 176 177#[tokio::test] 178async fn test_repost_lifecycle() { 179 let client = client(); 180 let (alice_did, alice_jwt) = setup_new_user("alice-repost").await; 181 let (bob_did, bob_jwt) = setup_new_user("bob-repost").await; 182 let (post_uri, post_cid) = create_post(&client, &alice_did, &alice_jwt, "Repost this!").await; 183 let (repost_uri, _) = create_repost(&client, &bob_did, &bob_jwt, &post_uri, &post_cid).await; 184 let repost_rkey = repost_uri.split('/').last().unwrap(); 185 let get_repost_res = client 186 .get(format!( 187 "{}/xrpc/com.atproto.repo.getRecord", 188 base_url().await 189 )) 190 .query(&[ 191 ("repo", bob_did.as_str()), 192 ("collection", "app.bsky.feed.repost"), 193 ("rkey", repost_rkey), 194 ]) 195 .send() 196 .await 197 .expect("Failed to get repost"); 198 assert_eq!(get_repost_res.status(), StatusCode::OK); 199 let repost_body: Value = get_repost_res.json().await.unwrap(); 200 assert_eq!(repost_body["value"]["subject"]["uri"], post_uri); 201 let delete_payload = json!({ 202 "repo": bob_did, 203 "collection": "app.bsky.feed.repost", 204 "rkey": repost_rkey 205 }); 206 let delete_res = client 207 .post(format!( 208 "{}/xrpc/com.atproto.repo.deleteRecord", 209 base_url().await 210 )) 211 .bearer_auth(&bob_jwt) 212 .json(&delete_payload) 213 .send() 214 .await 215 .expect("Failed to delete repost"); 216 assert_eq!( 217 delete_res.status(), 218 StatusCode::OK, 219 "Failed to delete repost" 220 ); 221} 222 223#[tokio::test] 224async fn test_unfollow_lifecycle() { 225 let client = client(); 226 let (alice_did, _alice_jwt) = setup_new_user("alice-unfollow").await; 227 let (bob_did, bob_jwt) = setup_new_user("bob-unfollow").await; 228 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 229 let follow_rkey = follow_uri.split('/').last().unwrap(); 230 let get_follow_res = client 231 .get(format!( 232 "{}/xrpc/com.atproto.repo.getRecord", 233 base_url().await 234 )) 235 .query(&[ 236 ("repo", bob_did.as_str()), 237 ("collection", "app.bsky.graph.follow"), 238 ("rkey", follow_rkey), 239 ]) 240 .send() 241 .await 242 .expect("Failed to get follow"); 243 assert_eq!(get_follow_res.status(), StatusCode::OK); 244 let unfollow_payload = json!({ 245 "repo": bob_did, 246 "collection": "app.bsky.graph.follow", 247 "rkey": follow_rkey 248 }); 249 let unfollow_res = client 250 .post(format!( 251 "{}/xrpc/com.atproto.repo.deleteRecord", 252 base_url().await 253 )) 254 .bearer_auth(&bob_jwt) 255 .json(&unfollow_payload) 256 .send() 257 .await 258 .expect("Failed to unfollow"); 259 assert_eq!(unfollow_res.status(), StatusCode::OK, "Failed to unfollow"); 260 let get_deleted_res = client 261 .get(format!( 262 "{}/xrpc/com.atproto.repo.getRecord", 263 base_url().await 264 )) 265 .query(&[ 266 ("repo", bob_did.as_str()), 267 ("collection", "app.bsky.graph.follow"), 268 ("rkey", follow_rkey), 269 ]) 270 .send() 271 .await 272 .expect("Failed to check deleted follow"); 273 assert_eq!( 274 get_deleted_res.status(), 275 StatusCode::NOT_FOUND, 276 "Follow should be deleted" 277 ); 278} 279 280#[tokio::test] 281async fn test_timeline_after_unfollow() { 282 let client = client(); 283 let (alice_did, alice_jwt) = setup_new_user("alice-tl-unfollow").await; 284 let (bob_did, bob_jwt) = setup_new_user("bob-tl-unfollow").await; 285 let (follow_uri, _) = create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 286 create_post(&client, &alice_did, &alice_jwt, "Post while following").await; 287 tokio::time::sleep(Duration::from_secs(1)).await; 288 let timeline_res = client 289 .get(format!( 290 "{}/xrpc/app.bsky.feed.getTimeline", 291 base_url().await 292 )) 293 .bearer_auth(&bob_jwt) 294 .send() 295 .await 296 .expect("Failed to get timeline"); 297 assert_eq!(timeline_res.status(), StatusCode::OK); 298 let timeline_body: Value = timeline_res.json().await.unwrap(); 299 let feed = timeline_body["feed"].as_array().unwrap(); 300 assert_eq!(feed.len(), 1, "Should see 1 post from Alice"); 301 let follow_rkey = follow_uri.split('/').last().unwrap(); 302 let unfollow_payload = json!({ 303 "repo": bob_did, 304 "collection": "app.bsky.graph.follow", 305 "rkey": follow_rkey 306 }); 307 client 308 .post(format!( 309 "{}/xrpc/com.atproto.repo.deleteRecord", 310 base_url().await 311 )) 312 .bearer_auth(&bob_jwt) 313 .json(&unfollow_payload) 314 .send() 315 .await 316 .expect("Failed to unfollow"); 317 tokio::time::sleep(Duration::from_secs(1)).await; 318 let timeline_after_res = client 319 .get(format!( 320 "{}/xrpc/app.bsky.feed.getTimeline", 321 base_url().await 322 )) 323 .bearer_auth(&bob_jwt) 324 .send() 325 .await 326 .expect("Failed to get timeline after unfollow"); 327 assert_eq!(timeline_after_res.status(), StatusCode::OK); 328 let timeline_after: Value = timeline_after_res.json().await.unwrap(); 329 let feed_after = timeline_after["feed"].as_array().unwrap(); 330 assert_eq!(feed_after.len(), 0, "Should see 0 posts after unfollowing"); 331} 332 333#[tokio::test] 334async fn test_mutual_follow_lifecycle() { 335 let client = client(); 336 let (alice_did, alice_jwt) = setup_new_user("alice-mutual").await; 337 let (bob_did, bob_jwt) = setup_new_user("bob-mutual").await; 338 create_follow(&client, &alice_did, &alice_jwt, &bob_did).await; 339 create_follow(&client, &bob_did, &bob_jwt, &alice_did).await; 340 create_post(&client, &alice_did, &alice_jwt, "Alice's post for mutual").await; 341 create_post(&client, &bob_did, &bob_jwt, "Bob's post for mutual").await; 342 tokio::time::sleep(Duration::from_secs(1)).await; 343 let alice_timeline_res = client 344 .get(format!( 345 "{}/xrpc/app.bsky.feed.getTimeline", 346 base_url().await 347 )) 348 .bearer_auth(&alice_jwt) 349 .send() 350 .await 351 .expect("Failed to get Alice's timeline"); 352 assert_eq!(alice_timeline_res.status(), StatusCode::OK); 353 let alice_tl: Value = alice_timeline_res.json().await.unwrap(); 354 let alice_feed = alice_tl["feed"].as_array().unwrap(); 355 assert_eq!(alice_feed.len(), 1, "Alice should see Bob's 1 post"); 356 let bob_timeline_res = client 357 .get(format!( 358 "{}/xrpc/app.bsky.feed.getTimeline", 359 base_url().await 360 )) 361 .bearer_auth(&bob_jwt) 362 .send() 363 .await 364 .expect("Failed to get Bob's timeline"); 365 assert_eq!(bob_timeline_res.status(), StatusCode::OK); 366 let bob_tl: Value = bob_timeline_res.json().await.unwrap(); 367 let bob_feed = bob_tl["feed"].as_array().unwrap(); 368 assert_eq!(bob_feed.len(), 1, "Bob should see Alice's 1 post"); 369} 370 371#[tokio::test] 372async fn test_account_to_post_full_lifecycle() { 373 let client = client(); 374 let ts = Utc::now().timestamp_millis(); 375 let handle = format!("fullcycle-{}.test", ts); 376 let email = format!("fullcycle-{}@test.com", ts); 377 let password = "fullcycle-password"; 378 let create_account_res = client 379 .post(format!( 380 "{}/xrpc/com.atproto.server.createAccount", 381 base_url().await 382 )) 383 .json(&json!({ 384 "handle": handle, 385 "email": email, 386 "password": password 387 })) 388 .send() 389 .await 390 .expect("Failed to create account"); 391 assert_eq!(create_account_res.status(), StatusCode::OK); 392 let account_body: Value = create_account_res.json().await.unwrap(); 393 let did = account_body["did"].as_str().unwrap().to_string(); 394 let handle = account_body["handle"].as_str().unwrap().to_string(); 395 let access_jwt = verify_new_account(&client, &did).await; 396 let get_session_res = client 397 .get(format!( 398 "{}/xrpc/com.atproto.server.getSession", 399 base_url().await 400 )) 401 .bearer_auth(&access_jwt) 402 .send() 403 .await 404 .expect("Failed to get session"); 405 assert_eq!(get_session_res.status(), StatusCode::OK); 406 let session_body: Value = get_session_res.json().await.unwrap(); 407 assert_eq!(session_body["did"], did); 408 let normalized_handle = session_body["handle"].as_str().unwrap().to_string(); 409 assert!( 410 normalized_handle.starts_with(&handle), 411 "Session handle should start with the requested handle" 412 ); 413 let profile_res = client 414 .post(format!( 415 "{}/xrpc/com.atproto.repo.putRecord", 416 base_url().await 417 )) 418 .bearer_auth(&access_jwt) 419 .json(&json!({ 420 "repo": did, 421 "collection": "app.bsky.actor.profile", 422 "rkey": "self", 423 "record": { 424 "$type": "app.bsky.actor.profile", 425 "displayName": "Full Cycle User" 426 } 427 })) 428 .send() 429 .await 430 .expect("Failed to create profile"); 431 assert_eq!(profile_res.status(), StatusCode::OK); 432 let (post_uri, post_cid) = create_post(&client, &did, &access_jwt, "My first post!").await; 433 let get_post_res = client 434 .get(format!( 435 "{}/xrpc/com.atproto.repo.getRecord", 436 base_url().await 437 )) 438 .query(&[ 439 ("repo", did.as_str()), 440 ("collection", "app.bsky.feed.post"), 441 ("rkey", post_uri.split('/').last().unwrap()), 442 ]) 443 .send() 444 .await 445 .expect("Failed to get post"); 446 assert_eq!(get_post_res.status(), StatusCode::OK); 447 create_like(&client, &did, &access_jwt, &post_uri, &post_cid).await; 448 let describe_res = client 449 .get(format!( 450 "{}/xrpc/com.atproto.repo.describeRepo", 451 base_url().await 452 )) 453 .query(&[("repo", did.as_str())]) 454 .send() 455 .await 456 .expect("Failed to describe repo"); 457 assert_eq!(describe_res.status(), StatusCode::OK); 458 let describe_body: Value = describe_res.json().await.unwrap(); 459 assert_eq!(describe_body["did"], did); 460 let describe_handle = describe_body["handle"].as_str().unwrap(); 461 assert!( 462 normalized_handle.starts_with(describe_handle) || describe_handle.starts_with(&handle), 463 "describeRepo handle should be related to the requested handle" 464 ); 465}