feat(schemas): T17 add Pydantic auth schemas
Add authentication schemas for user registration and login: - UserRegister: email, password (with strength validation), password_confirm - UserLogin: email, password - UserResponse: id, email, created_at, is_active (orm_mode=True) - TokenResponse: access_token, token_type, expires_in - TokenData: user_id, exp Includes field validators for password strength and password confirmation matching. Test coverage: 19 tests for all schemas
This commit is contained in:
262
tests/unit/schemas/test_auth_schemas.py
Normal file
262
tests/unit/schemas/test_auth_schemas.py
Normal file
@@ -0,0 +1,262 @@
|
||||
"""Tests for authentication Pydantic schemas.
|
||||
|
||||
T17: Test Pydantic schemas for user authentication.
|
||||
"""
|
||||
import pytest
|
||||
from pydantic import ValidationError, EmailStr
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
class TestUserRegister:
|
||||
"""Tests for UserRegister schema."""
|
||||
|
||||
def test_valid_registration(self):
|
||||
"""Test valid user registration data."""
|
||||
# This will fail until schema is implemented
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
data = UserRegister(
|
||||
email="test@example.com",
|
||||
password="SecurePass123!",
|
||||
password_confirm="SecurePass123!"
|
||||
)
|
||||
|
||||
assert data.email == "test@example.com"
|
||||
assert data.password == "SecurePass123!"
|
||||
assert data.password_confirm == "SecurePass123!"
|
||||
|
||||
def test_invalid_email_format(self):
|
||||
"""Test that invalid email format raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="email"):
|
||||
UserRegister(
|
||||
email="not-an-email",
|
||||
password="SecurePass123!",
|
||||
password_confirm="SecurePass123!"
|
||||
)
|
||||
|
||||
def test_password_too_short(self):
|
||||
"""Test that password shorter than 12 chars raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="password"):
|
||||
UserRegister(
|
||||
email="test@example.com",
|
||||
password="Short1!",
|
||||
password_confirm="Short1!"
|
||||
)
|
||||
|
||||
def test_password_missing_uppercase(self):
|
||||
"""Test that password without uppercase raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="password"):
|
||||
UserRegister(
|
||||
email="test@example.com",
|
||||
password="lowercase123!",
|
||||
password_confirm="lowercase123!"
|
||||
)
|
||||
|
||||
def test_password_missing_lowercase(self):
|
||||
"""Test that password without lowercase raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="password"):
|
||||
UserRegister(
|
||||
email="test@example.com",
|
||||
password="UPPERCASE123!",
|
||||
password_confirm="UPPERCASE123!"
|
||||
)
|
||||
|
||||
def test_password_missing_digit(self):
|
||||
"""Test that password without digit raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="password"):
|
||||
UserRegister(
|
||||
email="test@example.com",
|
||||
password="NoDigitsHere!",
|
||||
password_confirm="NoDigitsHere!"
|
||||
)
|
||||
|
||||
def test_password_missing_special_char(self):
|
||||
"""Test that password without special char raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="password"):
|
||||
UserRegister(
|
||||
email="test@example.com",
|
||||
password="NoSpecialChars123",
|
||||
password_confirm="NoSpecialChars123"
|
||||
)
|
||||
|
||||
def test_passwords_do_not_match(self):
|
||||
"""Test that mismatched passwords raise ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
with pytest.raises(ValidationError, match="match"):
|
||||
UserRegister(
|
||||
email="test@example.com",
|
||||
password="SecurePass123!",
|
||||
password_confirm="DifferentPass123!"
|
||||
)
|
||||
|
||||
def test_password_strength_validator_called(self):
|
||||
"""Test that validate_password_strength is called."""
|
||||
from openrouter_monitor.schemas.auth import UserRegister
|
||||
|
||||
# Valid password should pass
|
||||
data = UserRegister(
|
||||
email="test@example.com",
|
||||
password="ValidPass123!@#",
|
||||
password_confirm="ValidPass123!@#"
|
||||
)
|
||||
assert data.password == "ValidPass123!@#"
|
||||
|
||||
|
||||
class TestUserLogin:
|
||||
"""Tests for UserLogin schema."""
|
||||
|
||||
def test_valid_login(self):
|
||||
"""Test valid login credentials."""
|
||||
from openrouter_monitor.schemas.auth import UserLogin
|
||||
|
||||
data = UserLogin(
|
||||
email="test@example.com",
|
||||
password="anypassword"
|
||||
)
|
||||
|
||||
assert data.email == "test@example.com"
|
||||
assert data.password == "anypassword"
|
||||
|
||||
def test_invalid_email_format(self):
|
||||
"""Test that invalid email format raises ValidationError."""
|
||||
from openrouter_monitor.schemas.auth import UserLogin
|
||||
|
||||
with pytest.raises(ValidationError, match="email"):
|
||||
UserLogin(
|
||||
email="not-an-email",
|
||||
password="password"
|
||||
)
|
||||
|
||||
def test_empty_password(self):
|
||||
"""Test that empty password is allowed (validation happens elsewhere)."""
|
||||
from openrouter_monitor.schemas.auth import UserLogin
|
||||
|
||||
data = UserLogin(
|
||||
email="test@example.com",
|
||||
password=""
|
||||
)
|
||||
|
||||
assert data.password == ""
|
||||
|
||||
|
||||
class TestUserResponse:
|
||||
"""Tests for UserResponse schema."""
|
||||
|
||||
def test_valid_response(self):
|
||||
"""Test valid user response."""
|
||||
from openrouter_monitor.schemas.auth import UserResponse
|
||||
from datetime import datetime
|
||||
|
||||
created_at = datetime(2024, 1, 1, 12, 0, 0)
|
||||
|
||||
data = UserResponse(
|
||||
id=1,
|
||||
email="test@example.com",
|
||||
created_at=created_at,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
assert data.id == 1
|
||||
assert data.email == "test@example.com"
|
||||
assert data.created_at == created_at
|
||||
assert data.is_active is True
|
||||
|
||||
def test_from_orm(self):
|
||||
"""Test that UserResponse can be created from ORM model."""
|
||||
from openrouter_monitor.schemas.auth import UserResponse
|
||||
from datetime import datetime
|
||||
|
||||
# Mock ORM object
|
||||
class MockUser:
|
||||
id = 1
|
||||
email = "test@example.com"
|
||||
created_at = datetime(2024, 1, 1, 12, 0, 0)
|
||||
is_active = True
|
||||
|
||||
user = UserResponse.model_validate(MockUser())
|
||||
|
||||
assert user.id == 1
|
||||
assert user.email == "test@example.com"
|
||||
|
||||
|
||||
class TestTokenResponse:
|
||||
"""Tests for TokenResponse schema."""
|
||||
|
||||
def test_valid_token_response(self):
|
||||
"""Test valid token response."""
|
||||
from openrouter_monitor.schemas.auth import TokenResponse
|
||||
|
||||
data = TokenResponse(
|
||||
access_token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
token_type="bearer",
|
||||
expires_in=3600
|
||||
)
|
||||
|
||||
assert data.access_token == "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
assert data.token_type == "bearer"
|
||||
assert data.expires_in == 3600
|
||||
|
||||
def test_default_token_type(self):
|
||||
"""Test that default token_type is 'bearer'."""
|
||||
from openrouter_monitor.schemas.auth import TokenResponse
|
||||
|
||||
data = TokenResponse(
|
||||
access_token="some_token",
|
||||
expires_in=3600
|
||||
)
|
||||
|
||||
assert data.token_type == "bearer"
|
||||
|
||||
|
||||
class TestTokenData:
|
||||
"""Tests for TokenData schema."""
|
||||
|
||||
def test_valid_token_data(self):
|
||||
"""Test valid token data."""
|
||||
from openrouter_monitor.schemas.auth import TokenData
|
||||
|
||||
exp = datetime.now(timezone.utc)
|
||||
|
||||
data = TokenData(
|
||||
user_id="123",
|
||||
exp=exp
|
||||
)
|
||||
|
||||
assert data.user_id == "123"
|
||||
assert data.exp == exp
|
||||
|
||||
def test_user_id_from_sub(self):
|
||||
"""Test that user_id can be extracted from sub claim."""
|
||||
from openrouter_monitor.schemas.auth import TokenData
|
||||
|
||||
exp = datetime.now(timezone.utc)
|
||||
|
||||
# TokenData might be created from JWT payload with 'sub' field
|
||||
data = TokenData(user_id="456", exp=exp)
|
||||
assert data.user_id == "456"
|
||||
|
||||
def test_user_id_integer_conversion(self):
|
||||
"""Test that user_id handles integer IDs."""
|
||||
from openrouter_monitor.schemas.auth import TokenData
|
||||
|
||||
exp = datetime.now(timezone.utc)
|
||||
|
||||
data = TokenData(
|
||||
user_id=123, # Integer ID
|
||||
exp=exp
|
||||
)
|
||||
|
||||
assert data.user_id == 123
|
||||
Reference in New Issue
Block a user