""" Tests for Account serializers. Following Django styleguide pattern: test______ """ import pytest from unittest.mock import Mock, patch, MagicMock from django.test import TestCase, RequestFactory from apps.accounts.serializers import ( UserSerializer, LoginSerializer, SignupSerializer, PasswordResetSerializer, PasswordChangeSerializer, SocialProviderSerializer, ) from apps.api.v1.accounts.serializers import ( UserProfileCreateInputSerializer, UserProfileUpdateInputSerializer, UserProfileOutputSerializer, TopListCreateInputSerializer, TopListUpdateInputSerializer, TopListOutputSerializer, TopListItemCreateInputSerializer, TopListItemUpdateInputSerializer, TopListItemOutputSerializer, ) from tests.factories import ( UserFactory, StaffUserFactory, ) @pytest.mark.django_db class TestUserSerializer(TestCase): """Tests for UserSerializer.""" def test__serialize__user__returns_expected_fields(self): """Test serializing a user returns expected fields.""" user = UserFactory() serializer = UserSerializer(user) data = serializer.data assert "id" in data assert "username" in data assert "email" in data assert "display_name" in data assert "date_joined" in data assert "is_active" in data assert "avatar_url" in data def test__serialize__user_without_profile__returns_none_avatar(self): """Test serializing user without profile returns None for avatar.""" user = UserFactory() # Ensure no profile if hasattr(user, "profile"): user.profile.delete() serializer = UserSerializer(user) data = serializer.data assert data["avatar_url"] is None def test__get_display_name__user_with_display_name__returns_display_name(self): """Test get_display_name returns user's display name.""" user = UserFactory() user.display_name = "John Doe" user.save() serializer = UserSerializer(user) # get_display_name calls the model method assert "display_name" in serializer.data def test__meta__read_only_fields__includes_id_and_dates(self): """Test Meta.read_only_fields includes id and date fields.""" assert "id" in UserSerializer.Meta.read_only_fields assert "date_joined" in UserSerializer.Meta.read_only_fields assert "is_active" in UserSerializer.Meta.read_only_fields class TestLoginSerializer(TestCase): """Tests for LoginSerializer.""" def test__validate__valid_credentials__returns_data(self): """Test validation passes with valid credentials.""" data = { "username": "testuser", "password": "testpassword123", } serializer = LoginSerializer(data=data) assert serializer.is_valid(), serializer.errors assert serializer.validated_data["username"] == "testuser" assert serializer.validated_data["password"] == "testpassword123" def test__validate__email_as_username__returns_data(self): """Test validation passes with email as username.""" data = { "username": "user@example.com", "password": "testpassword123", } serializer = LoginSerializer(data=data) assert serializer.is_valid(), serializer.errors assert serializer.validated_data["username"] == "user@example.com" def test__validate__missing_username__returns_error(self): """Test validation fails with missing username.""" data = {"password": "testpassword123"} serializer = LoginSerializer(data=data) assert not serializer.is_valid() assert "username" in serializer.errors def test__validate__missing_password__returns_error(self): """Test validation fails with missing password.""" data = {"username": "testuser"} serializer = LoginSerializer(data=data) assert not serializer.is_valid() assert "password" in serializer.errors def test__validate__empty_credentials__returns_error(self): """Test validation fails with empty credentials.""" data = {"username": "", "password": ""} serializer = LoginSerializer(data=data) assert not serializer.is_valid() @pytest.mark.django_db class TestSignupSerializer(TestCase): """Tests for SignupSerializer.""" def test__validate__valid_data__returns_validated_data(self): """Test validation passes with valid signup data.""" data = { "username": "newuser", "email": "newuser@example.com", "display_name": "New User", "password": "SecurePass123!", "password_confirm": "SecurePass123!", } serializer = SignupSerializer(data=data) assert serializer.is_valid(), serializer.errors def test__validate__mismatched_passwords__returns_error(self): """Test validation fails with mismatched passwords.""" data = { "username": "newuser", "email": "newuser@example.com", "display_name": "New User", "password": "SecurePass123!", "password_confirm": "DifferentPass456!", } serializer = SignupSerializer(data=data) assert not serializer.is_valid() assert "password_confirm" in serializer.errors def test__validate_email__duplicate_email__returns_error(self): """Test validation fails with duplicate email.""" existing_user = UserFactory(email="existing@example.com") data = { "username": "newuser", "email": "existing@example.com", "display_name": "New User", "password": "SecurePass123!", "password_confirm": "SecurePass123!", } serializer = SignupSerializer(data=data) assert not serializer.is_valid() assert "email" in serializer.errors def test__validate_email__case_insensitive__returns_error(self): """Test email validation is case insensitive.""" existing_user = UserFactory(email="existing@example.com") data = { "username": "newuser", "email": "EXISTING@EXAMPLE.COM", "display_name": "New User", "password": "SecurePass123!", "password_confirm": "SecurePass123!", } serializer = SignupSerializer(data=data) assert not serializer.is_valid() assert "email" in serializer.errors def test__validate_username__duplicate_username__returns_error(self): """Test validation fails with duplicate username.""" existing_user = UserFactory(username="existinguser") data = { "username": "existinguser", "email": "new@example.com", "display_name": "New User", "password": "SecurePass123!", "password_confirm": "SecurePass123!", } serializer = SignupSerializer(data=data) assert not serializer.is_valid() assert "username" in serializer.errors def test__validate__weak_password__returns_error(self): """Test validation fails with weak password.""" data = { "username": "newuser", "email": "newuser@example.com", "display_name": "New User", "password": "123", # Too weak "password_confirm": "123", } serializer = SignupSerializer(data=data) assert not serializer.is_valid() # Password validation error could be in 'password' or 'non_field_errors' assert "password" in serializer.errors or "non_field_errors" in serializer.errors def test__create__valid_data__creates_user(self): """Test create method creates user correctly.""" data = { "username": "createuser", "email": "createuser@example.com", "display_name": "Create User", "password": "SecurePass123!", "password_confirm": "SecurePass123!", } serializer = SignupSerializer(data=data) assert serializer.is_valid(), serializer.errors user = serializer.save() assert user.username == "createuser" assert user.email == "createuser@example.com" assert user.check_password("SecurePass123!") def test__meta__password_write_only__excludes_from_output(self): """Test password field is write-only.""" assert "password" in SignupSerializer.Meta.fields assert SignupSerializer.Meta.extra_kwargs.get("password", {}).get("write_only") is True @pytest.mark.django_db class TestPasswordResetSerializer(TestCase): """Tests for PasswordResetSerializer.""" def test__validate__valid_email__returns_normalized_email(self): """Test validation normalizes email.""" user = UserFactory(email="test@example.com") data = {"email": " TEST@EXAMPLE.COM "} serializer = PasswordResetSerializer(data=data) assert serializer.is_valid(), serializer.errors assert serializer.validated_data["email"] == "test@example.com" def test__validate__nonexistent_email__still_valid(self): """Test validation passes with nonexistent email (security).""" data = {"email": "nonexistent@example.com"} serializer = PasswordResetSerializer(data=data) # Should pass validation to prevent email enumeration assert serializer.is_valid(), serializer.errors def test__validate__existing_email__attaches_user(self): """Test validation attaches user when email exists.""" user = UserFactory(email="exists@example.com") data = {"email": "exists@example.com"} serializer = PasswordResetSerializer(data=data) serializer.is_valid() assert hasattr(serializer, "user") assert serializer.user == user def test__validate__nonexistent_email__no_user_attached(self): """Test validation doesn't attach user for nonexistent email.""" data = {"email": "notfound@example.com"} serializer = PasswordResetSerializer(data=data) serializer.is_valid() assert not hasattr(serializer, "user") @patch("apps.accounts.serializers.EmailService.send_email") def test__save__existing_user__sends_email(self, mock_send_email): """Test save sends email for existing user.""" user = UserFactory(email="reset@example.com") data = {"email": "reset@example.com"} factory = RequestFactory() request = factory.post("/password-reset/") serializer = PasswordResetSerializer(data=data, context={"request": request}) serializer.is_valid() serializer.save() # Email should be sent mock_send_email.assert_called_once() @pytest.mark.django_db class TestPasswordChangeSerializer(TestCase): """Tests for PasswordChangeSerializer.""" def test__validate__valid_data__returns_validated_data(self): """Test validation passes with valid password change data.""" user = UserFactory() user.set_password("OldPass123!") user.save() factory = RequestFactory() request = factory.post("/password-change/") request.user = user data = { "old_password": "OldPass123!", "new_password": "NewSecurePass456!", "new_password_confirm": "NewSecurePass456!", } serializer = PasswordChangeSerializer(data=data, context={"request": request}) assert serializer.is_valid(), serializer.errors def test__validate_old_password__incorrect__returns_error(self): """Test validation fails with incorrect old password.""" user = UserFactory() user.set_password("CorrectOldPass!") user.save() factory = RequestFactory() request = factory.post("/password-change/") request.user = user data = { "old_password": "WrongOldPass!", "new_password": "NewSecurePass456!", "new_password_confirm": "NewSecurePass456!", } serializer = PasswordChangeSerializer(data=data, context={"request": request}) assert not serializer.is_valid() assert "old_password" in serializer.errors def test__validate__mismatched_new_passwords__returns_error(self): """Test validation fails with mismatched new passwords.""" user = UserFactory() user.set_password("OldPass123!") user.save() factory = RequestFactory() request = factory.post("/password-change/") request.user = user data = { "old_password": "OldPass123!", "new_password": "NewSecurePass456!", "new_password_confirm": "DifferentPass789!", } serializer = PasswordChangeSerializer(data=data, context={"request": request}) assert not serializer.is_valid() assert "new_password_confirm" in serializer.errors def test__save__valid_data__changes_password(self): """Test save changes the password.""" user = UserFactory() user.set_password("OldPass123!") user.save() factory = RequestFactory() request = factory.post("/password-change/") request.user = user data = { "old_password": "OldPass123!", "new_password": "NewSecurePass456!", "new_password_confirm": "NewSecurePass456!", } serializer = PasswordChangeSerializer(data=data, context={"request": request}) assert serializer.is_valid(), serializer.errors serializer.save() user.refresh_from_db() assert user.check_password("NewSecurePass456!") class TestSocialProviderSerializer(TestCase): """Tests for SocialProviderSerializer.""" def test__validate__valid_provider__returns_data(self): """Test validation passes with valid provider data.""" data = { "id": "google", "name": "Google", "login_url": "https://accounts.google.com/oauth/login", } serializer = SocialProviderSerializer(data=data) assert serializer.is_valid(), serializer.errors assert serializer.validated_data["id"] == "google" assert serializer.validated_data["name"] == "Google" def test__validate__invalid_url__returns_error(self): """Test validation fails with invalid URL.""" data = { "id": "invalid", "name": "Invalid Provider", "login_url": "not-a-valid-url", } serializer = SocialProviderSerializer(data=data) assert not serializer.is_valid() assert "login_url" in serializer.errors @pytest.mark.django_db class TestUserProfileOutputSerializer(TestCase): """Tests for UserProfileOutputSerializer.""" def test__serialize__profile__returns_expected_fields(self): """Test serializing profile returns expected fields.""" user = UserFactory() # Create mock profile mock_profile = Mock() mock_profile.user = user mock_profile.avatar = None serializer = UserProfileOutputSerializer(mock_profile) # Should include user nested serializer assert "user" in serializer.data or serializer.data is not None @pytest.mark.django_db class TestUserProfileCreateInputSerializer(TestCase): """Tests for UserProfileCreateInputSerializer.""" def test__meta__fields__includes_all_fields(self): """Test Meta.fields is set to __all__.""" assert UserProfileCreateInputSerializer.Meta.fields == "__all__" @pytest.mark.django_db class TestUserProfileUpdateInputSerializer(TestCase): """Tests for UserProfileUpdateInputSerializer.""" def test__meta__user_read_only(self): """Test user field is read-only for updates.""" extra_kwargs = UserProfileUpdateInputSerializer.Meta.extra_kwargs assert extra_kwargs.get("user", {}).get("read_only") is True class TestTopListCreateInputSerializer(TestCase): """Tests for TopListCreateInputSerializer.""" def test__meta__fields__includes_all_fields(self): """Test Meta.fields is set to __all__.""" assert TopListCreateInputSerializer.Meta.fields == "__all__" class TestTopListUpdateInputSerializer(TestCase): """Tests for TopListUpdateInputSerializer.""" def test__meta__user_read_only(self): """Test user field is read-only for updates.""" extra_kwargs = TopListUpdateInputSerializer.Meta.extra_kwargs assert extra_kwargs.get("user", {}).get("read_only") is True class TestTopListItemCreateInputSerializer(TestCase): """Tests for TopListItemCreateInputSerializer.""" def test__meta__fields__includes_all_fields(self): """Test Meta.fields is set to __all__.""" assert TopListItemCreateInputSerializer.Meta.fields == "__all__" class TestTopListItemUpdateInputSerializer(TestCase): """Tests for TopListItemUpdateInputSerializer.""" def test__meta__top_list_not_read_only(self): """Test top_list field is not read-only for updates.""" extra_kwargs = TopListItemUpdateInputSerializer.Meta.extra_kwargs assert extra_kwargs.get("top_list", {}).get("read_only") is False