this repo has no description
1mod common; 2mod helpers; 3 4use common::*; 5use helpers::*; 6 7use chrono::Utc; 8use reqwest::{StatusCode, header}; 9use serde_json::{Value, json}; 10use std::time::Duration; 11 12#[tokio::test] 13async fn test_post_crud_lifecycle() { 14 let client = client(); 15 let (did, jwt) = setup_new_user("lifecycle-crud").await; 16 let collection = "app.bsky.feed.post"; 17 18 let rkey = format!("e2e_lifecycle_{}", Utc::now().timestamp_millis()); 19 let now = Utc::now().to_rfc3339(); 20 21 let original_text = "Hello from the lifecycle test!"; 22 let create_payload = json!({ 23 "repo": did, 24 "collection": collection, 25 "rkey": rkey, 26 "record": { 27 "$type": collection, 28 "text": original_text, 29 "createdAt": now 30 } 31 }); 32 33 let create_res = client 34 .post(format!( 35 "{}/xrpc/com.atproto.repo.putRecord", 36 base_url().await 37 )) 38 .bearer_auth(&jwt) 39 .json(&create_payload) 40 .send() 41 .await 42 .expect("Failed to send create request"); 43 44 if create_res.status() != reqwest::StatusCode::OK { 45 let status = create_res.status(); 46 let body = create_res 47 .text() 48 .await 49 .unwrap_or_else(|_| "Could not get body".to_string()); 50 panic!( 51 "Failed to create record. Status: {}, Body: {}", 52 status, body 53 ); 54 } 55 56 let create_body: Value = create_res 57 .json() 58 .await 59 .expect("create response was not JSON"); 60 let uri = create_body["uri"].as_str().unwrap(); 61 62 let params = [ 63 ("repo", did.as_str()), 64 ("collection", collection), 65 ("rkey", &rkey), 66 ]; 67 let get_res = client 68 .get(format!( 69 "{}/xrpc/com.atproto.repo.getRecord", 70 base_url().await 71 )) 72 .query(&params) 73 .send() 74 .await 75 .expect("Failed to send get request"); 76 77 assert_eq!( 78 get_res.status(), 79 reqwest::StatusCode::OK, 80 "Failed to get record after create" 81 ); 82 let get_body: Value = get_res.json().await.expect("get response was not JSON"); 83 assert_eq!(get_body["uri"], uri); 84 assert_eq!(get_body["value"]["text"], original_text); 85 86 let updated_text = "This post has been updated."; 87 let update_payload = json!({ 88 "repo": did, 89 "collection": collection, 90 "rkey": rkey, 91 "record": { 92 "$type": collection, 93 "text": updated_text, 94 "createdAt": now 95 } 96 }); 97 98 let update_res = client 99 .post(format!( 100 "{}/xrpc/com.atproto.repo.putRecord", 101 base_url().await 102 )) 103 .bearer_auth(&jwt) 104 .json(&update_payload) 105 .send() 106 .await 107 .expect("Failed to send update request"); 108 109 assert_eq!( 110 update_res.status(), 111 reqwest::StatusCode::OK, 112 "Failed to update record" 113 ); 114 115 let get_updated_res = client 116 .get(format!( 117 "{}/xrpc/com.atproto.repo.getRecord", 118 base_url().await 119 )) 120 .query(&params) 121 .send() 122 .await 123 .expect("Failed to send get-after-update request"); 124 125 assert_eq!( 126 get_updated_res.status(), 127 reqwest::StatusCode::OK, 128 "Failed to get record after update" 129 ); 130 let get_updated_body: Value = get_updated_res 131 .json() 132 .await 133 .expect("get-updated response was not JSON"); 134 assert_eq!( 135 get_updated_body["value"]["text"], updated_text, 136 "Text was not updated" 137 ); 138 139 let delete_payload = json!({ 140 "repo": did, 141 "collection": collection, 142 "rkey": rkey 143 }); 144 145 let delete_res = client 146 .post(format!( 147 "{}/xrpc/com.atproto.repo.deleteRecord", 148 base_url().await 149 )) 150 .bearer_auth(&jwt) 151 .json(&delete_payload) 152 .send() 153 .await 154 .expect("Failed to send delete request"); 155 156 assert_eq!( 157 delete_res.status(), 158 reqwest::StatusCode::OK, 159 "Failed to delete record" 160 ); 161 162 let get_deleted_res = client 163 .get(format!( 164 "{}/xrpc/com.atproto.repo.getRecord", 165 base_url().await 166 )) 167 .query(&params) 168 .send() 169 .await 170 .expect("Failed to send get-after-delete request"); 171 172 assert_eq!( 173 get_deleted_res.status(), 174 reqwest::StatusCode::NOT_FOUND, 175 "Record was found, but it should be deleted" 176 ); 177} 178 179#[tokio::test] 180async fn test_record_update_conflict_lifecycle() { 181 let client = client(); 182 let (user_did, user_jwt) = setup_new_user("user-conflict").await; 183 184 let profile_payload = json!({ 185 "repo": user_did, 186 "collection": "app.bsky.actor.profile", 187 "rkey": "self", 188 "record": { 189 "$type": "app.bsky.actor.profile", 190 "displayName": "Original Name" 191 } 192 }); 193 let create_res = client 194 .post(format!( 195 "{}/xrpc/com.atproto.repo.putRecord", 196 base_url().await 197 )) 198 .bearer_auth(&user_jwt) 199 .json(&profile_payload) 200 .send() 201 .await 202 .expect("create profile failed"); 203 204 if create_res.status() != reqwest::StatusCode::OK { 205 return; 206 } 207 208 let get_res = client 209 .get(format!( 210 "{}/xrpc/com.atproto.repo.getRecord", 211 base_url().await 212 )) 213 .query(&[ 214 ("repo", &user_did), 215 ("collection", &"app.bsky.actor.profile".to_string()), 216 ("rkey", &"self".to_string()), 217 ]) 218 .send() 219 .await 220 .expect("getRecord failed"); 221 let get_body: Value = get_res.json().await.expect("getRecord not json"); 222 let cid_v1 = get_body["cid"] 223 .as_str() 224 .expect("Profile v1 had no CID") 225 .to_string(); 226 227 let update_payload_v2 = json!({ 228 "repo": user_did, 229 "collection": "app.bsky.actor.profile", 230 "rkey": "self", 231 "record": { 232 "$type": "app.bsky.actor.profile", 233 "displayName": "Updated Name (v2)" 234 }, 235 "swapRecord": cid_v1 236 }); 237 let update_res_v2 = client 238 .post(format!( 239 "{}/xrpc/com.atproto.repo.putRecord", 240 base_url().await 241 )) 242 .bearer_auth(&user_jwt) 243 .json(&update_payload_v2) 244 .send() 245 .await 246 .expect("putRecord v2 failed"); 247 assert_eq!( 248 update_res_v2.status(), 249 reqwest::StatusCode::OK, 250 "v2 update failed" 251 ); 252 let update_body_v2: Value = update_res_v2.json().await.expect("v2 body not json"); 253 let cid_v2 = update_body_v2["cid"] 254 .as_str() 255 .expect("v2 response had no CID") 256 .to_string(); 257 258 let update_payload_v3_stale = json!({ 259 "repo": user_did, 260 "collection": "app.bsky.actor.profile", 261 "rkey": "self", 262 "record": { 263 "$type": "app.bsky.actor.profile", 264 "displayName": "Stale Update (v3)" 265 }, 266 "swapRecord": cid_v1 267 }); 268 let update_res_v3_stale = client 269 .post(format!( 270 "{}/xrpc/com.atproto.repo.putRecord", 271 base_url().await 272 )) 273 .bearer_auth(&user_jwt) 274 .json(&update_payload_v3_stale) 275 .send() 276 .await 277 .expect("putRecord v3 (stale) failed"); 278 279 assert_eq!( 280 update_res_v3_stale.status(), 281 reqwest::StatusCode::CONFLICT, 282 "Stale update did not cause a 409 Conflict" 283 ); 284 285 let update_payload_v3_good = json!({ 286 "repo": user_did, 287 "collection": "app.bsky.actor.profile", 288 "rkey": "self", 289 "record": { 290 "$type": "app.bsky.actor.profile", 291 "displayName": "Good Update (v3)" 292 }, 293 "swapRecord": cid_v2 294 }); 295 let update_res_v3_good = client 296 .post(format!( 297 "{}/xrpc/com.atproto.repo.putRecord", 298 base_url().await 299 )) 300 .bearer_auth(&user_jwt) 301 .json(&update_payload_v3_good) 302 .send() 303 .await 304 .expect("putRecord v3 (good) failed"); 305 306 assert_eq!( 307 update_res_v3_good.status(), 308 reqwest::StatusCode::OK, 309 "v3 (good) update failed" 310 ); 311} 312 313#[tokio::test] 314async fn test_profile_lifecycle() { 315 let client = client(); 316 let (did, jwt) = setup_new_user("profile-lifecycle").await; 317 318 let profile_payload = json!({ 319 "repo": did, 320 "collection": "app.bsky.actor.profile", 321 "rkey": "self", 322 "record": { 323 "$type": "app.bsky.actor.profile", 324 "displayName": "Test User", 325 "description": "A test profile for lifecycle testing" 326 } 327 }); 328 329 let create_res = client 330 .post(format!( 331 "{}/xrpc/com.atproto.repo.putRecord", 332 base_url().await 333 )) 334 .bearer_auth(&jwt) 335 .json(&profile_payload) 336 .send() 337 .await 338 .expect("Failed to create profile"); 339 340 assert_eq!(create_res.status(), StatusCode::OK, "Failed to create profile"); 341 let create_body: Value = create_res.json().await.unwrap(); 342 let initial_cid = create_body["cid"].as_str().unwrap().to_string(); 343 344 let get_res = client 345 .get(format!( 346 "{}/xrpc/com.atproto.repo.getRecord", 347 base_url().await 348 )) 349 .query(&[ 350 ("repo", did.as_str()), 351 ("collection", "app.bsky.actor.profile"), 352 ("rkey", "self"), 353 ]) 354 .send() 355 .await 356 .expect("Failed to get profile"); 357 358 assert_eq!(get_res.status(), StatusCode::OK); 359 let get_body: Value = get_res.json().await.unwrap(); 360 assert_eq!(get_body["value"]["displayName"], "Test User"); 361 assert_eq!(get_body["value"]["description"], "A test profile for lifecycle testing"); 362 363 let update_payload = json!({ 364 "repo": did, 365 "collection": "app.bsky.actor.profile", 366 "rkey": "self", 367 "record": { 368 "$type": "app.bsky.actor.profile", 369 "displayName": "Updated User", 370 "description": "Profile has been updated" 371 }, 372 "swapRecord": initial_cid 373 }); 374 375 let update_res = client 376 .post(format!( 377 "{}/xrpc/com.atproto.repo.putRecord", 378 base_url().await 379 )) 380 .bearer_auth(&jwt) 381 .json(&update_payload) 382 .send() 383 .await 384 .expect("Failed to update profile"); 385 386 assert_eq!(update_res.status(), StatusCode::OK, "Failed to update profile"); 387 388 let get_updated_res = client 389 .get(format!( 390 "{}/xrpc/com.atproto.repo.getRecord", 391 base_url().await 392 )) 393 .query(&[ 394 ("repo", did.as_str()), 395 ("collection", "app.bsky.actor.profile"), 396 ("rkey", "self"), 397 ]) 398 .send() 399 .await 400 .expect("Failed to get updated profile"); 401 402 let updated_body: Value = get_updated_res.json().await.unwrap(); 403 assert_eq!(updated_body["value"]["displayName"], "Updated User"); 404} 405 406#[tokio::test] 407async fn test_reply_thread_lifecycle() { 408 let client = client(); 409 410 let (alice_did, alice_jwt) = setup_new_user("alice-thread").await; 411 let (bob_did, bob_jwt) = setup_new_user("bob-thread").await; 412 413 let (root_uri, root_cid) = create_post(&client, &alice_did, &alice_jwt, "This is the root post").await; 414 415 tokio::time::sleep(Duration::from_millis(100)).await; 416 417 let reply_collection = "app.bsky.feed.post"; 418 let reply_rkey = format!("e2e_reply_{}", Utc::now().timestamp_millis()); 419 let now = Utc::now().to_rfc3339(); 420 421 let reply_payload = json!({ 422 "repo": bob_did, 423 "collection": reply_collection, 424 "rkey": reply_rkey, 425 "record": { 426 "$type": reply_collection, 427 "text": "This is Bob's reply to Alice", 428 "createdAt": now, 429 "reply": { 430 "root": { 431 "uri": root_uri, 432 "cid": root_cid 433 }, 434 "parent": { 435 "uri": root_uri, 436 "cid": root_cid 437 } 438 } 439 } 440 }); 441 442 let reply_res = client 443 .post(format!( 444 "{}/xrpc/com.atproto.repo.putRecord", 445 base_url().await 446 )) 447 .bearer_auth(&bob_jwt) 448 .json(&reply_payload) 449 .send() 450 .await 451 .expect("Failed to create reply"); 452 453 assert_eq!(reply_res.status(), StatusCode::OK, "Failed to create reply"); 454 let reply_body: Value = reply_res.json().await.unwrap(); 455 let reply_uri = reply_body["uri"].as_str().unwrap(); 456 let reply_cid = reply_body["cid"].as_str().unwrap(); 457 458 let get_reply_res = client 459 .get(format!( 460 "{}/xrpc/com.atproto.repo.getRecord", 461 base_url().await 462 )) 463 .query(&[ 464 ("repo", bob_did.as_str()), 465 ("collection", reply_collection), 466 ("rkey", reply_rkey.as_str()), 467 ]) 468 .send() 469 .await 470 .expect("Failed to get reply"); 471 472 assert_eq!(get_reply_res.status(), StatusCode::OK); 473 let reply_record: Value = get_reply_res.json().await.unwrap(); 474 assert_eq!(reply_record["value"]["reply"]["root"]["uri"], root_uri); 475 assert_eq!(reply_record["value"]["reply"]["parent"]["uri"], root_uri); 476 477 tokio::time::sleep(Duration::from_millis(100)).await; 478 479 let nested_reply_rkey = format!("e2e_nested_reply_{}", Utc::now().timestamp_millis()); 480 let nested_payload = json!({ 481 "repo": alice_did, 482 "collection": reply_collection, 483 "rkey": nested_reply_rkey, 484 "record": { 485 "$type": reply_collection, 486 "text": "Alice replies to Bob's reply", 487 "createdAt": Utc::now().to_rfc3339(), 488 "reply": { 489 "root": { 490 "uri": root_uri, 491 "cid": root_cid 492 }, 493 "parent": { 494 "uri": reply_uri, 495 "cid": reply_cid 496 } 497 } 498 } 499 }); 500 501 let nested_res = client 502 .post(format!( 503 "{}/xrpc/com.atproto.repo.putRecord", 504 base_url().await 505 )) 506 .bearer_auth(&alice_jwt) 507 .json(&nested_payload) 508 .send() 509 .await 510 .expect("Failed to create nested reply"); 511 512 assert_eq!(nested_res.status(), StatusCode::OK, "Failed to create nested reply"); 513} 514 515#[tokio::test] 516async fn test_blob_in_record_lifecycle() { 517 let client = client(); 518 let (did, jwt) = setup_new_user("blob-record").await; 519 520 let blob_data = b"This is test blob data for a profile avatar"; 521 let upload_res = client 522 .post(format!( 523 "{}/xrpc/com.atproto.repo.uploadBlob", 524 base_url().await 525 )) 526 .header(header::CONTENT_TYPE, "text/plain") 527 .bearer_auth(&jwt) 528 .body(blob_data.to_vec()) 529 .send() 530 .await 531 .expect("Failed to upload blob"); 532 533 assert_eq!(upload_res.status(), StatusCode::OK); 534 let upload_body: Value = upload_res.json().await.unwrap(); 535 let blob_ref = upload_body["blob"].clone(); 536 537 let profile_payload = json!({ 538 "repo": did, 539 "collection": "app.bsky.actor.profile", 540 "rkey": "self", 541 "record": { 542 "$type": "app.bsky.actor.profile", 543 "displayName": "User With Avatar", 544 "avatar": blob_ref 545 } 546 }); 547 548 let create_res = client 549 .post(format!( 550 "{}/xrpc/com.atproto.repo.putRecord", 551 base_url().await 552 )) 553 .bearer_auth(&jwt) 554 .json(&profile_payload) 555 .send() 556 .await 557 .expect("Failed to create profile with blob"); 558 559 assert_eq!(create_res.status(), StatusCode::OK, "Failed to create profile with blob"); 560 561 let get_res = client 562 .get(format!( 563 "{}/xrpc/com.atproto.repo.getRecord", 564 base_url().await 565 )) 566 .query(&[ 567 ("repo", did.as_str()), 568 ("collection", "app.bsky.actor.profile"), 569 ("rkey", "self"), 570 ]) 571 .send() 572 .await 573 .expect("Failed to get profile"); 574 575 assert_eq!(get_res.status(), StatusCode::OK); 576 let profile: Value = get_res.json().await.unwrap(); 577 assert!(profile["value"]["avatar"]["ref"]["$link"].is_string()); 578} 579 580#[tokio::test] 581async fn test_authorization_cannot_modify_other_repo() { 582 let client = client(); 583 584 let (alice_did, _alice_jwt) = setup_new_user("alice-auth").await; 585 let (_bob_did, bob_jwt) = setup_new_user("bob-auth").await; 586 587 let post_payload = json!({ 588 "repo": alice_did, 589 "collection": "app.bsky.feed.post", 590 "rkey": "unauthorized-post", 591 "record": { 592 "$type": "app.bsky.feed.post", 593 "text": "Bob trying to post as Alice", 594 "createdAt": Utc::now().to_rfc3339() 595 } 596 }); 597 598 let res = client 599 .post(format!( 600 "{}/xrpc/com.atproto.repo.putRecord", 601 base_url().await 602 )) 603 .bearer_auth(&bob_jwt) 604 .json(&post_payload) 605 .send() 606 .await 607 .expect("Failed to send request"); 608 609 assert!( 610 res.status() == StatusCode::FORBIDDEN || res.status() == StatusCode::UNAUTHORIZED, 611 "Expected 403 or 401 when writing to another user's repo, got {}", 612 res.status() 613 ); 614} 615 616#[tokio::test] 617async fn test_authorization_cannot_delete_other_record() { 618 let client = client(); 619 620 let (alice_did, alice_jwt) = setup_new_user("alice-del-auth").await; 621 let (_bob_did, bob_jwt) = setup_new_user("bob-del-auth").await; 622 623 let (post_uri, _) = create_post(&client, &alice_did, &alice_jwt, "Alice's post").await; 624 let post_rkey = post_uri.split('/').last().unwrap(); 625 626 let delete_payload = json!({ 627 "repo": alice_did, 628 "collection": "app.bsky.feed.post", 629 "rkey": post_rkey 630 }); 631 632 let res = client 633 .post(format!( 634 "{}/xrpc/com.atproto.repo.deleteRecord", 635 base_url().await 636 )) 637 .bearer_auth(&bob_jwt) 638 .json(&delete_payload) 639 .send() 640 .await 641 .expect("Failed to send request"); 642 643 assert!( 644 res.status() == StatusCode::FORBIDDEN || res.status() == StatusCode::UNAUTHORIZED, 645 "Expected 403 or 401 when deleting another user's record, got {}", 646 res.status() 647 ); 648 649 let get_res = client 650 .get(format!( 651 "{}/xrpc/com.atproto.repo.getRecord", 652 base_url().await 653 )) 654 .query(&[ 655 ("repo", alice_did.as_str()), 656 ("collection", "app.bsky.feed.post"), 657 ("rkey", post_rkey), 658 ]) 659 .send() 660 .await 661 .expect("Failed to verify record exists"); 662 663 assert_eq!(get_res.status(), StatusCode::OK, "Record should still exist"); 664} 665 666#[tokio::test] 667async fn test_list_records_pagination() { 668 let client = client(); 669 let (did, jwt) = setup_new_user("list-pagination").await; 670 671 for i in 0..5 { 672 tokio::time::sleep(Duration::from_millis(50)).await; 673 create_post(&client, &did, &jwt, &format!("Post number {}", i)).await; 674 } 675 676 let list_res = client 677 .get(format!( 678 "{}/xrpc/com.atproto.repo.listRecords", 679 base_url().await 680 )) 681 .query(&[ 682 ("repo", did.as_str()), 683 ("collection", "app.bsky.feed.post"), 684 ("limit", "2"), 685 ]) 686 .send() 687 .await 688 .expect("Failed to list records"); 689 690 assert_eq!(list_res.status(), StatusCode::OK); 691 let list_body: Value = list_res.json().await.unwrap(); 692 let records = list_body["records"].as_array().unwrap(); 693 assert_eq!(records.len(), 2, "Should return 2 records with limit=2"); 694 695 if let Some(cursor) = list_body["cursor"].as_str() { 696 let list_page2_res = client 697 .get(format!( 698 "{}/xrpc/com.atproto.repo.listRecords", 699 base_url().await 700 )) 701 .query(&[ 702 ("repo", did.as_str()), 703 ("collection", "app.bsky.feed.post"), 704 ("limit", "2"), 705 ("cursor", cursor), 706 ]) 707 .send() 708 .await 709 .expect("Failed to list records page 2"); 710 711 assert_eq!(list_page2_res.status(), StatusCode::OK); 712 let page2_body: Value = list_page2_res.json().await.unwrap(); 713 let page2_records = page2_body["records"].as_array().unwrap(); 714 assert_eq!(page2_records.len(), 2, "Page 2 should have 2 more records"); 715 } 716} 717 718#[tokio::test] 719async fn test_apply_writes_batch_lifecycle() { 720 let client = client(); 721 let (did, jwt) = setup_new_user("apply-writes-batch").await; 722 723 let now = Utc::now().to_rfc3339(); 724 let writes_payload = json!({ 725 "repo": did, 726 "writes": [ 727 { 728 "$type": "com.atproto.repo.applyWrites#create", 729 "collection": "app.bsky.feed.post", 730 "rkey": "batch-post-1", 731 "value": { 732 "$type": "app.bsky.feed.post", 733 "text": "First batch post", 734 "createdAt": now 735 } 736 }, 737 { 738 "$type": "com.atproto.repo.applyWrites#create", 739 "collection": "app.bsky.feed.post", 740 "rkey": "batch-post-2", 741 "value": { 742 "$type": "app.bsky.feed.post", 743 "text": "Second batch post", 744 "createdAt": now 745 } 746 }, 747 { 748 "$type": "com.atproto.repo.applyWrites#create", 749 "collection": "app.bsky.actor.profile", 750 "rkey": "self", 751 "value": { 752 "$type": "app.bsky.actor.profile", 753 "displayName": "Batch User" 754 } 755 } 756 ] 757 }); 758 759 let apply_res = client 760 .post(format!( 761 "{}/xrpc/com.atproto.repo.applyWrites", 762 base_url().await 763 )) 764 .bearer_auth(&jwt) 765 .json(&writes_payload) 766 .send() 767 .await 768 .expect("Failed to apply writes"); 769 770 assert_eq!(apply_res.status(), StatusCode::OK); 771 772 let get_post1 = client 773 .get(format!( 774 "{}/xrpc/com.atproto.repo.getRecord", 775 base_url().await 776 )) 777 .query(&[ 778 ("repo", did.as_str()), 779 ("collection", "app.bsky.feed.post"), 780 ("rkey", "batch-post-1"), 781 ]) 782 .send() 783 .await 784 .expect("Failed to get post 1"); 785 assert_eq!(get_post1.status(), StatusCode::OK); 786 let post1_body: Value = get_post1.json().await.unwrap(); 787 assert_eq!(post1_body["value"]["text"], "First batch post"); 788 789 let get_post2 = client 790 .get(format!( 791 "{}/xrpc/com.atproto.repo.getRecord", 792 base_url().await 793 )) 794 .query(&[ 795 ("repo", did.as_str()), 796 ("collection", "app.bsky.feed.post"), 797 ("rkey", "batch-post-2"), 798 ]) 799 .send() 800 .await 801 .expect("Failed to get post 2"); 802 assert_eq!(get_post2.status(), StatusCode::OK); 803 804 let get_profile = client 805 .get(format!( 806 "{}/xrpc/com.atproto.repo.getRecord", 807 base_url().await 808 )) 809 .query(&[ 810 ("repo", did.as_str()), 811 ("collection", "app.bsky.actor.profile"), 812 ("rkey", "self"), 813 ]) 814 .send() 815 .await 816 .expect("Failed to get profile"); 817 assert_eq!(get_profile.status(), StatusCode::OK); 818 let profile_body: Value = get_profile.json().await.unwrap(); 819 assert_eq!(profile_body["value"]["displayName"], "Batch User"); 820 821 let update_writes = json!({ 822 "repo": did, 823 "writes": [ 824 { 825 "$type": "com.atproto.repo.applyWrites#update", 826 "collection": "app.bsky.actor.profile", 827 "rkey": "self", 828 "value": { 829 "$type": "app.bsky.actor.profile", 830 "displayName": "Updated Batch User" 831 } 832 }, 833 { 834 "$type": "com.atproto.repo.applyWrites#delete", 835 "collection": "app.bsky.feed.post", 836 "rkey": "batch-post-1" 837 } 838 ] 839 }); 840 841 let update_res = client 842 .post(format!( 843 "{}/xrpc/com.atproto.repo.applyWrites", 844 base_url().await 845 )) 846 .bearer_auth(&jwt) 847 .json(&update_writes) 848 .send() 849 .await 850 .expect("Failed to apply update writes"); 851 assert_eq!(update_res.status(), StatusCode::OK); 852 853 let get_updated_profile = client 854 .get(format!( 855 "{}/xrpc/com.atproto.repo.getRecord", 856 base_url().await 857 )) 858 .query(&[ 859 ("repo", did.as_str()), 860 ("collection", "app.bsky.actor.profile"), 861 ("rkey", "self"), 862 ]) 863 .send() 864 .await 865 .expect("Failed to get updated profile"); 866 let updated_profile: Value = get_updated_profile.json().await.unwrap(); 867 assert_eq!(updated_profile["value"]["displayName"], "Updated Batch User"); 868 869 let get_deleted_post = client 870 .get(format!( 871 "{}/xrpc/com.atproto.repo.getRecord", 872 base_url().await 873 )) 874 .query(&[ 875 ("repo", did.as_str()), 876 ("collection", "app.bsky.feed.post"), 877 ("rkey", "batch-post-1"), 878 ]) 879 .send() 880 .await 881 .expect("Failed to check deleted post"); 882 assert_eq!( 883 get_deleted_post.status(), 884 StatusCode::NOT_FOUND, 885 "Batch-deleted post should be gone" 886 ); 887}