Files
root 29c7040b20
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
feat: migrate persistence from JSON to SQLModel (Phase 1)
- 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
2026-03-24 10:40:36 +00:00

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"