//! Account deletion: create user + project + item -> delete -> verify cascade use crate::harness::TestHarness; use serde_json::Value; #[tokio::test] async fn account_deletion_cascades() { let mut h = TestHarness::new().await; // Create user with content let user_id = h .signup("doomed", "doomed@example.com", "password123") .await; h.grant_creator(user_id).await; h.client.post_form("/logout", "").await; h.login("doomed", "password123").await; // Create project + item so we can verify cascade let resp = h .client .post_form("/api/projects", "slug=farewell&title=Farewell") .await; let project: Value = resp.json(); let project_id = project["id"].as_str().unwrap(); let resp = h .client .post_form( &format!("/api/projects/{}/items", project_id), "title=Last+Item&price_cents=0", ) .await; assert!(resp.status.is_success(), "Create item failed: {}", resp.text); // Request account deletion — this sends an email in dev mode (logged) let resp = h .client .post_form("/api/account/request-deletion", "username=doomed") .await; assert!( resp.status.is_success(), "Request deletion failed: {} {}", resp.status, resp.text ); // Delete the user via the two-step confirm-delete flow. // Step 1: GET the confirmation page (validates the signed link, renders a form). // Step 2: POST the form to perform the actual deletion. let expires = chrono::Utc::now().timestamp() + 3600; let sig = makenotwork::email::generate_deletion_signature( "test-signing-secret-for-integration-tests", user_id, expires, "doomed@example.com", ); let confirm_url = format!( "/confirm-delete?user={}&expires={}&sig={}", user_id, expires, sig ); // Step 1: GET renders the confirmation page (no deletion yet) let resp = h.client.get(&confirm_url).await; assert!( resp.status.is_success(), "Confirm delete page failed: {} {}", resp.status, resp.text ); assert!( resp.text.contains("Delete My Account Permanently"), "Confirmation page should contain the delete button" ); // Step 2: POST performs the actual deletion let form_body = format!("user={}&expires={}&sig={}", user_id, expires, sig); let resp = h.client.post_form("/confirm-delete", &form_body).await; assert!( resp.status.is_success(), "Confirm delete POST failed: {} {}", resp.status, resp.text ); // Verify cascade: user, projects, and items should all be gone let user_count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM users WHERE id = $1") .bind(user_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(user_count, 0, "User should be deleted"); let project_count = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM projects WHERE user_id = $1", ) .bind(user_id) .fetch_one(&h.db) .await .unwrap(); assert_eq!(project_count, 0, "Projects should be cascade-deleted"); let item_count = sqlx::query_scalar::<_, i64>( "SELECT COUNT(*) FROM items WHERE project_id = $1", ) .bind(project_id.parse::().unwrap()) .fetch_one(&h.db) .await .unwrap(); assert_eq!(item_count, 0, "Items should be cascade-deleted"); }