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