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