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