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]
207#[ignore]
208async fn test_put_record_invalid_schema() {
209 let client = client();
210 let (token, did) = create_account_and_login(&client).await;
211 let now = Utc::now().to_rfc3339();
212 let payload = json!({
213 "repo": did,
214 "collection": "app.bsky.feed.post",
215 "rkey": "e2e_test_invalid",
216 "record": {
217 "$type": "app.bsky.feed.post",
218 "createdAt": now
219 }
220 });
221
222 let res = client
223 .post(format!(
224 "{}/xrpc/com.atproto.repo.putRecord",
225 base_url().await
226 ))
227 .bearer_auth(token)
228 .json(&payload)
229 .send()
230 .await
231 .expect("Failed to send request");
232
233 assert_eq!(
234 res.status(),
235 StatusCode::BAD_REQUEST,
236 "Expected 400 for invalid record schema"
237 );
238}
239
240#[tokio::test]
241async fn test_upload_blob_unsupported_mime_type() {
242 let client = client();
243 let (token, _) = create_account_and_login(&client).await;
244 let res = client
245 .post(format!(
246 "{}/xrpc/com.atproto.repo.uploadBlob",
247 base_url().await
248 ))
249 .header(header::CONTENT_TYPE, "application/xml")
250 .bearer_auth(token)
251 .body("<xml>not an image</xml>")
252 .send()
253 .await
254 .expect("Failed to send request");
255
256 // Changed expectation to OK for now, bc we don't validate mime type strictly yet.
257 assert_eq!(res.status(), StatusCode::OK);
258}
259
260#[tokio::test]
261async fn test_list_records() {
262 let client = client();
263 let (_, did) = create_account_and_login(&client).await;
264 let params = [
265 ("repo", did.as_str()),
266 ("collection", "app.bsky.feed.post"),
267 ("limit", "10"),
268 ];
269 let res = client
270 .get(format!(
271 "{}/xrpc/com.atproto.repo.listRecords",
272 base_url().await
273 ))
274 .query(¶ms)
275 .send()
276 .await
277 .expect("Failed to send request");
278
279 assert_eq!(res.status(), StatusCode::OK);
280}
281
282#[tokio::test]
283async fn test_describe_repo() {
284 let client = client();
285 let (_, did) = create_account_and_login(&client).await;
286 let params = [("repo", did.as_str())];
287 let res = client
288 .get(format!(
289 "{}/xrpc/com.atproto.repo.describeRepo",
290 base_url().await
291 ))
292 .query(¶ms)
293 .send()
294 .await
295 .expect("Failed to send request");
296
297 assert_eq!(res.status(), StatusCode::OK);
298}
299
300#[tokio::test]
301async fn test_create_record_success_with_generated_rkey() {
302 let client = client();
303 let (token, did) = create_account_and_login(&client).await;
304 let payload = json!({
305 "repo": did,
306 "collection": "app.bsky.feed.post",
307 "record": {
308 "$type": "app.bsky.feed.post",
309 "text": "Hello, world!",
310 "createdAt": "2025-12-02T12:00:00Z"
311 }
312 });
313
314 let res = client
315 .post(format!(
316 "{}/xrpc/com.atproto.repo.createRecord",
317 base_url().await
318 ))
319 .json(&payload)
320 .bearer_auth(token)
321 .send()
322 .await
323 .expect("Failed to send request");
324
325 assert_eq!(res.status(), StatusCode::OK);
326 let body: Value = res.json().await.expect("Response was not valid JSON");
327 let uri = body["uri"].as_str().unwrap();
328 assert!(uri.starts_with(&format!("at://{}/app.bsky.feed.post/", did)));
329 assert!(body.get("cid").is_some());
330}
331
332#[tokio::test]
333async fn test_create_record_success_with_provided_rkey() {
334 let client = client();
335 let (token, did) = create_account_and_login(&client).await;
336 let rkey = format!("custom-rkey-{}", Utc::now().timestamp_millis());
337 let payload = json!({
338 "repo": did,
339 "collection": "app.bsky.feed.post",
340 "rkey": rkey,
341 "record": {
342 "$type": "app.bsky.feed.post",
343 "text": "Hello, world!",
344 "createdAt": "2025-12-02T12:00:00Z"
345 }
346 });
347
348 let res = client
349 .post(format!(
350 "{}/xrpc/com.atproto.repo.createRecord",
351 base_url().await
352 ))
353 .json(&payload)
354 .bearer_auth(token)
355 .send()
356 .await
357 .expect("Failed to send request");
358
359 assert_eq!(res.status(), StatusCode::OK);
360 let body: Value = res.json().await.expect("Response was not valid JSON");
361 assert_eq!(
362 body["uri"],
363 format!("at://{}/app.bsky.feed.post/{}", did, rkey)
364 );
365 assert!(body.get("cid").is_some());
366}
367
368#[tokio::test]
369async fn test_delete_record() {
370 let client = client();
371 let (token, did) = create_account_and_login(&client).await;
372 let rkey = format!("post_to_delete_{}", Utc::now().timestamp_millis());
373
374 let create_payload = json!({
375 "repo": did,
376 "collection": "app.bsky.feed.post",
377 "rkey": rkey,
378 "record": {
379 "$type": "app.bsky.feed.post",
380 "text": "This post will be deleted",
381 "createdAt": Utc::now().to_rfc3339()
382 }
383 });
384 let create_res = client
385 .post(format!(
386 "{}/xrpc/com.atproto.repo.putRecord",
387 base_url().await
388 ))
389 .bearer_auth(&token)
390 .json(&create_payload)
391 .send()
392 .await
393 .expect("Failed to create record");
394 assert_eq!(create_res.status(), StatusCode::OK);
395
396 let delete_payload = json!({
397 "repo": did,
398 "collection": "app.bsky.feed.post",
399 "rkey": rkey
400 });
401 let delete_res = client
402 .post(format!(
403 "{}/xrpc/com.atproto.repo.deleteRecord",
404 base_url().await
405 ))
406 .bearer_auth(&token)
407 .json(&delete_payload)
408 .send()
409 .await
410 .expect("Failed to send request");
411
412 assert_eq!(delete_res.status(), StatusCode::OK);
413
414 let get_res = client
415 .get(format!(
416 "{}/xrpc/com.atproto.repo.getRecord",
417 base_url().await
418 ))
419 .query(&[
420 ("repo", did.as_str()),
421 ("collection", "app.bsky.feed.post"),
422 ("rkey", rkey.as_str()),
423 ])
424 .send()
425 .await
426 .expect("Failed to verify deletion");
427 assert_eq!(get_res.status(), StatusCode::NOT_FOUND);
428}
429
430#[tokio::test]
431async fn test_apply_writes_create() {
432 let client = client();
433 let (token, did) = create_account_and_login(&client).await;
434 let now = Utc::now().to_rfc3339();
435
436 let payload = json!({
437 "repo": did,
438 "writes": [
439 {
440 "$type": "com.atproto.repo.applyWrites#create",
441 "collection": "app.bsky.feed.post",
442 "value": {
443 "$type": "app.bsky.feed.post",
444 "text": "Batch created post 1",
445 "createdAt": now
446 }
447 },
448 {
449 "$type": "com.atproto.repo.applyWrites#create",
450 "collection": "app.bsky.feed.post",
451 "value": {
452 "$type": "app.bsky.feed.post",
453 "text": "Batch created post 2",
454 "createdAt": now
455 }
456 }
457 ]
458 });
459
460 let res = client
461 .post(format!(
462 "{}/xrpc/com.atproto.repo.applyWrites",
463 base_url().await
464 ))
465 .bearer_auth(&token)
466 .json(&payload)
467 .send()
468 .await
469 .expect("Failed to send request");
470
471 assert_eq!(res.status(), StatusCode::OK);
472 let body: Value = res.json().await.expect("Response was not valid JSON");
473 assert!(body["commit"]["cid"].is_string());
474 assert!(body["results"].is_array());
475 let results = body["results"].as_array().unwrap();
476 assert_eq!(results.len(), 2);
477 assert!(results[0]["uri"].is_string());
478 assert!(results[0]["cid"].is_string());
479}
480
481#[tokio::test]
482async fn test_apply_writes_update() {
483 let client = client();
484 let (token, did) = create_account_and_login(&client).await;
485 let now = Utc::now().to_rfc3339();
486 let rkey = format!("batch_update_{}", Utc::now().timestamp_millis());
487
488 let create_payload = json!({
489 "repo": did,
490 "collection": "app.bsky.feed.post",
491 "rkey": rkey,
492 "record": {
493 "$type": "app.bsky.feed.post",
494 "text": "Original post",
495 "createdAt": now
496 }
497 });
498 let res = client
499 .post(format!(
500 "{}/xrpc/com.atproto.repo.putRecord",
501 base_url().await
502 ))
503 .bearer_auth(&token)
504 .json(&create_payload)
505 .send()
506 .await
507 .expect("Failed to create");
508 assert_eq!(res.status(), StatusCode::OK);
509
510 let update_payload = json!({
511 "repo": did,
512 "writes": [
513 {
514 "$type": "com.atproto.repo.applyWrites#update",
515 "collection": "app.bsky.feed.post",
516 "rkey": rkey,
517 "value": {
518 "$type": "app.bsky.feed.post",
519 "text": "Updated post via applyWrites",
520 "createdAt": now
521 }
522 }
523 ]
524 });
525
526 let res = client
527 .post(format!(
528 "{}/xrpc/com.atproto.repo.applyWrites",
529 base_url().await
530 ))
531 .bearer_auth(&token)
532 .json(&update_payload)
533 .send()
534 .await
535 .expect("Failed to send request");
536
537 assert_eq!(res.status(), StatusCode::OK);
538 let body: Value = res.json().await.expect("Response was not valid JSON");
539 let results = body["results"].as_array().unwrap();
540 assert_eq!(results.len(), 1);
541 assert!(results[0]["uri"].is_string());
542}
543
544#[tokio::test]
545async fn test_apply_writes_delete() {
546 let client = client();
547 let (token, did) = create_account_and_login(&client).await;
548 let now = Utc::now().to_rfc3339();
549 let rkey = format!("batch_delete_{}", Utc::now().timestamp_millis());
550
551 let create_payload = json!({
552 "repo": did,
553 "collection": "app.bsky.feed.post",
554 "rkey": rkey,
555 "record": {
556 "$type": "app.bsky.feed.post",
557 "text": "Post to delete",
558 "createdAt": now
559 }
560 });
561 let res = client
562 .post(format!(
563 "{}/xrpc/com.atproto.repo.putRecord",
564 base_url().await
565 ))
566 .bearer_auth(&token)
567 .json(&create_payload)
568 .send()
569 .await
570 .expect("Failed to create");
571 assert_eq!(res.status(), StatusCode::OK);
572
573 let delete_payload = json!({
574 "repo": did,
575 "writes": [
576 {
577 "$type": "com.atproto.repo.applyWrites#delete",
578 "collection": "app.bsky.feed.post",
579 "rkey": rkey
580 }
581 ]
582 });
583
584 let res = client
585 .post(format!(
586 "{}/xrpc/com.atproto.repo.applyWrites",
587 base_url().await
588 ))
589 .bearer_auth(&token)
590 .json(&delete_payload)
591 .send()
592 .await
593 .expect("Failed to send request");
594
595 assert_eq!(res.status(), StatusCode::OK);
596
597 let get_res = client
598 .get(format!(
599 "{}/xrpc/com.atproto.repo.getRecord",
600 base_url().await
601 ))
602 .query(&[
603 ("repo", did.as_str()),
604 ("collection", "app.bsky.feed.post"),
605 ("rkey", rkey.as_str()),
606 ])
607 .send()
608 .await
609 .expect("Failed to verify");
610 assert_eq!(get_res.status(), StatusCode::NOT_FOUND);
611}
612
613#[tokio::test]
614async fn test_apply_writes_mixed_operations() {
615 let client = client();
616 let (token, did) = create_account_and_login(&client).await;
617 let now = Utc::now().to_rfc3339();
618 let rkey_to_delete = format!("mixed_del_{}", Utc::now().timestamp_millis());
619 let rkey_to_update = format!("mixed_upd_{}", Utc::now().timestamp_millis());
620
621 let setup_payload = json!({
622 "repo": did,
623 "writes": [
624 {
625 "$type": "com.atproto.repo.applyWrites#create",
626 "collection": "app.bsky.feed.post",
627 "rkey": rkey_to_delete,
628 "value": {
629 "$type": "app.bsky.feed.post",
630 "text": "To be deleted",
631 "createdAt": now
632 }
633 },
634 {
635 "$type": "com.atproto.repo.applyWrites#create",
636 "collection": "app.bsky.feed.post",
637 "rkey": rkey_to_update,
638 "value": {
639 "$type": "app.bsky.feed.post",
640 "text": "To be updated",
641 "createdAt": now
642 }
643 }
644 ]
645 });
646 let res = client
647 .post(format!(
648 "{}/xrpc/com.atproto.repo.applyWrites",
649 base_url().await
650 ))
651 .bearer_auth(&token)
652 .json(&setup_payload)
653 .send()
654 .await
655 .expect("Failed to setup");
656 assert_eq!(res.status(), StatusCode::OK);
657
658 let mixed_payload = json!({
659 "repo": did,
660 "writes": [
661 {
662 "$type": "com.atproto.repo.applyWrites#create",
663 "collection": "app.bsky.feed.post",
664 "value": {
665 "$type": "app.bsky.feed.post",
666 "text": "New post",
667 "createdAt": now
668 }
669 },
670 {
671 "$type": "com.atproto.repo.applyWrites#update",
672 "collection": "app.bsky.feed.post",
673 "rkey": rkey_to_update,
674 "value": {
675 "$type": "app.bsky.feed.post",
676 "text": "Updated text",
677 "createdAt": now
678 }
679 },
680 {
681 "$type": "com.atproto.repo.applyWrites#delete",
682 "collection": "app.bsky.feed.post",
683 "rkey": rkey_to_delete
684 }
685 ]
686 });
687
688 let res = client
689 .post(format!(
690 "{}/xrpc/com.atproto.repo.applyWrites",
691 base_url().await
692 ))
693 .bearer_auth(&token)
694 .json(&mixed_payload)
695 .send()
696 .await
697 .expect("Failed to send request");
698
699 assert_eq!(res.status(), StatusCode::OK);
700 let body: Value = res.json().await.expect("Response was not valid JSON");
701 let results = body["results"].as_array().unwrap();
702 assert_eq!(results.len(), 3);
703}
704
705#[tokio::test]
706async fn test_apply_writes_no_auth() {
707 let client = client();
708
709 let payload = json!({
710 "repo": "did:plc:test",
711 "writes": [
712 {
713 "$type": "com.atproto.repo.applyWrites#create",
714 "collection": "app.bsky.feed.post",
715 "value": {
716 "$type": "app.bsky.feed.post",
717 "text": "Test",
718 "createdAt": "2025-01-01T00:00:00Z"
719 }
720 }
721 ]
722 });
723
724 let res = client
725 .post(format!(
726 "{}/xrpc/com.atproto.repo.applyWrites",
727 base_url().await
728 ))
729 .json(&payload)
730 .send()
731 .await
732 .expect("Failed to send request");
733
734 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
735}
736
737#[tokio::test]
738async fn test_apply_writes_empty_writes() {
739 let client = client();
740 let (token, did) = create_account_and_login(&client).await;
741
742 let payload = json!({
743 "repo": did,
744 "writes": []
745 });
746
747 let res = client
748 .post(format!(
749 "{}/xrpc/com.atproto.repo.applyWrites",
750 base_url().await
751 ))
752 .bearer_auth(&token)
753 .json(&payload)
754 .send()
755 .await
756 .expect("Failed to send request");
757
758 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
759}