Files
MyDocManager/tests/models/test_user_models.py

365 lines
11 KiB
Python

"""
Unit tests for user models and validation.
Tests the Pydantic models used for user creation, update, and response.
Validates email format, password strength, and data serialization.
"""
import pytest
from pydantic import ValidationError
from datetime import datetime
from bson import ObjectId
from app.models.user import UserCreate, UserUpdate, UserInDB
from app.models.auth import UserRole, UserResponse
class TestUserCreateModel:
"""Tests for UserCreate Pydantic model validation."""
def test_i_can_create_user_create_model(self):
"""Test creation of valid UserCreate model with all required fields."""
user_data = {
"username": "testuser",
"email": "test@example.com",
"password": "TestPass123!",
"role": UserRole.USER
}
user = UserCreate(**user_data)
assert user.username == "testuser"
assert user.email == "test@example.com"
assert user.password == "TestPass123!"
assert user.role == UserRole.USER
def test_i_can_create_admin_user(self):
"""Test creation of admin user with admin role."""
user_data = {
"username": "adminuser",
"email": "admin@example.com",
"password": "AdminPass123!",
"role": UserRole.ADMIN
}
user = UserCreate(**user_data)
assert user.role == UserRole.ADMIN
def test_i_cannot_create_user_with_invalid_email(self):
"""Test that invalid email formats are rejected."""
invalid_emails = [
"notanemail",
"@example.com",
"test@",
"test.example.com",
"",
"test@.com"
]
for invalid_email in invalid_emails:
user_data = {
"username": "testuser",
"email": invalid_email,
"password": "TestPass123!",
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "email" in str(exc_info.value).lower()
def test_i_cannot_create_user_with_short_password(self):
"""Test that passwords shorter than 8 characters are rejected."""
short_passwords = [
"Test1!", # 6 chars
"Te1!", # 4 chars
"1234567", # 7 chars
"" # empty
]
for short_password in short_passwords:
user_data = {
"username": "testuser",
"email": "test@example.com",
"password": short_password,
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "password" in str(exc_info.value).lower()
def test_i_cannot_create_user_without_uppercase(self):
"""Test that passwords without uppercase letters are rejected."""
passwords_without_uppercase = [
"testpass123!",
"mypassword1!",
"lowercase123!"
]
for password in passwords_without_uppercase:
user_data = {
"username": "testuser",
"email": "test@example.com",
"password": password,
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "password" in str(exc_info.value).lower()
def test_i_cannot_create_user_without_lowercase(self):
"""Test that passwords without lowercase letters are rejected."""
passwords_without_lowercase = [
"TESTPASS123!",
"MYPASSWORD1!",
"UPPERCASE123!"
]
for password in passwords_without_lowercase:
user_data = {
"username": "testuser",
"email": "test@example.com",
"password": password,
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "password" in str(exc_info.value).lower()
def test_i_cannot_create_user_without_digit(self):
"""Test that passwords without digits are rejected."""
passwords_without_digit = [
"TestPassword!",
"MyPassword!",
"UpperLower!"
]
for password in passwords_without_digit:
user_data = {
"username": "testuser",
"email": "test@example.com",
"password": password,
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "password" in str(exc_info.value).lower()
def test_i_cannot_create_user_without_special_character(self):
"""Test that passwords without special characters are rejected."""
passwords_without_special = [
"TestPass123",
"MyPassword1",
"UpperLower1"
]
for password in passwords_without_special:
user_data = {
"username": "testuser",
"email": "test@example.com",
"password": password,
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "password" in str(exc_info.value).lower()
def test_i_cannot_create_user_with_empty_username(self):
"""Test that empty username is rejected."""
user_data = {
"username": "",
"email": "test@example.com",
"password": "TestPass123!",
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "username" in str(exc_info.value).lower()
def test_i_cannot_create_user_with_whitespace_username(self):
"""Test that username with only whitespace is rejected."""
whitespace_usernames = [" ", "\t", "\n", " \t\n "]
for username in whitespace_usernames:
user_data = {
"username": username,
"email": "test@example.com",
"password": "TestPass123!",
"role": UserRole.USER
}
with pytest.raises(ValidationError) as exc_info:
UserCreate(**user_data)
assert "username" in str(exc_info.value).lower()
class TestUserUpdateModel:
"""Tests for UserUpdate Pydantic model validation."""
def test_i_can_create_user_update_model(self):
"""Test creation of valid UserUpdate model with optional fields."""
update_data = {
"email": "newemail@example.com",
"role": UserRole.ADMIN
}
user_update = UserUpdate(**update_data)
assert user_update.email == "newemail@example.com"
assert user_update.role == UserRole.ADMIN
assert user_update.username is None
assert user_update.password is None
def test_i_can_create_empty_user_update_model(self):
"""Test creation of UserUpdate model with no fields (all optional)."""
user_update = UserUpdate()
assert user_update.username is None
assert user_update.email is None
assert user_update.password is None
assert user_update.role is None
def test_i_can_update_password_with_valid_format(self):
"""Test that valid password can be used in update."""
update_data = {
"password": "NewPass123!"
}
user_update = UserUpdate(**update_data)
assert user_update.password == "NewPass123!"
def test_i_cannot_update_with_invalid_password(self):
"""Test that invalid password format is rejected in update."""
update_data = {
"password": "weak"
}
with pytest.raises(ValidationError) as exc_info:
UserUpdate(**update_data)
assert "password" in str(exc_info.value).lower()
class TestUserInDBModel:
"""Tests for UserInDB Pydantic model (database representation)."""
def test_i_can_create_user_in_db_model(self):
"""Test creation of valid UserInDB model with all fields."""
user_id = ObjectId()
created_at = datetime.now()
updated_at = datetime.now()
user_data = {
"id": user_id,
"username": "testuser",
"email": "test@example.com",
"hashed_password": "$2b$12$hashedpassword",
"role": UserRole.USER,
"is_active": True,
"created_at": created_at,
"updated_at": updated_at
}
user = UserInDB(**user_data)
assert user.id == user_id
assert user.username == "testuser"
assert user.email == "test@example.com"
assert user.hashed_password == "$2b$12$hashedpassword"
assert user.role == UserRole.USER
assert user.is_active is True
assert user.created_at == created_at
assert user.updated_at == updated_at
class TestUserResponseModel:
"""Tests for UserResponse Pydantic model (API response)."""
def test_i_can_create_user_response_model(self):
"""Test creation of valid UserResponse model without password."""
user_id = ObjectId()
created_at = datetime.now()
updated_at = datetime.now()
user_data = {
"id": user_id,
"username": "testuser",
"email": "test@example.com",
"role": UserRole.USER,
"is_active": True,
"created_at": created_at,
"updated_at": updated_at
}
user = UserResponse(**user_data)
assert user.id == user_id
assert user.username == "testuser"
assert user.email == "test@example.com"
assert user.role == UserRole.USER
assert user.is_active is True
assert user.created_at == created_at
assert user.updated_at == updated_at
# Verify password_hash is not included
assert not hasattr(user, 'password_hash')
def test_user_response_excludes_password_hash(self):
"""Test that UserResponse model does not expose password_hash."""
# This test verifies the model structure doesn't include password_hash
response_fields = UserResponse.__fields__.keys()
assert 'password_hash' not in response_fields
assert 'username' in response_fields
assert 'email' in response_fields
assert 'role' in response_fields
assert 'is_active' in response_fields
def test_i_can_convert_user_in_db_to_response(self):
"""Test conversion from UserInDB to UserResponse model."""
user_id = ObjectId()
created_at = datetime.now()
updated_at = datetime.now()
user_in_db = UserInDB(
id=user_id,
username="testuser",
email="test@example.com",
hashed_password="$2b$12$hashedpassword",
role=UserRole.USER,
is_active=True,
created_at=created_at,
updated_at=updated_at
)
# Convert to response model (excluding password_hash)
user_response = UserResponse(
_id=user_in_db.id,
username=user_in_db.username,
email=user_in_db.email,
role=user_in_db.role,
is_active=user_in_db.is_active,
created_at=user_in_db.created_at,
updated_at=user_in_db.updated_at
)
assert user_response.id == user_in_db.id
assert user_response.username == user_in_db.username
assert user_response.email == user_in_db.email
assert user_response.role == user_in_db.role
assert not hasattr(user_response, 'password_hash')