Numerous WebUI animations, improvements, AI fixes, opporitunity cost-based decision logic, etc.
This commit is contained in:
288
server/test_auth.py
Normal file
288
server/test_auth.py
Normal file
@@ -0,0 +1,288 @@
|
||||
"""
|
||||
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"])
|
||||
Reference in New Issue
Block a user