this repo has no description
1mod common;
2use common::*;
3
4use chrono::Utc;
5use reqwest::{StatusCode, header};
6use serde_json::{Value, json};
7
8#[tokio::test]
9async fn test_get_record_not_found() {
10 let client = client();
11 let (_, did) = create_account_and_login(&client).await;
12
13 let params = [
14 ("repo", did.as_str()),
15 ("collection", "app.bsky.feed.post"),
16 ("rkey", "nonexistent"),
17 ];
18
19 let res = client
20 .get(format!(
21 "{}/xrpc/com.atproto.repo.getRecord",
22 base_url().await
23 ))
24 .query(¶ms)
25 .send()
26 .await
27 .expect("Failed to send request");
28
29 assert_eq!(res.status(), StatusCode::NOT_FOUND);
30}
31
32#[tokio::test]
33async fn test_upload_blob_no_auth() {
34 let client = client();
35 let res = client
36 .post(format!(
37 "{}/xrpc/com.atproto.repo.uploadBlob",
38 base_url().await
39 ))
40 .header(header::CONTENT_TYPE, "text/plain")
41 .body("no auth")
42 .send()
43 .await
44 .expect("Failed to send request");
45
46 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
47 let body: Value = res.json().await.expect("Response was not valid JSON");
48 assert_eq!(body["error"], "AuthenticationRequired");
49}
50
51#[tokio::test]
52async fn test_upload_blob_success() {
53 let client = client();
54 let (token, _) = create_account_and_login(&client).await;
55 let res = client
56 .post(format!(
57 "{}/xrpc/com.atproto.repo.uploadBlob",
58 base_url().await
59 ))
60 .header(header::CONTENT_TYPE, "text/plain")
61 .bearer_auth(token)
62 .body("This is our blob data")
63 .send()
64 .await
65 .expect("Failed to send request");
66
67 assert_eq!(res.status(), StatusCode::OK);
68 let body: Value = res.json().await.expect("Response was not valid JSON");
69 assert!(body["blob"]["ref"]["$link"].as_str().is_some());
70}
71
72#[tokio::test]
73async fn test_put_record_no_auth() {
74 let client = client();
75 let payload = json!({
76 "repo": "did:plc:123",
77 "collection": "app.bsky.feed.post",
78 "rkey": "fake",
79 "record": {}
80 });
81
82 let res = client
83 .post(format!(
84 "{}/xrpc/com.atproto.repo.putRecord",
85 base_url().await
86 ))
87 .json(&payload)
88 .send()
89 .await
90 .expect("Failed to send request");
91
92 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
93 let body: Value = res.json().await.expect("Response was not valid JSON");
94 assert_eq!(body["error"], "AuthenticationRequired");
95}
96
97#[tokio::test]
98async fn test_put_record_success() {
99 let client = client();
100 let (token, did) = create_account_and_login(&client).await;
101 let now = Utc::now().to_rfc3339();
102 let payload = json!({
103 "repo": did,
104 "collection": "app.bsky.feed.post",
105 "rkey": "e2e_test_post",
106 "record": {
107 "$type": "app.bsky.feed.post",
108 "text": "Hello from the e2e test script!",
109 "createdAt": now
110 }
111 });
112
113 let res = client
114 .post(format!(
115 "{}/xrpc/com.atproto.repo.putRecord",
116 base_url().await
117 ))
118 .bearer_auth(token)
119 .json(&payload)
120 .send()
121 .await
122 .expect("Failed to send request");
123
124 assert_eq!(res.status(), StatusCode::OK);
125 let body: Value = res.json().await.expect("Response was not valid JSON");
126 assert!(body.get("uri").is_some());
127 assert!(body.get("cid").is_some());
128}
129
130#[tokio::test]
131async fn test_get_record_missing_params() {
132 let client = client();
133 let params = [("repo", "did:plc:12345")];
134
135 let res = client
136 .get(format!(
137 "{}/xrpc/com.atproto.repo.getRecord",
138 base_url().await
139 ))
140 .query(¶ms)
141 .send()
142 .await
143 .expect("Failed to send request");
144
145 assert_eq!(
146 res.status(),
147 StatusCode::BAD_REQUEST,
148 "Expected 400 for missing params"
149 );
150}
151
152#[tokio::test]
153async fn test_upload_blob_bad_token() {
154 let client = client();
155 let res = client
156 .post(format!(
157 "{}/xrpc/com.atproto.repo.uploadBlob",
158 base_url().await
159 ))
160 .header(header::CONTENT_TYPE, "text/plain")
161 .bearer_auth(BAD_AUTH_TOKEN)
162 .body("This is our blob data")
163 .send()
164 .await
165 .expect("Failed to send request");
166
167 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
168 let body: Value = res.json().await.expect("Response was not valid JSON");
169 assert_eq!(body["error"], "AuthenticationFailed");
170}
171
172#[tokio::test]
173async fn test_put_record_mismatched_repo() {
174 let client = client();
175 let (token, _) = create_account_and_login(&client).await;
176 let now = Utc::now().to_rfc3339();
177 let payload = json!({
178 "repo": "did:plc:OTHER-USER",
179 "collection": "app.bsky.feed.post",
180 "rkey": "e2e_test_post",
181 "record": {
182 "$type": "app.bsky.feed.post",
183 "text": "Hello from the e2e test script!",
184 "createdAt": now
185 }
186 });
187
188 let res = client
189 .post(format!(
190 "{}/xrpc/com.atproto.repo.putRecord",
191 base_url().await
192 ))
193 .bearer_auth(token)
194 .json(&payload)
195 .send()
196 .await
197 .expect("Failed to send request");
198
199 assert!(
200 res.status() == StatusCode::FORBIDDEN || res.status() == StatusCode::UNAUTHORIZED,
201 "Expected 403 or 401 for mismatched repo and auth, got {}",
202 res.status()
203 );
204}
205
206#[tokio::test]
207async fn test_put_record_invalid_schema() {
208 let client = client();
209 let (token, did) = create_account_and_login(&client).await;
210 let now = Utc::now().to_rfc3339();
211 let payload = json!({
212 "repo": did,
213 "collection": "app.bsky.feed.post",
214 "rkey": "e2e_test_invalid",
215 "record": {
216 "$type": "app.bsky.feed.post",
217 "createdAt": now
218 }
219 });
220
221 let res = client
222 .post(format!(
223 "{}/xrpc/com.atproto.repo.putRecord",
224 base_url().await
225 ))
226 .bearer_auth(token)
227 .json(&payload)
228 .send()
229 .await
230 .expect("Failed to send request");
231
232 assert_eq!(
233 res.status(),
234 StatusCode::BAD_REQUEST,
235 "Expected 400 for invalid record schema"
236 );
237}
238
239#[tokio::test]
240async fn test_upload_blob_unsupported_mime_type() {
241 let client = client();
242 let (token, _) = create_account_and_login(&client).await;
243 let res = client
244 .post(format!(
245 "{}/xrpc/com.atproto.repo.uploadBlob",
246 base_url().await
247 ))
248 .header(header::CONTENT_TYPE, "application/xml")
249 .bearer_auth(token)
250 .body("<xml>not an image</xml>")
251 .send()
252 .await
253 .expect("Failed to send request");
254
255 // Changed expectation to OK for now, bc we don't validate mime type strictly yet.
256 assert_eq!(res.status(), StatusCode::OK);
257}
258
259#[tokio::test]
260async fn test_list_records() {
261 let client = client();
262 let (_, did) = create_account_and_login(&client).await;
263 let params = [
264 ("repo", did.as_str()),
265 ("collection", "app.bsky.feed.post"),
266 ("limit", "10"),
267 ];
268 let res = client
269 .get(format!(
270 "{}/xrpc/com.atproto.repo.listRecords",
271 base_url().await
272 ))
273 .query(¶ms)
274 .send()
275 .await
276 .expect("Failed to send request");
277
278 assert_eq!(res.status(), StatusCode::OK);
279}
280
281#[tokio::test]
282async fn test_describe_repo() {
283 let client = client();
284 let (_, did) = create_account_and_login(&client).await;
285 let params = [("repo", did.as_str())];
286 let res = client
287 .get(format!(
288 "{}/xrpc/com.atproto.repo.describeRepo",
289 base_url().await
290 ))
291 .query(¶ms)
292 .send()
293 .await
294 .expect("Failed to send request");
295
296 assert_eq!(res.status(), StatusCode::OK);
297}
298
299#[tokio::test]
300async fn test_create_record_success_with_generated_rkey() {
301 let client = client();
302 let (token, did) = create_account_and_login(&client).await;
303 let payload = json!({
304 "repo": did,
305 "collection": "app.bsky.feed.post",
306 "record": {
307 "$type": "app.bsky.feed.post",
308 "text": "Hello, world!",
309 "createdAt": "2025-12-02T12:00:00Z"
310 }
311 });
312
313 let res = client
314 .post(format!(
315 "{}/xrpc/com.atproto.repo.createRecord",
316 base_url().await
317 ))
318 .json(&payload)
319 .bearer_auth(token)
320 .send()
321 .await
322 .expect("Failed to send request");
323
324 assert_eq!(res.status(), StatusCode::OK);
325 let body: Value = res.json().await.expect("Response was not valid JSON");
326 let uri = body["uri"].as_str().unwrap();
327 assert!(uri.starts_with(&format!("at://{}/app.bsky.feed.post/", did)));
328 assert!(body.get("cid").is_some());
329}
330
331#[tokio::test]
332async fn test_create_record_success_with_provided_rkey() {
333 let client = client();
334 let (token, did) = create_account_and_login(&client).await;
335 let rkey = format!("custom-rkey-{}", Utc::now().timestamp_millis());
336 let payload = json!({
337 "repo": did,
338 "collection": "app.bsky.feed.post",
339 "rkey": rkey,
340 "record": {
341 "$type": "app.bsky.feed.post",
342 "text": "Hello, world!",
343 "createdAt": "2025-12-02T12:00:00Z"
344 }
345 });
346
347 let res = client
348 .post(format!(
349 "{}/xrpc/com.atproto.repo.createRecord",
350 base_url().await
351 ))
352 .json(&payload)
353 .bearer_auth(token)
354 .send()
355 .await
356 .expect("Failed to send request");
357
358 assert_eq!(res.status(), StatusCode::OK);
359 let body: Value = res.json().await.expect("Response was not valid JSON");
360 assert_eq!(
361 body["uri"],
362 format!("at://{}/app.bsky.feed.post/{}", did, rkey)
363 );
364 assert!(body.get("cid").is_some());
365}
366
367#[tokio::test]
368async fn test_delete_record() {
369 let client = client();
370 let (token, did) = create_account_and_login(&client).await;
371 let rkey = format!("post_to_delete_{}", Utc::now().timestamp_millis());
372
373 let create_payload = json!({
374 "repo": did,
375 "collection": "app.bsky.feed.post",
376 "rkey": rkey,
377 "record": {
378 "$type": "app.bsky.feed.post",
379 "text": "This post will be deleted",
380 "createdAt": Utc::now().to_rfc3339()
381 }
382 });
383 let create_res = client
384 .post(format!(
385 "{}/xrpc/com.atproto.repo.putRecord",
386 base_url().await
387 ))
388 .bearer_auth(&token)
389 .json(&create_payload)
390 .send()
391 .await
392 .expect("Failed to create record");
393 assert_eq!(create_res.status(), StatusCode::OK);
394
395 let delete_payload = json!({
396 "repo": did,
397 "collection": "app.bsky.feed.post",
398 "rkey": rkey
399 });
400 let delete_res = client
401 .post(format!(
402 "{}/xrpc/com.atproto.repo.deleteRecord",
403 base_url().await
404 ))
405 .bearer_auth(&token)
406 .json(&delete_payload)
407 .send()
408 .await
409 .expect("Failed to send request");
410
411 assert_eq!(delete_res.status(), StatusCode::OK);
412
413 let get_res = client
414 .get(format!(
415 "{}/xrpc/com.atproto.repo.getRecord",
416 base_url().await
417 ))
418 .query(&[
419 ("repo", did.as_str()),
420 ("collection", "app.bsky.feed.post"),
421 ("rkey", rkey.as_str()),
422 ])
423 .send()
424 .await
425 .expect("Failed to verify deletion");
426 assert_eq!(get_res.status(), StatusCode::NOT_FOUND);
427}
428
429#[tokio::test]
430async fn test_apply_writes_create() {
431 let client = client();
432 let (token, did) = create_account_and_login(&client).await;
433 let now = Utc::now().to_rfc3339();
434
435 let payload = json!({
436 "repo": did,
437 "writes": [
438 {
439 "$type": "com.atproto.repo.applyWrites#create",
440 "collection": "app.bsky.feed.post",
441 "value": {
442 "$type": "app.bsky.feed.post",
443 "text": "Batch created post 1",
444 "createdAt": now
445 }
446 },
447 {
448 "$type": "com.atproto.repo.applyWrites#create",
449 "collection": "app.bsky.feed.post",
450 "value": {
451 "$type": "app.bsky.feed.post",
452 "text": "Batch created post 2",
453 "createdAt": now
454 }
455 }
456 ]
457 });
458
459 let res = client
460 .post(format!(
461 "{}/xrpc/com.atproto.repo.applyWrites",
462 base_url().await
463 ))
464 .bearer_auth(&token)
465 .json(&payload)
466 .send()
467 .await
468 .expect("Failed to send request");
469
470 assert_eq!(res.status(), StatusCode::OK);
471 let body: Value = res.json().await.expect("Response was not valid JSON");
472 assert!(body["commit"]["cid"].is_string());
473 assert!(body["results"].is_array());
474 let results = body["results"].as_array().unwrap();
475 assert_eq!(results.len(), 2);
476 assert!(results[0]["uri"].is_string());
477 assert!(results[0]["cid"].is_string());
478}
479
480#[tokio::test]
481async fn test_apply_writes_update() {
482 let client = client();
483 let (token, did) = create_account_and_login(&client).await;
484 let now = Utc::now().to_rfc3339();
485 let rkey = format!("batch_update_{}", Utc::now().timestamp_millis());
486
487 let create_payload = json!({
488 "repo": did,
489 "collection": "app.bsky.feed.post",
490 "rkey": rkey,
491 "record": {
492 "$type": "app.bsky.feed.post",
493 "text": "Original post",
494 "createdAt": now
495 }
496 });
497 let res = client
498 .post(format!(
499 "{}/xrpc/com.atproto.repo.putRecord",
500 base_url().await
501 ))
502 .bearer_auth(&token)
503 .json(&create_payload)
504 .send()
505 .await
506 .expect("Failed to create");
507 assert_eq!(res.status(), StatusCode::OK);
508
509 let update_payload = json!({
510 "repo": did,
511 "writes": [
512 {
513 "$type": "com.atproto.repo.applyWrites#update",
514 "collection": "app.bsky.feed.post",
515 "rkey": rkey,
516 "value": {
517 "$type": "app.bsky.feed.post",
518 "text": "Updated post via applyWrites",
519 "createdAt": now
520 }
521 }
522 ]
523 });
524
525 let res = client
526 .post(format!(
527 "{}/xrpc/com.atproto.repo.applyWrites",
528 base_url().await
529 ))
530 .bearer_auth(&token)
531 .json(&update_payload)
532 .send()
533 .await
534 .expect("Failed to send request");
535
536 assert_eq!(res.status(), StatusCode::OK);
537 let body: Value = res.json().await.expect("Response was not valid JSON");
538 let results = body["results"].as_array().unwrap();
539 assert_eq!(results.len(), 1);
540 assert!(results[0]["uri"].is_string());
541}
542
543#[tokio::test]
544async fn test_apply_writes_delete() {
545 let client = client();
546 let (token, did) = create_account_and_login(&client).await;
547 let now = Utc::now().to_rfc3339();
548 let rkey = format!("batch_delete_{}", Utc::now().timestamp_millis());
549
550 let create_payload = json!({
551 "repo": did,
552 "collection": "app.bsky.feed.post",
553 "rkey": rkey,
554 "record": {
555 "$type": "app.bsky.feed.post",
556 "text": "Post to delete",
557 "createdAt": now
558 }
559 });
560 let res = client
561 .post(format!(
562 "{}/xrpc/com.atproto.repo.putRecord",
563 base_url().await
564 ))
565 .bearer_auth(&token)
566 .json(&create_payload)
567 .send()
568 .await
569 .expect("Failed to create");
570 assert_eq!(res.status(), StatusCode::OK);
571
572 let delete_payload = json!({
573 "repo": did,
574 "writes": [
575 {
576 "$type": "com.atproto.repo.applyWrites#delete",
577 "collection": "app.bsky.feed.post",
578 "rkey": rkey
579 }
580 ]
581 });
582
583 let res = client
584 .post(format!(
585 "{}/xrpc/com.atproto.repo.applyWrites",
586 base_url().await
587 ))
588 .bearer_auth(&token)
589 .json(&delete_payload)
590 .send()
591 .await
592 .expect("Failed to send request");
593
594 assert_eq!(res.status(), StatusCode::OK);
595
596 let get_res = client
597 .get(format!(
598 "{}/xrpc/com.atproto.repo.getRecord",
599 base_url().await
600 ))
601 .query(&[
602 ("repo", did.as_str()),
603 ("collection", "app.bsky.feed.post"),
604 ("rkey", rkey.as_str()),
605 ])
606 .send()
607 .await
608 .expect("Failed to verify");
609 assert_eq!(get_res.status(), StatusCode::NOT_FOUND);
610}
611
612#[tokio::test]
613async fn test_apply_writes_mixed_operations() {
614 let client = client();
615 let (token, did) = create_account_and_login(&client).await;
616 let now = Utc::now().to_rfc3339();
617 let rkey_to_delete = format!("mixed_del_{}", Utc::now().timestamp_millis());
618 let rkey_to_update = format!("mixed_upd_{}", Utc::now().timestamp_millis());
619
620 let setup_payload = json!({
621 "repo": did,
622 "writes": [
623 {
624 "$type": "com.atproto.repo.applyWrites#create",
625 "collection": "app.bsky.feed.post",
626 "rkey": rkey_to_delete,
627 "value": {
628 "$type": "app.bsky.feed.post",
629 "text": "To be deleted",
630 "createdAt": now
631 }
632 },
633 {
634 "$type": "com.atproto.repo.applyWrites#create",
635 "collection": "app.bsky.feed.post",
636 "rkey": rkey_to_update,
637 "value": {
638 "$type": "app.bsky.feed.post",
639 "text": "To be updated",
640 "createdAt": now
641 }
642 }
643 ]
644 });
645 let res = client
646 .post(format!(
647 "{}/xrpc/com.atproto.repo.applyWrites",
648 base_url().await
649 ))
650 .bearer_auth(&token)
651 .json(&setup_payload)
652 .send()
653 .await
654 .expect("Failed to setup");
655 assert_eq!(res.status(), StatusCode::OK);
656
657 let mixed_payload = json!({
658 "repo": did,
659 "writes": [
660 {
661 "$type": "com.atproto.repo.applyWrites#create",
662 "collection": "app.bsky.feed.post",
663 "value": {
664 "$type": "app.bsky.feed.post",
665 "text": "New post",
666 "createdAt": now
667 }
668 },
669 {
670 "$type": "com.atproto.repo.applyWrites#update",
671 "collection": "app.bsky.feed.post",
672 "rkey": rkey_to_update,
673 "value": {
674 "$type": "app.bsky.feed.post",
675 "text": "Updated text",
676 "createdAt": now
677 }
678 },
679 {
680 "$type": "com.atproto.repo.applyWrites#delete",
681 "collection": "app.bsky.feed.post",
682 "rkey": rkey_to_delete
683 }
684 ]
685 });
686
687 let res = client
688 .post(format!(
689 "{}/xrpc/com.atproto.repo.applyWrites",
690 base_url().await
691 ))
692 .bearer_auth(&token)
693 .json(&mixed_payload)
694 .send()
695 .await
696 .expect("Failed to send request");
697
698 assert_eq!(res.status(), StatusCode::OK);
699 let body: Value = res.json().await.expect("Response was not valid JSON");
700 let results = body["results"].as_array().unwrap();
701 assert_eq!(results.len(), 3);
702}
703
704#[tokio::test]
705async fn test_apply_writes_no_auth() {
706 let client = client();
707
708 let payload = json!({
709 "repo": "did:plc:test",
710 "writes": [
711 {
712 "$type": "com.atproto.repo.applyWrites#create",
713 "collection": "app.bsky.feed.post",
714 "value": {
715 "$type": "app.bsky.feed.post",
716 "text": "Test",
717 "createdAt": "2025-01-01T00:00:00Z"
718 }
719 }
720 ]
721 });
722
723 let res = client
724 .post(format!(
725 "{}/xrpc/com.atproto.repo.applyWrites",
726 base_url().await
727 ))
728 .json(&payload)
729 .send()
730 .await
731 .expect("Failed to send request");
732
733 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
734}
735
736#[tokio::test]
737async fn test_apply_writes_empty_writes() {
738 let client = client();
739 let (token, did) = create_account_and_login(&client).await;
740
741 let payload = json!({
742 "repo": did,
743 "writes": []
744 });
745
746 let res = client
747 .post(format!(
748 "{}/xrpc/com.atproto.repo.applyWrites",
749 base_url().await
750 ))
751 .bearer_auth(&token)
752 .json(&payload)
753 .send()
754 .await
755 .expect("Failed to send request");
756
757 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
758}
759
760#[tokio::test]
761async fn test_list_missing_blobs() {
762 let client = client();
763 let (access_jwt, _) = create_account_and_login(&client).await;
764
765 let res = client
766 .get(format!(
767 "{}/xrpc/com.atproto.repo.listMissingBlobs",
768 base_url().await
769 ))
770 .bearer_auth(&access_jwt)
771 .send()
772 .await
773 .expect("Failed to send request");
774
775 assert_eq!(res.status(), StatusCode::OK);
776 let body: Value = res.json().await.expect("Response was not valid JSON");
777 assert!(body["blobs"].is_array());
778}
779
780#[tokio::test]
781async fn test_list_missing_blobs_no_auth() {
782 let client = client();
783 let res = client
784 .get(format!(
785 "{}/xrpc/com.atproto.repo.listMissingBlobs",
786 base_url().await
787 ))
788 .send()
789 .await
790 .expect("Failed to send request");
791
792 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
793}