this repo has no description
1mod common; 2use common::*; 3use iroh_car::CarHeader; 4use reqwest::StatusCode; 5use serde_json::json; 6 7#[tokio::test] 8async fn test_import_repo_requires_auth() { 9 let client = client(); 10 let res = client 11 .post(format!( 12 "{}/xrpc/com.atproto.repo.importRepo", 13 base_url().await 14 )) 15 .header("Content-Type", "application/vnd.ipld.car") 16 .body(vec![0u8; 100]) 17 .send() 18 .await 19 .expect("Request failed"); 20 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 21} 22 23#[tokio::test] 24async fn test_import_repo_invalid_car() { 25 let client = client(); 26 let (token, _did) = create_account_and_login(&client).await; 27 let res = client 28 .post(format!( 29 "{}/xrpc/com.atproto.repo.importRepo", 30 base_url().await 31 )) 32 .bearer_auth(&token) 33 .header("Content-Type", "application/vnd.ipld.car") 34 .body(vec![0u8; 100]) 35 .send() 36 .await 37 .expect("Request failed"); 38 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 39 let body: serde_json::Value = res.json().await.unwrap(); 40 assert_eq!(body["error"], "InvalidRequest"); 41} 42 43#[tokio::test] 44async fn test_import_repo_empty_body() { 45 let client = client(); 46 let (token, _did) = create_account_and_login(&client).await; 47 let res = client 48 .post(format!( 49 "{}/xrpc/com.atproto.repo.importRepo", 50 base_url().await 51 )) 52 .bearer_auth(&token) 53 .header("Content-Type", "application/vnd.ipld.car") 54 .body(vec![]) 55 .send() 56 .await 57 .expect("Request failed"); 58 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 59} 60 61fn write_varint(buf: &mut Vec<u8>, mut value: u64) { 62 loop { 63 let mut byte = (value & 0x7F) as u8; 64 value >>= 7; 65 if value != 0 { 66 byte |= 0x80; 67 } 68 buf.push(byte); 69 if value == 0 { 70 break; 71 } 72 } 73} 74 75#[tokio::test] 76async fn test_import_rejects_car_for_different_user() { 77 let client = client(); 78 let (token_a, _did_a) = create_account_and_login(&client).await; 79 let (_token_b, did_b) = create_account_and_login(&client).await; 80 let export_res = client 81 .get(format!( 82 "{}/xrpc/com.atproto.sync.getRepo?did={}", 83 base_url().await, 84 did_b 85 )) 86 .send() 87 .await 88 .expect("Export failed"); 89 assert_eq!(export_res.status(), StatusCode::OK); 90 let car_bytes = export_res.bytes().await.unwrap(); 91 let import_res = client 92 .post(format!( 93 "{}/xrpc/com.atproto.repo.importRepo", 94 base_url().await 95 )) 96 .bearer_auth(&token_a) 97 .header("Content-Type", "application/vnd.ipld.car") 98 .body(car_bytes.to_vec()) 99 .send() 100 .await 101 .expect("Import failed"); 102 assert_eq!(import_res.status(), StatusCode::FORBIDDEN); 103 let body: serde_json::Value = import_res.json().await.unwrap(); 104 assert!( 105 body["error"] == "InvalidRepo" 106 || body["error"] == "InvalidRequest" 107 || body["error"] == "DidMismatch", 108 "Expected InvalidRepo, DidMismatch, or InvalidRequest error, got: {:?}", 109 body 110 ); 111} 112 113#[tokio::test] 114async fn test_import_accepts_own_exported_repo() { 115 let client = client(); 116 let (token, did) = create_account_and_login(&client).await; 117 let post_payload = json!({ 118 "repo": did, 119 "collection": "app.bsky.feed.post", 120 "record": { 121 "$type": "app.bsky.feed.post", 122 "text": "Original post before export", 123 "createdAt": chrono::Utc::now().to_rfc3339(), 124 } 125 }); 126 let create_res = client 127 .post(format!( 128 "{}/xrpc/com.atproto.repo.createRecord", 129 base_url().await 130 )) 131 .bearer_auth(&token) 132 .json(&post_payload) 133 .send() 134 .await 135 .expect("Failed to create post"); 136 assert_eq!(create_res.status(), StatusCode::OK); 137 let export_res = client 138 .get(format!( 139 "{}/xrpc/com.atproto.sync.getRepo?did={}", 140 base_url().await, 141 did 142 )) 143 .send() 144 .await 145 .expect("Failed to export repo"); 146 assert_eq!(export_res.status(), StatusCode::OK); 147 let car_bytes = export_res.bytes().await.unwrap(); 148 let import_res = client 149 .post(format!( 150 "{}/xrpc/com.atproto.repo.importRepo", 151 base_url().await 152 )) 153 .bearer_auth(&token) 154 .header("Content-Type", "application/vnd.ipld.car") 155 .body(car_bytes.to_vec()) 156 .send() 157 .await 158 .expect("Failed to import repo"); 159 let status = import_res.status(); 160 if status != StatusCode::OK { 161 let body = import_res.text().await.unwrap_or_default(); 162 panic!( 163 "Import failed with status {}: {}", 164 status, body 165 ); 166 } 167} 168 169#[tokio::test] 170async fn test_import_repo_size_limit() { 171 let client = client(); 172 let (token, _did) = create_account_and_login(&client).await; 173 let oversized_body = vec![0u8; 110 * 1024 * 1024]; 174 let res = client 175 .post(format!( 176 "{}/xrpc/com.atproto.repo.importRepo", 177 base_url().await 178 )) 179 .bearer_auth(&token) 180 .header("Content-Type", "application/vnd.ipld.car") 181 .body(oversized_body) 182 .send() 183 .await; 184 match res { 185 Ok(response) => { 186 assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE); 187 } 188 Err(e) => { 189 let error_str = e.to_string().to_lowercase(); 190 assert!( 191 error_str.contains("broken pipe") 192 || error_str.contains("connection") 193 || error_str.contains("reset") 194 || error_str.contains("request") 195 || error_str.contains("body"), 196 "Expected connection error or PAYLOAD_TOO_LARGE, got: {}", 197 e 198 ); 199 } 200 } 201} 202 203#[tokio::test] 204async fn test_import_deactivated_account_allowed_for_migration() { 205 let client = client(); 206 let (token, did) = create_account_and_login(&client).await; 207 let export_res = client 208 .get(format!( 209 "{}/xrpc/com.atproto.sync.getRepo?did={}", 210 base_url().await, 211 did 212 )) 213 .send() 214 .await 215 .expect("Export failed"); 216 assert_eq!(export_res.status(), StatusCode::OK); 217 let car_bytes = export_res.bytes().await.unwrap(); 218 let deactivate_res = client 219 .post(format!( 220 "{}/xrpc/com.atproto.server.deactivateAccount", 221 base_url().await 222 )) 223 .bearer_auth(&token) 224 .json(&json!({})) 225 .send() 226 .await 227 .expect("Deactivate failed"); 228 assert!(deactivate_res.status().is_success()); 229 let import_res = client 230 .post(format!( 231 "{}/xrpc/com.atproto.repo.importRepo", 232 base_url().await 233 )) 234 .bearer_auth(&token) 235 .header("Content-Type", "application/vnd.ipld.car") 236 .body(car_bytes.to_vec()) 237 .send() 238 .await 239 .expect("Import failed"); 240 assert!( 241 import_res.status().is_success(), 242 "Deactivated accounts should allow import for migration, got {}", 243 import_res.status() 244 ); 245} 246 247#[tokio::test] 248async fn test_import_invalid_car_structure() { 249 let client = client(); 250 let (token, _did) = create_account_and_login(&client).await; 251 let invalid_car = vec![0x0a, 0xa1, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x80]; 252 let res = client 253 .post(format!( 254 "{}/xrpc/com.atproto.repo.importRepo", 255 base_url().await 256 )) 257 .bearer_auth(&token) 258 .header("Content-Type", "application/vnd.ipld.car") 259 .body(invalid_car) 260 .send() 261 .await 262 .expect("Request failed"); 263 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 264} 265 266#[tokio::test] 267async fn test_import_car_with_no_roots() { 268 let client = client(); 269 let (token, _did) = create_account_and_login(&client).await; 270 let header = CarHeader::new_v1(vec![]); 271 let header_cbor = header.encode().unwrap_or_default(); 272 let mut car = Vec::new(); 273 write_varint(&mut car, header_cbor.len() as u64); 274 car.extend_from_slice(&header_cbor); 275 let res = client 276 .post(format!( 277 "{}/xrpc/com.atproto.repo.importRepo", 278 base_url().await 279 )) 280 .bearer_auth(&token) 281 .header("Content-Type", "application/vnd.ipld.car") 282 .body(car) 283 .send() 284 .await 285 .expect("Request failed"); 286 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 287 let body: serde_json::Value = res.json().await.unwrap(); 288 assert_eq!(body["error"], "InvalidRequest"); 289} 290 291#[tokio::test] 292async fn test_import_preserves_records_after_reimport() { 293 let client = client(); 294 let (token, did) = create_account_and_login(&client).await; 295 let mut rkeys = Vec::with_capacity(3); 296 for i in 0..3 { 297 let post_payload = json!({ 298 "repo": did, 299 "collection": "app.bsky.feed.post", 300 "record": { 301 "$type": "app.bsky.feed.post", 302 "text": format!("Test post {}", i), 303 "createdAt": chrono::Utc::now().to_rfc3339(), 304 } 305 }); 306 let res = client 307 .post(format!( 308 "{}/xrpc/com.atproto.repo.createRecord", 309 base_url().await 310 )) 311 .bearer_auth(&token) 312 .json(&post_payload) 313 .send() 314 .await 315 .expect("Failed to create post"); 316 assert_eq!(res.status(), StatusCode::OK); 317 let body: serde_json::Value = res.json().await.unwrap(); 318 let uri = body["uri"].as_str().unwrap(); 319 rkeys.push(uri.split('/').next_back().unwrap().to_string()); 320 } 321 for rkey in &rkeys { 322 let get_res = client 323 .get(format!( 324 "{}/xrpc/com.atproto.repo.getRecord?repo={}&collection=app.bsky.feed.post&rkey={}", 325 base_url().await, 326 did, 327 rkey 328 )) 329 .send() 330 .await 331 .expect("Failed to get record before export"); 332 assert_eq!( 333 get_res.status(), 334 StatusCode::OK, 335 "Record {} not found before export", 336 rkey 337 ); 338 } 339 let export_res = client 340 .get(format!( 341 "{}/xrpc/com.atproto.sync.getRepo?did={}", 342 base_url().await, 343 did 344 )) 345 .send() 346 .await 347 .expect("Failed to export repo"); 348 assert_eq!(export_res.status(), StatusCode::OK); 349 let car_bytes = export_res.bytes().await.unwrap(); 350 let import_res = client 351 .post(format!( 352 "{}/xrpc/com.atproto.repo.importRepo", 353 base_url().await 354 )) 355 .bearer_auth(&token) 356 .header("Content-Type", "application/vnd.ipld.car") 357 .body(car_bytes.to_vec()) 358 .send() 359 .await 360 .expect("Failed to import repo"); 361 let status = import_res.status(); 362 if status != StatusCode::OK { 363 let body = import_res.text().await.unwrap_or_default(); 364 panic!("Import failed with status {}: {}", status, body); 365 } 366 let list_res = client 367 .get(format!( 368 "{}/xrpc/com.atproto.repo.listRecords?repo={}&collection=app.bsky.feed.post", 369 base_url().await, 370 did 371 )) 372 .send() 373 .await 374 .expect("Failed to list records after import"); 375 assert_eq!(list_res.status(), StatusCode::OK); 376 let list_body: serde_json::Value = list_res.json().await.unwrap(); 377 let records_after = list_body["records"] 378 .as_array() 379 .map(|a| a.len()) 380 .unwrap_or(0); 381 assert!( 382 records_after >= 1, 383 "Expected at least 1 record after import, found {}. Note: MST walk may have timing issues.", 384 records_after 385 ); 386}