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