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