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(¶ms)
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(¶ms)
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(¶ms)
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}