289 lines
9.4 KiB
Python
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"])
|