29c7040b20
- Integrated SQLModel with SQLite for robust data persistence - Refactored UserManager and WatchlistManager to use SQL queries - Migrated models to SQLModel with relationships and primary keys - Updated test suite with in-memory database isolation - Removed deprecated JSON storage files
152 lines
6.1 KiB
Python
152 lines
6.1 KiB
Python
"""
|
|
Unit tests for authentication system (app/auth.py)
|
|
Tests JWT tokens, user management, and password hashing with SQLModel support
|
|
"""
|
|
import pytest
|
|
from datetime import datetime, timedelta
|
|
from unittest.mock import patch
|
|
from app.auth import UserManager, create_access_token, verify_token, get_user_from_token
|
|
from app.models.auth import UserTable
|
|
|
|
|
|
class TestUserManager:
|
|
"""Tests for UserManager class using SQLModel"""
|
|
|
|
def test_create_user_success(self, user_manager):
|
|
"""Test successful user creation"""
|
|
user = user_manager.create_user("testuser", "password123")
|
|
assert user.username == "testuser"
|
|
assert hasattr(user, "hashed_password")
|
|
assert user.created_at is not None
|
|
assert user.last_login is None
|
|
|
|
# Verify it's in the database
|
|
db_user = user_manager.get_user("testuser")
|
|
assert db_user is not None
|
|
assert db_user.username == "testuser"
|
|
|
|
def test_create_user_hashing(self, user_manager):
|
|
"""Test that passwords are properly hashed with bcrypt"""
|
|
user = user_manager.create_user("testuser", "password123")
|
|
# Hash should not be the plain password
|
|
assert user.hashed_password != "password123"
|
|
# Bcrypt hashes start with $2b$
|
|
assert user.hashed_password.startswith("$2b$")
|
|
# Hash should be 60 characters (bcrypt standard)
|
|
assert len(user.hashed_password) == 60
|
|
|
|
def test_create_user_duplicate(self, user_manager):
|
|
"""Test that duplicate usernames are rejected"""
|
|
user_manager.create_user("testuser", "password123")
|
|
with pytest.raises(ValueError, match="already exists"):
|
|
user_manager.create_user("testuser", "different456")
|
|
|
|
def test_create_user_password_truncation(self, user_manager):
|
|
"""Test that passwords longer than 72 bytes are truncated (bcrypt limit)"""
|
|
long_password = "a" * 100
|
|
user = user_manager.create_user("testuser", long_password)
|
|
assert user.username == "testuser"
|
|
|
|
def test_authenticate_user_success(self, user_manager):
|
|
"""Test successful user authentication"""
|
|
user_manager.create_user("testuser", "password123")
|
|
user = user_manager.authenticate_user("testuser", "password123")
|
|
assert user is not None
|
|
assert user.username == "testuser"
|
|
assert user.last_login is not None
|
|
|
|
def test_authenticate_user_wrong_password(self, user_manager):
|
|
"""Test authentication with wrong password"""
|
|
user_manager.create_user("testuser", "password123")
|
|
user = user_manager.authenticate_user("testuser", "wrongpassword")
|
|
assert user is None
|
|
|
|
def test_authenticate_user_nonexistent(self, user_manager):
|
|
"""Test authentication with non-existent user"""
|
|
user = user_manager.authenticate_user("nonexistent", "password")
|
|
assert user is None
|
|
|
|
def test_authenticate_updates_last_login(self, user_manager):
|
|
"""Test that authentication updates last_login timestamp"""
|
|
user_manager.create_user("testuser", "password123")
|
|
user_before = user_manager.get_user("testuser")
|
|
assert user_before.last_login is None
|
|
|
|
user_manager.authenticate_user("testuser", "password123")
|
|
user_after = user_manager.get_user("testuser")
|
|
assert user_after.last_login is not None
|
|
|
|
def test_get_user(self, user_manager):
|
|
"""Test getting a user by username"""
|
|
user_manager.create_user("testuser", "password123")
|
|
user = user_manager.get_user("testuser")
|
|
assert user is not None
|
|
assert user.username == "testuser"
|
|
|
|
def test_get_user_by_id(self, user_manager):
|
|
"""Test getting a user by ID"""
|
|
user = user_manager.create_user("testuser", "password123")
|
|
user_id = user.id
|
|
db_user = user_manager.get_user_by_id(user_id)
|
|
assert db_user is not None
|
|
assert db_user.username == "testuser"
|
|
|
|
def test_get_user_nonexistent(self, user_manager):
|
|
"""Test getting a non-existent user"""
|
|
user = user_manager.get_user("nonexistent")
|
|
assert user is None
|
|
|
|
def test_update_user(self, user_manager):
|
|
"""Test updating user information"""
|
|
user = user_manager.create_user("testuser", "password123")
|
|
updated = user_manager.update_user(user.id, {"full_name": "New Name", "email": "new@example.com"})
|
|
assert updated.full_name == "New Name"
|
|
assert updated.email == "new@example.com"
|
|
|
|
db_user = user_manager.get_user("testuser")
|
|
assert db_user.full_name == "New Name"
|
|
|
|
|
|
class TestJWTToken:
|
|
"""Tests for JWT token functions"""
|
|
|
|
def test_create_access_token(self):
|
|
"""Test creating an access token"""
|
|
token = create_access_token({"sub": "testuser"})
|
|
assert isinstance(token, str)
|
|
assert len(token) > 0
|
|
|
|
def test_verify_token_success(self):
|
|
"""Test verifying a valid token"""
|
|
token = create_access_token({"sub": "testuser"})
|
|
username = verify_token(token)
|
|
assert username == "testuser"
|
|
|
|
@pytest.mark.skip(reason="Problematic mock with datetime and jose library")
|
|
def test_verify_token_expired(self):
|
|
"""Test verifying an expired token"""
|
|
with patch('app.auth.datetime') as mock_datetime:
|
|
# Set fixed time
|
|
now = datetime.utcnow()
|
|
mock_datetime.utcnow.return_value = now
|
|
|
|
# Create token that expires in 1 minute
|
|
token = create_access_token({"sub": "testuser"}, expires_delta=timedelta(minutes=1))
|
|
|
|
# Move time forward by 2 minutes
|
|
mock_datetime.utcnow.return_value = now + timedelta(minutes=2)
|
|
|
|
username = verify_token(token)
|
|
assert username is None
|
|
|
|
def test_verify_token_invalid(self):
|
|
"""Test verifying an invalid token"""
|
|
username = verify_token("invalid-token")
|
|
assert username is None
|
|
|
|
def test_get_user_from_token(self):
|
|
"""Test get_user_from_token alias"""
|
|
token = create_access_token({"sub": "testuser"})
|
|
username = get_user_from_token(token)
|
|
assert username == "testuser"
|