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