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}