Skip to main content

max / makenotwork

6.4 KB · 207 lines History Blame Raw
1 //! Auth workflow: signup -> dashboard -> logout -> dashboard (redirect)
2
3 use crate::harness::TestHarness;
4
5 #[tokio::test]
6 async fn signup_login_logout_flow() {
7 let mut h = TestHarness::new().await;
8
9 // Sign up
10 let _user_id = h.signup("testuser", "test@example.com", "password123").await;
11
12 // Should be logged in — dashboard returns 200
13 let resp = h.client.get("/dashboard").await;
14 assert_eq!(resp.status, 200, "Dashboard should be accessible after signup");
15
16 // Logout
17 let resp = h.client.post_form("/logout", "").await;
18 assert!(
19 resp.status.is_success() || resp.status.is_redirection(),
20 "Logout should succeed"
21 );
22
23 // Dashboard should now redirect (302) or return 401
24 let resp = h.client.get("/dashboard").await;
25 assert!(
26 resp.status == 302 || resp.status == 303 || resp.status == 401,
27 "Dashboard should redirect after logout, got {}",
28 resp.status
29 );
30 }
31
32 #[tokio::test]
33 async fn login_with_existing_account() {
34 let mut h = TestHarness::new().await;
35
36 // Sign up and then log out
37 let _user_id = h.signup("alice", "alice@example.com", "secure_pass99").await;
38 h.client.post_form("/logout", "").await;
39
40 // Log back in
41 h.login("alice", "secure_pass99").await;
42
43 // Dashboard should be accessible
44 let resp = h.client.get("/dashboard").await;
45 assert_eq!(resp.status, 200, "Dashboard should be accessible after login");
46 }
47
48 #[tokio::test]
49 async fn wrong_password_rejected() {
50 let mut h = TestHarness::new().await;
51 let _user_id = h.signup("wp_user", "wp@example.com", "correctpass1").await;
52 h.client.post_form("/logout", "").await;
53
54 let resp = h
55 .client
56 .post_form("/login", "login=wp_user&password=totallyWrong")
57 .await;
58 assert!(
59 resp.status != 200 && resp.status != 303,
60 "Wrong password should not yield 200 or 303, got {}",
61 resp.status
62 );
63 }
64
65 #[tokio::test]
66 async fn nonexistent_user_rejected() {
67 let mut h = TestHarness::new().await;
68
69 let resp = h
70 .client
71 .post_form("/login", "login=ghost_user_xyz&password=anypass123")
72 .await;
73 assert!(
74 resp.status != 200 && resp.status != 303,
75 "Nonexistent user login should not yield 200 or 303, got {}",
76 resp.status
77 );
78 }
79
80 #[tokio::test]
81 async fn duplicate_email_rejected() {
82 let mut h = TestHarness::new().await;
83 let _user_id = h.signup("orig_user", "dupe@example.com", "password123").await;
84 h.client.post_form("/logout", "").await;
85
86 // Attempt signup with the same email but a different username
87 let resp = h
88 .client
89 .post_form(
90 "/join",
91 "username=other_user&email=dupe@example.com&password=password123&password_confirm=password123",
92 )
93 .await;
94 assert!(
95 resp.status.is_client_error() || resp.text.contains("already") || resp.text.contains("taken"),
96 "Duplicate email signup should fail: {} {}",
97 resp.status, resp.text
98 );
99 }
100
101 #[tokio::test]
102 async fn duplicate_username_rejected() {
103 let mut h = TestHarness::new().await;
104 let _user_id = h.signup("taken_name", "first@example.com", "password123").await;
105 h.client.post_form("/logout", "").await;
106
107 // Attempt signup with the same username but a different email
108 let resp = h
109 .client
110 .post_form(
111 "/join",
112 "username=taken_name&email=second@example.com&password=password123&password_confirm=password123",
113 )
114 .await;
115 assert!(
116 resp.status.is_client_error() || resp.text.contains("already") || resp.text.contains("taken"),
117 "Duplicate username signup should fail: {} {}",
118 resp.status, resp.text
119 );
120 }
121
122 #[tokio::test]
123 async fn login_with_email() {
124 let mut h = TestHarness::new().await;
125 let _user_id = h.signup("emaillogin", "emaillogin@example.com", "password123").await;
126 h.client.post_form("/logout", "").await;
127
128 // Login using email address instead of username
129 h.login("emaillogin@example.com", "password123").await;
130
131 let resp = h.client.get("/dashboard").await;
132 assert_eq!(
133 resp.status, 200,
134 "Dashboard should be accessible after login with email"
135 );
136 }
137
138 #[tokio::test]
139 async fn password_change_flow() {
140 let mut h = TestHarness::new().await;
141 let _user_id = h.signup("pwchange", "pwchange@example.com", "oldpass123").await;
142
143 // Change password via PUT form
144 let resp = h
145 .client
146 .put_form(
147 "/api/users/me/password",
148 "current_password=oldpass123&new_password=newpass456",
149 )
150 .await;
151 assert!(
152 resp.status.is_success(),
153 "Password change should succeed: {} {}",
154 resp.status, resp.text
155 );
156
157 // Logout
158 h.client.post_form("/logout", "").await;
159
160 // Login with new password should succeed
161 h.login("pwchange", "newpass456").await;
162 let resp = h.client.get("/dashboard").await;
163 assert_eq!(resp.status, 200, "New password should grant dashboard access");
164
165 // Logout and try old password
166 h.client.post_form("/logout", "").await;
167 let resp = h
168 .client
169 .post_form("/login", "login=pwchange&password=oldpass123")
170 .await;
171 assert!(
172 resp.status != 200 && resp.status != 303,
173 "Old password should no longer work, got {}",
174 resp.status
175 );
176 }
177
178 #[tokio::test]
179 async fn lockout_after_failed_attempts() {
180 let mut h = TestHarness::new().await;
181 let _user_id = h.signup("lockme", "lockme@example.com", "rightpass1").await;
182 h.client.post_form("/logout", "").await;
183
184 // 5 failed login attempts. `/login` is Manual-CSRF now (Phase 2), so
185 // each attempt must refresh the token — the helper handles that.
186 for _ in 0..5 {
187 h.failed_login_attempt("lockme", "wrongwrong").await;
188 }
189
190 // Now try with correct password -- should be locked out. login_handler
191 // returns 200 with the form re-rendered + inline "Account is locked"
192 // message (same UX convention as the inline-error pattern); a successful
193 // login would be a 303 redirect, so absence of redirect + body containing
194 // "locked" together prove lockout.
195 let resp = h.failed_login_attempt("lockme", "rightpass1").await;
196 assert!(
197 !resp.status.is_redirection(),
198 "Lockout should not redirect to dashboard, got {}",
199 resp.status
200 );
201 assert!(
202 resp.text.to_lowercase().contains("locked"),
203 "Response should mention lockout: {}",
204 resp.text
205 );
206 }
207