Skip to main content

max / makenotwork

7.0 KB · 246 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_eq!(
21 resp.status, 200,
22 "Update profile failed: {} {}",
23 resp.status, resp.text
24 );
25
26 // Non-HTMX form request returns JSON ProfileResponse
27 let profile: Value = resp.json();
28 assert_eq!(profile["username"].as_str().unwrap(), "profuser");
29 assert_eq!(profile["display_name"].as_str().unwrap(), "New Name");
30 assert_eq!(profile["bio"].as_str().unwrap(), "Hello world");
31 }
32
33 #[tokio::test]
34 async fn change_password() {
35 let mut h = TestHarness::new().await;
36 let _user_id = h
37 .signup("passuser", "passuser@test.com", "oldpassword1")
38 .await;
39
40 // Change password
41 let resp = h
42 .client
43 .put_form(
44 "/api/users/me/password",
45 "current_password=oldpassword1&new_password=newpassword1",
46 )
47 .await;
48 assert_eq!(
49 resp.status, 204,
50 "Change password failed: {} {}",
51 resp.status, resp.text
52 );
53
54 h.client.post_form("/logout", "").await;
55
56 // Login with new password should work
57 h.login("passuser", "newpassword1").await;
58 let resp = h.client.get("/dashboard").await;
59 assert_eq!(
60 resp.status, 200,
61 "Should access dashboard with new password"
62 );
63 }
64
65 #[tokio::test]
66 async fn change_password_wrong_current() {
67 let mut h = TestHarness::new().await;
68 let _user_id = h.signup("badpass", "badpass@test.com", "password123").await;
69
70 // Try to change with wrong current password
71 let resp = h
72 .client
73 .put_form(
74 "/api/users/me/password",
75 "current_password=wrongpassword&new_password=newpassword1",
76 )
77 .await;
78 assert_eq!(
79 resp.status, 400,
80 "Should reject wrong current password, got {} {}",
81 resp.status, resp.text
82 );
83 }
84
85 #[tokio::test]
86 async fn email_verification_via_signed_link() {
87 let mut h = TestHarness::new().await;
88 let user_id = h
89 .signup("verifyuser", "verify@test.com", "password123")
90 .await;
91
92 // Ensure email_verified is false so the verification link works
93 sqlx::query("UPDATE users SET email_verified = false WHERE id = $1")
94 .bind(user_id)
95 .execute(&h.db)
96 .await
97 .unwrap();
98
99 // Generate a verification URL the same way the app does
100 let url = makenotwork::email::generate_verification_url(
101 "",
102 user_id,
103 "verify@test.com",
104 SIGNING_SECRET,
105 );
106 // URL is like "/verify-email?user=...&expires=...&sig=..."
107 let path = url.strip_prefix("").unwrap_or(&url);
108
109 let resp = h.client.get(path).await;
110 assert_eq!(
111 resp.status, 200,
112 "Verify email failed: {} {}",
113 resp.status, resp.text
114 );
115 assert!(
116 resp.text.contains("Email Verified"),
117 "Should show verification success page"
118 );
119
120 // Check DB
121 let verified: bool = 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_eq!(
195 resp.status, 303,
196 "Login link failed: {} {}",
197 resp.status, resp.text
198 );
199
200 // Verify we're logged in
201 let resp = h.client.get("/dashboard").await;
202 assert_eq!(
203 resp.status, 200,
204 "Should be logged in after login link, got {}",
205 resp.status
206 );
207 }
208
209 #[tokio::test]
210 async fn resend_verification_when_unverified() {
211 let mut h = TestHarness::new().await;
212 let user_id = h
213 .signup("resenduser", "resend@test.com", "password123")
214 .await;
215
216 // Set email_verified to false so we can test resend
217 sqlx::query("UPDATE users SET email_verified = false WHERE id = $1")
218 .bind(user_id)
219 .execute(&h.db)
220 .await
221 .unwrap();
222
223 // Resend verification, should succeed (email logged in dev mode)
224 let resp = h.client.post_form("/api/resend-verification", "").await;
225 assert_eq!(
226 resp.status, 200,
227 "Resend verification failed: {} {}",
228 resp.status, resp.text
229 );
230
231 // Now verify the email directly in DB
232 sqlx::query("UPDATE users SET email_verified = true WHERE id = $1")
233 .bind(user_id)
234 .execute(&h.db)
235 .await
236 .unwrap();
237
238 // Resend again, should return "already verified" info (still 200, not an error)
239 let resp = h.client.post_form("/api/resend-verification", "").await;
240 assert_eq!(
241 resp.status, 200,
242 "Resend when verified should still succeed: {} {}",
243 resp.status, resp.text
244 );
245 }
246