Unit testing AuthService

This commit is contained in:
2025-10-18 12:26:55 +02:00
commit 79a31ecf40
26 changed files with 3467 additions and 0 deletions

View File

View File

@@ -0,0 +1,75 @@
from uuid import uuid4
from datetime import datetime, timedelta
from pathlib import Path
import pytest
from my_auth.models.token import TokenData
from my_auth.models.user import UserCreate
from my_auth.persistence.sqlite import SQLiteUserRepository, SQLiteTokenRepository
@pytest.fixture
def test_user_data_create():
"""Provides valid data for creating a test user."""
return UserCreate(
email="test.user@example.com",
username="TestUser",
password="ValidPassword123!", # Meets all strength criteria
roles=["member"],
user_settings={"theme": "dark"}
)
@pytest.fixture
def test_user_hashed_password():
"""Provides a dummy hashed password for persistence (hashing is tested elsewhere)."""
return "$2b$12$R.S/XfI2tQYt3Kk.iF1XwOQz0Qe.L0T0mD/O1H8E2V5D4Q6F7G8H9I0"
@pytest.fixture
def test_token_data():
"""Provides valid data for a refresh token."""
now = datetime.now()
return TokenData(
token=f"opaque_refresh_token_{uuid4()}",
token_type="refresh",
user_id="user_id_for_token_test",
expires_at=now + timedelta(days=7),
is_revoked=False,
created_at=now
)
@pytest.fixture()
def sqlite_db_path(tmp_path_factory):
"""
Creates a temporary directory and an SQLite file path for the test session.
The directory and its contents are deleted after the session.
"""
temp_dir = tmp_path_factory.mktemp("sqlite_auth_test")
db_file: Path = temp_dir / "auth_test.db"
yield str(db_file)
try:
if temp_dir.exists():
import shutil
shutil.rmtree(temp_dir)
except OSError as e:
# Handle case where directory might be locked, though rare in tests
print(f"Error during cleanup of temporary DB directory: {e}")
@pytest.fixture
def user_repository(sqlite_db_path: str) -> SQLiteUserRepository:
"""Provides an instance of SQLiteUserRepository initialized with the in-memory DB."""
# Assuming the repository takes the connection object or path (using connection here)
return SQLiteUserRepository(sqlite_db_path)
@pytest.fixture
def token_repository(sqlite_db_path: str) -> SQLiteTokenRepository:
"""Provides an instance of SQLiteTokenRepository initialized with the in-memory DB."""
# Assuming the repository takes the connection object or path (using connection here)
return SQLiteTokenRepository(sqlite_db_path)

View File

@@ -0,0 +1,87 @@
# tests/persistence/test_sqlite_token.py
from datetime import datetime, timedelta
from my_auth.models.token import TokenData
from my_auth.persistence.sqlite import SQLiteTokenRepository
def test_i_can_save_and_retrieve_token(token_repository: SQLiteTokenRepository,
test_token_data: TokenData):
"""Verifies token saving and successful retrieval by token string and type."""
# 1. Save Token
token_repository.save_token(test_token_data)
# 2. Retrieve Token
retrieved_token = token_repository.get_token(test_token_data.token, test_token_data.token_type)
# Assertions
assert retrieved_token is not None
assert retrieved_token.token == test_token_data.token
assert retrieved_token.user_id == test_token_data.user_id
assert retrieved_token.is_revoked is False
assert retrieved_token.token_type == test_token_data.token_type
def test_i_can_revoke_token(token_repository: SQLiteTokenRepository,
test_token_data: TokenData):
"""Verifies a token can be revoked and its revoked status is updated."""
# Setup: Save the token
token_repository.save_token(test_token_data)
# 1. Revoke the token
was_revoked = token_repository.revoke_token(test_token_data.token)
assert was_revoked is True
# 2. Retrieve and check status
revoked_token = token_repository.get_token(test_token_data.token, test_token_data.token_type)
assert revoked_token is not None
assert revoked_token.is_revoked is True
# 3. Attempt to revoke a non-existent token
was_revoked_again = token_repository.revoke_token("non_existent_token")
assert was_revoked_again is False
def test_i_can_use_is_token_valid_for_valid_token(token_repository: SQLiteTokenRepository,
test_token_data: TokenData):
"""Verifies the convenience method returns True for a fresh, unexpired token."""
token_repository.save_token(test_token_data)
is_valid = token_repository.is_token_valid(test_token_data.token, test_token_data.token_type)
assert is_valid is True
is_valid = token_repository.is_token_valid("non_existent_token", test_token_data.token_type)
assert is_valid is False
def test_i_can_use_is_token_valid_for_revoked_token(token_repository: SQLiteTokenRepository,
test_token_data: TokenData):
"""Verifies is_token_valid returns False for a token marked as revoked."""
token_repository.save_token(test_token_data)
token_repository.revoke_token(test_token_data.token)
is_valid = token_repository.is_token_valid(test_token_data.token, test_token_data.token_type)
assert is_valid is False
def test_i_can_use_is_token_valid_for_expired_token(token_repository: SQLiteTokenRepository):
"""Verifies is_token_valid returns False for a token whose expiration is in the past."""
expired_token_data = TokenData(
token="expired_token_test",
token_type="password_reset",
user_id="user_id_expired",
expires_at=datetime.now() - timedelta(hours=1), # Set expiration to 1 hour ago
is_revoked=False,
created_at=datetime.now() - timedelta(hours=2)
)
token_repository.save_token(expired_token_data)
is_valid = token_repository.is_token_valid(expired_token_data.token, expired_token_data.token_type)
assert is_valid is False

View File

@@ -0,0 +1,155 @@
# tests/persistence/test_sqlite_user.py
import pytest
import json
from datetime import datetime
from my_auth.persistence.sqlite import SQLiteUserRepository
from my_auth.models.user import UserCreate, UserUpdate
from my_auth.exceptions import UserAlreadyExistsError, UserNotFoundError
def test_i_can_create_and_retrieve_user_by_email(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Verifies user creation and successful retrieval by email."""
# 1. Create User
created_user = user_repository.create_user(
user_data=test_user_data_create,
hashed_password=test_user_hashed_password
)
# Assertions on creation
assert created_user is not None
assert created_user.email == test_user_data_create.email
assert created_user.hashed_password == test_user_hashed_password
assert created_user.is_active is True
assert created_user.is_verified is False
assert created_user.id is not None
assert isinstance(created_user.created_at, datetime)
# 2. Retrieve User
retrieved_user = user_repository.get_user_by_email(test_user_data_create.email)
# Assertions on retrieval
assert retrieved_user is not None
assert retrieved_user.id == created_user.id
assert retrieved_user.username == "TestUser"
def test_i_can_retrieve_user_by_id(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Verifies user retrieval by unique ID."""
created_user = user_repository.create_user(test_user_data_create, test_user_hashed_password)
retrieved_user = user_repository.get_user_by_id(created_user.id)
assert retrieved_user is not None
assert retrieved_user.email == created_user.email
def test_i_cannot_create_user_if_email_exists(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Ensures that creating a user with an existing email raises UserAlreadyExistsError."""
# First creation should succeed
user_repository.create_user(test_user_data_create, test_user_hashed_password)
# Second creation with same email should fail
with pytest.raises(UserAlreadyExistsError):
user_repository.create_user(test_user_data_create, test_user_hashed_password)
def test_i_can_check_if_email_exists(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Verifies the email_exists method returns correct boolean results."""
email = test_user_data_create.email
non_existent_email = "non.existent@example.com"
# 1. Check before creation (Should be False)
assert user_repository.email_exists(email) is False
user_repository.create_user(test_user_data_create, test_user_hashed_password)
# 2. Check after creation (Should be True)
assert user_repository.email_exists(email) is True
# 3. Check for another non-existent email
assert user_repository.email_exists(non_existent_email) is False
def test_i_can_update_username_and_roles(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Tests partial update of user fields (username and roles)."""
created_user = user_repository.create_user(test_user_data_create, test_user_hashed_password)
updates = UserUpdate(username="NewUsername", roles=["admin", "staff"])
updated_user = user_repository.update_user(created_user.id, updates)
assert updated_user.username == "NewUsername"
assert updated_user.roles == ["admin", "staff"]
# Check that unrelated fields remain the same
assert updated_user.email == created_user.email
# Check that update timestamp changed
assert updated_user.updated_at > created_user.updated_at
assert updated_user.is_verified == created_user.is_verified
def test_i_can_update_is_active_status(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Tests the specific update of the 'is_active' status."""
created_user = user_repository.create_user(test_user_data_create, test_user_hashed_password)
# Deactivate
updates_deactivate = UserUpdate(is_active=False)
deactivated_user = user_repository.update_user(created_user.id, updates_deactivate)
assert deactivated_user.is_active is False
# Reactivate
updates_activate = UserUpdate(is_active=True)
reactivated_user = user_repository.update_user(created_user.id, updates_activate)
assert reactivated_user.is_active is True
def test_i_cannot_update_non_existent_user(user_repository: SQLiteUserRepository):
"""Ensures that updating a user with an unknown ID raises UserNotFoundError."""
non_existent_id = "unknown_id_123"
updates = UserUpdate(username="Phantom")
with pytest.raises(UserNotFoundError):
user_repository.update_user(non_existent_id, updates)
def test_i_can_delete_user(user_repository: SQLiteUserRepository,
test_user_data_create: UserCreate,
test_user_hashed_password: str):
"""Verifies user deletion and subsequent failure to retrieve."""
created_user = user_repository.create_user(test_user_data_create, test_user_hashed_password)
# 1. Delete the user
was_deleted = user_repository.delete_user(created_user.id)
assert was_deleted is True
# 2. Verify deletion by attempting retrieval
retrieved_user = user_repository.get_user_by_id(created_user.id)
assert retrieved_user is None
# 3. Verify attempting to delete again returns False
was_deleted_again = user_repository.delete_user(created_user.id)
assert was_deleted_again is False
def test_i_cannot_retrieve_non_existent_user_by_email(user_repository: SQLiteUserRepository):
"""Ensures retrieval by email returns None for non-existent email."""
retrieved_user = user_repository.get_user_by_email("ghost@example.com")
assert retrieved_user is None