66# See https://aboutcode.org for more information about AboutCode FOSS projects.
77#
88
9+ from datetime import timedelta
910from unittest .mock import patch
1011
1112from django .conf import settings
1213from django .contrib .auth import get_user_model
1314from django .contrib .auth .models import Group
1415from django .core import mail
16+ from django .core import signing
1517from django .test import TestCase
1618from django .test import override_settings
1719from django .urls import reverse
20+ from django .utils import timezone
1821
1922from django_altcha import AltchaField
23+ from django_registration .backends .activation .views import REGISTRATION_SALT
2024from django_registration .backends .activation .views import RegistrationView
2125
2226from dje .registration import REGISTRATION_DEFAULT_GROUPS
2630@override_settings (
2731 ENABLE_SELF_REGISTRATION = True ,
2832 ADMINS = [("admin" , "admin@nexb.com" )],
33+ ALTCHA_HMAC_KEY = "abcdef123456" ,
2934)
3035class DejaCodeUserRegistrationTestCase (TestCase ):
3136 """Tests for the dejacode.com registration workflow."""
@@ -48,7 +53,6 @@ def setUp(self):
4853 def tearDown (self ):
4954 self .captcha_patch .stop ()
5055
51- @override_settings (ALTCHA_HMAC_KEY = "abcdef123456" )
5256 def test_user_registration_form_submit (self ):
5357 url = reverse ("django_registration_register" )
5458 response = self .client .get (url )
@@ -67,6 +71,8 @@ def test_user_registration_form_submit(self):
6771 self .assertTrue ("Your DejaCode Evaluation account is pending activation." in body )
6872 self .assertTrue ("Username: {}" .format (self .registration_data ["username" ]) in body )
6973 self .assertTrue ("{} days to activate" .format (settings .ACCOUNT_ACTIVATION_DAYS ) in body )
74+ # Verify the activation URL uses the querystring format
75+ self .assertTrue ("?activation_key=" in body )
7076
7177 new_user = get_user_model ().objects .get (username = self .registration_data ["username" ])
7278 self .assertEqual (new_user .email , self .registration_data ["email" ])
@@ -84,7 +90,6 @@ def test_user_registration_form_submit(self):
8490 body = mail .outbox [0 ].body
8591 self .assertTrue ("New registration for user username username@company.com" in body )
8692
87- @override_settings (ALTCHA_HMAC_KEY = "abcdef123456" )
8893 def test_user_registration_form_validators (self ):
8994 self .captcha_patch .stop ()
9095
@@ -126,15 +131,19 @@ def test_user_registration_account_activation(self):
126131
127132 self .assertEqual ("[DejaCode] Please activate your account" , mail .outbox [1 ].subject )
128133 activation_key = RegistrationView ().get_activation_key (user )
129- activation_url = reverse ("django_registration_activate" , args = [ activation_key ] )
134+ activation_url = reverse ("django_registration_activate" )
130135 # Check the validity of URL in activation email
131136 # WARNING: The key of the URL set in the email may be different since it is not
132- # generated at the same time as the `activation_key` and the results is based on
137+ # generated at the same time as the `activation_key` and the result is based on
133138 # the timestamp.
134- self .assertTrue (activation_url in mail .outbox [1 ].body )
135-
136- # Call the url to activate the account
137- response = self .client .get (activation_url , follow = True )
139+ expected_url_in_email = f"{ activation_url } ?activation_key={ activation_key } "
140+ self .assertTrue (expected_url_in_email in mail .outbox [1 ].body )
141+ # Activate the account via POST (django-registration 5.x requires POST)
142+ response = self .client .post (
143+ activation_url ,
144+ data = {"activation_key" : activation_key },
145+ follow = True ,
146+ )
138147 self .assertRedirects (response , reverse ("django_registration_activation_complete" ))
139148 self .assertContains (response , "account is now active" )
140149 # Now the user is active
@@ -143,16 +152,100 @@ def test_user_registration_account_activation(self):
143152 self .assertTrue (user .has_usable_password ())
144153 self .assertTrue (user .is_staff )
145154 self .assertFalse (user .is_superuser )
146-
147- # Make sure the activation link can be use multiple time within the
155+ # Make sure the activation link can be used multiple times within the
148156 # `ACCOUNT_ACTIVATION_DAYS` period.
149- response = self .client .get (activation_url , follow = True )
157+ response = self .client .post (
158+ activation_url ,
159+ data = {"activation_key" : activation_key },
160+ follow = True ,
161+ )
150162 self .assertContains (response , "account is now active" )
151163
164+ def test_user_registration_activation_form_displayed_on_get (self ):
165+ url = reverse ("django_registration_register" )
166+ self .client .post (url , self .registration_data )
167+ user = get_user_model ().objects .get (username = self .registration_data ["username" ])
168+ activation_key = RegistrationView ().get_activation_key (user )
169+ activation_url = reverse ("django_registration_activate" )
170+
171+ response = self .client .get (f"{ activation_url } ?activation_key={ activation_key } " )
172+ self .assertEqual (response .status_code , 200 )
173+ # The page should display the activation form, not redirect or auto-activate
174+ self .assertContains (response , "Activate my account" )
175+ # User should NOT be active yet (security: GET should not activate)
176+ user .refresh_from_db ()
177+ self .assertFalse (user .is_active )
178+
152179 def test_user_registration_activate_wrong_key (self ):
153- activation_url = reverse ("django_registration_activate" , args = ["wrong_key" ])
154- response = self .client .get (activation_url )
155- self .assertContains (response , "Error during your account activation" )
180+ activation_url = reverse ("django_registration_activate" )
181+ response = self .client .post (activation_url , data = {"activation_key" : "wrong_key" })
182+ self .assertEqual (response .status_code , 200 )
183+ # The form should reject the invalid activation key
184+ self .assertContains (response , "invalid" )
185+ # No user should have been created or activated
186+ self .assertEqual (get_user_model ().objects .count (), 0 )
187+
188+ def test_user_registration_activate_empty_key (self ):
189+ activation_url = reverse ("django_registration_activate" )
190+ response = self .client .post (activation_url , data = {"activation_key" : "" })
191+ self .assertEqual (response .status_code , 200 )
192+ # Form should reject the empty key
193+ self .assertContains (response , "required" )
194+
195+ def test_user_registration_activate_expired_key (self ):
196+ url = reverse ("django_registration_register" )
197+ self .client .post (url , self .registration_data )
198+ user = get_user_model ().objects .get (username = self .registration_data ["username" ])
199+
200+ # Generate a key with a timestamp older than ACCOUNT_ACTIVATION_DAYS
201+ expired_days = settings .ACCOUNT_ACTIVATION_DAYS + 1
202+ with patch ("django.core.signing.time.time" ) as mock_time :
203+ past_timestamp = (timezone .now () - timedelta (days = expired_days )).timestamp ()
204+ mock_time .return_value = past_timestamp
205+ expired_key = signing .dumps (obj = user .username , salt = REGISTRATION_SALT )
206+
207+ activation_url = reverse ("django_registration_activate" )
208+ response = self .client .post (activation_url , data = {"activation_key" : expired_key })
209+ self .assertEqual (response .status_code , 200 )
210+ self .assertContains (response , "expired" )
211+ # User should remain inactive
212+ user .refresh_from_db ()
213+ self .assertFalse (user .is_active )
214+
215+ def test_user_registration_activate_unknown_user (self ):
216+ # Generate a key for a user that doesn't exist
217+ bogus_key = signing .dumps (obj = "nonexistent_user" , salt = REGISTRATION_SALT )
218+ activation_url = reverse ("django_registration_activate" )
219+ response = self .client .post (activation_url , data = {"activation_key" : bogus_key }, follow = True )
220+ self .assertEqual (response .status_code , 200 )
221+ # Should display the activation error template content
222+ self .assertContains (response , "The account you attempted to activate is invalid" )
223+
224+ def test_user_registration_unique_email (self ):
225+ url = reverse ("django_registration_register" )
226+ # First registration succeeds
227+ self .client .post (url , self .registration_data )
228+ # Second registration with same email but different username
229+ duplicate_data = dict (self .registration_data )
230+ duplicate_data ["username" ] = "different_username"
231+ response = self .client .post (url , duplicate_data )
232+ self .assertEqual (response .status_code , 200 )
233+ self .assertIn ("email" , response .context ["form" ].errors )
234+
235+ def test_user_registration_unique_username (self ):
236+ url = reverse ("django_registration_register" )
237+ self .client .post (url , self .registration_data )
238+ duplicate_data = dict (self .registration_data )
239+ duplicate_data ["email" ] = "different@company.com"
240+ response = self .client .post (url , duplicate_data )
241+ self .assertEqual (response .status_code , 200 )
242+ self .assertIn ("username" , response .context ["form" ].errors )
243+
244+ def test_user_registration_default_dataspace_assigned (self ):
245+ url = reverse ("django_registration_register" )
246+ self .client .post (url , self .registration_data )
247+ new_user = get_user_model ().objects .get (username = self .registration_data ["username" ])
248+ self .assertEqual (new_user .dataspace .name , "Evaluation" )
156249
157250 def test_user_registration_default_groups (self ):
158251 for group_name in REGISTRATION_DEFAULT_GROUPS :
@@ -164,7 +257,41 @@ def test_user_registration_default_groups(self):
164257 new_user = get_user_model ().objects .get (username = self .registration_data ["username" ])
165258 self .assertEqual (len (REGISTRATION_DEFAULT_GROUPS ), new_user .groups .count ())
166259
260+ def test_user_registration_default_groups_missing (self ):
261+ # Don't create any groups
262+ url = reverse ("django_registration_register" )
263+ response = self .client .post (url , self .registration_data , follow = True )
264+ # Registration should succeed
265+ self .assertRedirects (response , reverse ("django_registration_complete" ))
266+ new_user = get_user_model ().objects .get (username = self .registration_data ["username" ])
267+ self .assertEqual (0 , new_user .groups .count ())
268+
269+ def test_user_registration_password_field_only_password1 (self ):
270+ url = reverse ("django_registration_register" )
271+ response = self .client .get (url )
272+ self .assertContains (response , 'name="password1"' )
273+ self .assertNotContains (response , 'name="password2"' )
274+
167275 @override_settings (REGISTRATION_OPEN = False )
168276 def test_user_registration_closed (self ):
169277 resp = self .client .get (reverse ("django_registration_register" ))
170278 self .assertRedirects (resp , reverse ("django_registration_disallowed" ))
279+
280+ @override_settings (REGISTRATION_OPEN = False )
281+ def test_user_registration_closed_post_blocked (self ):
282+ resp = self .client .post (reverse ("django_registration_register" ), self .registration_data )
283+ self .assertRedirects (resp , reverse ("django_registration_disallowed" ))
284+ # No user should have been created
285+ self .assertEqual (get_user_model ().objects .count (), 0 )
286+
287+ def test_user_registration_admin_notification_email_sent (self ):
288+ url = reverse ("django_registration_register" )
289+ self .client .post (url , self .registration_data )
290+
291+ admin_email = mail .outbox [0 ]
292+ self .assertEqual ("[DejaCode] New User registration" , admin_email .subject )
293+ # Check that admin@nexb.com is in any of the recipient tuples or strings
294+ recipients_str = str (admin_email .to )
295+ self .assertIn ("admin@nexb.com" , recipients_str )
296+ self .assertIn (self .registration_data ["username" ], admin_email .body )
297+ self .assertIn (self .registration_data ["email" ], admin_email .body )
0 commit comments