Skip to main content

max / makenotwork

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