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) =
408 create_post(&client, &did, &jwt, "Post for sync record test").await;
409 let post_rkey = post_uri.split('/').last().unwrap();
410 let sync_record_res = client
411 .get(format!(
412 "{}/xrpc/com.atproto.sync.getRecord",
413 base_url().await
414 ))
415 .query(&[
416 ("did", did.as_str()),
417 ("collection", "app.bsky.feed.post"),
418 ("rkey", post_rkey),
419 ])
420 .send()
421 .await
422 .expect("Failed to get sync record");
423 assert_eq!(sync_record_res.status(), StatusCode::OK);
424 assert_eq!(
425 sync_record_res
426 .headers()
427 .get("content-type")
428 .and_then(|h| h.to_str().ok()),
429 Some("application/vnd.ipld.car")
430 );
431 let car_bytes = sync_record_res.bytes().await.unwrap();
432 assert!(!car_bytes.is_empty(), "CAR data should not be empty");
433 let latest_before = client
434 .get(format!(
435 "{}/xrpc/com.atproto.sync.getLatestCommit",
436 base_url().await
437 ))
438 .query(&[("did", did.as_str())])
439 .send()
440 .await
441 .expect("Failed to get latest commit");
442 let latest_before_body: Value = latest_before.json().await.unwrap();
443 let rev_before = latest_before_body["rev"].as_str().unwrap().to_string();
444 let (post2_uri, _) = create_post(&client, &did, &jwt, "Second post for sync test").await;
445 let latest_after = client
446 .get(format!(
447 "{}/xrpc/com.atproto.sync.getLatestCommit",
448 base_url().await
449 ))
450 .query(&[("did", did.as_str())])
451 .send()
452 .await
453 .expect("Failed to get latest commit after");
454 let latest_after_body: Value = latest_after.json().await.unwrap();
455 let rev_after = latest_after_body["rev"].as_str().unwrap().to_string();
456 assert_ne!(rev_before, rev_after, "Revision should change after new record");
457 let delete_payload = json!({
458 "repo": did,
459 "collection": "app.bsky.feed.post",
460 "rkey": post_rkey
461 });
462 let delete_res = client
463 .post(format!(
464 "{}/xrpc/com.atproto.repo.deleteRecord",
465 base_url().await
466 ))
467 .bearer_auth(&jwt)
468 .json(&delete_payload)
469 .send()
470 .await
471 .expect("Failed to delete record");
472 assert_eq!(delete_res.status(), StatusCode::OK);
473 let sync_deleted_res = client
474 .get(format!(
475 "{}/xrpc/com.atproto.sync.getRecord",
476 base_url().await
477 ))
478 .query(&[
479 ("did", did.as_str()),
480 ("collection", "app.bsky.feed.post"),
481 ("rkey", post_rkey),
482 ])
483 .send()
484 .await
485 .expect("Failed to check deleted record via sync");
486 assert_eq!(
487 sync_deleted_res.status(),
488 StatusCode::NOT_FOUND,
489 "Deleted record should return 404 via sync.getRecord"
490 );
491 let post2_rkey = post2_uri.split('/').last().unwrap();
492 let sync_post2_res = client
493 .get(format!(
494 "{}/xrpc/com.atproto.sync.getRecord",
495 base_url().await
496 ))
497 .query(&[
498 ("did", did.as_str()),
499 ("collection", "app.bsky.feed.post"),
500 ("rkey", post2_rkey),
501 ])
502 .send()
503 .await
504 .expect("Failed to get second post via sync");
505 assert_eq!(
506 sync_post2_res.status(),
507 StatusCode::OK,
508 "Second post should still be accessible"
509 );
510}
511
512#[tokio::test]
513async fn test_sync_repo_export_lifecycle() {
514 let client = client();
515 let (did, jwt) = setup_new_user("sync-repo-export").await;
516 let profile_payload = json!({
517 "repo": did,
518 "collection": "app.bsky.actor.profile",
519 "rkey": "self",
520 "record": {
521 "$type": "app.bsky.actor.profile",
522 "displayName": "Sync Export User"
523 }
524 });
525 let profile_res = client
526 .post(format!(
527 "{}/xrpc/com.atproto.repo.putRecord",
528 base_url().await
529 ))
530 .bearer_auth(&jwt)
531 .json(&profile_payload)
532 .send()
533 .await
534 .expect("Failed to create profile");
535 assert_eq!(profile_res.status(), StatusCode::OK);
536 for i in 0..3 {
537 tokio::time::sleep(std::time::Duration::from_millis(50)).await;
538 create_post(&client, &did, &jwt, &format!("Export test post {}", i)).await;
539 }
540 let blob_data = b"blob data for sync export test";
541 let upload_res = client
542 .post(format!(
543 "{}/xrpc/com.atproto.repo.uploadBlob",
544 base_url().await
545 ))
546 .header(header::CONTENT_TYPE, "application/octet-stream")
547 .bearer_auth(&jwt)
548 .body(blob_data.to_vec())
549 .send()
550 .await
551 .expect("Failed to upload blob");
552 assert_eq!(upload_res.status(), StatusCode::OK);
553 let blob_body: Value = upload_res.json().await.unwrap();
554 let blob_cid = blob_body["blob"]["ref"]["$link"].as_str().unwrap().to_string();
555 let repo_status_res = client
556 .get(format!(
557 "{}/xrpc/com.atproto.sync.getRepoStatus",
558 base_url().await
559 ))
560 .query(&[("did", did.as_str())])
561 .send()
562 .await
563 .expect("Failed to get repo status");
564 assert_eq!(repo_status_res.status(), StatusCode::OK);
565 let status_body: Value = repo_status_res.json().await.unwrap();
566 assert_eq!(status_body["did"], did);
567 assert_eq!(status_body["active"], true);
568 let get_repo_res = client
569 .get(format!(
570 "{}/xrpc/com.atproto.sync.getRepo",
571 base_url().await
572 ))
573 .query(&[("did", did.as_str())])
574 .send()
575 .await
576 .expect("Failed to get full repo");
577 assert_eq!(get_repo_res.status(), StatusCode::OK);
578 assert_eq!(
579 get_repo_res
580 .headers()
581 .get("content-type")
582 .and_then(|h| h.to_str().ok()),
583 Some("application/vnd.ipld.car")
584 );
585 let repo_car = get_repo_res.bytes().await.unwrap();
586 assert!(repo_car.len() > 100, "Repo CAR should have substantial data");
587 let list_blobs_res = client
588 .get(format!(
589 "{}/xrpc/com.atproto.sync.listBlobs",
590 base_url().await
591 ))
592 .query(&[("did", did.as_str())])
593 .send()
594 .await
595 .expect("Failed to list blobs");
596 assert_eq!(list_blobs_res.status(), StatusCode::OK);
597 let blobs_body: Value = list_blobs_res.json().await.unwrap();
598 let cids = blobs_body["cids"].as_array().unwrap();
599 assert!(!cids.is_empty(), "Should have at least one blob");
600 let get_blob_res = client
601 .get(format!(
602 "{}/xrpc/com.atproto.sync.getBlob",
603 base_url().await
604 ))
605 .query(&[("did", did.as_str()), ("cid", &blob_cid)])
606 .send()
607 .await
608 .expect("Failed to get blob");
609 assert_eq!(get_blob_res.status(), StatusCode::OK);
610 let retrieved_blob = get_blob_res.bytes().await.unwrap();
611 assert_eq!(
612 retrieved_blob.as_ref(),
613 blob_data,
614 "Retrieved blob should match uploaded data"
615 );
616 let latest_commit_res = client
617 .get(format!(
618 "{}/xrpc/com.atproto.sync.getLatestCommit",
619 base_url().await
620 ))
621 .query(&[("did", did.as_str())])
622 .send()
623 .await
624 .expect("Failed to get latest commit");
625 assert_eq!(latest_commit_res.status(), StatusCode::OK);
626 let commit_body: Value = latest_commit_res.json().await.unwrap();
627 let root_cid = commit_body["cid"].as_str().unwrap();
628 let get_blocks_url = format!(
629 "{}/xrpc/com.atproto.sync.getBlocks?did={}&cids={}",
630 base_url().await,
631 did,
632 root_cid
633 );
634 let get_blocks_res = client
635 .get(&get_blocks_url)
636 .send()
637 .await
638 .expect("Failed to get blocks");
639 assert_eq!(get_blocks_res.status(), StatusCode::OK);
640 assert_eq!(
641 get_blocks_res
642 .headers()
643 .get("content-type")
644 .and_then(|h| h.to_str().ok()),
645 Some("application/vnd.ipld.car")
646 );
647}