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) = 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}