""" Comprehensive tests for Auth API endpoints. This module provides extensive test coverage for: - LoginAPIView: User login with JWT tokens - SignupAPIView: User registration with email verification - LogoutAPIView: User logout with token blacklisting - CurrentUserAPIView: Get current user info - PasswordResetAPIView: Password reset request - PasswordChangeAPIView: Password change for authenticated users - SocialProvidersAPIView: Available social providers - AuthStatusAPIView: Check authentication status - EmailVerificationAPIView: Email verification - ResendVerificationAPIView: Resend verification email Test patterns follow Django styleguide conventions. """ import pytest from unittest.mock import patch, MagicMock from django.test import TestCase from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase, APIClient from tests.factories import ( UserFactory, StaffUserFactory, SuperUserFactory, ) from tests.test_utils import EnhancedAPITestCase class TestLoginAPIView(EnhancedAPITestCase): """Test cases for LoginAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.user.set_password('testpass123') self.user.save() self.url = '/api/v1/auth/login/' def test__login__with_valid_credentials__returns_tokens(self): """Test successful login returns JWT tokens.""" response = self.client.post(self.url, { 'username': self.user.username, 'password': 'testpass123' }) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('access', response.data) self.assertIn('refresh', response.data) self.assertIn('user', response.data) def test__login__with_email__returns_tokens(self): """Test login with email instead of username.""" response = self.client.post(self.url, { 'username': self.user.email, 'password': 'testpass123' }) self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST]) def test__login__with_invalid_password__returns_400(self): """Test login with wrong password returns error.""" response = self.client.post(self.url, { 'username': self.user.username, 'password': 'wrongpassword' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertIn('error', response.data) def test__login__with_nonexistent_user__returns_400(self): """Test login with nonexistent username returns error.""" response = self.client.post(self.url, { 'username': 'nonexistentuser', 'password': 'testpass123' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__login__with_missing_username__returns_400(self): """Test login without username returns error.""" response = self.client.post(self.url, { 'password': 'testpass123' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__login__with_missing_password__returns_400(self): """Test login without password returns error.""" response = self.client.post(self.url, { 'username': self.user.username }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__login__with_empty_credentials__returns_400(self): """Test login with empty credentials returns error.""" response = self.client.post(self.url, {}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__login__inactive_user__returns_error(self): """Test login with inactive user returns appropriate error.""" self.user.is_active = False self.user.save() response = self.client.post(self.url, { 'username': self.user.username, 'password': 'testpass123' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class TestSignupAPIView(EnhancedAPITestCase): """Test cases for SignupAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.url = '/api/v1/auth/signup/' self.valid_data = { 'username': 'newuser', 'email': 'newuser@example.com', 'password1': 'ComplexPass123!', 'password2': 'ComplexPass123!' } def test__signup__with_valid_data__creates_user(self): """Test successful signup creates user.""" response = self.client.post(self.url, self.valid_data) self.assertIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_400_BAD_REQUEST]) def test__signup__with_existing_username__returns_400(self): """Test signup with existing username returns error.""" UserFactory(username='existinguser') data = self.valid_data.copy() data['username'] = 'existinguser' response = self.client.post(self.url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__signup__with_existing_email__returns_400(self): """Test signup with existing email returns error.""" UserFactory(email='existing@example.com') data = self.valid_data.copy() data['email'] = 'existing@example.com' response = self.client.post(self.url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__signup__with_password_mismatch__returns_400(self): """Test signup with mismatched passwords returns error.""" data = self.valid_data.copy() data['password2'] = 'DifferentPass123!' response = self.client.post(self.url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__signup__with_weak_password__returns_400(self): """Test signup with weak password returns error.""" data = self.valid_data.copy() data['password1'] = '123' data['password2'] = '123' response = self.client.post(self.url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__signup__with_invalid_email__returns_400(self): """Test signup with invalid email returns error.""" data = self.valid_data.copy() data['email'] = 'notanemail' response = self.client.post(self.url, data) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__signup__with_missing_fields__returns_400(self): """Test signup with missing required fields returns error.""" response = self.client.post(self.url, {'username': 'onlyusername'}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class TestLogoutAPIView(EnhancedAPITestCase): """Test cases for LogoutAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.url = '/api/v1/auth/logout/' def test__logout__authenticated_user__returns_success(self): """Test successful logout for authenticated user.""" self.client.force_authenticate(user=self.user) response = self.client.post(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('message', response.data) def test__logout__unauthenticated_user__returns_401(self): """Test logout without authentication returns 401.""" response = self.client.post(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test__logout__with_refresh_token__blacklists_token(self): """Test logout with refresh token blacklists the token.""" self.client.force_authenticate(user=self.user) # Simulate providing a refresh token response = self.client.post(self.url, {'refresh': 'dummy-token'}) self.assertEqual(response.status_code, status.HTTP_200_OK) class TestCurrentUserAPIView(EnhancedAPITestCase): """Test cases for CurrentUserAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.url = '/api/v1/auth/user/' def test__current_user__authenticated__returns_user_data(self): """Test getting current user data when authenticated.""" self.client.force_authenticate(user=self.user) response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data['username'], self.user.username) def test__current_user__unauthenticated__returns_401(self): """Test getting current user without auth returns 401.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) class TestPasswordResetAPIView(EnhancedAPITestCase): """Test cases for PasswordResetAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.url = '/api/v1/auth/password/reset/' def test__password_reset__with_valid_email__returns_success(self): """Test password reset request with valid email.""" response = self.client.post(self.url, {'email': self.user.email}) # Should return success (don't reveal if email exists) self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST]) def test__password_reset__with_nonexistent_email__returns_success(self): """Test password reset with nonexistent email returns success (security).""" response = self.client.post(self.url, {'email': 'nonexistent@example.com'}) # Should return success to not reveal email existence self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST]) def test__password_reset__with_missing_email__returns_400(self): """Test password reset without email returns error.""" response = self.client.post(self.url, {}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__password_reset__with_invalid_email_format__returns_400(self): """Test password reset with invalid email format returns error.""" response = self.client.post(self.url, {'email': 'notanemail'}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class TestPasswordChangeAPIView(EnhancedAPITestCase): """Test cases for PasswordChangeAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.user.set_password('oldpassword123') self.user.save() self.url = '/api/v1/auth/password/change/' def test__password_change__with_valid_data__changes_password(self): """Test password change with valid data.""" self.client.force_authenticate(user=self.user) response = self.client.post(self.url, { 'old_password': 'oldpassword123', 'new_password1': 'NewComplexPass123!', 'new_password2': 'NewComplexPass123!' }) self.assertIn(response.status_code, [status.HTTP_200_OK, status.HTTP_400_BAD_REQUEST]) def test__password_change__with_wrong_old_password__returns_400(self): """Test password change with wrong old password.""" self.client.force_authenticate(user=self.user) response = self.client.post(self.url, { 'old_password': 'wrongpassword', 'new_password1': 'NewComplexPass123!', 'new_password2': 'NewComplexPass123!' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__password_change__unauthenticated__returns_401(self): """Test password change without authentication.""" response = self.client.post(self.url, { 'old_password': 'oldpassword123', 'new_password1': 'NewComplexPass123!', 'new_password2': 'NewComplexPass123!' }) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) class TestSocialProvidersAPIView(EnhancedAPITestCase): """Test cases for SocialProvidersAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.url = '/api/v1/auth/social/providers/' def test__social_providers__returns_list(self): """Test getting list of social providers.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.data, list) class TestAuthStatusAPIView(EnhancedAPITestCase): """Test cases for AuthStatusAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.url = '/api/v1/auth/status/' def test__auth_status__authenticated__returns_authenticated_true(self): """Test auth status for authenticated user.""" self.client.force_authenticate(user=self.user) response = self.client.post(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertTrue(response.data.get('authenticated')) self.assertIsNotNone(response.data.get('user')) def test__auth_status__unauthenticated__returns_authenticated_false(self): """Test auth status for unauthenticated user.""" response = self.client.post(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertFalse(response.data.get('authenticated')) class TestAvailableProvidersAPIView(EnhancedAPITestCase): """Test cases for AvailableProvidersAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.url = '/api/v1/auth/social/available/' def test__available_providers__returns_provider_list(self): """Test getting available social providers.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.data, list) class TestConnectedProvidersAPIView(EnhancedAPITestCase): """Test cases for ConnectedProvidersAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.url = '/api/v1/auth/social/connected/' def test__connected_providers__authenticated__returns_list(self): """Test getting connected providers for authenticated user.""" self.client.force_authenticate(user=self.user) response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIsInstance(response.data, list) def test__connected_providers__unauthenticated__returns_401(self): """Test getting connected providers without auth.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) class TestConnectProviderAPIView(EnhancedAPITestCase): """Test cases for ConnectProviderAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() def test__connect_provider__unauthenticated__returns_401(self): """Test connecting provider without auth.""" response = self.client.post('/api/v1/auth/social/connect/google/', { 'access_token': 'dummy-token' }) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test__connect_provider__invalid_provider__returns_400(self): """Test connecting invalid provider.""" self.client.force_authenticate(user=self.user) response = self.client.post('/api/v1/auth/social/connect/invalid/', { 'access_token': 'dummy-token' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__connect_provider__missing_token__returns_400(self): """Test connecting provider without token.""" self.client.force_authenticate(user=self.user) response = self.client.post('/api/v1/auth/social/connect/google/', {}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class TestDisconnectProviderAPIView(EnhancedAPITestCase): """Test cases for DisconnectProviderAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() def test__disconnect_provider__unauthenticated__returns_401(self): """Test disconnecting provider without auth.""" response = self.client.post('/api/v1/auth/social/disconnect/google/') self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) def test__disconnect_provider__invalid_provider__returns_400(self): """Test disconnecting invalid provider.""" self.client.force_authenticate(user=self.user) response = self.client.post('/api/v1/auth/social/disconnect/invalid/') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) class TestSocialAuthStatusAPIView(EnhancedAPITestCase): """Test cases for SocialAuthStatusAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory() self.url = '/api/v1/auth/social/status/' def test__social_auth_status__authenticated__returns_status(self): """Test getting social auth status.""" self.client.force_authenticate(user=self.user) response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_200_OK) def test__social_auth_status__unauthenticated__returns_401(self): """Test getting social auth status without auth.""" response = self.client.get(self.url) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) class TestEmailVerificationAPIView(EnhancedAPITestCase): """Test cases for EmailVerificationAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() def test__email_verification__invalid_token__returns_404(self): """Test email verification with invalid token.""" response = self.client.get('/api/v1/auth/verify-email/invalid-token/') self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) class TestResendVerificationAPIView(EnhancedAPITestCase): """Test cases for ResendVerificationAPIView.""" def setUp(self): """Set up test data.""" self.client = APIClient() self.user = UserFactory(is_active=False) self.url = '/api/v1/auth/resend-verification/' def test__resend_verification__missing_email__returns_400(self): """Test resend verification without email.""" response = self.client.post(self.url, {}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__resend_verification__already_verified__returns_400(self): """Test resend verification for already verified user.""" active_user = UserFactory(is_active=True) response = self.client.post(self.url, {'email': active_user.email}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__resend_verification__nonexistent_email__returns_success(self): """Test resend verification for nonexistent email (security).""" response = self.client.post(self.url, {'email': 'nonexistent@example.com'}) # Should return success to not reveal email existence self.assertEqual(response.status_code, status.HTTP_200_OK) class TestAuthAPIEdgeCases(EnhancedAPITestCase): """Test cases for edge cases in auth APIs.""" def setUp(self): """Set up test data.""" self.client = APIClient() def test__login__with_special_characters_in_username__handled_safely(self): """Test login with special characters in username.""" special_usernames = [ "user", "user'; DROP TABLE users;--", "user&password=hacked", ] for username in special_usernames: response = self.client.post('/api/v1/auth/login/', { 'username': username, 'password': 'testpass123' }) # Should not crash, return appropriate error self.assertIn(response.status_code, [ status.HTTP_400_BAD_REQUEST, status.HTTP_401_UNAUTHORIZED ]) def test__signup__with_very_long_username__handled_safely(self): """Test signup with very long username.""" response = self.client.post('/api/v1/auth/signup/', { 'username': 'a' * 1000, 'email': 'test@example.com', 'password1': 'ComplexPass123!', 'password2': 'ComplexPass123!' }) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) def test__login__with_unicode_characters__handled_safely(self): """Test login with unicode characters.""" response = self.client.post('/api/v1/auth/login/', { 'username': 'user\u202e', 'password': 'pass\u202e' }) self.assertIn(response.status_code, [ status.HTTP_400_BAD_REQUEST, status.HTTP_401_UNAUTHORIZED ])