mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-23 03:11:08 -05:00
515 lines
17 KiB
Python
515 lines
17 KiB
Python
"""
|
|
Tests for Account serializers.
|
|
|
|
Following Django styleguide pattern: test__<context>__<action>__<expected_outcome>
|
|
"""
|
|
|
|
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
|