golfgame/server/test_auth.py

289 lines
9.4 KiB
Python

"""
Tests for the authentication system.
Run with: pytest test_auth.py -v
"""
import os
import pytest
import tempfile
from datetime import datetime, timedelta
from auth import AuthManager, User, UserRole, Session, InviteCode
@pytest.fixture
def auth_manager():
"""Create a fresh auth manager with temporary database."""
# Use a temporary file for testing
fd, path = tempfile.mkstemp(suffix=".db")
os.close(fd)
# Create manager (this will create default admin)
manager = AuthManager(db_path=path)
yield manager
# Cleanup
os.unlink(path)
class TestUserCreation:
"""Test user creation and retrieval."""
def test_create_user(self, auth_manager):
"""Can create a new user."""
user = auth_manager.create_user(
username="testuser",
password="password123",
email="test@example.com",
)
assert user is not None
assert user.username == "testuser"
assert user.email == "test@example.com"
assert user.role == UserRole.USER
assert user.is_active is True
def test_create_duplicate_username_fails(self, auth_manager):
"""Cannot create user with duplicate username."""
auth_manager.create_user(username="testuser", password="pass1")
user2 = auth_manager.create_user(username="testuser", password="pass2")
assert user2 is None
def test_create_duplicate_email_fails(self, auth_manager):
"""Cannot create user with duplicate email."""
auth_manager.create_user(
username="user1",
password="pass1",
email="test@example.com"
)
user2 = auth_manager.create_user(
username="user2",
password="pass2",
email="test@example.com"
)
assert user2 is None
def test_create_admin_user(self, auth_manager):
"""Can create admin user."""
user = auth_manager.create_user(
username="newadmin",
password="adminpass",
role=UserRole.ADMIN,
)
assert user is not None
assert user.is_admin() is True
def test_get_user_by_id(self, auth_manager):
"""Can retrieve user by ID."""
created = auth_manager.create_user(username="testuser", password="pass")
retrieved = auth_manager.get_user_by_id(created.id)
assert retrieved is not None
assert retrieved.username == "testuser"
def test_get_user_by_username(self, auth_manager):
"""Can retrieve user by username."""
auth_manager.create_user(username="testuser", password="pass")
retrieved = auth_manager.get_user_by_username("testuser")
assert retrieved is not None
assert retrieved.username == "testuser"
class TestAuthentication:
"""Test login and session management."""
def test_authenticate_valid_credentials(self, auth_manager):
"""Can authenticate with valid credentials."""
auth_manager.create_user(username="testuser", password="correctpass")
user = auth_manager.authenticate("testuser", "correctpass")
assert user is not None
assert user.username == "testuser"
def test_authenticate_invalid_password(self, auth_manager):
"""Invalid password returns None."""
auth_manager.create_user(username="testuser", password="correctpass")
user = auth_manager.authenticate("testuser", "wrongpass")
assert user is None
def test_authenticate_nonexistent_user(self, auth_manager):
"""Nonexistent user returns None."""
user = auth_manager.authenticate("nonexistent", "anypass")
assert user is None
def test_authenticate_inactive_user(self, auth_manager):
"""Inactive user cannot authenticate."""
created = auth_manager.create_user(username="testuser", password="pass")
auth_manager.update_user(created.id, is_active=False)
user = auth_manager.authenticate("testuser", "pass")
assert user is None
def test_create_session(self, auth_manager):
"""Can create session for authenticated user."""
user = auth_manager.create_user(username="testuser", password="pass")
session = auth_manager.create_session(user)
assert session is not None
assert session.user_id == user.id
assert session.is_expired() is False
def test_get_user_from_session(self, auth_manager):
"""Can get user from valid session token."""
user = auth_manager.create_user(username="testuser", password="pass")
session = auth_manager.create_session(user)
retrieved = auth_manager.get_user_from_session(session.token)
assert retrieved is not None
assert retrieved.id == user.id
def test_invalid_session_token(self, auth_manager):
"""Invalid session token returns None."""
user = auth_manager.get_user_from_session("invalid_token")
assert user is None
def test_invalidate_session(self, auth_manager):
"""Can invalidate a session."""
user = auth_manager.create_user(username="testuser", password="pass")
session = auth_manager.create_session(user)
auth_manager.invalidate_session(session.token)
retrieved = auth_manager.get_user_from_session(session.token)
assert retrieved is None
class TestInviteCodes:
"""Test invite code functionality."""
def test_create_invite_code(self, auth_manager):
"""Can create invite code."""
admin = auth_manager.get_user_by_username("admin")
invite = auth_manager.create_invite_code(created_by=admin.id)
assert invite is not None
assert len(invite.code) == 8
assert invite.is_valid() is True
def test_use_invite_code(self, auth_manager):
"""Can use invite code."""
admin = auth_manager.get_user_by_username("admin")
invite = auth_manager.create_invite_code(created_by=admin.id, max_uses=1)
result = auth_manager.use_invite_code(invite.code)
assert result is True
# Check use count increased
updated = auth_manager.get_invite_code(invite.code)
assert updated.use_count == 1
def test_invite_code_max_uses(self, auth_manager):
"""Invite code respects max uses."""
admin = auth_manager.get_user_by_username("admin")
invite = auth_manager.create_invite_code(created_by=admin.id, max_uses=1)
# First use should work
auth_manager.use_invite_code(invite.code)
# Second use should fail (max_uses=1)
updated = auth_manager.get_invite_code(invite.code)
assert updated.is_valid() is False
def test_invite_code_case_insensitive(self, auth_manager):
"""Invite code lookup is case insensitive."""
admin = auth_manager.get_user_by_username("admin")
invite = auth_manager.create_invite_code(created_by=admin.id)
retrieved_lower = auth_manager.get_invite_code(invite.code.lower())
retrieved_upper = auth_manager.get_invite_code(invite.code.upper())
assert retrieved_lower is not None
assert retrieved_upper is not None
def test_deactivate_invite_code(self, auth_manager):
"""Can deactivate invite code."""
admin = auth_manager.get_user_by_username("admin")
invite = auth_manager.create_invite_code(created_by=admin.id)
auth_manager.deactivate_invite_code(invite.code)
updated = auth_manager.get_invite_code(invite.code)
assert updated.is_valid() is False
class TestAdminFunctions:
"""Test admin-only functions."""
def test_list_users(self, auth_manager):
"""Admin can list all users."""
auth_manager.create_user(username="user1", password="pass1")
auth_manager.create_user(username="user2", password="pass2")
users = auth_manager.list_users()
# Should include admin + 2 created users
assert len(users) >= 3
def test_update_user_role(self, auth_manager):
"""Admin can change user role."""
user = auth_manager.create_user(username="testuser", password="pass")
updated = auth_manager.update_user(user.id, role=UserRole.ADMIN)
assert updated.is_admin() is True
def test_change_password(self, auth_manager):
"""Admin can change user password."""
user = auth_manager.create_user(username="testuser", password="oldpass")
auth_manager.change_password(user.id, "newpass")
# Old password should not work
auth_fail = auth_manager.authenticate("testuser", "oldpass")
assert auth_fail is None
# New password should work
auth_ok = auth_manager.authenticate("testuser", "newpass")
assert auth_ok is not None
def test_delete_user(self, auth_manager):
"""Admin can deactivate user."""
user = auth_manager.create_user(username="testuser", password="pass")
auth_manager.delete_user(user.id)
# User should be inactive
updated = auth_manager.get_user_by_id(user.id)
assert updated.is_active is False
# User should not be able to login
auth_fail = auth_manager.authenticate("testuser", "pass")
assert auth_fail is None
class TestDefaultAdmin:
"""Test default admin creation."""
def test_default_admin_created(self, auth_manager):
"""Default admin is created if no admins exist."""
admin = auth_manager.get_user_by_username("admin")
assert admin is not None
assert admin.is_admin() is True
if __name__ == "__main__":
pytest.main([__file__, "-v"])