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