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 .last()
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 not decrease after deleting a record (was {}, now {})",
98 after_create_blocks,
99 after_delete_blocks
100 );
101}
102
103#[tokio::test]
104async fn test_check_account_status_returns_valid_repo_rev() {
105 let client = client();
106 let base = base_url().await;
107 let (access_jwt, _) = create_account_and_login(&client).await;
108
109 let status = client
110 .get(format!(
111 "{}/xrpc/com.atproto.server.checkAccountStatus",
112 base
113 ))
114 .bearer_auth(&access_jwt)
115 .send()
116 .await
117 .unwrap();
118 assert_eq!(status.status(), StatusCode::OK);
119 let body: Value = status.json().await.unwrap();
120
121 let repo_rev = body["repoRev"].as_str().unwrap();
122 assert!(!repo_rev.is_empty(), "repoRev should not be empty");
123 assert!(
124 repo_rev.chars().all(|c| c.is_alphanumeric()),
125 "repoRev should be alphanumeric TID"
126 );
127}
128
129#[tokio::test]
130async fn test_check_account_status_valid_did_is_true_for_active_account() {
131 let client = client();
132 let base = base_url().await;
133 let (access_jwt, _) = create_account_and_login(&client).await;
134
135 let status = client
136 .get(format!(
137 "{}/xrpc/com.atproto.server.checkAccountStatus",
138 base
139 ))
140 .bearer_auth(&access_jwt)
141 .send()
142 .await
143 .unwrap();
144 assert_eq!(status.status(), StatusCode::OK);
145 let body: Value = status.json().await.unwrap();
146
147 assert_eq!(
148 body["validDid"], true,
149 "validDid should be true for active account with correct DID document"
150 );
151 assert_eq!(
152 body["activated"], true,
153 "activated should be true for active account"
154 );
155}
156
157#[tokio::test]
158async fn test_deactivate_account_with_delete_after() {
159 let client = client();
160 let base = base_url().await;
161 let (access_jwt, _) = create_account_and_login(&client).await;
162
163 let future_time = chrono::Utc::now() + chrono::Duration::hours(24);
164 let delete_after = future_time.to_rfc3339();
165
166 let deactivate = client
167 .post(format!(
168 "{}/xrpc/com.atproto.server.deactivateAccount",
169 base
170 ))
171 .bearer_auth(&access_jwt)
172 .json(&json!({
173 "deleteAfter": delete_after
174 }))
175 .send()
176 .await
177 .unwrap();
178 assert_eq!(deactivate.status(), StatusCode::OK);
179
180 let status = client
181 .get(format!(
182 "{}/xrpc/com.atproto.server.checkAccountStatus",
183 base
184 ))
185 .bearer_auth(&access_jwt)
186 .send()
187 .await
188 .unwrap();
189 assert_eq!(status.status(), StatusCode::OK);
190 let body: Value = status.json().await.unwrap();
191 assert_eq!(body["activated"], false, "Account should be deactivated");
192}
193
194#[tokio::test]
195async fn test_create_account_returns_did_doc() {
196 let client = client();
197 let base = base_url().await;
198
199 let handle = format!("dd{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
200 let payload = json!({
201 "handle": handle,
202 "email": format!("{}@example.com", handle),
203 "password": "Testpass123!"
204 });
205
206 let create_res = client
207 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
208 .json(&payload)
209 .send()
210 .await
211 .unwrap();
212 assert_eq!(create_res.status(), StatusCode::OK);
213 let body: Value = create_res.json().await.unwrap();
214
215 assert!(
216 body["accessJwt"].is_string(),
217 "accessJwt should always be returned"
218 );
219 assert!(
220 body["refreshJwt"].is_string(),
221 "refreshJwt should always be returned"
222 );
223 assert!(body["did"].is_string(), "did should be returned");
224
225 if body["didDoc"].is_object() {
226 let did_doc = &body["didDoc"];
227 assert!(did_doc["id"].is_string(), "didDoc should have id field");
228 }
229}
230
231#[tokio::test]
232async fn test_create_account_always_returns_tokens() {
233 let client = client();
234 let base = base_url().await;
235
236 let handle = format!("tt{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
237 let payload = json!({
238 "handle": handle,
239 "email": format!("{}@example.com", handle),
240 "password": "Testpass123!"
241 });
242
243 let create_res = client
244 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
245 .json(&payload)
246 .send()
247 .await
248 .unwrap();
249 assert_eq!(create_res.status(), StatusCode::OK);
250 let body: Value = create_res.json().await.unwrap();
251
252 let access_jwt = body["accessJwt"]
253 .as_str()
254 .expect("accessJwt should be present");
255 let refresh_jwt = body["refreshJwt"]
256 .as_str()
257 .expect("refreshJwt should be present");
258
259 assert!(!access_jwt.is_empty(), "accessJwt should not be empty");
260 assert!(!refresh_jwt.is_empty(), "refreshJwt should not be empty");
261
262 let parts: Vec<&str> = access_jwt.split('.').collect();
263 assert_eq!(
264 parts.len(),
265 3,
266 "accessJwt should be a valid JWT with 3 parts"
267 );
268}
269
270#[tokio::test]
271async fn test_describe_server_has_links_and_contact() {
272 let client = client();
273 let base = base_url().await;
274
275 let describe = client
276 .get(format!("{}/xrpc/com.atproto.server.describeServer", base))
277 .send()
278 .await
279 .unwrap();
280 assert_eq!(describe.status(), StatusCode::OK);
281 let body: Value = describe.json().await.unwrap();
282
283 assert!(
284 body.get("links").is_some(),
285 "describeServer should include links object"
286 );
287 assert!(
288 body.get("contact").is_some(),
289 "describeServer should include contact object"
290 );
291
292 let links = &body["links"];
293 assert!(
294 links.get("privacyPolicy").is_some() || links["privacyPolicy"].is_null(),
295 "links should have privacyPolicy field (can be null)"
296 );
297 assert!(
298 links.get("termsOfService").is_some() || links["termsOfService"].is_null(),
299 "links should have termsOfService field (can be null)"
300 );
301
302 let contact = &body["contact"];
303 assert!(
304 contact.get("email").is_some() || contact["email"].is_null(),
305 "contact should have email field (can be null)"
306 );
307}
308
309#[tokio::test]
310async fn test_delete_account_password_max_length() {
311 let client = client();
312 let base = base_url().await;
313
314 let handle = format!("pl{}", &uuid::Uuid::new_v4().simple().to_string()[..12]);
315 let payload = json!({
316 "handle": handle,
317 "email": format!("{}@example.com", handle),
318 "password": "Testpass123!"
319 });
320
321 let create_res = client
322 .post(format!("{}/xrpc/com.atproto.server.createAccount", base))
323 .json(&payload)
324 .send()
325 .await
326 .unwrap();
327 assert_eq!(create_res.status(), StatusCode::OK);
328 let body: Value = create_res.json().await.unwrap();
329 let did = body["did"].as_str().unwrap();
330
331 let too_long_password = "a".repeat(600);
332 let delete_res = client
333 .post(format!("{}/xrpc/com.atproto.server.deleteAccount", base))
334 .json(&json!({
335 "did": did,
336 "password": too_long_password,
337 "token": "fake-token"
338 }))
339 .send()
340 .await
341 .unwrap();
342
343 assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
344 let error_body: Value = delete_res.json().await.unwrap();
345 assert!(
346 error_body["message"]
347 .as_str()
348 .unwrap()
349 .contains("password length")
350 || error_body["error"].as_str().unwrap() == "InvalidRequest"
351 );
352}