Skip to main content

max / makenotwork

3.5 KB · 111 lines History Blame Raw
1 //! Account deletion: create user + project + item -> delete -> verify cascade
2
3 use crate::harness::TestHarness;
4 use serde_json::Value;
5
6 #[tokio::test]
7 async fn account_deletion_cascades() {
8 let mut h = TestHarness::new().await;
9
10 // Create user with content
11 let user_id = h
12 .signup("doomed", "doomed@example.com", "password123")
13 .await;
14 h.grant_creator(user_id).await;
15 h.client.post_form("/logout", "").await;
16 h.login("doomed", "password123").await;
17
18 // Create project + item so we can verify cascade
19 let resp = h
20 .client
21 .post_form("/api/projects", "slug=farewell&title=Farewell")
22 .await;
23 let project: Value = resp.json();
24 let project_id = project["id"].as_str().unwrap();
25
26 let resp = h
27 .client
28 .post_form(
29 &format!("/api/projects/{project_id}/items"),
30 "title=Last+Item&price_cents=0",
31 )
32 .await;
33 assert!(
34 resp.status.is_success(),
35 "Create item failed: {}",
36 resp.text
37 );
38
39 // Request account deletion, this sends an email in dev mode (logged)
40 let resp = h
41 .client
42 .post_form("/api/account/request-deletion", "username=doomed")
43 .await;
44 assert!(
45 resp.status.is_success(),
46 "Request deletion failed: {} {}",
47 resp.status,
48 resp.text
49 );
50
51 // Delete the user via the two-step confirm-delete flow.
52 // Step 1: GET the confirmation page (validates the signed link, renders a form).
53 // Step 2: POST the form to perform the actual deletion.
54 let expires = chrono::Utc::now().timestamp() + 3600;
55 let sig = makenotwork::email::generate_deletion_signature(
56 "test-signing-secret-for-integration-tests",
57 user_id,
58 expires,
59 "doomed@example.com",
60 );
61
62 let confirm_url = format!("/confirm-delete?user={user_id}&expires={expires}&sig={sig}");
63
64 // Step 1: GET renders the confirmation page (no deletion yet)
65 let resp = h.client.get(&confirm_url).await;
66 assert!(
67 resp.status.is_success(),
68 "Confirm delete page failed: {} {}",
69 resp.status,
70 resp.text
71 );
72 assert!(
73 resp.text.contains("Delete My Account Permanently"),
74 "Confirmation page should contain the delete button"
75 );
76
77 // Step 2: POST performs the actual deletion
78 let form_body = format!("user={user_id}&expires={expires}&sig={sig}");
79 let resp = h.client.post_form("/confirm-delete", &form_body).await;
80 assert!(
81 resp.status.is_success(),
82 "Confirm delete POST failed: {} {}",
83 resp.status,
84 resp.text
85 );
86
87 // Verify cascade: user, projects, and items should all be gone
88 let user_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users WHERE id = $1")
89 .bind(user_id)
90 .fetch_one(&h.db)
91 .await
92 .unwrap();
93 assert_eq!(user_count, 0, "User should be deleted");
94
95 let project_count =
96 sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM projects WHERE user_id = $1")
97 .bind(user_id)
98 .fetch_one(&h.db)
99 .await
100 .unwrap();
101 assert_eq!(project_count, 0, "Projects should be cascade-deleted");
102
103 let item_count =
104 sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM items WHERE project_id = $1")
105 .bind(project_id.parse::<uuid::Uuid>().unwrap())
106 .fetch_one(&h.db)
107 .await
108 .unwrap();
109 assert_eq!(item_count, 0, "Items should be cascade-deleted");
110 }
111