Skip to main content

max / makenotwork

3.4 KB · 104 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_eq!(resp.status, 200, "Create item failed: {}", resp.text);
34
35 // Request account deletion, this sends an email in dev mode (logged)
36 let resp = h
37 .client
38 .post_form("/api/account/request-deletion", "username=doomed")
39 .await;
40 assert_eq!(
41 resp.status, 200,
42 "Request deletion failed: {} {}",
43 resp.status, resp.text
44 );
45
46 // Delete the user via the two-step confirm-delete flow.
47 // Step 1: GET the confirmation page (validates the signed link, renders a form).
48 // Step 2: POST the form to perform the actual deletion.
49 let expires = chrono::Utc::now().timestamp() + 3600;
50 let sig = makenotwork::email::generate_deletion_signature(
51 "test-signing-secret-for-integration-tests",
52 user_id,
53 expires,
54 "doomed@example.com",
55 );
56
57 let confirm_url = format!("/confirm-delete?user={user_id}&expires={expires}&sig={sig}");
58
59 // Step 1: GET renders the confirmation page (no deletion yet)
60 let resp = h.client.get(&confirm_url).await;
61 assert_eq!(
62 resp.status, 200,
63 "Confirm delete page failed: {} {}",
64 resp.status, resp.text
65 );
66 assert!(
67 resp.text.contains("Delete My Account Permanently"),
68 "Confirmation page should contain the delete button"
69 );
70
71 // Step 2: POST performs the actual deletion
72 let form_body = format!("user={user_id}&expires={expires}&sig={sig}");
73 let resp = h.client.post_form("/confirm-delete", &form_body).await;
74 assert_eq!(
75 resp.status, 200,
76 "Confirm delete POST failed: {} {}",
77 resp.status, resp.text
78 );
79
80 // Verify cascade: user, projects, and items should all be gone
81 let user_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users WHERE id = $1")
82 .bind(user_id)
83 .fetch_one(&h.db)
84 .await
85 .unwrap();
86 assert_eq!(user_count, 0, "User should be deleted");
87
88 let project_count =
89 sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM projects WHERE user_id = $1")
90 .bind(user_id)
91 .fetch_one(&h.db)
92 .await
93 .unwrap();
94 assert_eq!(project_count, 0, "Projects should be cascade-deleted");
95
96 let item_count =
97 sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM items WHERE project_id = $1")
98 .bind(project_id.parse::<uuid::Uuid>().unwrap())
99 .fetch_one(&h.db)
100 .await
101 .unwrap();
102 assert_eq!(item_count, 0, "Items should be cascade-deleted");
103 }
104