Skip to main content

max / makenotwork

3.5 KB · 113 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/{}/items", project_id),
30 "title=Last+Item&price_cents=0",
31 )
32 .await;
33 assert!(resp.status.is_success(), "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!(
41 resp.status.is_success(),
42 "Request deletion failed: {} {}",
43 resp.status,
44 resp.text
45 );
46
47 // Delete the user via the two-step confirm-delete flow.
48 // Step 1: GET the confirmation page (validates the signed link, renders a form).
49 // Step 2: POST the form to perform the actual deletion.
50 let expires = chrono::Utc::now().timestamp() + 3600;
51 let sig = makenotwork::email::generate_deletion_signature(
52 "test-signing-secret-for-integration-tests",
53 user_id,
54 expires,
55 "doomed@example.com",
56 );
57
58 let confirm_url = format!(
59 "/confirm-delete?user={}&expires={}&sig={}",
60 user_id, expires, sig
61 );
62
63 // Step 1: GET renders the confirmation page (no deletion yet)
64 let resp = h.client.get(&confirm_url).await;
65 assert!(
66 resp.status.is_success(),
67 "Confirm delete page failed: {} {}",
68 resp.status,
69 resp.text
70 );
71 assert!(
72 resp.text.contains("Delete My Account Permanently"),
73 "Confirmation page should contain the delete button"
74 );
75
76 // Step 2: POST performs the actual deletion
77 let form_body = format!("user={}&expires={}&sig={}", user_id, expires, sig);
78 let resp = h.client.post_form("/confirm-delete", &form_body).await;
79 assert!(
80 resp.status.is_success(),
81 "Confirm delete POST failed: {} {}",
82 resp.status,
83 resp.text
84 );
85
86 // Verify cascade: user, projects, and items should all be gone
87 let user_count =
88 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 = sqlx::query_scalar::<_, i64>(
96 "SELECT COUNT(*) FROM projects WHERE user_id = $1",
97 )
98 .bind(user_id)
99 .fetch_one(&h.db)
100 .await
101 .unwrap();
102 assert_eq!(project_count, 0, "Projects should be cascade-deleted");
103
104 let item_count = sqlx::query_scalar::<_, i64>(
105 "SELECT COUNT(*) FROM items WHERE project_id = $1",
106 )
107 .bind(project_id.parse::<uuid::Uuid>().unwrap())
108 .fetch_one(&h.db)
109 .await
110 .unwrap();
111 assert_eq!(item_count, 0, "Items should be cascade-deleted");
112 }
113