Testing

Testing Authentication

Authentication testing is crucial for ensuring your Django application properly handles user registration, login, logout, permissions, and access control. Since authentication affects security and user experience, comprehensive testing helps prevent vulnerabilities and ensures reliable user management.

Testing Authentication

Authentication testing is crucial for ensuring your Django application properly handles user registration, login, logout, permissions, and access control. Since authentication affects security and user experience, comprehensive testing helps prevent vulnerabilities and ensures reliable user management.

Testing User Registration

Testing User Creation

from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth.models import User
from django.contrib.auth.forms import UserCreationForm
from django.core import mail

class UserRegistrationTests(TestCase):
    """Test user registration functionality"""
    
    def setUp(self):
        """Set up test client"""
        self.client = Client()
    
    def test_user_registration_form_valid_data(self):
        """Test user registration with valid data"""
        
        registration_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password1': 'complexpassword123',
            'password2': 'complexpassword123',
            'first_name': 'John',
            'last_name': 'Doe'
        }
        
        url = reverse('accounts:register')
        response = self.client.post(url, registration_data)
        
        # Should redirect after successful registration
        self.assertEqual(response.status_code, 302)
        
        # User should be created
        self.assertTrue(User.objects.filter(username='newuser').exists())
        
        # Check user data
        user = User.objects.get(username='newuser')
        self.assertEqual(user.email, 'newuser@example.com')
        self.assertEqual(user.first_name, 'John')
        self.assertEqual(user.last_name, 'Doe')
        self.assertTrue(user.check_password('complexpassword123'))
    
    def test_user_registration_form_invalid_data(self):
        """Test user registration with invalid data"""
        
        # Test with mismatched passwords
        registration_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password1': 'complexpassword123',
            'password2': 'differentpassword123',  # Mismatched
        }
        
        url = reverse('accounts:register')
        response = self.client.post(url, registration_data)
        
        # Should return form with errors
        self.assertEqual(response.status_code, 200)
        self.assertFormError(
            response, 
            'form', 
            'password2', 
            "The two password fields didn't match."
        )
        
        # User should not be created
        self.assertFalse(User.objects.filter(username='newuser').exists())
    
    def test_user_registration_duplicate_username(self):
        """Test registration with existing username"""
        
        # Create existing user
        User.objects.create_user(
            username='existinguser',
            email='existing@example.com',
            password='password123'
        )
        
        # Try to register with same username
        registration_data = {
            'username': 'existinguser',  # Duplicate
            'email': 'different@example.com',
            'password1': 'newpassword123',
            'password2': 'newpassword123',
        }
        
        url = reverse('accounts:register')
        response = self.client.post(url, registration_data)
        
        self.assertEqual(response.status_code, 200)
        self.assertFormError(
            response,
            'form',
            'username',
            'A user with that username already exists.'
        )
    
    def test_user_registration_weak_password(self):
        """Test registration with weak password"""
        
        registration_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password1': '123',  # Too weak
            'password2': '123',
        }
        
        url = reverse('accounts:register')
        response = self.client.post(url, registration_data)
        
        self.assertEqual(response.status_code, 200)
        
        # Should have password validation errors
        form_errors = response.context['form'].errors
        self.assertIn('password2', form_errors)
    
    def test_user_registration_sends_welcome_email(self):
        """Test registration sends welcome email"""
        
        registration_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password1': 'complexpassword123',
            'password2': 'complexpassword123',
        }
        
        url = reverse('accounts:register')
        response = self.client.post(url, registration_data)
        
        # Check email was sent
        self.assertEqual(len(mail.outbox), 1)
        
        email = mail.outbox[0]
        self.assertEqual(email.to, ['newuser@example.com'])
        self.assertIn('Welcome', email.subject)
        self.assertIn('newuser', email.body)
    
    def test_user_registration_auto_login(self):
        """Test user is automatically logged in after registration"""
        
        registration_data = {
            'username': 'newuser',
            'email': 'newuser@example.com',
            'password1': 'complexpassword123',
            'password2': 'complexpassword123',
        }
        
        url = reverse('accounts:register')
        response = self.client.post(url, registration_data, follow=True)
        
        # User should be logged in
        self.assertTrue(response.context['user'].is_authenticated)
        self.assertEqual(response.context['user'].username, 'newuser')

Testing Email Verification

from django.contrib.auth.tokens import default_token_generator
from django.utils.http import urlsafe_base64_encode
from django.utils.encoding import force_bytes

class EmailVerificationTests(TestCase):
    """Test email verification functionality"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123',
            is_active=False  # Inactive until email verified
        )
    
    def test_email_verification_token_generation(self):
        """Test email verification token generation"""
        
        # Generate verification token
        token = default_token_generator.make_token(self.user)
        uid = urlsafe_base64_encode(force_bytes(self.user.pk))
        
        # Token should be valid for this user
        self.assertTrue(
            default_token_generator.check_token(self.user, token)
        )
        
        # Token should be invalid for different user
        other_user = User.objects.create_user(
            username='otheruser',
            email='other@example.com',
            password='pass123'
        )
        
        self.assertFalse(
            default_token_generator.check_token(other_user, token)
        )
    
    def test_email_verification_link_activation(self):
        """Test email verification link activates account"""
        
        # Generate verification link components
        token = default_token_generator.make_token(self.user)
        uid = urlsafe_base64_encode(force_bytes(self.user.pk))
        
        # Visit verification link
        url = reverse('accounts:verify_email', kwargs={
            'uidb64': uid,
            'token': token
        })
        
        response = self.client.get(url)
        
        # Should redirect after successful verification
        self.assertEqual(response.status_code, 302)
        
        # User should now be active
        self.user.refresh_from_db()
        self.assertTrue(self.user.is_active)
    
    def test_email_verification_invalid_token(self):
        """Test email verification with invalid token"""
        
        uid = urlsafe_base64_encode(force_bytes(self.user.pk))
        invalid_token = 'invalid-token-123'
        
        url = reverse('accounts:verify_email', kwargs={
            'uidb64': uid,
            'token': invalid_token
        })
        
        response = self.client.get(url)
        
        # Should show error page
        self.assertEqual(response.status_code, 400)
        
        # User should remain inactive
        self.user.refresh_from_db()
        self.assertFalse(self.user.is_active)
    
    def test_email_verification_expired_token(self):
        """Test email verification with expired token"""
        
        # Create old user (simulate expired token)
        from django.utils import timezone
        from datetime import timedelta
        
        old_user = User.objects.create_user(
            username='olduser',
            email='old@example.com',
            password='pass123',
            is_active=False
        )
        
        # Manually set old date_joined to simulate expired token
        old_date = timezone.now() - timedelta(days=8)  # Assuming 7-day expiry
        User.objects.filter(id=old_user.id).update(date_joined=old_date)
        old_user.refresh_from_db()
        
        # Generate token (will be expired due to old date_joined)
        token = default_token_generator.make_token(old_user)
        uid = urlsafe_base64_encode(force_bytes(old_user.pk))
        
        url = reverse('accounts:verify_email', kwargs={
            'uidb64': uid,
            'token': token
        })
        
        response = self.client.get(url)
        
        # Should show error for expired token
        self.assertEqual(response.status_code, 400)

Testing Login and Logout

Testing User Authentication

class LoginLogoutTests(TestCase):
    """Test user login and logout functionality"""
    
    def setUp(self):
        """Set up test user"""
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
        self.client = Client()
    
    def test_login_with_valid_credentials(self):
        """Test login with valid username and password"""
        
        login_data = {
            'username': 'testuser',
            'password': 'testpass123'
        }
        
        url = reverse('accounts:login')
        response = self.client.post(url, login_data)
        
        # Should redirect after successful login
        self.assertEqual(response.status_code, 302)
        
        # User should be authenticated
        user = response.wsgi_request.user
        self.assertTrue(user.is_authenticated)
        self.assertEqual(user.username, 'testuser')
    
    def test_login_with_invalid_credentials(self):
        """Test login with invalid credentials"""
        
        login_data = {
            'username': 'testuser',
            'password': 'wrongpassword'
        }
        
        url = reverse('accounts:login')
        response = self.client.post(url, login_data)
        
        # Should return form with errors
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Please enter a correct username and password')
        
        # User should not be authenticated
        user = response.wsgi_request.user
        self.assertFalse(user.is_authenticated)
    
    def test_login_with_inactive_user(self):
        """Test login with inactive user account"""
        
        # Create inactive user
        inactive_user = User.objects.create_user(
            username='inactive',
            email='inactive@example.com',
            password='pass123',
            is_active=False
        )
        
        login_data = {
            'username': 'inactive',
            'password': 'pass123'
        }
        
        url = reverse('accounts:login')
        response = self.client.post(url, login_data)
        
        # Should reject inactive user
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'This account is inactive')
        
        # User should not be authenticated
        user = response.wsgi_request.user
        self.assertFalse(user.is_authenticated)
    
    def test_login_redirect_to_next_parameter(self):
        """Test login redirects to 'next' parameter"""
        
        # Try to access protected page
        protected_url = '/dashboard/'
        response = self.client.get(protected_url)
        
        # Should redirect to login with next parameter
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login/', response.url)
        self.assertIn('next=', response.url)
        
        # Login with next parameter
        login_data = {
            'username': 'testuser',
            'password': 'testpass123'
        }
        
        login_url = f"{reverse('accounts:login')}?next={protected_url}"
        response = self.client.post(login_url, login_data)
        
        # Should redirect to original protected page
        self.assertRedirects(response, protected_url)
    
    def test_logout_functionality(self):
        """Test user logout"""
        
        # Login first
        self.client.login(username='testuser', password='testpass123')
        
        # Verify user is logged in
        response = self.client.get('/dashboard/')
        self.assertTrue(response.context['user'].is_authenticated)
        
        # Logout
        logout_url = reverse('accounts:logout')
        response = self.client.post(logout_url)
        
        # Should redirect after logout
        self.assertEqual(response.status_code, 302)
        
        # User should no longer be authenticated
        response = self.client.get('/dashboard/')
        self.assertEqual(response.status_code, 302)  # Redirect to login
    
    def test_login_rate_limiting(self):
        """Test login rate limiting for security"""
        
        login_data = {
            'username': 'testuser',
            'password': 'wrongpassword'
        }
        
        url = reverse('accounts:login')
        
        # Attempt multiple failed logins
        for i in range(6):  # Assuming 5 attempts limit
            response = self.client.post(url, login_data)
        
        # Should be rate limited after too many attempts
        # Implementation depends on your rate limiting strategy
        # This might return 429 status or show captcha
        
    def test_remember_me_functionality(self):
        """Test 'remember me' checkbox extends session"""
        
        login_data = {
            'username': 'testuser',
            'password': 'testpass123',
            'remember_me': True
        }
        
        url = reverse('accounts:login')
        response = self.client.post(url, login_data)
        
        # Check session expiry is extended
        # Implementation depends on your remember me logic
        session = self.client.session
        self.assertIsNotNone(session.get_expiry_age())

Testing Session Management

class SessionManagementTests(TestCase):
    """Test session management and security"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
    
    def test_session_created_on_login(self):
        """Test session is created on login"""
        
        # No session initially
        self.assertNotIn('_auth_user_id', self.client.session)
        
        # Login
        self.client.login(username='testuser', password='testpass123')
        
        # Session should contain user ID
        self.assertIn('_auth_user_id', self.client.session)
        self.assertEqual(
            int(self.client.session['_auth_user_id']), 
            self.user.id
        )
    
    def test_session_cleared_on_logout(self):
        """Test session is cleared on logout"""
        
        # Login first
        self.client.login(username='testuser', password='testpass123')
        self.assertIn('_auth_user_id', self.client.session)
        
        # Logout
        self.client.logout()
        
        # Session should be cleared
        self.assertNotIn('_auth_user_id', self.client.session)
    
    def test_session_security_on_login(self):
        """Test session key changes on login for security"""
        
        # Get initial session key
        self.client.get('/')  # Create session
        initial_session_key = self.client.session.session_key
        
        # Login
        self.client.login(username='testuser', password='testpass123')
        
        # Session key should change for security
        new_session_key = self.client.session.session_key
        self.assertNotEqual(initial_session_key, new_session_key)
    
    def test_concurrent_session_handling(self):
        """Test handling of concurrent sessions"""
        
        # Create two clients for same user
        client1 = Client()
        client2 = Client()
        
        # Login with both clients
        client1.login(username='testuser', password='testpass123')
        client2.login(username='testuser', password='testpass123')
        
        # Both should be authenticated
        response1 = client1.get('/dashboard/')
        response2 = client2.get('/dashboard/')
        
        self.assertTrue(response1.context['user'].is_authenticated)
        self.assertTrue(response2.context['user'].is_authenticated)
        
        # Depending on your session policy, you might:
        # - Allow multiple sessions (default Django behavior)
        # - Invalidate previous sessions
        # - Limit concurrent sessions

Testing Permissions and Authorization

Testing User Permissions

from django.contrib.auth.models import Permission, Group
from django.contrib.contenttypes.models import ContentType

class PermissionTests(TestCase):
    """Test user permissions and authorization"""
    
    def setUp(self):
        """Set up users and permissions"""
        
        # Create users
        self.regular_user = User.objects.create_user(
            username='regular',
            password='pass123'
        )
        
        self.staff_user = User.objects.create_user(
            username='staff',
            password='pass123',
            is_staff=True
        )
        
        self.superuser = User.objects.create_superuser(
            username='admin',
            password='pass123'
        )
        
        # Create custom permissions
        from blog.models import BlogPost
        content_type = ContentType.objects.get_for_model(BlogPost)
        
        self.publish_permission = Permission.objects.create(
            codename='can_publish_post',
            name='Can publish blog post',
            content_type=content_type
        )
        
        self.moderate_permission = Permission.objects.create(
            codename='can_moderate_comments',
            name='Can moderate comments',
            content_type=content_type
        )
    
    def test_superuser_has_all_permissions(self):
        """Test superuser has all permissions"""
        
        self.assertTrue(self.superuser.has_perm('blog.add_blogpost'))
        self.assertTrue(self.superuser.has_perm('blog.change_blogpost'))
        self.assertTrue(self.superuser.has_perm('blog.delete_blogpost'))
        self.assertTrue(self.superuser.has_perm('blog.can_publish_post'))
        self.assertTrue(self.superuser.has_perm('blog.can_moderate_comments'))
    
    def test_regular_user_has_no_special_permissions(self):
        """Test regular user has no special permissions"""
        
        self.assertFalse(self.regular_user.has_perm('blog.add_blogpost'))
        self.assertFalse(self.regular_user.has_perm('blog.change_blogpost'))
        self.assertFalse(self.regular_user.has_perm('blog.delete_blogpost'))
        self.assertFalse(self.regular_user.has_perm('blog.can_publish_post'))
    
    def test_user_permission_assignment(self):
        """Test assigning permissions to user"""
        
        # Assign permission to user
        self.regular_user.user_permissions.add(self.publish_permission)
        
        # User should now have the permission
        self.assertTrue(self.regular_user.has_perm('blog.can_publish_post'))
        self.assertFalse(self.regular_user.has_perm('blog.can_moderate_comments'))
    
    def test_group_permission_assignment(self):
        """Test permissions through groups"""
        
        # Create group with permissions
        editors_group = Group.objects.create(name='Editors')
        editors_group.permissions.add(
            self.publish_permission,
            self.moderate_permission
        )
        
        # Add user to group
        self.regular_user.groups.add(editors_group)
        
        # User should have group permissions
        self.assertTrue(self.regular_user.has_perm('blog.can_publish_post'))
        self.assertTrue(self.regular_user.has_perm('blog.can_moderate_comments'))
    
    def test_permission_required_decorator(self):
        """Test permission_required decorator"""
        
        from django.contrib.auth.decorators import permission_required
        from django.http import HttpResponse
        
        @permission_required('blog.can_publish_post')
        def publish_view(request):
            return HttpResponse('Published!')
        
        # Test without permission
        self.client.force_login(self.regular_user)
        response = self.client.get('/publish/')  # Would need URL mapping
        
        # Should be forbidden or redirect to login
        # Implementation depends on your permission handling
        
        # Test with permission
        self.regular_user.user_permissions.add(self.publish_permission)
        response = self.client.get('/publish/')
        
        # Should allow access
        # self.assertEqual(response.status_code, 200)
    
    def test_object_level_permissions(self):
        """Test object-level permissions"""
        
        from blog.models import BlogPost, Category
        
        # Create posts by different users
        category = Category.objects.create(name='Tech', slug='tech')
        
        user1_post = BlogPost.objects.create(
            title='User 1 Post',
            content='Content by user 1',
            author=self.regular_user,
            category=category
        )
        
        user2_post = BlogPost.objects.create(
            title='User 2 Post',
            content='Content by user 2',
            author=self.staff_user,
            category=category
        )
        
        # Test object-level permission checking
        # (This would require django-guardian or custom implementation)
        
        # User should be able to edit their own post
        # self.assertTrue(
        #     self.regular_user.has_perm('blog.change_blogpost', user1_post)
        # )
        
        # User should not be able to edit other's post
        # self.assertFalse(
        #     self.regular_user.has_perm('blog.change_blogpost', user2_post)
        # )

Testing Access Control in Views

class ViewAccessControlTests(TestCase):
    """Test access control in views"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        
        self.staff_user = User.objects.create_user(
            username='staffuser',
            password='testpass123',
            is_staff=True
        )
    
    def test_login_required_view_anonymous_user(self):
        """Test login required view blocks anonymous users"""
        
        url = reverse('accounts:profile')
        response = self.client.get(url)
        
        # Should redirect to login
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login/', response.url)
    
    def test_login_required_view_authenticated_user(self):
        """Test login required view allows authenticated users"""
        
        self.client.force_login(self.user)
        
        url = reverse('accounts:profile')
        response = self.client.get(url)
        
        # Should allow access
        self.assertEqual(response.status_code, 200)
    
    def test_staff_required_view_regular_user(self):
        """Test staff required view blocks regular users"""
        
        self.client.force_login(self.user)
        
        url = reverse('admin:index')
        response = self.client.get(url)
        
        # Should redirect or show forbidden
        self.assertIn(response.status_code, [302, 403])
    
    def test_staff_required_view_staff_user(self):
        """Test staff required view allows staff users"""
        
        self.client.force_login(self.staff_user)
        
        url = reverse('admin:index')
        response = self.client.get(url)
        
        # Should allow access
        self.assertEqual(response.status_code, 200)
    
    def test_custom_permission_mixin(self):
        """Test custom permission mixin"""
        
        # Assuming you have a custom permission mixin
        from django.contrib.auth.mixins import PermissionRequiredMixin
        from django.views.generic import TemplateView
        
        class PublishView(PermissionRequiredMixin, TemplateView):
            permission_required = 'blog.can_publish_post'
            template_name = 'blog/publish.html'
        
        # Test without permission
        self.client.force_login(self.user)
        # response = self.client.get('/publish/')
        # self.assertEqual(response.status_code, 403)
        
        # Test with permission
        from django.contrib.auth.models import Permission
        from django.contrib.contenttypes.models import ContentType
        from blog.models import BlogPost
        
        content_type = ContentType.objects.get_for_model(BlogPost)
        permission = Permission.objects.create(
            codename='can_publish_post',
            name='Can publish post',
            content_type=content_type
        )
        
        self.user.user_permissions.add(permission)
        # response = self.client.get('/publish/')
        # self.assertEqual(response.status_code, 200)

Testing Custom Authentication Backends

Testing Custom Authentication

from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import authenticate

class EmailAuthenticationBackend(BaseBackend):
    """Custom authentication backend using email"""
    
    def authenticate(self, request, username=None, password=None, **kwargs):
        """Authenticate user by email instead of username"""
        
        try:
            # Try to find user by email
            user = User.objects.get(email=username)
            
            # Check password
            if user.check_password(password) and user.is_active:
                return user
                
        except User.DoesNotExist:
            return None
        
        return None
    
    def get_user(self, user_id):
        """Get user by ID"""
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

class CustomAuthenticationBackendTests(TestCase):
    """Test custom authentication backend"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
    
    def test_email_authentication_success(self):
        """Test authentication with email succeeds"""
        
        # Authenticate using email
        authenticated_user = authenticate(
            username='test@example.com',  # Using email as username
            password='testpass123'
        )
        
        self.assertIsNotNone(authenticated_user)
        self.assertEqual(authenticated_user, self.user)
    
    def test_email_authentication_wrong_password(self):
        """Test authentication with wrong password fails"""
        
        authenticated_user = authenticate(
            username='test@example.com',
            password='wrongpassword'
        )
        
        self.assertIsNone(authenticated_user)
    
    def test_email_authentication_nonexistent_email(self):
        """Test authentication with nonexistent email fails"""
        
        authenticated_user = authenticate(
            username='nonexistent@example.com',
            password='testpass123'
        )
        
        self.assertIsNone(authenticated_user)
    
    def test_email_authentication_inactive_user(self):
        """Test authentication with inactive user fails"""
        
        # Make user inactive
        self.user.is_active = False
        self.user.save()
        
        authenticated_user = authenticate(
            username='test@example.com',
            password='testpass123'
        )
        
        self.assertIsNone(authenticated_user)
    
    def test_get_user_method(self):
        """Test get_user method"""
        
        backend = EmailAuthenticationBackend()
        
        # Test with valid user ID
        retrieved_user = backend.get_user(self.user.id)
        self.assertEqual(retrieved_user, self.user)
        
        # Test with invalid user ID
        retrieved_user = backend.get_user(99999)
        self.assertIsNone(retrieved_user)

Testing Password Reset

Testing Password Reset Flow

class PasswordResetTests(TestCase):
    """Test password reset functionality"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='oldpassword123'
        )
    
    def test_password_reset_request(self):
        """Test password reset request"""
        
        url = reverse('password_reset')
        response = self.client.post(url, {'email': 'test@example.com'})
        
        # Should redirect after successful request
        self.assertEqual(response.status_code, 302)
        
        # Should send reset email
        self.assertEqual(len(mail.outbox), 1)
        
        email = mail.outbox[0]
        self.assertEqual(email.to, ['test@example.com'])
        self.assertIn('Password reset', email.subject)
    
    def test_password_reset_request_invalid_email(self):
        """Test password reset with invalid email"""
        
        url = reverse('password_reset')
        response = self.client.post(url, {'email': 'nonexistent@example.com'})
        
        # Should still redirect (don't reveal if email exists)
        self.assertEqual(response.status_code, 302)
        
        # Should not send email
        self.assertEqual(len(mail.outbox), 0)
    
    def test_password_reset_confirm_valid_token(self):
        """Test password reset confirmation with valid token"""
        
        # Generate reset token
        from django.contrib.auth.tokens import default_token_generator
        from django.utils.http import urlsafe_base64_encode
        from django.utils.encoding import force_bytes
        
        token = default_token_generator.make_token(self.user)
        uid = urlsafe_base64_encode(force_bytes(self.user.pk))
        
        # Visit reset confirmation page
        url = reverse('password_reset_confirm', kwargs={
            'uidb64': uid,
            'token': token
        })
        
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        
        # Submit new password
        new_password_data = {
            'new_password1': 'newpassword123',
            'new_password2': 'newpassword123'
        }
        
        response = self.client.post(url, new_password_data)
        
        # Should redirect after successful reset
        self.assertEqual(response.status_code, 302)
        
        # Password should be changed
        self.user.refresh_from_db()
        self.assertTrue(self.user.check_password('newpassword123'))
        self.assertFalse(self.user.check_password('oldpassword123'))
    
    def test_password_reset_confirm_invalid_token(self):
        """Test password reset confirmation with invalid token"""
        
        from django.utils.http import urlsafe_base64_encode
        from django.utils.encoding import force_bytes
        
        uid = urlsafe_base64_encode(force_bytes(self.user.pk))
        invalid_token = 'invalid-token-123'
        
        url = reverse('password_reset_confirm', kwargs={
            'uidb64': uid,
            'token': invalid_token
        })
        
        response = self.client.get(url)
        
        # Should show invalid token page
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'invalid')
    
    def test_password_reset_complete(self):
        """Test password reset complete page"""
        
        url = reverse('password_reset_complete')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'password has been set')

Testing Two-Factor Authentication

Testing 2FA Implementation

class TwoFactorAuthTests(TestCase):
    """Test two-factor authentication"""
    
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            email='test@example.com',
            password='testpass123'
        )
    
    def test_2fa_setup_qr_code_generation(self):
        """Test 2FA setup generates QR code"""
        
        self.client.force_login(self.user)
        
        url = reverse('accounts:2fa_setup')
        response = self.client.get(url)
        
        self.assertEqual(response.status_code, 200)
        
        # Should contain QR code data
        self.assertIn('qr_code', response.context)
        self.assertIn('secret_key', response.context)
    
    def test_2fa_verification_valid_token(self):
        """Test 2FA verification with valid token"""
        
        # Mock TOTP token generation
        from unittest.mock import patch
        
        with patch('pyotp.TOTP.verify') as mock_verify:
            mock_verify.return_value = True
            
            self.client.force_login(self.user)
            
            url = reverse('accounts:2fa_verify')
            response = self.client.post(url, {'token': '123456'})
            
            # Should redirect after successful verification
            self.assertEqual(response.status_code, 302)
    
    def test_2fa_verification_invalid_token(self):
        """Test 2FA verification with invalid token"""
        
        from unittest.mock import patch
        
        with patch('pyotp.TOTP.verify') as mock_verify:
            mock_verify.return_value = False
            
            self.client.force_login(self.user)
            
            url = reverse('accounts:2fa_verify')
            response = self.client.post(url, {'token': '000000'})
            
            # Should show error
            self.assertEqual(response.status_code, 200)
            self.assertContains(response, 'Invalid token')
    
    def test_2fa_required_middleware(self):
        """Test 2FA required middleware"""
        
        # Assuming you have 2FA middleware that redirects to verification
        
        # Enable 2FA for user
        # user_profile = UserProfile.objects.get(user=self.user)
        # user_profile.two_factor_enabled = True
        # user_profile.save()
        
        self.client.force_login(self.user)
        
        # Try to access protected page
        response = self.client.get('/dashboard/')
        
        # Should redirect to 2FA verification
        # self.assertEqual(response.status_code, 302)
        # self.assertIn('2fa-verify', response.url)

Next Steps

With comprehensive authentication testing in place, you're ready to move on to advanced testing topics. The next chapter will cover advanced testing techniques including mocking, testing async code, performance testing, and testing third-party integrations.

Key authentication testing concepts covered:

  • User registration and email verification testing
  • Login/logout and session management testing
  • Permission and authorization testing
  • Custom authentication backend testing
  • Password reset flow testing
  • Two-factor authentication testing

Authentication tests ensure your application's security mechanisms work correctly, protecting user accounts and maintaining proper access control throughout your application.