Skip to main content

max / makenotwork

7.2 KB · 249 lines History Blame Raw
1 //! Account management: profile updates, password changes, email verification, login links.
2
3 use crate::harness::TestHarness;
4 use serde_json::Value;
5
6 const SIGNING_SECRET: &str = "test-signing-secret-for-integration-tests";
7
8 #[tokio::test]
9 async fn update_profile() {
10 let mut h = TestHarness::new().await;
11 let _user_id = h.signup("profuser", "profuser@test.com", "password123").await;
12
13 // Update profile via form (route uses Form extractor)
14 let resp = h
15 .client
16 .put_form(
17 "/api/users/me",
18 "display_name=New+Name&bio=Hello+world",
19 )
20 .await;
21 assert!(
22 resp.status.is_success(),
23 "Update profile failed: {} {}",
24 resp.status,
25 resp.text
26 );
27
28 // Non-HTMX form request returns JSON ProfileResponse
29 let profile: Value = resp.json();
30 assert_eq!(profile["username"].as_str().unwrap(), "profuser");
31 assert_eq!(profile["display_name"].as_str().unwrap(), "New Name");
32 assert_eq!(profile["bio"].as_str().unwrap(), "Hello world");
33 }
34
35 #[tokio::test]
36 async fn change_password() {
37 let mut h = TestHarness::new().await;
38 let _user_id = h
39 .signup("passuser", "passuser@test.com", "oldpassword1")
40 .await;
41
42 // Change password
43 let resp = h
44 .client
45 .put_form(
46 "/api/users/me/password",
47 "current_password=oldpassword1&new_password=newpassword1",
48 )
49 .await;
50 assert!(
51 resp.status.is_success(),
52 "Change password failed: {} {}",
53 resp.status,
54 resp.text
55 );
56
57 // Logout
58 h.client.post_form("/logout", "").await;
59
60 // Login with new password should work
61 h.login("passuser", "newpassword1").await;
62 let resp = h.client.get("/dashboard").await;
63 assert_eq!(resp.status, 200, "Should access dashboard with new password");
64 }
65
66 #[tokio::test]
67 async fn change_password_wrong_current() {
68 let mut h = TestHarness::new().await;
69 let _user_id = h
70 .signup("badpass", "badpass@test.com", "password123")
71 .await;
72
73 // Try to change with wrong current password
74 let resp = h
75 .client
76 .put_form(
77 "/api/users/me/password",
78 "current_password=wrongpassword&new_password=newpassword1",
79 )
80 .await;
81 assert_eq!(
82 resp.status, 400,
83 "Should reject wrong current password, got {} {}",
84 resp.status, resp.text
85 );
86 }
87
88 #[tokio::test]
89 async fn email_verification_via_signed_link() {
90 let mut h = TestHarness::new().await;
91 let user_id = h
92 .signup("verifyuser", "verify@test.com", "password123")
93 .await;
94
95 // Ensure email_verified is false so the verification link works
96 sqlx::query("UPDATE users SET email_verified = false WHERE id = $1")
97 .bind(user_id)
98 .execute(&h.db)
99 .await
100 .unwrap();
101
102 // Generate a verification URL the same way the app does
103 let url = makenotwork::email::generate_verification_url("", user_id, "verify@test.com", SIGNING_SECRET);
104 // URL is like "/verify-email?user=...&expires=...&sig=..."
105 let path = url.strip_prefix("").unwrap_or(&url);
106
107 let resp = h.client.get(path).await;
108 assert!(
109 resp.status.is_success(),
110 "Verify email failed: {} {}",
111 resp.status,
112 resp.text
113 );
114 assert!(
115 resp.text.contains("Email Verified"),
116 "Should show verification success page"
117 );
118
119 // Check DB
120 let verified: bool =
121 sqlx::query_scalar("SELECT email_verified FROM users WHERE id = $1")
122 .bind(user_id)
123 .fetch_one(&h.db)
124 .await
125 .unwrap();
126 assert!(verified, "email_verified should be true after verification");
127 }
128
129 #[tokio::test]
130 async fn email_verification_already_verified() {
131 let mut h = TestHarness::new().await;
132 let user_id = h
133 .signup("alreadyv", "alreadyv@test.com", "password123")
134 .await;
135
136 // Set email_verified = true so we can test the "already verified" path
137 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
138 .bind(user_id)
139 .execute(&h.db)
140 .await
141 .unwrap();
142
143 // Generate URL and hit it — should redirect to dashboard (not error)
144 let url = makenotwork::email::generate_verification_url(
145 "",
146 user_id,
147 "alreadyv@test.com",
148 SIGNING_SECRET,
149 );
150
151 let resp = h.client.get(&url).await;
152 // Already verified → redirect to /dashboard (302/303)
153 assert!(
154 resp.status.is_redirection(),
155 "Already verified should redirect, got {} {}",
156 resp.status,
157 resp.text
158 );
159 }
160
161 #[tokio::test]
162 async fn login_link() {
163 let mut h = TestHarness::new().await;
164 let user_id = h
165 .signup("linkuser", "linkuser@test.com", "password123")
166 .await;
167
168 // Generate a one-time login token
169 let (token, token_hash) = makenotwork::email::generate_login_token();
170
171 // Store it in the DB via direct SQL (db::auth is pub(crate))
172 let expires_at = chrono::Utc::now() + chrono::Duration::minutes(15);
173 sqlx::query("INSERT INTO login_tokens (user_id, token_hash, expires_at) VALUES ($1, $2, $3)")
174 .bind(user_id)
175 .bind(&token_hash)
176 .bind(expires_at)
177 .execute(&h.db)
178 .await
179 .expect("Failed to create login token");
180
181 // Logout first
182 h.client.post_form("/logout", "").await;
183
184 // Verify we're logged out
185 let resp = h.client.get("/dashboard").await;
186 assert!(
187 resp.status == 302 || resp.status == 303 || resp.status == 401,
188 "Should be logged out, got {}",
189 resp.status
190 );
191
192 // Use the login link
193 let resp = h.client.get(&format!("/login-link?token={}", token)).await;
194 assert!(
195 resp.status.is_success() || resp.status.is_redirection(),
196 "Login link failed: {} {}",
197 resp.status,
198 resp.text
199 );
200
201 // Verify we're logged in
202 let resp = h.client.get("/dashboard").await;
203 assert_eq!(
204 resp.status, 200,
205 "Should be logged in after login link, got {}",
206 resp.status
207 );
208 }
209
210 #[tokio::test]
211 async fn resend_verification_when_unverified() {
212 let mut h = TestHarness::new().await;
213 let user_id = h
214 .signup("resenduser", "resend@test.com", "password123")
215 .await;
216
217 // Set email_verified to false so we can test resend
218 sqlx::query("UPDATE users SET email_verified = false WHERE id = $1")
219 .bind(user_id)
220 .execute(&h.db)
221 .await
222 .unwrap();
223
224 // Resend verification — should succeed (email logged in dev mode)
225 let resp = h.client.post_form("/api/resend-verification", "").await;
226 assert!(
227 resp.status.is_success(),
228 "Resend verification failed: {} {}",
229 resp.status,
230 resp.text
231 );
232
233 // Now verify the email directly in DB
234 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
235 .bind(user_id)
236 .execute(&h.db)
237 .await
238 .unwrap();
239
240 // Resend again — should return "already verified" info (still 200, not an error)
241 let resp = h.client.post_form("/api/resend-verification", "").await;
242 assert!(
243 resp.status.is_success(),
244 "Resend when verified should still succeed: {} {}",
245 resp.status,
246 resp.text
247 );
248 }
249