this repo has no description
1mod common; 2use common::*; 3 4use iroh_car::CarHeader; 5use reqwest::StatusCode; 6use serde_json::json; 7 8#[tokio::test] 9async fn test_import_repo_requires_auth() { 10 let client = client(); 11 12 let res = client 13 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 14 .header("Content-Type", "application/vnd.ipld.car") 15 .body(vec![0u8; 100]) 16 .send() 17 .await 18 .expect("Request failed"); 19 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 28 let res = client 29 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 30 .bearer_auth(&token) 31 .header("Content-Type", "application/vnd.ipld.car") 32 .body(vec![0u8; 100]) 33 .send() 34 .await 35 .expect("Request failed"); 36 37 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 38 let body: serde_json::Value = res.json().await.unwrap(); 39 assert_eq!(body["error"], "InvalidRequest"); 40} 41 42#[tokio::test] 43async fn test_import_repo_empty_body() { 44 let client = client(); 45 let (token, _did) = create_account_and_login(&client).await; 46 47 let res = client 48 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 49 .bearer_auth(&token) 50 .header("Content-Type", "application/vnd.ipld.car") 51 .body(vec![]) 52 .send() 53 .await 54 .expect("Request failed"); 55 56 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 57} 58 59fn write_varint(buf: &mut Vec<u8>, mut value: u64) { 60 loop { 61 let mut byte = (value & 0x7F) as u8; 62 value >>= 7; 63 if value != 0 { 64 byte |= 0x80; 65 } 66 buf.push(byte); 67 if value == 0 { 68 break; 69 } 70 } 71} 72 73#[tokio::test] 74async fn test_import_rejects_car_for_different_user() { 75 let client = client(); 76 77 let (token_a, _did_a) = create_account_and_login(&client).await; 78 let (_token_b, did_b) = create_account_and_login(&client).await; 79 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 90 assert_eq!(export_res.status(), StatusCode::OK); 91 let car_bytes = export_res.bytes().await.unwrap(); 92 93 let import_res = client 94 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 95 .bearer_auth(&token_a) 96 .header("Content-Type", "application/vnd.ipld.car") 97 .body(car_bytes.to_vec()) 98 .send() 99 .await 100 .expect("Import failed"); 101 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 116 let post_payload = json!({ 117 "repo": did, 118 "collection": "app.bsky.feed.post", 119 "record": { 120 "$type": "app.bsky.feed.post", 121 "text": "Original post before export", 122 "createdAt": chrono::Utc::now().to_rfc3339(), 123 } 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 138 let export_res = client 139 .get(format!( 140 "{}/xrpc/com.atproto.sync.getRepo?did={}", 141 base_url().await, 142 did 143 )) 144 .send() 145 .await 146 .expect("Failed to export repo"); 147 assert_eq!(export_res.status(), StatusCode::OK); 148 let car_bytes = export_res.bytes().await.unwrap(); 149 150 let import_res = client 151 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 152 .bearer_auth(&token) 153 .header("Content-Type", "application/vnd.ipld.car") 154 .body(car_bytes.to_vec()) 155 .send() 156 .await 157 .expect("Failed to import repo"); 158 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 167 let oversized_body = vec![0u8; 110 * 1024 * 1024]; 168 169 let res = client 170 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 171 .bearer_auth(&token) 172 .header("Content-Type", "application/vnd.ipld.car") 173 .body(oversized_body) 174 .send() 175 .await; 176 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_rejected() { 198 let client = client(); 199 let (token, did) = create_account_and_login(&client).await; 200 201 let export_res = client 202 .get(format!( 203 "{}/xrpc/com.atproto.sync.getRepo?did={}", 204 base_url().await, 205 did 206 )) 207 .send() 208 .await 209 .expect("Export failed"); 210 assert_eq!(export_res.status(), StatusCode::OK); 211 let car_bytes = export_res.bytes().await.unwrap(); 212 213 let deactivate_res = client 214 .post(format!( 215 "{}/xrpc/com.atproto.server.deactivateAccount", 216 base_url().await 217 )) 218 .bearer_auth(&token) 219 .json(&json!({})) 220 .send() 221 .await 222 .expect("Deactivate failed"); 223 assert!(deactivate_res.status().is_success()); 224 225 let import_res = client 226 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 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 234 assert!( 235 import_res.status() == StatusCode::FORBIDDEN || import_res.status() == StatusCode::UNAUTHORIZED, 236 "Expected FORBIDDEN (403) or UNAUTHORIZED (401), got {}", 237 import_res.status() 238 ); 239} 240 241#[tokio::test] 242async fn test_import_invalid_car_structure() { 243 let client = client(); 244 let (token, _did) = create_account_and_login(&client).await; 245 246 let invalid_car = vec![0x0a, 0xa1, 0x65, 0x72, 0x6f, 0x6f, 0x74, 0x73, 0x80]; 247 248 let res = client 249 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 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 257 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 258} 259 260#[tokio::test] 261async fn test_import_car_with_no_roots() { 262 let client = client(); 263 let (token, _did) = create_account_and_login(&client).await; 264 265 let header = CarHeader::new_v1(vec![]); 266 let header_cbor = header.encode().unwrap_or_default(); 267 let mut car = Vec::new(); 268 write_varint(&mut car, header_cbor.len() as u64); 269 car.extend_from_slice(&header_cbor); 270 271 let res = client 272 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 273 .bearer_auth(&token) 274 .header("Content-Type", "application/vnd.ipld.car") 275 .body(car) 276 .send() 277 .await 278 .expect("Request failed"); 279 280 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 281 let body: serde_json::Value = res.json().await.unwrap(); 282 assert_eq!(body["error"], "InvalidRequest"); 283} 284 285#[tokio::test] 286async fn test_import_preserves_records_after_reimport() { 287 let client = client(); 288 let (token, did) = create_account_and_login(&client).await; 289 290 let mut rkeys = Vec::new(); 291 for i in 0..3 { 292 let post_payload = json!({ 293 "repo": did, 294 "collection": "app.bsky.feed.post", 295 "record": { 296 "$type": "app.bsky.feed.post", 297 "text": format!("Test post {}", i), 298 "createdAt": chrono::Utc::now().to_rfc3339(), 299 } 300 }); 301 302 let res = client 303 .post(format!( 304 "{}/xrpc/com.atproto.repo.createRecord", 305 base_url().await 306 )) 307 .bearer_auth(&token) 308 .json(&post_payload) 309 .send() 310 .await 311 .expect("Failed to create post"); 312 assert_eq!(res.status(), StatusCode::OK); 313 314 let body: serde_json::Value = res.json().await.unwrap(); 315 let uri = body["uri"].as_str().unwrap(); 316 let rkey = uri.split('/').last().unwrap().to_string(); 317 rkeys.push(rkey); 318 } 319 320 for rkey in &rkeys { 321 let get_res = client 322 .get(format!( 323 "{}/xrpc/com.atproto.repo.getRecord?repo={}&collection=app.bsky.feed.post&rkey={}", 324 base_url().await, 325 did, 326 rkey 327 )) 328 .send() 329 .await 330 .expect("Failed to get record before export"); 331 assert_eq!(get_res.status(), StatusCode::OK, "Record {} not found before export", rkey); 332 } 333 334 let export_res = client 335 .get(format!( 336 "{}/xrpc/com.atproto.sync.getRepo?did={}", 337 base_url().await, 338 did 339 )) 340 .send() 341 .await 342 .expect("Failed to export repo"); 343 assert_eq!(export_res.status(), StatusCode::OK); 344 let car_bytes = export_res.bytes().await.unwrap(); 345 346 let import_res = client 347 .post(format!("{}/xrpc/com.atproto.repo.importRepo", base_url().await)) 348 .bearer_auth(&token) 349 .header("Content-Type", "application/vnd.ipld.car") 350 .body(car_bytes.to_vec()) 351 .send() 352 .await 353 .expect("Failed to import repo"); 354 assert_eq!(import_res.status(), StatusCode::OK); 355 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"].as_array().map(|a| a.len()).unwrap_or(0); 368 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}