this repo has no description
1mod common;
2mod helpers;
3use common::*;
4use helpers::*;
5use reqwest::StatusCode;
6use reqwest::header;
7use serde_json::{Value, json};
8
9#[tokio::test]
10async fn test_get_latest_commit_success() {
11 let client = client();
12 let (_, did) = create_account_and_login(&client).await;
13 let params = [("did", did.as_str())];
14 let res = client
15 .get(format!(
16 "{}/xrpc/com.atproto.sync.getLatestCommit",
17 base_url().await
18 ))
19 .query(¶ms)
20 .send()
21 .await
22 .expect("Failed to send request");
23 assert_eq!(res.status(), StatusCode::OK);
24 let body: Value = res.json().await.expect("Response was not valid JSON");
25 assert!(body["cid"].is_string());
26 assert!(body["rev"].is_string());
27}
28
29#[tokio::test]
30async fn test_get_latest_commit_not_found() {
31 let client = client();
32 let params = [("did", "did:plc:nonexistent12345")];
33 let res = client
34 .get(format!(
35 "{}/xrpc/com.atproto.sync.getLatestCommit",
36 base_url().await
37 ))
38 .query(¶ms)
39 .send()
40 .await
41 .expect("Failed to send request");
42 assert_eq!(res.status(), StatusCode::NOT_FOUND);
43 let body: Value = res.json().await.expect("Response was not valid JSON");
44 assert_eq!(body["error"], "RepoNotFound");
45}
46
47#[tokio::test]
48async fn test_get_latest_commit_missing_param() {
49 let client = client();
50 let res = client
51 .get(format!(
52 "{}/xrpc/com.atproto.sync.getLatestCommit",
53 base_url().await
54 ))
55 .send()
56 .await
57 .expect("Failed to send request");
58 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
59}
60
61#[tokio::test]
62async fn test_list_repos() {
63 let client = client();
64 let _ = create_account_and_login(&client).await;
65 let res = client
66 .get(format!(
67 "{}/xrpc/com.atproto.sync.listRepos",
68 base_url().await
69 ))
70 .send()
71 .await
72 .expect("Failed to send request");
73 assert_eq!(res.status(), StatusCode::OK);
74 let body: Value = res.json().await.expect("Response was not valid JSON");
75 assert!(body["repos"].is_array());
76 let repos = body["repos"].as_array().unwrap();
77 assert!(!repos.is_empty());
78 let repo = &repos[0];
79 assert!(repo["did"].is_string());
80 assert!(repo["head"].is_string());
81 assert!(repo["active"].is_boolean());
82}
83
84#[tokio::test]
85async fn test_list_repos_with_limit() {
86 let client = client();
87 let _ = create_account_and_login(&client).await;
88 let _ = create_account_and_login(&client).await;
89 let _ = create_account_and_login(&client).await;
90 let params = [("limit", "2")];
91 let res = client
92 .get(format!(
93 "{}/xrpc/com.atproto.sync.listRepos",
94 base_url().await
95 ))
96 .query(¶ms)
97 .send()
98 .await
99 .expect("Failed to send request");
100 assert_eq!(res.status(), StatusCode::OK);
101 let body: Value = res.json().await.expect("Response was not valid JSON");
102 let repos = body["repos"].as_array().unwrap();
103 assert!(repos.len() <= 2);
104}
105
106#[tokio::test]
107async fn test_list_repos_pagination() {
108 let client = client();
109 let _ = create_account_and_login(&client).await;
110 let _ = create_account_and_login(&client).await;
111 let _ = create_account_and_login(&client).await;
112 let params = [("limit", "1")];
113 let res = client
114 .get(format!(
115 "{}/xrpc/com.atproto.sync.listRepos",
116 base_url().await
117 ))
118 .query(¶ms)
119 .send()
120 .await
121 .expect("Failed to send request");
122 assert_eq!(res.status(), StatusCode::OK);
123 let body: Value = res.json().await.expect("Response was not valid JSON");
124 let repos = body["repos"].as_array().unwrap();
125 assert_eq!(repos.len(), 1);
126 if let Some(cursor) = body["cursor"].as_str() {
127 let params = [("limit", "1"), ("cursor", cursor)];
128 let res = client
129 .get(format!(
130 "{}/xrpc/com.atproto.sync.listRepos",
131 base_url().await
132 ))
133 .query(¶ms)
134 .send()
135 .await
136 .expect("Failed to send request");
137 assert_eq!(res.status(), StatusCode::OK);
138 let body: Value = res.json().await.expect("Response was not valid JSON");
139 let repos2 = body["repos"].as_array().unwrap();
140 assert_eq!(repos2.len(), 1);
141 assert_ne!(repos[0]["did"], repos2[0]["did"]);
142 }
143}
144
145#[tokio::test]
146async fn test_get_repo_status_success() {
147 let client = client();
148 let (_, did) = create_account_and_login(&client).await;
149 let params = [("did", did.as_str())];
150 let res = client
151 .get(format!(
152 "{}/xrpc/com.atproto.sync.getRepoStatus",
153 base_url().await
154 ))
155 .query(¶ms)
156 .send()
157 .await
158 .expect("Failed to send request");
159 assert_eq!(res.status(), StatusCode::OK);
160 let body: Value = res.json().await.expect("Response was not valid JSON");
161 assert_eq!(body["did"], did);
162 assert_eq!(body["active"], true);
163 assert!(body["rev"].is_string());
164}
165
166#[tokio::test]
167async fn test_get_repo_status_not_found() {
168 let client = client();
169 let params = [("did", "did:plc:nonexistent12345")];
170 let res = client
171 .get(format!(
172 "{}/xrpc/com.atproto.sync.getRepoStatus",
173 base_url().await
174 ))
175 .query(¶ms)
176 .send()
177 .await
178 .expect("Failed to send request");
179 assert_eq!(res.status(), StatusCode::NOT_FOUND);
180 let body: Value = res.json().await.expect("Response was not valid JSON");
181 assert_eq!(body["error"], "RepoNotFound");
182}
183
184#[tokio::test]
185async fn test_notify_of_update() {
186 let client = client();
187 let params = [("hostname", "example.com")];
188 let res = client
189 .post(format!(
190 "{}/xrpc/com.atproto.sync.notifyOfUpdate",
191 base_url().await
192 ))
193 .query(¶ms)
194 .send()
195 .await
196 .expect("Failed to send request");
197 assert_eq!(res.status(), StatusCode::OK);
198}
199
200#[tokio::test]
201async fn test_request_crawl() {
202 let client = client();
203 let payload = serde_json::json!({"hostname": "example.com"});
204 let res = client
205 .post(format!(
206 "{}/xrpc/com.atproto.sync.requestCrawl",
207 base_url().await
208 ))
209 .json(&payload)
210 .send()
211 .await
212 .expect("Failed to send request");
213 assert_eq!(res.status(), StatusCode::OK);
214}
215
216#[tokio::test]
217async fn test_get_repo_success() {
218 let client = client();
219 let (access_jwt, did) = create_account_and_login(&client).await;
220 let post_payload = serde_json::json!({
221 "repo": did,
222 "collection": "app.bsky.feed.post",
223 "record": {
224 "$type": "app.bsky.feed.post",
225 "text": "Test post for getRepo",
226 "createdAt": chrono::Utc::now().to_rfc3339()
227 }
228 });
229 let _ = client
230 .post(format!(
231 "{}/xrpc/com.atproto.repo.createRecord",
232 base_url().await
233 ))
234 .bearer_auth(&access_jwt)
235 .json(&post_payload)
236 .send()
237 .await
238 .expect("Failed to create record");
239 let params = [("did", did.as_str())];
240 let res = client
241 .get(format!(
242 "{}/xrpc/com.atproto.sync.getRepo",
243 base_url().await
244 ))
245 .query(¶ms)
246 .send()
247 .await
248 .expect("Failed to send request");
249 assert_eq!(res.status(), StatusCode::OK);
250 assert_eq!(
251 res.headers()
252 .get("content-type")
253 .and_then(|h| h.to_str().ok()),
254 Some("application/vnd.ipld.car")
255 );
256 let body = res.bytes().await.expect("Failed to get body");
257 assert!(!body.is_empty());
258}
259
260#[tokio::test]
261async fn test_get_repo_not_found() {
262 let client = client();
263 let params = [("did", "did:plc:nonexistent12345")];
264 let res = client
265 .get(format!(
266 "{}/xrpc/com.atproto.sync.getRepo",
267 base_url().await
268 ))
269 .query(¶ms)
270 .send()
271 .await
272 .expect("Failed to send request");
273 assert_eq!(res.status(), StatusCode::NOT_FOUND);
274 let body: Value = res.json().await.expect("Response was not valid JSON");
275 assert_eq!(body["error"], "RepoNotFound");
276}
277
278#[tokio::test]
279async fn test_get_record_sync_success() {
280 let client = client();
281 let (access_jwt, did) = create_account_and_login(&client).await;
282 let post_payload = serde_json::json!({
283 "repo": did,
284 "collection": "app.bsky.feed.post",
285 "record": {
286 "$type": "app.bsky.feed.post",
287 "text": "Test post for sync getRecord",
288 "createdAt": chrono::Utc::now().to_rfc3339()
289 }
290 });
291 let create_res = client
292 .post(format!(
293 "{}/xrpc/com.atproto.repo.createRecord",
294 base_url().await
295 ))
296 .bearer_auth(&access_jwt)
297 .json(&post_payload)
298 .send()
299 .await
300 .expect("Failed to create record");
301 let create_body: Value = create_res.json().await.expect("Invalid JSON");
302 let uri = create_body["uri"].as_str().expect("No URI");
303 let rkey = uri.split('/').last().expect("Invalid URI");
304 let params = [
305 ("did", did.as_str()),
306 ("collection", "app.bsky.feed.post"),
307 ("rkey", rkey),
308 ];
309 let res = client
310 .get(format!(
311 "{}/xrpc/com.atproto.sync.getRecord",
312 base_url().await
313 ))
314 .query(¶ms)
315 .send()
316 .await
317 .expect("Failed to send request");
318 assert_eq!(res.status(), StatusCode::OK);
319 assert_eq!(
320 res.headers()
321 .get("content-type")
322 .and_then(|h| h.to_str().ok()),
323 Some("application/vnd.ipld.car")
324 );
325 let body = res.bytes().await.expect("Failed to get body");
326 assert!(!body.is_empty());
327}
328
329#[tokio::test]
330async fn test_get_record_sync_not_found() {
331 let client = client();
332 let (_, did) = create_account_and_login(&client).await;
333 let params = [
334 ("did", did.as_str()),
335 ("collection", "app.bsky.feed.post"),
336 ("rkey", "nonexistent12345"),
337 ];
338 let res = client
339 .get(format!(
340 "{}/xrpc/com.atproto.sync.getRecord",
341 base_url().await
342 ))
343 .query(¶ms)
344 .send()
345 .await
346 .expect("Failed to send request");
347 assert_eq!(res.status(), StatusCode::NOT_FOUND);
348 let body: Value = res.json().await.expect("Response was not valid JSON");
349 assert_eq!(body["error"], "RecordNotFound");
350}
351
352#[tokio::test]
353async fn test_get_blocks_success() {
354 let client = client();
355 let (_, did) = create_account_and_login(&client).await;
356 let params = [("did", did.as_str())];
357 let latest_res = client
358 .get(format!(
359 "{}/xrpc/com.atproto.sync.getLatestCommit",
360 base_url().await
361 ))
362 .query(¶ms)
363 .send()
364 .await
365 .expect("Failed to get latest commit");
366 let latest_body: Value = latest_res.json().await.expect("Invalid JSON");
367 let root_cid = latest_body["cid"].as_str().expect("No CID");
368 let url = format!(
369 "{}/xrpc/com.atproto.sync.getBlocks?did={}&cids={}",
370 base_url().await,
371 did,
372 root_cid
373 );
374 let res = client
375 .get(&url)
376 .send()
377 .await
378 .expect("Failed to send request");
379 assert_eq!(res.status(), StatusCode::OK);
380 assert_eq!(
381 res.headers()
382 .get("content-type")
383 .and_then(|h| h.to_str().ok()),
384 Some("application/vnd.ipld.car")
385 );
386}
387
388#[tokio::test]
389async fn test_get_blocks_not_found() {
390 let client = client();
391 let url = format!(
392 "{}/xrpc/com.atproto.sync.getBlocks?did=did:plc:nonexistent12345&cids=bafkreihdwdcefgh4dqkjv67uzcmw7ojee6xedzdetojuzjevtenxquvyku",
393 base_url().await
394 );
395 let res = client
396 .get(&url)
397 .send()
398 .await
399 .expect("Failed to send request");
400 assert_eq!(res.status(), StatusCode::NOT_FOUND);
401}
402
403#[tokio::test]
404async fn test_sync_record_lifecycle() {
405 let client = client();
406 let (did, jwt) = setup_new_user("sync-record-lifecycle").await;
407 let (post_uri, _post_cid) = create_post(&client, &did, &jwt, "Post for sync record test").await;
408 let post_rkey = post_uri.split('/').last().unwrap();
409 let sync_record_res = client
410 .get(format!(
411 "{}/xrpc/com.atproto.sync.getRecord",
412 base_url().await
413 ))
414 .query(&[
415 ("did", did.as_str()),
416 ("collection", "app.bsky.feed.post"),
417 ("rkey", post_rkey),
418 ])
419 .send()
420 .await
421 .expect("Failed to get sync record");
422 assert_eq!(sync_record_res.status(), StatusCode::OK);
423 assert_eq!(
424 sync_record_res
425 .headers()
426 .get("content-type")
427 .and_then(|h| h.to_str().ok()),
428 Some("application/vnd.ipld.car")
429 );
430 let car_bytes = sync_record_res.bytes().await.unwrap();
431 assert!(!car_bytes.is_empty(), "CAR data should not be empty");
432 let latest_before = client
433 .get(format!(
434 "{}/xrpc/com.atproto.sync.getLatestCommit",
435 base_url().await
436 ))
437 .query(&[("did", did.as_str())])
438 .send()
439 .await
440 .expect("Failed to get latest commit");
441 let latest_before_body: Value = latest_before.json().await.unwrap();
442 let rev_before = latest_before_body["rev"].as_str().unwrap().to_string();
443 let (post2_uri, _) = create_post(&client, &did, &jwt, "Second post for sync test").await;
444 let latest_after = client
445 .get(format!(
446 "{}/xrpc/com.atproto.sync.getLatestCommit",
447 base_url().await
448 ))
449 .query(&[("did", did.as_str())])
450 .send()
451 .await
452 .expect("Failed to get latest commit after");
453 let latest_after_body: Value = latest_after.json().await.unwrap();
454 let rev_after = latest_after_body["rev"].as_str().unwrap().to_string();
455 assert_ne!(
456 rev_before, rev_after,
457 "Revision should change after new record"
458 );
459 let delete_payload = json!({
460 "repo": did,
461 "collection": "app.bsky.feed.post",
462 "rkey": post_rkey
463 });
464 let delete_res = client
465 .post(format!(
466 "{}/xrpc/com.atproto.repo.deleteRecord",
467 base_url().await
468 ))
469 .bearer_auth(&jwt)
470 .json(&delete_payload)
471 .send()
472 .await
473 .expect("Failed to delete record");
474 assert_eq!(delete_res.status(), StatusCode::OK);
475 let sync_deleted_res = client
476 .get(format!(
477 "{}/xrpc/com.atproto.sync.getRecord",
478 base_url().await
479 ))
480 .query(&[
481 ("did", did.as_str()),
482 ("collection", "app.bsky.feed.post"),
483 ("rkey", post_rkey),
484 ])
485 .send()
486 .await
487 .expect("Failed to check deleted record via sync");
488 assert_eq!(
489 sync_deleted_res.status(),
490 StatusCode::NOT_FOUND,
491 "Deleted record should return 404 via sync.getRecord"
492 );
493 let post2_rkey = post2_uri.split('/').last().unwrap();
494 let sync_post2_res = client
495 .get(format!(
496 "{}/xrpc/com.atproto.sync.getRecord",
497 base_url().await
498 ))
499 .query(&[
500 ("did", did.as_str()),
501 ("collection", "app.bsky.feed.post"),
502 ("rkey", post2_rkey),
503 ])
504 .send()
505 .await
506 .expect("Failed to get second post via sync");
507 assert_eq!(
508 sync_post2_res.status(),
509 StatusCode::OK,
510 "Second post should still be accessible"
511 );
512}
513
514#[tokio::test]
515async fn test_sync_repo_export_lifecycle() {
516 let client = client();
517 let (did, jwt) = setup_new_user("sync-repo-export").await;
518 let profile_payload = json!({
519 "repo": did,
520 "collection": "app.bsky.actor.profile",
521 "rkey": "self",
522 "record": {
523 "$type": "app.bsky.actor.profile",
524 "displayName": "Sync Export User"
525 }
526 });
527 let profile_res = client
528 .post(format!(
529 "{}/xrpc/com.atproto.repo.putRecord",
530 base_url().await
531 ))
532 .bearer_auth(&jwt)
533 .json(&profile_payload)
534 .send()
535 .await
536 .expect("Failed to create profile");
537 assert_eq!(profile_res.status(), StatusCode::OK);
538 for i in 0..3 {
539 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
540 create_post(&client, &did, &jwt, &format!("Export test post {}", i)).await;
541 }
542 let blob_data = b"blob data for sync export test";
543 let upload_res = client
544 .post(format!(
545 "{}/xrpc/com.atproto.repo.uploadBlob",
546 base_url().await
547 ))
548 .header(header::CONTENT_TYPE, "application/octet-stream")
549 .bearer_auth(&jwt)
550 .body(blob_data.to_vec())
551 .send()
552 .await
553 .expect("Failed to upload blob");
554 assert_eq!(upload_res.status(), StatusCode::OK);
555 let blob_body: Value = upload_res.json().await.unwrap();
556 let blob_cid = blob_body["blob"]["ref"]["$link"]
557 .as_str()
558 .unwrap()
559 .to_string();
560 let repo_status_res = client
561 .get(format!(
562 "{}/xrpc/com.atproto.sync.getRepoStatus",
563 base_url().await
564 ))
565 .query(&[("did", did.as_str())])
566 .send()
567 .await
568 .expect("Failed to get repo status");
569 assert_eq!(repo_status_res.status(), StatusCode::OK);
570 let status_body: Value = repo_status_res.json().await.unwrap();
571 assert_eq!(status_body["did"], did);
572 assert_eq!(status_body["active"], true);
573 let get_repo_res = client
574 .get(format!(
575 "{}/xrpc/com.atproto.sync.getRepo",
576 base_url().await
577 ))
578 .query(&[("did", did.as_str())])
579 .send()
580 .await
581 .expect("Failed to get full repo");
582 assert_eq!(get_repo_res.status(), StatusCode::OK);
583 assert_eq!(
584 get_repo_res
585 .headers()
586 .get("content-type")
587 .and_then(|h| h.to_str().ok()),
588 Some("application/vnd.ipld.car")
589 );
590 let repo_car = get_repo_res.bytes().await.unwrap();
591 assert!(
592 repo_car.len() > 100,
593 "Repo CAR should have substantial data"
594 );
595 let list_blobs_res = client
596 .get(format!(
597 "{}/xrpc/com.atproto.sync.listBlobs",
598 base_url().await
599 ))
600 .query(&[("did", did.as_str())])
601 .send()
602 .await
603 .expect("Failed to list blobs");
604 assert_eq!(list_blobs_res.status(), StatusCode::OK);
605 let blobs_body: Value = list_blobs_res.json().await.unwrap();
606 let cids = blobs_body["cids"].as_array().unwrap();
607 assert!(!cids.is_empty(), "Should have at least one blob");
608 let get_blob_res = client
609 .get(format!(
610 "{}/xrpc/com.atproto.sync.getBlob",
611 base_url().await
612 ))
613 .query(&[("did", did.as_str()), ("cid", &blob_cid)])
614 .send()
615 .await
616 .expect("Failed to get blob");
617 assert_eq!(get_blob_res.status(), StatusCode::OK);
618 let retrieved_blob = get_blob_res.bytes().await.unwrap();
619 assert_eq!(
620 retrieved_blob.as_ref(),
621 blob_data,
622 "Retrieved blob should match uploaded data"
623 );
624 let latest_commit_res = client
625 .get(format!(
626 "{}/xrpc/com.atproto.sync.getLatestCommit",
627 base_url().await
628 ))
629 .query(&[("did", did.as_str())])
630 .send()
631 .await
632 .expect("Failed to get latest commit");
633 assert_eq!(latest_commit_res.status(), StatusCode::OK);
634 let commit_body: Value = latest_commit_res.json().await.unwrap();
635 let root_cid = commit_body["cid"].as_str().unwrap();
636 let get_blocks_url = format!(
637 "{}/xrpc/com.atproto.sync.getBlocks?did={}&cids={}",
638 base_url().await,
639 did,
640 root_cid
641 );
642 let get_blocks_res = client
643 .get(&get_blocks_url)
644 .send()
645 .await
646 .expect("Failed to get blocks");
647 assert_eq!(get_blocks_res.status(), StatusCode::OK);
648 assert_eq!(
649 get_blocks_res
650 .headers()
651 .get("content-type")
652 .and_then(|h| h.to_str().ok()),
653 Some("application/vnd.ipld.car")
654 );
655}