this repo has no description
1mod common;
2mod helpers;
3use common::*;
4use helpers::*;
5use reqwest::StatusCode;
6use serde_json::Value;
7
8#[tokio::test]
9async fn test_get_head_comprehensive() {
10 let client = client();
11 let (did, jwt) = setup_new_user("gethead").await;
12 let res = client
13 .get(format!(
14 "{}/xrpc/com.atproto.sync.getHead",
15 base_url().await
16 ))
17 .query(&[("did", did.as_str())])
18 .send()
19 .await
20 .expect("Failed to send request");
21 assert_eq!(res.status(), StatusCode::OK);
22 let body: Value = res.json().await.expect("Response was not valid JSON");
23 assert!(body["root"].is_string());
24 let root1 = body["root"].as_str().unwrap().to_string();
25 assert!(root1.starts_with("bafy"), "Root CID should be a CID");
26 let latest_res = client
27 .get(format!(
28 "{}/xrpc/com.atproto.sync.getLatestCommit",
29 base_url().await
30 ))
31 .query(&[("did", did.as_str())])
32 .send()
33 .await
34 .expect("Failed to get latest commit");
35 let latest_body: Value = latest_res.json().await.unwrap();
36 let latest_cid = latest_body["cid"].as_str().unwrap();
37 assert_eq!(
38 root1, latest_cid,
39 "getHead root should match getLatestCommit cid"
40 );
41 create_post(&client, &did, &jwt, "Post to change head").await;
42 let res2 = client
43 .get(format!(
44 "{}/xrpc/com.atproto.sync.getHead",
45 base_url().await
46 ))
47 .query(&[("did", did.as_str())])
48 .send()
49 .await
50 .expect("Failed to get head after record");
51 let body2: Value = res2.json().await.unwrap();
52 let root2 = body2["root"].as_str().unwrap().to_string();
53 assert_ne!(root1, root2, "Head CID should change after record creation");
54 let not_found_res = client
55 .get(format!(
56 "{}/xrpc/com.atproto.sync.getHead",
57 base_url().await
58 ))
59 .query(&[("did", "did:plc:nonexistent12345")])
60 .send()
61 .await
62 .expect("Failed to send request");
63 assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST);
64 let error_body: Value = not_found_res.json().await.unwrap();
65 assert_eq!(error_body["error"], "RepoNotFound");
66 let missing_res = client
67 .get(format!(
68 "{}/xrpc/com.atproto.sync.getHead",
69 base_url().await
70 ))
71 .send()
72 .await
73 .expect("Failed to send request");
74 assert_eq!(missing_res.status(), StatusCode::BAD_REQUEST);
75 let empty_res = client
76 .get(format!(
77 "{}/xrpc/com.atproto.sync.getHead",
78 base_url().await
79 ))
80 .query(&[("did", "")])
81 .send()
82 .await
83 .expect("Failed to send request");
84 assert_eq!(empty_res.status(), StatusCode::BAD_REQUEST);
85 let whitespace_res = client
86 .get(format!(
87 "{}/xrpc/com.atproto.sync.getHead",
88 base_url().await
89 ))
90 .query(&[("did", " ")])
91 .send()
92 .await
93 .expect("Failed to send request");
94 assert_eq!(whitespace_res.status(), StatusCode::BAD_REQUEST);
95}
96
97#[tokio::test]
98async fn test_get_checkout_comprehensive() {
99 let client = client();
100 let (did, jwt) = setup_new_user("getcheckout").await;
101 let empty_res = client
102 .get(format!(
103 "{}/xrpc/com.atproto.sync.getCheckout",
104 base_url().await
105 ))
106 .query(&[("did", did.as_str())])
107 .send()
108 .await
109 .expect("Failed to send request");
110 assert_eq!(empty_res.status(), StatusCode::OK);
111 let empty_body = empty_res.bytes().await.expect("Failed to get body");
112 assert!(
113 !empty_body.is_empty(),
114 "Even empty repo should return CAR header"
115 );
116 create_post(&client, &did, &jwt, "Post for checkout test").await;
117 let res = client
118 .get(format!(
119 "{}/xrpc/com.atproto.sync.getCheckout",
120 base_url().await
121 ))
122 .query(&[("did", did.as_str())])
123 .send()
124 .await
125 .expect("Failed to send request");
126 assert_eq!(res.status(), StatusCode::OK);
127 assert_eq!(
128 res.headers()
129 .get("content-type")
130 .and_then(|h| h.to_str().ok()),
131 Some("application/vnd.ipld.car")
132 );
133 let body = res.bytes().await.expect("Failed to get body");
134 assert!(!body.is_empty(), "CAR file should not be empty");
135 assert!(body.len() > 50, "CAR file should contain actual data");
136 assert!(
137 body.len() >= 2,
138 "CAR file should have at least header length"
139 );
140 for i in 0..4 {
141 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
142 create_post(&client, &did, &jwt, &format!("Checkout post {}", i)).await;
143 }
144 let multi_res = client
145 .get(format!(
146 "{}/xrpc/com.atproto.sync.getCheckout",
147 base_url().await
148 ))
149 .query(&[("did", did.as_str())])
150 .send()
151 .await
152 .expect("Failed to send request");
153 assert_eq!(multi_res.status(), StatusCode::OK);
154 let multi_body = multi_res.bytes().await.expect("Failed to get body");
155 assert!(
156 multi_body.len() > 500,
157 "CAR file with 5 records should be larger"
158 );
159 let not_found_res = client
160 .get(format!(
161 "{}/xrpc/com.atproto.sync.getCheckout",
162 base_url().await
163 ))
164 .query(&[("did", "did:plc:nonexistent12345")])
165 .send()
166 .await
167 .expect("Failed to send request");
168 assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST);
169 let error_body: Value = not_found_res.json().await.unwrap();
170 assert_eq!(error_body["error"], "RepoNotFound");
171 let missing_res = client
172 .get(format!(
173 "{}/xrpc/com.atproto.sync.getCheckout",
174 base_url().await
175 ))
176 .send()
177 .await
178 .expect("Failed to send request");
179 assert_eq!(missing_res.status(), StatusCode::BAD_REQUEST);
180 let empty_did_res = client
181 .get(format!(
182 "{}/xrpc/com.atproto.sync.getCheckout",
183 base_url().await
184 ))
185 .query(&[("did", "")])
186 .send()
187 .await
188 .expect("Failed to send request");
189 assert_eq!(empty_did_res.status(), StatusCode::BAD_REQUEST);
190}
191
192#[tokio::test]
193async fn test_get_head_deactivated_account_returns_error() {
194 let client = client();
195 let base = base_url().await;
196 let (did, jwt) = setup_new_user("deactheadtest").await;
197 let res = client
198 .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
199 .query(&[("did", did.as_str())])
200 .send()
201 .await
202 .unwrap();
203 assert_eq!(res.status(), StatusCode::OK);
204 client
205 .post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
206 .bearer_auth(&jwt)
207 .json(&serde_json::json!({}))
208 .send()
209 .await
210 .unwrap();
211 let deact_res = client
212 .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
213 .query(&[("did", did.as_str())])
214 .send()
215 .await
216 .unwrap();
217 assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST);
218 let body: Value = deact_res.json().await.unwrap();
219 assert_eq!(body["error"], "RepoDeactivated");
220}
221
222#[tokio::test]
223async fn test_get_head_takendown_account_returns_error() {
224 let client = client();
225 let base = base_url().await;
226 let (admin_jwt, _) = create_admin_account_and_login(&client).await;
227 let (_, target_did) = create_account_and_login(&client).await;
228 let res = client
229 .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
230 .query(&[("did", target_did.as_str())])
231 .send()
232 .await
233 .unwrap();
234 assert_eq!(res.status(), StatusCode::OK);
235 client
236 .post(format!("{}/xrpc/com.atproto.admin.updateSubjectStatus", base))
237 .bearer_auth(&admin_jwt)
238 .json(&serde_json::json!({
239 "subject": {
240 "$type": "com.atproto.admin.defs#repoRef",
241 "did": target_did
242 },
243 "takedown": {
244 "applied": true,
245 "ref": "test-takedown"
246 }
247 }))
248 .send()
249 .await
250 .unwrap();
251 let takedown_res = client
252 .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
253 .query(&[("did", target_did.as_str())])
254 .send()
255 .await
256 .unwrap();
257 assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST);
258 let body: Value = takedown_res.json().await.unwrap();
259 assert_eq!(body["error"], "RepoTakendown");
260}
261
262#[tokio::test]
263async fn test_get_head_admin_can_access_deactivated() {
264 let client = client();
265 let base = base_url().await;
266 let (admin_jwt, _) = create_admin_account_and_login(&client).await;
267 let (user_jwt, did) = create_account_and_login(&client).await;
268 client
269 .post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
270 .bearer_auth(&user_jwt)
271 .json(&serde_json::json!({}))
272 .send()
273 .await
274 .unwrap();
275 let res = client
276 .get(format!("{}/xrpc/com.atproto.sync.getHead", base))
277 .bearer_auth(&admin_jwt)
278 .query(&[("did", did.as_str())])
279 .send()
280 .await
281 .unwrap();
282 assert_eq!(res.status(), StatusCode::OK);
283}
284
285#[tokio::test]
286async fn test_get_checkout_deactivated_account_returns_error() {
287 let client = client();
288 let base = base_url().await;
289 let (did, jwt) = setup_new_user("deactcheckouttest").await;
290 let res = client
291 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
292 .query(&[("did", did.as_str())])
293 .send()
294 .await
295 .unwrap();
296 assert_eq!(res.status(), StatusCode::OK);
297 client
298 .post(format!("{}/xrpc/com.atproto.server.deactivateAccount", base))
299 .bearer_auth(&jwt)
300 .json(&serde_json::json!({}))
301 .send()
302 .await
303 .unwrap();
304 let deact_res = client
305 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
306 .query(&[("did", did.as_str())])
307 .send()
308 .await
309 .unwrap();
310 assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST);
311 let body: Value = deact_res.json().await.unwrap();
312 assert_eq!(body["error"], "RepoDeactivated");
313}
314
315#[tokio::test]
316async fn test_get_checkout_takendown_account_returns_error() {
317 let client = client();
318 let base = base_url().await;
319 let (admin_jwt, _) = create_admin_account_and_login(&client).await;
320 let (_, target_did) = create_account_and_login(&client).await;
321 let res = client
322 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
323 .query(&[("did", target_did.as_str())])
324 .send()
325 .await
326 .unwrap();
327 assert_eq!(res.status(), StatusCode::OK);
328 client
329 .post(format!("{}/xrpc/com.atproto.admin.updateSubjectStatus", base))
330 .bearer_auth(&admin_jwt)
331 .json(&serde_json::json!({
332 "subject": {
333 "$type": "com.atproto.admin.defs#repoRef",
334 "did": target_did
335 },
336 "takedown": {
337 "applied": true,
338 "ref": "test-takedown"
339 }
340 }))
341 .send()
342 .await
343 .unwrap();
344 let takedown_res = client
345 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base))
346 .query(&[("did", target_did.as_str())])
347 .send()
348 .await
349 .unwrap();
350 assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST);
351 let body: Value = takedown_res.json().await.unwrap();
352 assert_eq!(body["error"], "RepoTakendown");
353}