|
| 1 | +import time |
| 2 | + |
| 3 | +from django.utils import timezone |
| 4 | + |
| 5 | +from appointment.models import PasswordResetToken |
| 6 | +from appointment.tests.base.base_test import BaseTest |
| 7 | +import datetime |
| 8 | + |
| 9 | + |
| 10 | +class PasswordResetTokenTests(BaseTest): |
| 11 | + def setUp(self): |
| 12 | + super().setUp() |
| 13 | + self. user = self. create_user_( username='test_user', email='[email protected]', password='test_pass123') |
| 14 | + self.expired_time = timezone.now() - datetime.timedelta(minutes=5) |
| 15 | + |
| 16 | + def test_create_token(self): |
| 17 | + """Test token creation for a user.""" |
| 18 | + token = PasswordResetToken.create_token(user=self.user) |
| 19 | + self.assertIsNotNone(token) |
| 20 | + self.assertFalse(token.is_expired) |
| 21 | + self.assertFalse(token.is_verified) |
| 22 | + |
| 23 | + def test_str_representation(self): |
| 24 | + """Test the string representation of the token.""" |
| 25 | + token = PasswordResetToken.create_token(self.user) |
| 26 | + expected_str = (f"Password reset token for {self.user} " |
| 27 | + f"[{token.token} status: {token.status} expires at {token.expires_at}]") |
| 28 | + self.assertEqual(str(token), expected_str) |
| 29 | + |
| 30 | + def test_is_verified_property(self): |
| 31 | + """Test the is_verified property to check if the token status is correctly identified as verified.""" |
| 32 | + token = PasswordResetToken.create_token(self.user) |
| 33 | + self.assertFalse(token.is_verified, "Newly created token should not be verified.") |
| 34 | + token.mark_as_verified() |
| 35 | + self.assertTrue(token.is_verified, "Token should be marked as verified after calling mark_as_verified.") |
| 36 | + |
| 37 | + def test_is_active_property(self): |
| 38 | + """Test the is_active property to check if the token status is correctly identified as active.""" |
| 39 | + token = PasswordResetToken.create_token(self.user) |
| 40 | + self.assertTrue(token.is_active, "Newly created token should be active.") |
| 41 | + token.mark_as_verified() |
| 42 | + token.refresh_from_db() |
| 43 | + self.assertFalse(token.is_active, "Token should not be active after being verified.") |
| 44 | + |
| 45 | + # Invalidate the token and check is_active property |
| 46 | + token.status = PasswordResetToken.TokenStatus.INVALIDATED |
| 47 | + token.save() |
| 48 | + self.assertFalse(token.is_active, "Token should not be active after being invalidated.") |
| 49 | + |
| 50 | + def test_is_invalidated_property(self): |
| 51 | + """Test the is_invalidated property to check if the token status is correctly identified as invalidated.""" |
| 52 | + token = PasswordResetToken.create_token(self.user) |
| 53 | + self.assertFalse(token.is_invalidated, "Newly created token should not be invalidated.") |
| 54 | + |
| 55 | + # Invalidate the token and check is_invalidated property |
| 56 | + token.status = PasswordResetToken.TokenStatus.INVALIDATED |
| 57 | + token.save() |
| 58 | + self.assertTrue(token.is_invalidated, "Token should be marked as invalidated after status change.") |
| 59 | + |
| 60 | + def test_token_expiration(self): |
| 61 | + """Test that a token is considered expired after the expiration time.""" |
| 62 | + token = PasswordResetToken.create_token(user=self.user, expiration_minutes=-1) # Token already expired |
| 63 | + self.assertTrue(token.is_expired) |
| 64 | + |
| 65 | + def test_verify_token_success(self): |
| 66 | + """Test successful token verification.""" |
| 67 | + token = PasswordResetToken.create_token(user=self.user) |
| 68 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=token.token) |
| 69 | + self.assertIsNotNone(verified_token) |
| 70 | + |
| 71 | + def test_verify_token_failure_expired(self): |
| 72 | + """Test token verification fails if the token has expired.""" |
| 73 | + token = PasswordResetToken.create_token(user=self.user, expiration_minutes=-1) # Token already expired |
| 74 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=token.token) |
| 75 | + self.assertIsNone(verified_token) |
| 76 | + |
| 77 | + def test_verify_token_failure_wrong_user(self): |
| 78 | + """Test token verification fails if the token does not belong to the given user.""" |
| 79 | + another_user = self. create_user_( username='another_user', email='[email protected]', |
| 80 | + password='test_pass456') |
| 81 | + token = PasswordResetToken.create_token(user=self.user) |
| 82 | + verified_token = PasswordResetToken.verify_token(user=another_user, token=token.token) |
| 83 | + self.assertIsNone(verified_token) |
| 84 | + |
| 85 | + def test_verify_token_failure_already_verified(self): |
| 86 | + """Test token verification fails if the token has already been verified.""" |
| 87 | + token = PasswordResetToken.create_token(user=self.user) |
| 88 | + token.mark_as_verified() |
| 89 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=token.token) |
| 90 | + self.assertIsNone(verified_token) |
| 91 | + |
| 92 | + def test_mark_as_verified(self): |
| 93 | + """Test marking a token as verified.""" |
| 94 | + token = PasswordResetToken.create_token(user=self.user) |
| 95 | + self.assertFalse(token.is_verified) |
| 96 | + token.mark_as_verified() |
| 97 | + token.refresh_from_db() # Refresh the token object from the database |
| 98 | + self.assertTrue(token.is_verified) |
| 99 | + |
| 100 | + def test_verify_token_invalid_token(self): |
| 101 | + """Test token verification fails if the token does not exist.""" |
| 102 | + PasswordResetToken.create_token(user=self.user) |
| 103 | + invalid_token_uuid = "12345678-1234-1234-1234-123456789012" # An invalid token UUID |
| 104 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=invalid_token_uuid) |
| 105 | + self.assertIsNone(verified_token) |
| 106 | + |
| 107 | + def test_token_expiration_boundary(self): |
| 108 | + """Test token verification at the exact moment of expiration.""" |
| 109 | + token = PasswordResetToken.create_token(user=self.user, expiration_minutes=0) # Token expires now |
| 110 | + # Assuming there might be a very slight delay before verification, we wait a second |
| 111 | + time.sleep(1) |
| 112 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=token.token) |
| 113 | + self.assertIsNone(verified_token) |
| 114 | + |
| 115 | + def test_create_multiple_tokens_for_user(self): |
| 116 | + """Test that multiple tokens can be created for a single user and only the latest is valid.""" |
| 117 | + old_token = PasswordResetToken.create_token(user=self.user) |
| 118 | + new_token = PasswordResetToken.create_token(user=self.user) |
| 119 | + |
| 120 | + old_verified = PasswordResetToken.verify_token(user=self.user, token=old_token.token) |
| 121 | + new_verified = PasswordResetToken.verify_token(user=self.user, token=new_token.token) |
| 122 | + |
| 123 | + self.assertIsNone(old_verified, "Old token should not be valid after creating a new one") |
| 124 | + self.assertIsNotNone(new_verified, "New token should be valid") |
| 125 | + |
| 126 | + def test_expired_token_does_not_verify(self): |
| 127 | + """Test that an expired token does not verify even if correct.""" |
| 128 | + token = PasswordResetToken.create_token(user=self.user, expiration_minutes=-5) # Already expired |
| 129 | + # Fast-forward time to after expiration |
| 130 | + token.expires_at = timezone.now() - datetime.timedelta(minutes=5) |
| 131 | + token.save() |
| 132 | + |
| 133 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=token.token) |
| 134 | + self.assertIsNone(verified_token, "Expired token should not verify") |
| 135 | + |
| 136 | + def test_mark_as_verified_is_idempotent(self): |
| 137 | + """Test that marking a token as verified multiple times has no adverse effect.""" |
| 138 | + token = PasswordResetToken.create_token(user=self.user) |
| 139 | + token.mark_as_verified() |
| 140 | + first_verification_time = token.updated_at |
| 141 | + |
| 142 | + time.sleep(1) # Ensure time has passed |
| 143 | + token.mark_as_verified() |
| 144 | + token.refresh_from_db() |
| 145 | + |
| 146 | + self.assertTrue(token.is_verified) |
| 147 | + self.assertEqual(first_verification_time, token.updated_at, |
| 148 | + "Token verification time should not update on subsequent calls") |
| 149 | + |
| 150 | + def test_deleting_user_cascades_to_tokens(self): |
| 151 | + """Test that deleting a user deletes associated password reset tokens.""" |
| 152 | + token = PasswordResetToken.create_token(user=self.user) |
| 153 | + self.user.delete() |
| 154 | + |
| 155 | + with self.assertRaises(PasswordResetToken.DoesNotExist): |
| 156 | + PasswordResetToken.objects.get(pk=token.pk) |
| 157 | + |
| 158 | + def test_token_verification_resets_after_expiration(self): |
| 159 | + """Test that an expired token cannot be verified after its expiration, even if marked as verified.""" |
| 160 | + token = PasswordResetToken.create_token(user=self.user, expiration_minutes=-1) # Already expired |
| 161 | + token.mark_as_verified() |
| 162 | + |
| 163 | + verified_token = PasswordResetToken.verify_token(user=self.user, token=token.token) |
| 164 | + self.assertIsNone(verified_token, "Expired token should not verify, even if marked as verified") |
| 165 | + |
| 166 | + def test_verify_token_invalidated(self): |
| 167 | + """Test token verification fails if the token has been invalidated.""" |
| 168 | + token = PasswordResetToken.create_token(self.user) |
| 169 | + # Invalidate the token by creating a new one |
| 170 | + PasswordResetToken.create_token(self.user) |
| 171 | + verified_token = PasswordResetToken.verify_token(self.user, token.token) |
| 172 | + self.assertIsNone(verified_token) |
| 173 | + |
| 174 | + def test_expired_token_verification(self): |
| 175 | + """Test that an expired token cannot be verified.""" |
| 176 | + token = PasswordResetToken.objects.create(user=self.user, expires_at=self.expired_time, |
| 177 | + status=PasswordResetToken.TokenStatus.ACTIVE) |
| 178 | + self.assertTrue(token.is_expired) |
| 179 | + verified_token = PasswordResetToken.verify_token(self.user, token.token) |
| 180 | + self.assertIsNone(verified_token, "Expired token should not verify") |
| 181 | + |
| 182 | + def test_token_verification_after_user_deletion(self): |
| 183 | + """Test that a token cannot be verified after the associated user is deleted.""" |
| 184 | + token = PasswordResetToken.create_token(self.user) |
| 185 | + self.user.delete() |
| 186 | + verified_token = PasswordResetToken.verify_token(self.user, token.token) |
| 187 | + self.assertIsNone(verified_token, "Token should not verify after user deletion") |
0 commit comments