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