Add tests for authentication and utilities, and update dependencies

- Created a new test package for services and added tests for AuthService.
- Implemented tests for user registration, login, and token creation.
- Added a new test package for utilities and included tests for password and JWT utilities.
- Updated `uv.lock` to include new dependencies: bcrypt, email-validator, pyjwt, and pytest-asyncio.
This commit is contained in:
JSC
2025-07-25 17:48:43 +02:00
parent af20bc8724
commit e456d34897
23 changed files with 2381 additions and 8 deletions

1
tests/utils/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Utils tests package."""

View File

@@ -0,0 +1,182 @@
"""Tests for authentication utilities."""
from datetime import UTC, datetime, timedelta
import pytest
from fastapi import HTTPException
from app.utils.auth import JWTUtils, PasswordUtils, TokenUtils
PASSWORD_HASH_LENGTH_GT = 50
TOKEN_LENGTH_GT = 20
TOKEN_DOTS_COUNT = 2
STATUS_CODE_UNAUTHORIZED = 401
class TestPasswordUtils:
"""Test password utility functions."""
def test_hash_password(self) -> None:
"""Test password hashing."""
password = "testpassword123"
hashed = PasswordUtils.hash_password(password)
# Hash should be different from original password
assert hashed != password
# Hash should be a string
assert isinstance(hashed, str)
# Hash should have reasonable length (bcrypt produces ~60 chars)
assert len(hashed) > PASSWORD_HASH_LENGTH_GT
def test_hash_password_different_salts(self) -> None:
"""Test that same password produces different hashes (different salts)."""
password = "testpassword123"
hash1 = PasswordUtils.hash_password(password)
hash2 = PasswordUtils.hash_password(password)
# Same password should produce different hashes due to different salts
assert hash1 != hash2
def test_verify_password_correct(self) -> None:
"""Test password verification with correct password."""
password = "testpassword123"
hashed = PasswordUtils.hash_password(password)
# Correct password should verify
assert PasswordUtils.verify_password(password, hashed) is True
def test_verify_password_incorrect(self) -> None:
"""Test password verification with incorrect password."""
password = "testpassword123"
wrong_password = "wrongpassword"
hashed = PasswordUtils.hash_password(password)
# Wrong password should not verify
assert PasswordUtils.verify_password(wrong_password, hashed) is False
def test_verify_password_empty(self) -> None:
"""Test password verification with empty password."""
password = "testpassword123"
hashed = PasswordUtils.hash_password(password)
# Empty password should not verify
assert PasswordUtils.verify_password("", hashed) is False
class TestJWTUtils:
"""Test JWT utility functions."""
def test_create_access_token(self) -> None:
"""Test JWT token creation."""
data = {"sub": "123", "email": "test@example.com"}
token = JWTUtils.create_access_token(data)
# Token should be a string
assert isinstance(token, str)
# Token should have reasonable length
assert len(token) > PASSWORD_HASH_LENGTH_GT
# Token should contain dots (JWT format)
assert token.count(".") == TOKEN_DOTS_COUNT
def test_create_access_token_with_expiry(self) -> None:
"""Test JWT token creation with custom expiry."""
data = {"sub": "123", "email": "test@example.com"}
expires_delta = timedelta(minutes=5)
token = JWTUtils.create_access_token(data, expires_delta)
# Should create a valid token
assert isinstance(token, str)
assert len(token) > PASSWORD_HASH_LENGTH_GT
def test_decode_access_token(self) -> None:
"""Test JWT token decoding."""
original_data = {"sub": "123", "email": "test@example.com", "role": "user"}
token = JWTUtils.create_access_token(original_data)
decoded_data = JWTUtils.decode_access_token(token)
# Should decode to original data (plus exp)
assert decoded_data["sub"] == original_data["sub"]
assert decoded_data["email"] == original_data["email"]
assert decoded_data["role"] == original_data["role"]
assert "exp" in decoded_data
def test_decode_invalid_token(self) -> None:
"""Test decoding invalid JWT token."""
invalid_token = "invalid.token.here"
with pytest.raises(HTTPException) as exc_info:
JWTUtils.decode_access_token(invalid_token)
assert exc_info.value.status_code == STATUS_CODE_UNAUTHORIZED
assert "Could not validate credentials" in exc_info.value.detail
def test_decode_expired_token(self) -> None:
"""Test decoding expired JWT token."""
data = {"sub": "123", "email": "test@example.com"}
# Create token that expires immediately
expires_delta = timedelta(seconds=-1)
token = JWTUtils.create_access_token(data, expires_delta)
with pytest.raises(HTTPException) as exc_info:
JWTUtils.decode_access_token(token)
assert exc_info.value.status_code == STATUS_CODE_UNAUTHORIZED
assert "Token has expired" in exc_info.value.detail
def test_decode_empty_token(self) -> None:
"""Test decoding empty token."""
with pytest.raises(HTTPException) as exc_info:
JWTUtils.decode_access_token("")
assert exc_info.value.status_code == STATUS_CODE_UNAUTHORIZED
class TestTokenUtils:
"""Test token utility functions."""
def test_generate_api_token(self) -> None:
"""Test API token generation."""
token = TokenUtils.generate_api_token()
# Token should be a string
assert isinstance(token, str)
# Token should have reasonable length
assert len(token) > TOKEN_LENGTH_GT
# Token should be URL-safe
assert all(c.isalnum() or c in "-_" for c in token)
def test_generate_api_token_unique(self) -> None:
"""Test that API tokens are unique."""
token1 = TokenUtils.generate_api_token()
token2 = TokenUtils.generate_api_token()
# Tokens should be different
assert token1 != token2
def test_is_token_expired_none(self) -> None:
"""Test token expiry check with None."""
# None expiry should not be expired
assert TokenUtils.is_token_expired(None) is False
def test_is_token_expired_future(self) -> None:
"""Test token expiry check with future date."""
future_date = datetime.now(UTC) + timedelta(hours=1)
# Future date should not be expired
assert TokenUtils.is_token_expired(future_date) is False
def test_is_token_expired_past(self) -> None:
"""Test token expiry check with past date."""
past_date = datetime.now(UTC) - timedelta(hours=1)
# Past date should be expired
assert TokenUtils.is_token_expired(past_date) is True
def test_is_token_expired_naive_datetime(self) -> None:
"""Test token expiry check with naive datetime."""
# Create a past naive datetime (using UTC time to be consistent)
past_date = datetime.now(UTC) - timedelta(hours=1)
# Should handle naive datetime (treat as UTC)
assert TokenUtils.is_token_expired(past_date) is True