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"] == "InvalidRequest" || body["error"] == "DidMismatch", 106 "Expected DidMismatch or InvalidRequest error, got: {:?}", 107 body 108 ); 109} 110 111#[tokio::test] 112async fn test_import_accepts_own_exported_repo() { 113 let client = client(); 114 let (token, did) = create_account_and_login(&client).await; 115 let post_payload = json!({ 116 "repo": did, 117 "collection": "app.bsky.feed.post", 118 "record": { 119 "$type": "app.bsky.feed.post", 120 "text": "Original post before export", 121 "createdAt": chrono::Utc::now().to_rfc3339(), 122 } 123 }); 124 let create_res = client 125 .post(format!( 126 "{}/xrpc/com.atproto.repo.createRecord", 127 base_url().await 128 )) 129 .bearer_auth(&token) 130 .json(&post_payload) 131 .send() 132 .await 133 .expect("Failed to create post"); 134 assert_eq!(create_res.status(), StatusCode::OK); 135 let export_res = client 136 .get(format!( 137 "{}/xrpc/com.atproto.sync.getRepo?did={}", 138 base_url().await, 139 did 140 )) 141 .send() 142 .await 143 .expect("Failed to export repo"); 144 assert_eq!(export_res.status(), StatusCode::OK); 145 let car_bytes = export_res.bytes().await.unwrap(); 146 let import_res = client 147 .post(format!( 148 "{}/xrpc/com.atproto.repo.importRepo", 149 base_url().await 150 )) 151 .bearer_auth(&token) 152 .header("Content-Type", "application/vnd.ipld.car") 153 .body(car_bytes.to_vec()) 154 .send() 155 .await 156 .expect("Failed to import repo"); 157 assert_eq!(import_res.status(), StatusCode::OK); 158} 159 160#[tokio::test] 161async fn test_import_repo_size_limit() { 162 let client = client(); 163 let (token, _did) = create_account_and_login(&client).await; 164 let oversized_body = vec![0u8; 110 * 1024 * 1024]; 165 let res = client 166 .post(format!( 167 "{}/xrpc/com.atproto.repo.importRepo", 168 base_url().await 169 )) 170 .bearer_auth(&token) 171 .header("Content-Type", "application/vnd.ipld.car") 172 .body(oversized_body) 173 .send() 174 .await; 175 match res { 176 Ok(response) => { 177 assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE); 178 } 179 Err(e) => { 180 let error_str = e.to_string().to_lowercase(); 181 assert!( 182 error_str.contains("broken pipe") 183 || error_str.contains("connection") 184 || error_str.contains("reset") 185 || error_str.contains("request") 186 || error_str.contains("body"), 187 "Expected connection error or PAYLOAD_TOO_LARGE, got: {}", 188 e 189 ); 190 } 191 } 192} 193 194#[tokio::test] 195async fn test_import_deactivated_account_allowed_for_migration() { 196 let client = client(); 197 let (token, did) = create_account_and_login(&client).await; 198 let export_res = client 199 .get(format!( 200 "{}/xrpc/com.atproto.sync.getRepo?did={}", 201 base_url().await, 202 did 203 )) 204 .send() 205 .await 206 .expect("Export failed"); 207 assert_eq!(export_res.status(), StatusCode::OK); 208 let car_bytes = export_res.bytes().await.unwrap(); 209 let deactivate_res = client 210 .post(format!( 211 "{}/xrpc/com.atproto.server.deactivateAccount", 212 base_url().await 213 )) 214 .bearer_auth(&token) 215 .json(&json!({})) 216 .send() 217 .await 218 .expect("Deactivate failed"); 219 assert!(deactivate_res.status().is_success()); 220 let import_res = client 221 .post(format!( 222 "{}/xrpc/com.atproto.repo.importRepo", 223 base_url().await 224 )) 225 .bearer_auth(&token) 226 .header("Content-Type", "application/vnd.ipld.car") 227 .body(car_bytes.to_vec()) 228 .send() 229 .await 230 .expect("Import failed"); 231 assert!( 232 import_res.status().is_success(), 233 "Deactivated accounts should allow import for migration, got {}", 234 import_res.status() 235 ); 236} 237 238#[tokio::test] 239async fn test_import_invalid_car_structure() { 240 let client = client(); 241 let (token, _did) = create_account_and_login(&client).await; 242 let invalid_car = vec![0x0a, 0xa1, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x80]; 243 let res = client 244 .post(format!( 245 "{}/xrpc/com.atproto.repo.importRepo", 246 base_url().await 247 )) 248 .bearer_auth(&token) 249 .header("Content-Type", "application/vnd.ipld.car") 250 .body(invalid_car) 251 .send() 252 .await 253 .expect("Request failed"); 254 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 255} 256 257#[tokio::test] 258async fn test_import_car_with_no_roots() { 259 let client = client(); 260 let (token, _did) = create_account_and_login(&client).await; 261 let header = CarHeader::new_v1(vec![]); 262 let header_cbor = header.encode().unwrap_or_default(); 263 let mut car = Vec::new(); 264 write_varint(&mut car, header_cbor.len() as u64); 265 car.extend_from_slice(&header_cbor); 266 let res = client 267 .post(format!( 268 "{}/xrpc/com.atproto.repo.importRepo", 269 base_url().await 270 )) 271 .bearer_auth(&token) 272 .header("Content-Type", "application/vnd.ipld.car") 273 .body(car) 274 .send() 275 .await 276 .expect("Request failed"); 277 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 278 let body: serde_json::Value = res.json().await.unwrap(); 279 assert_eq!(body["error"], "InvalidRequest"); 280} 281 282#[tokio::test] 283async fn test_import_preserves_records_after_reimport() { 284 let client = client(); 285 let (token, did) = create_account_and_login(&client).await; 286 let mut rkeys = Vec::new(); 287 for i in 0..3 { 288 let post_payload = json!({ 289 "repo": did, 290 "collection": "app.bsky.feed.post", 291 "record": { 292 "$type": "app.bsky.feed.post", 293 "text": format!("Test post {}", i), 294 "createdAt": chrono::Utc::now().to_rfc3339(), 295 } 296 }); 297 let res = client 298 .post(format!( 299 "{}/xrpc/com.atproto.repo.createRecord", 300 base_url().await 301 )) 302 .bearer_auth(&token) 303 .json(&post_payload) 304 .send() 305 .await 306 .expect("Failed to create post"); 307 assert_eq!(res.status(), StatusCode::OK); 308 let body: serde_json::Value = res.json().await.unwrap(); 309 let uri = body["uri"].as_str().unwrap(); 310 let rkey = uri.split('/').last().unwrap().to_string(); 311 rkeys.push(rkey); 312 } 313 for rkey in &rkeys { 314 let get_res = client 315 .get(format!( 316 "{}/xrpc/com.atproto.repo.getRecord?repo={}&collection=app.bsky.feed.post&rkey={}", 317 base_url().await, 318 did, 319 rkey 320 )) 321 .send() 322 .await 323 .expect("Failed to get record before export"); 324 assert_eq!( 325 get_res.status(), 326 StatusCode::OK, 327 "Record {} not found before export", 328 rkey 329 ); 330 } 331 let export_res = client 332 .get(format!( 333 "{}/xrpc/com.atproto.sync.getRepo?did={}", 334 base_url().await, 335 did 336 )) 337 .send() 338 .await 339 .expect("Failed to export repo"); 340 assert_eq!(export_res.status(), StatusCode::OK); 341 let car_bytes = export_res.bytes().await.unwrap(); 342 let import_res = client 343 .post(format!( 344 "{}/xrpc/com.atproto.repo.importRepo", 345 base_url().await 346 )) 347 .bearer_auth(&token) 348 .header("Content-Type", "application/vnd.ipld.car") 349 .body(car_bytes.to_vec()) 350 .send() 351 .await 352 .expect("Failed to import repo"); 353 assert_eq!(import_res.status(), StatusCode::OK); 354 let list_res = client 355 .get(format!( 356 "{}/xrpc/com.atproto.repo.listRecords?repo={}&collection=app.bsky.feed.post", 357 base_url().await, 358 did 359 )) 360 .send() 361 .await 362 .expect("Failed to list records after import"); 363 assert_eq!(list_res.status(), StatusCode::OK); 364 let list_body: serde_json::Value = list_res.json().await.unwrap(); 365 let records_after = list_body["records"] 366 .as_array() 367 .map(|a| a.len()) 368 .unwrap_or(0); 369 assert!( 370 records_after >= 1, 371 "Expected at least 1 record after import, found {}. Note: MST walk may have timing issues.", 372 records_after 373 ); 374}