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