this repo has no description
1mod common;
2mod helpers;
3
4use common::*;
5
6use chrono::Utc;
7use reqwest::StatusCode;
8use serde_json::{Value, json};
9use sqlx::PgPool;
10
11async fn get_pool() -> PgPool {
12 let conn_str = get_db_connection_string().await;
13 sqlx::postgres::PgPoolOptions::new()
14 .max_connections(5)
15 .connect(&conn_str)
16 .await
17 .expect("Failed to connect to test database")
18}
19
20async fn create_verified_account(client: &reqwest::Client, base_url: &str, handle: &str, email: &str, password: &str) -> (String, String) {
21 let res = client
22 .post(format!("{}/xrpc/com.atproto.server.createAccount", base_url))
23 .json(&json!({
24 "handle": handle,
25 "email": email,
26 "password": password
27 }))
28 .send()
29 .await
30 .expect("Failed to create account");
31 assert_eq!(res.status(), StatusCode::OK);
32 let body: Value = res.json().await.expect("Invalid JSON");
33 let did = body["did"].as_str().expect("No did").to_string();
34 let jwt = verify_new_account(client, &did).await;
35 (did, jwt)
36}
37
38#[tokio::test]
39async fn test_delete_account_full_flow() {
40 let client = client();
41 let base_url = base_url().await;
42 let ts = Utc::now().timestamp_millis();
43 let handle = format!("delete-test-{}.test", ts);
44 let email = format!("delete-test-{}@test.com", ts);
45 let password = "delete-password-123";
46
47 let (did, jwt) = create_verified_account(&client, &base_url, &handle, &email, password).await;
48
49 let request_delete_res = client
50 .post(format!(
51 "{}/xrpc/com.atproto.server.requestAccountDelete",
52 base_url
53 ))
54 .bearer_auth(&jwt)
55 .send()
56 .await
57 .expect("Failed to request account deletion");
58 assert_eq!(request_delete_res.status(), StatusCode::OK);
59
60 let pool = get_pool().await;
61
62 let row = sqlx::query!("SELECT token FROM account_deletion_requests WHERE did = $1", did)
63 .fetch_one(&pool)
64 .await
65 .expect("Failed to query deletion token");
66 let token = row.token;
67
68 let delete_payload = json!({
69 "did": did,
70 "password": password,
71 "token": token
72 });
73 let delete_res = client
74 .post(format!(
75 "{}/xrpc/com.atproto.server.deleteAccount",
76 base_url
77 ))
78 .json(&delete_payload)
79 .send()
80 .await
81 .expect("Failed to delete account");
82 assert_eq!(delete_res.status(), StatusCode::OK);
83
84 let user_row = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
85 .fetch_optional(&pool)
86 .await
87 .expect("Failed to query user");
88 assert!(user_row.is_none(), "User should be deleted from database");
89
90 let session_res = client
91 .get(format!(
92 "{}/xrpc/com.atproto.server.getSession",
93 base_url
94 ))
95 .bearer_auth(&jwt)
96 .send()
97 .await
98 .expect("Failed to check session");
99 assert_eq!(session_res.status(), StatusCode::UNAUTHORIZED);
100}
101
102#[tokio::test]
103async fn test_delete_account_wrong_password() {
104 let client = client();
105 let base_url = base_url().await;
106 let ts = Utc::now().timestamp_millis();
107 let handle = format!("delete-wrongpw-{}.test", ts);
108 let email = format!("delete-wrongpw-{}@test.com", ts);
109 let password = "correct-password";
110
111 let (did, jwt) = create_verified_account(&client, &base_url, &handle, &email, password).await;
112
113 let request_delete_res = client
114 .post(format!(
115 "{}/xrpc/com.atproto.server.requestAccountDelete",
116 base_url
117 ))
118 .bearer_auth(&jwt)
119 .send()
120 .await
121 .expect("Failed to request account deletion");
122 assert_eq!(request_delete_res.status(), StatusCode::OK);
123
124 let pool = get_pool().await;
125
126 let row = sqlx::query!("SELECT token FROM account_deletion_requests WHERE did = $1", did)
127 .fetch_one(&pool)
128 .await
129 .expect("Failed to query deletion token");
130 let token = row.token;
131
132 let delete_payload = json!({
133 "did": did,
134 "password": "wrong-password",
135 "token": token
136 });
137 let delete_res = client
138 .post(format!(
139 "{}/xrpc/com.atproto.server.deleteAccount",
140 base_url
141 ))
142 .json(&delete_payload)
143 .send()
144 .await
145 .expect("Failed to send delete request");
146 assert_eq!(delete_res.status(), StatusCode::UNAUTHORIZED);
147
148 let body: Value = delete_res.json().await.unwrap();
149 assert_eq!(body["error"], "AuthenticationFailed");
150}
151
152#[tokio::test]
153async fn test_delete_account_invalid_token() {
154 let client = client();
155 let base_url = base_url().await;
156 let ts = Utc::now().timestamp_millis();
157 let handle = format!("delete-badtoken-{}.test", ts);
158 let email = format!("delete-badtoken-{}@test.com", ts);
159 let password = "delete-password";
160
161 let create_res = client
162 .post(format!(
163 "{}/xrpc/com.atproto.server.createAccount",
164 base_url
165 ))
166 .json(&json!({
167 "handle": handle,
168 "email": email,
169 "password": password
170 }))
171 .send()
172 .await
173 .expect("Failed to create account");
174 assert_eq!(create_res.status(), StatusCode::OK);
175 let create_body: Value = create_res.json().await.unwrap();
176 let did = create_body["did"].as_str().unwrap().to_string();
177
178 let delete_payload = json!({
179 "did": did,
180 "password": password,
181 "token": "invalid-token-12345"
182 });
183 let delete_res = client
184 .post(format!(
185 "{}/xrpc/com.atproto.server.deleteAccount",
186 base_url
187 ))
188 .json(&delete_payload)
189 .send()
190 .await
191 .expect("Failed to send delete request");
192 assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
193
194 let body: Value = delete_res.json().await.unwrap();
195 assert_eq!(body["error"], "InvalidToken");
196}
197
198#[tokio::test]
199async fn test_delete_account_expired_token() {
200 let client = client();
201 let base_url = base_url().await;
202 let ts = Utc::now().timestamp_millis();
203 let handle = format!("delete-expired-{}.test", ts);
204 let email = format!("delete-expired-{}@test.com", ts);
205 let password = "delete-password";
206
207 let (did, jwt) = create_verified_account(&client, &base_url, &handle, &email, password).await;
208
209 let request_delete_res = client
210 .post(format!(
211 "{}/xrpc/com.atproto.server.requestAccountDelete",
212 base_url
213 ))
214 .bearer_auth(&jwt)
215 .send()
216 .await
217 .expect("Failed to request account deletion");
218 assert_eq!(request_delete_res.status(), StatusCode::OK);
219
220 let pool = get_pool().await;
221
222 let row = sqlx::query!("SELECT token FROM account_deletion_requests WHERE did = $1", did)
223 .fetch_one(&pool)
224 .await
225 .expect("Failed to query deletion token");
226 let token = row.token;
227
228 sqlx::query!(
229 "UPDATE account_deletion_requests SET expires_at = NOW() - INTERVAL '1 hour' WHERE token = $1",
230 token
231 )
232 .execute(&pool)
233 .await
234 .expect("Failed to expire token");
235
236 let delete_payload = json!({
237 "did": did,
238 "password": password,
239 "token": token
240 });
241 let delete_res = client
242 .post(format!(
243 "{}/xrpc/com.atproto.server.deleteAccount",
244 base_url
245 ))
246 .json(&delete_payload)
247 .send()
248 .await
249 .expect("Failed to send delete request");
250 assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
251
252 let body: Value = delete_res.json().await.unwrap();
253 assert_eq!(body["error"], "ExpiredToken");
254}
255
256#[tokio::test]
257async fn test_delete_account_token_mismatch() {
258 let client = client();
259 let base_url = base_url().await;
260 let ts = Utc::now().timestamp_millis();
261
262 let handle1 = format!("delete-user1-{}.test", ts);
263 let email1 = format!("delete-user1-{}@test.com", ts);
264 let password1 = "user1-password";
265
266 let (did1, jwt1) = create_verified_account(&client, &base_url, &handle1, &email1, password1).await;
267
268 let handle2 = format!("delete-user2-{}.test", ts);
269 let email2 = format!("delete-user2-{}@test.com", ts);
270 let password2 = "user2-password";
271
272 let (did2, _) = create_verified_account(&client, &base_url, &handle2, &email2, password2).await;
273
274 let request_delete_res = client
275 .post(format!(
276 "{}/xrpc/com.atproto.server.requestAccountDelete",
277 base_url
278 ))
279 .bearer_auth(&jwt1)
280 .send()
281 .await
282 .expect("Failed to request account deletion");
283 assert_eq!(request_delete_res.status(), StatusCode::OK);
284
285 let pool = get_pool().await;
286
287 let row = sqlx::query!("SELECT token FROM account_deletion_requests WHERE did = $1", did1)
288 .fetch_one(&pool)
289 .await
290 .expect("Failed to query deletion token");
291 let token = row.token;
292
293 let delete_payload = json!({
294 "did": did2,
295 "password": password2,
296 "token": token
297 });
298 let delete_res = client
299 .post(format!(
300 "{}/xrpc/com.atproto.server.deleteAccount",
301 base_url
302 ))
303 .json(&delete_payload)
304 .send()
305 .await
306 .expect("Failed to send delete request");
307 assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
308
309 let body: Value = delete_res.json().await.unwrap();
310 assert_eq!(body["error"], "InvalidToken");
311}
312
313#[tokio::test]
314async fn test_delete_account_with_app_password() {
315 let client = client();
316 let base_url = base_url().await;
317 let ts = Utc::now().timestamp_millis();
318 let handle = format!("delete-apppw-{}.test", ts);
319 let email = format!("delete-apppw-{}@test.com", ts);
320 let main_password = "main-password-123";
321
322 let (did, jwt) = create_verified_account(&client, &base_url, &handle, &email, main_password).await;
323
324 let app_password_res = client
325 .post(format!(
326 "{}/xrpc/com.atproto.server.createAppPassword",
327 base_url
328 ))
329 .bearer_auth(&jwt)
330 .json(&json!({ "name": "delete-test-app" }))
331 .send()
332 .await
333 .expect("Failed to create app password");
334 assert_eq!(app_password_res.status(), StatusCode::OK);
335 let app_password_body: Value = app_password_res.json().await.unwrap();
336 let app_password = app_password_body["password"].as_str().unwrap().to_string();
337
338 let request_delete_res = client
339 .post(format!(
340 "{}/xrpc/com.atproto.server.requestAccountDelete",
341 base_url
342 ))
343 .bearer_auth(&jwt)
344 .send()
345 .await
346 .expect("Failed to request account deletion");
347 assert_eq!(request_delete_res.status(), StatusCode::OK);
348
349 let pool = get_pool().await;
350
351 let row = sqlx::query!("SELECT token FROM account_deletion_requests WHERE did = $1", did)
352 .fetch_one(&pool)
353 .await
354 .expect("Failed to query deletion token");
355 let token = row.token;
356
357 let delete_payload = json!({
358 "did": did,
359 "password": app_password,
360 "token": token
361 });
362 let delete_res = client
363 .post(format!(
364 "{}/xrpc/com.atproto.server.deleteAccount",
365 base_url
366 ))
367 .json(&delete_payload)
368 .send()
369 .await
370 .expect("Failed to delete account");
371 assert_eq!(delete_res.status(), StatusCode::OK);
372
373 let user_row = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
374 .fetch_optional(&pool)
375 .await
376 .expect("Failed to query user");
377 assert!(user_row.is_none(), "User should be deleted from database");
378}
379
380#[tokio::test]
381async fn test_delete_account_missing_fields() {
382 let client = client();
383 let base_url = base_url().await;
384
385 let res1 = client
386 .post(format!(
387 "{}/xrpc/com.atproto.server.deleteAccount",
388 base_url
389 ))
390 .json(&json!({
391 "password": "test",
392 "token": "test"
393 }))
394 .send()
395 .await
396 .expect("Failed to send request");
397 assert_eq!(res1.status(), StatusCode::UNPROCESSABLE_ENTITY);
398
399 let res2 = client
400 .post(format!(
401 "{}/xrpc/com.atproto.server.deleteAccount",
402 base_url
403 ))
404 .json(&json!({
405 "did": "did:web:test",
406 "token": "test"
407 }))
408 .send()
409 .await
410 .expect("Failed to send request");
411 assert_eq!(res2.status(), StatusCode::UNPROCESSABLE_ENTITY);
412
413 let res3 = client
414 .post(format!(
415 "{}/xrpc/com.atproto.server.deleteAccount",
416 base_url
417 ))
418 .json(&json!({
419 "did": "did:web:test",
420 "password": "test"
421 }))
422 .send()
423 .await
424 .expect("Failed to send request");
425 assert_eq!(res3.status(), StatusCode::UNPROCESSABLE_ENTITY);
426}
427
428#[tokio::test]
429async fn test_delete_account_nonexistent_user() {
430 let client = client();
431 let base_url = base_url().await;
432
433 let delete_payload = json!({
434 "did": "did:web:nonexistent.user",
435 "password": "any-password",
436 "token": "any-token"
437 });
438 let delete_res = client
439 .post(format!(
440 "{}/xrpc/com.atproto.server.deleteAccount",
441 base_url
442 ))
443 .json(&delete_payload)
444 .send()
445 .await
446 .expect("Failed to send delete request");
447 assert_eq!(delete_res.status(), StatusCode::BAD_REQUEST);
448
449 let body: Value = delete_res.json().await.unwrap();
450 assert_eq!(body["error"], "AccountNotFound");
451}