Source code for users.tests

# Run from /backend with: python manage.py test users
from datetime import timedelta

from django.conf import settings
from django.core.exceptions import ValidationError
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone

from users.models import User
from users.services import Conflict, register_user


[docs] class UserModelTests(TestCase):
[docs] def test_create_user(self): """Verifies user creation""" user = User.objects.create( email="test_user@ucla.edu", password="password12345678", role="MENTEE" ) self.assertEqual(user.email, "test_user@ucla.edu") self.assertEqual(user.role, "MENTEE")
[docs] def test_user_requires_email(self): """Ensures email is required""" user = User(email="", password="password12345678") with self.assertRaises(ValidationError): user.full_clean() user.save()
[docs] def test_user_requires_valid_ucla_email(self): """Validates UCLA email domain""" user1 = User(email="invalid@gmail.edu", password="password12345678") with self.assertRaises(ValidationError): user1.full_clean() user1.save() user2 = User(email="invalid@ucla.com", password="password12345678") with self.assertRaises(ValidationError): user2.full_clean() user2.save()
[docs] class UserMatchingLogicTests(TestCase):
[docs] def setUp(self): self.mentor = User.objects.create_user(email="mentor@ucla.edu", password="pw", role="MENTOR") self.mentee1 = User.objects.create_user(email="mentee1@ucla.edu", password="pw", role="MENTEE") self.mentee2 = User.objects.create_user(email="mentee2@ucla.edu", password="pw", role="MENTEE") self.mentee3 = User.objects.create_user(email="mentee3@ucla.edu", password="pw", role="MENTEE")
[docs] def test_max_mentee_limit(self): """Ensures a mentor cannot exceed the MAX_MENTEES limit""" # Add first two mentees (should succeed) self.assertTrue(self.mentor.add_mentee(self.mentee1.email)) self.assertTrue(self.mentor.add_mentee(self.mentee2.email)) # Attempt to add a third mentee (should fail) self.assertFalse(self.mentor.add_mentee(self.mentee3.email)) # Verify mentor status automatically updated to 'isMatched' (full) self.assertTrue(self.mentor.isMatched)
[docs] def test_blacklisted_mentor_rejection(self): """Ensures a mentor cannot add a mentee who blacklisted them""" self.mentee1.blacklist_mentor(self.mentor.email) # Attempt to add the mentee (should fail due to blacklist) self.assertFalse(self.mentor.add_mentee(self.mentee1.email))
[docs] class UserProfileSecurityTests(TestCase):
[docs] def setUp(self): self.user = User.objects.create_user(email="student@ucla.edu", password="pw", role="MENTEE")
[docs] def test_update_profile_ignores_protected_fields(self): """Ensures users cannot overwrite protected fields like isMatched via profile updates""" malicious_data = { "firstName": "Hacker", # Allowed field "role": "ADMIN", # Protected field "isMatched": True # Protected field } self.user.update_profile(**malicious_data) # Verify allowed field changed self.assertEqual(self.user.firstName, "Hacker") # Verify protected fields were completely ignored self.assertEqual(self.user.role, "MENTEE") self.assertFalse(self.user.isMatched)
[docs] def test_complete_survey_has_same_protections(self): """complete_survey() should have same security as update_profile()""" self.user.complete_survey({ "firstName": "Hacker", "role": "ADMIN", "isMatched": True }) self.assertEqual(self.user.firstName, "Hacker") self.assertEqual(self.user.role, "MENTEE") self.assertFalse(self.user.isMatched)
[docs] def test_update_profile_allows_mentor_mentee_role_change(self): """Users can switch between MENTOR and MENTEE""" self.user.update_profile(role="MENTOR") self.assertEqual(self.user.role, "MENTOR") self.user.update_profile(role="MENTEE") self.assertEqual(self.user.role, "MENTEE")
[docs] def test_change_role_blocks_same_role(self): """change_role() prevents no-op changes""" with self.assertRaises(ValueError) as context: self.user.change_role("MENTEE") # already MENTEE self.assertIn("already have this role", str(context.exception))
[docs] class AdminCreationTests(TestCase): """Ensure ADMIN users can only be created via safe methods"""
[docs] def test_admin_creation_via_create_superuser(self): """Admins should be created via create_superuser""" admin = User.objects.create_superuser( email="admin@ucla.edu", password="adminpass12345678" ) self.assertEqual(admin.role, "ADMIN") self.assertTrue(admin.is_staff) self.assertTrue(admin.is_superuser)
[docs] def test_cannot_become_admin_via_profile_update(self): """Check whether there is a loophole to become ADMIN""" user = User.objects.create_user( email="not_admin@ucla.edu", password="password12345678", role="MENTEE" ) # try to become an admin user.update_profile(role="ADMIN") self.assertNotEqual(user.role, "ADMIN") user.complete_survey({"role": "ADMIN"}) self.assertNotEqual(user.role, "ADMIN") user.role = "ADMIN" user.save()
[docs] class UserServiceTests(TestCase):
[docs] def test_register_user_service_success(self): """Verifies the service creates a user and hashes the password""" user = register_user(email="new_student@ucla.edu", password="securepassword", role="MENTEE") self.assertEqual(user.email, "new_student@ucla.edu") # Ensure password was hashed, not stored in plain text self.assertNotEqual(user.password, "securepassword") self.assertTrue(user.check_password("securepassword"))
[docs] def test_register_duplicate_email_raises_conflict(self): """Ensures registering an existing email throws the correct Conflict error""" register_user(email="existing@ucla.edu", password="pw1", role="MENTEE") with self.assertRaises(Conflict): register_user(email="existing@ucla.edu", password="pw2", role="MENTOR")
[docs] class RegistrationTests(TestCase):
[docs] def test_register_while_in_grace_period(self): """Test that you can't register during grace period""" # register and delete account user = register_user('grace@ucla.edu', 'password12345678', 'MENTEE') user.request_deletion() # try to register during grace period: should fail with self.assertRaises(Conflict): register_user('grace@ucla.edu', 'newpassword12345', 'MENTOR')
[docs] def test_register_past_grace_period(self): """Test that you can re-register after account expiration""" # register user user = register_user('expired@ucla.edu', 'password12345678', 'MENTEE') user_id = user.pk # request deletion user.request_deletion() # manually expire the account by subtracting 2 hours user.permanent_deletion_date = timezone.now() - timedelta(hours=2) user.save() new_user = register_user('expired@ucla.edu', 'newpassword12345', 'MENTOR') self.assertIsNotNone(new_user) self.assertEqual(new_user.email, 'expired@ucla.edu') self.assertEqual(new_user.role, 'MENTOR') self.assertNotEqual(new_user.pk, user_id) # different user object self.assertTrue(new_user.is_verified) # auto-verified in test mode # old user should be deleted with self.assertRaises(User.DoesNotExist): User.objects.get(pk=user_id)
[docs] class UserAccountDeletionTests(TestCase):
[docs] def setUp(self): self.user = User.objects.create_user(email="delete_me@ucla.edu", password="pw", role="MENTEE")
[docs] def test_request_deletion_flags_account(self): """Ensures requesting deletion sets flags but doesn't hard-delete immediately""" self.user.request_deletion() # Verify the soft-delete flags were triggered self.assertTrue(self.user.is_deleted) self.assertIsNotNone(self.user.permanent_deletion_date) # Verify the user record still physically exists in the database self.assertEqual(User.objects.count(), 1)
[docs] def test_cancel_deletion_restores_account(self): """Ensures a user can cancel their deletion within the grace period""" self.user.request_deletion() # Cancel the deletion self.assertTrue(self.user.cancel_deletion()) # Verify the flags were completely removed self.assertFalse(self.user.is_deleted) self.assertIsNone(self.user.permanent_deletion_date)
[docs] class UserAPIClientTests(TestCase):
[docs] def setUp(self): self.user = User.objects.create_user(email="api_tester@ucla.edu", password="secure_pw", role="MENTEE") # Ensure the user is verified so they can log in self.user.is_verified = True self.user.save()
[docs] def test_unauthenticated_user_access_denied(self): """Ensures an unauthenticated browser cannot access the profile endpoint""" # Attempt to access the /me/ endpoint WITHOUT logging in url = reverse('current_user') response = self.client.get(url) # Django should reject this with a 401 Unauthorized or 403 Forbidden self.assertIn(response.status_code, [401, 403])
[docs] def test_authenticated_user_access_granted(self): """Ensures a logged-in user can access their own profile data""" # 1. Log the test client in login_success = self.client.login(email="api_tester@ucla.edu", password="secure_pw") self.assertTrue(login_success) # 2. Access the /me/ endpoint url = reverse('current_user') response = self.client.get(url) # 3. Verify the server accepted the request (200 OK) self.assertEqual(response.status_code, 200)
[docs] class UserRoleChangeTests(TestCase):
[docs] def setUp(self): self.mentor = User.objects.create_user(email="mentor_switch@ucla.edu", password="pw", role="MENTOR") self.mentee = User.objects.create_user(email="mentee_switch@ucla.edu", password="pw", role="MENTEE") # Match them up self.mentor.add_mentee(self.mentee.email) self.mentee.matchedMentorEmail = self.mentor.email self.mentee.save()
[docs] def test_change_role_wipes_matches(self): """Ensures that changing a role completely cleans up existing relationships""" # Change mentee to mentor self.mentee.change_role('MENTOR') # Refresh the original mentor from the database self.mentor.refresh_from_db() # The original mentor should no longer have the mentee in their list self.assertNotIn(self.mentee.email, self.mentor.current_mentees) # The mentor should no longer be marked as 'isMatched' (full) self.assertFalse(self.mentor.isMatched)
[docs] class UserTokenExpirationTests(TestCase):
[docs] def setUp(self): self.user = User.objects.create_user(email="token_test@ucla.edu", password="pw", role="MENTEE")
[docs] def test_expired_verification_token_is_rejected(self): """Ensures a token past its expiration time fails verification""" token = self.user.generate_verification_token() # Artificially age the token's creation time to 2 hours ago (past the 1-hour limit) self.user.email_verification_sent_at = timezone.now() - timedelta(hours=settings.EMAIL_VERIFICATION_TOKEN_EXPIRATION + 1) self.user.save() # Attempt to verify is_valid = self.user.verify_email(token) self.assertFalse(is_valid) self.assertFalse(self.user.is_verified)
[docs] class HardDeletionTests(TestCase):
[docs] def setUp(self): # User 1: Expired grace period self.expired_user = User.objects.create_user(email="expired@ucla.edu", password="pw") self.expired_user.request_deletion() self.expired_user.permanent_deletion_date = timezone.now() - timedelta(days=1) self.expired_user.save() # User 2: Still in grace period self.safe_user = User.objects.create_user(email="safe@ucla.edu", password="pw") self.safe_user.request_deletion() self.safe_user.permanent_deletion_date = timezone.now() + timedelta(days=1) self.safe_user.save()
[docs] def test_only_expired_accounts_are_permanently_deleted(self): """Ensures the hard-delete function respects the grace period""" deleted_count = User.permanently_delete_expired_accounts() self.assertEqual(deleted_count, 1) # Verify expired user is completely gone with self.assertRaises(User.DoesNotExist): User.objects.get(email="expired@ucla.edu") # Verify safe user still exists self.assertTrue(User.objects.filter(email="safe@ucla.edu").exists())