""" 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. """ from rest_framework import status from rest_framework.test import APIClient from tests.factories import ( UserFactory, ) 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("detail", 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("detail", 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])