this repo has no description
1mod common;
2mod helpers;
3use common::*;
4use reqwest::StatusCode;
5use serde_json::{Value, json};
6
7#[tokio::test]
8async fn test_check_account_status_returns_correct_block_count() {
9 let client = client();
10 let base = base_url().await;
11 let (access_jwt, did) = create_account_and_login(&client).await;
12
13 let status1 = client
14 .get(format!(
15 "{}/xrpc/com.atproto.server.checkAccountStatus",
16 base
17 ))
18 .bearer_auth(&access_jwt)
19 .send()
20 .await
21 .unwrap();
22 assert_eq!(status1.status(), StatusCode::OK);
23 let body1: Value = status1.json().await.unwrap();
24 let initial_blocks = body1["repoBlocks"].as_i64().unwrap();
25 assert!(
26 initial_blocks >= 2,
27 "New account should have at least 2 blocks (commit + empty MST)"
28 );
29
30 let create_res = client
31 .post(format!("{}/xrpc/com.atproto.repo.createRecord", base))
32 .bearer_auth(&access_jwt)
33 .json(&json!({
34 "repo": did,
35 "collection": "app.bsky.feed.post",
36 "record": {
37 "$type": "app.bsky.feed.post",
38 "text": "Test post for block counting",
39 "createdAt": chrono::Utc::now().to_rfc3339()
40 }
41 }))
42 .send()
43 .await
44 .unwrap();
45 assert_eq!(create_res.status(), StatusCode::OK);
46 let create_body: Value = create_res.json().await.unwrap();
47 let rkey = create_body["uri"]
48 .as_str()
49 .unwrap()
50 .split('/')
51 .next_back()
52 .unwrap()
53 .to_string();
54
55 let status2 = client
56 .get(format!(
57 "{}/xrpc/com.atproto.server.checkAccountStatus",
58 base
59 ))
60 .bearer_auth(&access_jwt)
61 .send()
62 .await
63 .unwrap();
64 let body2: Value = status2.json().await.unwrap();
65 let after_create_blocks = body2["repoBlocks"].as_i64().unwrap();
66 assert!(
67 after_create_blocks > initial_blocks,
68 "Block count should increase after creating a record"
69 );
70
71 let delete_res = client
72 .post(format!("{}/xrpc/com.atproto.repo.deleteRecord", base))
73 .bearer_auth(&access_jwt)
74 .json(&json!({
75 "repo": did,
76 "collection": "app.bsky.feed.post",
77 "rkey": rkey
78 }))
79 .send()
80 .await
81 .unwrap();
82 assert_eq!(delete_res.status(), StatusCode::OK);
83
84 let status3 = client
85 .get(format!(
86 "{}/xrpc/com.atproto.server.checkAccountStatus",
87 base
88 ))
89 .bearer_auth(&access_jwt)
90 .send()
91 .await
92 .unwrap();
93 let body3: Value = status3.json().await.unwrap();
94 let after_delete_blocks = body3["repoBlocks"].as_i64().unwrap();
95 assert!(
96 after_delete_blocks <= after_create_blocks,
97 "Block count should decrease or stay same after deleting a record (was {}, now {})",
98 after_create_blocks,
99 after_delete_blocks
100 );
101 assert!(
102 after_delete_blocks >= initial_blocks,
103 "Block count after delete should be at least initial count (initial {}, now {})",
104 initial_blocks,
105 after_delete_blocks
106 );
107}
108
109#[tokio::test]
110async fn test_check_account_status_returns_valid_repo_rev() {
111 let client = client();
112 let base = base_url().await;
113 let (access_jwt, _) = create_account_and_login(&client).await;
114
115 let status = client
116 .get(format!(
117 "{}/xrpc/com.atproto.server.checkAccountStatus",
118 base
119 ))
120 .bearer_auth(&access_jwt)
121 .send()
122 .await
123 .unwrap();
124 assert_eq!(status.status(), StatusCode::OK);
125 let body: Value = status.json().await.unwrap();
126
127 let repo_rev = body["repoRev"].as_str().unwrap();
128 assert!(!repo_rev.is_empty(), "repoRev should not be empty");
129 assert!(
130 repo_rev.chars().all(|c| c.is_alphanumeric()),
131 "repoRev should be alphanumeric TID"
132 );
133}
134
135#[tokio::test]
136async fn test_check_account_status_valid_did_is_true_for_active_account() {
137 let client = client();
138 let base = base_url().await;
139 let (access_jwt, _) = create_account_and_login(&client).await;
140
141 let status = client
142 .get(format!(
143 "{}/xrpc/com.atproto.server.checkAccountStatus",
144 base
145 ))
146 .bearer_auth(&access_jwt)
147 .send()
148 .await
149 .unwrap();
150 assert_eq!(status.status(), StatusCode::OK);
151 let body: Value = status.json().await.unwrap();
152
153 assert_eq!(
154 body["validDid"], true,
155 "validDid should be true for active account with correct DID document"
156 );
157 assert_eq!(
158 body["activated"], true,
159 "activated should be true for active account"
160 );
161}
162
163#[tokio::test]
164async fn test_deactivate_account_with_delete_after() {
165 let client = client();
166 let base = base_url().await;
167 let (access_jwt, _) = create_account_and_login(&client).await;
168
169 let future_time = chrono::Utc::now() + chrono::Duration::hours(24);
170 let delete_after = future_time.to_rfc3339();
171
172 let deactivate = client
173 .post(format!(
174 "{}/xrpc/com.atproto.server.deactivateAccount",
175 base
176 ))
177 .bearer_auth(&access_jwt)
178 .json(&json!({
179 "deleteAfter": delete_after
180 }))
181 .send()
182 .await
183 .unwrap();
184 assert_eq!(deactivate.status(), StatusCode::OK);
185
186 let status = client
187 .get(format!(
188 "{}/xrpc/com.atproto.server.checkAccountStatus",
189 base
190 ))
191 .bearer_auth(&access_jwt)
192 .send()
193 .await
194 .unwrap();
195 assert_eq!(status.status(), StatusCode::OK);
196 let body: Value = status.json().await.unwrap();
197 assert_eq!(body["activated"], false, "Account should be deactivated");
198}
199
200#[tokio::test]
201async fn test_create_account_returns_did_doc() {
202 let client = client();
203 let base = base_url().await;
204
205 let handle = format!("dd{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
206 let payload = json!({
207 "handle": handle,
208 "email": format!("{}@example.com", handle),
209 "password": "Testpass123!"
210 });
211
212 let create_res = client
213 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
214 .json(&payload)
215 .send()
216 .await
217 .unwrap();
218 assert_eq!(create_res.status(), StatusCode::OK);
219 let body: Value = create_res.json().await.unwrap();
220
221 assert!(
222 body["accessJwt"].is_string(),
223 "accessJwt should always be returned"
224 );
225 assert!(
226 body["refreshJwt"].is_string(),
227 "refreshJwt should always be returned"
228 );
229 assert!(body["did"].is_string(), "did should be returned");
230
231 if body["didDoc"].is_object() {
232 let did_doc = &body["didDoc"];
233 assert!(did_doc["id"].is_string(), "didDoc should have id field");
234 }
235}
236
237#[tokio::test]
238async fn test_create_account_always_returns_tokens() {
239 let client = client();
240 let base = base_url().await;
241
242 let handle = format!("tt{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
243 let payload = json!({
244 "handle": handle,
245 "email": format!("{}@example.com", handle),
246 "password": "Testpass123!"
247 });
248
249 let create_res = client
250 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
251 .json(&payload)
252 .send()
253 .await
254 .unwrap();
255 assert_eq!(create_res.status(), StatusCode::OK);
256 let body: Value = create_res.json().await.unwrap();
257
258 let access_jwt = body["accessJwt"]
259 .as_str()
260 .expect("accessJwt should be present");
261 let refresh_jwt = body["refreshJwt"]
262 .as_str()
263 .expect("refreshJwt should be present");
264
265 assert!(!access_jwt.is_empty(), "accessJwt should not be empty");
266 assert!(!refresh_jwt.is_empty(), "refreshJwt should not be empty");
267
268 let parts: Vec<&str> = access_jwt.split('.').collect();
269 assert_eq!(
270 parts.len(),
271 3,
272 "accessJwt should be a valid JWT with 3 parts"
273 );
274}
275
276#[tokio::test]
277async fn test_describe_server_has_links_and_contact() {
278 let client = client();
279 let base = base_url().await;
280
281 let describe = client
282 .get(format!("{}/xrpc/com.atproto.server.describeServer", base))
283 .send()
284 .await
285 .unwrap();
286 assert_eq!(describe.status(), StatusCode::OK);
287 let body: Value = describe.json().await.unwrap();
288
289 assert!(
290 body.get("links").is_some(),
291 "describeServer should include links object"
292 );
293 assert!(
294 body.get("contact").is_some(),
295 "describeServer should include contact object"
296 );
297
298 let links = &body["links"];
299 assert!(
300 links.get("privacyPolicy").is_some() || links["privacyPolicy"].is_null(),
301 "links should have privacyPolicy field (can be null)"
302 );
303 assert!(
304 links.get("termsOfService").is_some() || links["termsOfService"].is_null(),
305 "links should have termsOfService field (can be null)"
306 );
307
308 let contact = &body["contact"];
309 assert!(
310 contact.get("email").is_some() || contact["email"].is_null(),
311 "contact should have email field (can be null)"
312 );
313}
314
315#[tokio::test]
316async fn test_delete_account_password_max_length() {
317 let client = client();
318 let base = base_url().await;
319
320 let handle = format!("pl{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
321 let payload = json!({
322 "handle": handle,
323 "email": format!("{}@example.com", handle),
324 "password": "Testpass123!"
325 });
326
327 let create_res = client
328 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
329 .json(&payload)
330 .send()
331 .await
332 .unwrap();
333 assert_eq!(create_res.status(), StatusCode::OK);
334 let body: Value = create_res.json().await.unwrap();
335 let did = body["did"].as_str().unwrap();
336
337 let too_long_password = "a".repeat(600);
338 let delete_res = client
339 .post(format!("{}/xrpc/com.atproto.server.deleteAccount", base))
340 .json(&json!({
341 "did": did,
342 "password": too_long_password,
343 "token": "fake-token"
344 }))
345 .send()
346 .await
347 .unwrap();
348
349 assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
350 let error_body: Value = delete_res.json().await.unwrap();
351 assert!(
352 error_body["message"]
353 .as_str()
354 .unwrap()
355 .contains("password length")
356 || error_body["error"].as_str().unwrap() == "InvalidRequest"
357 );
358}