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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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(&params) 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}