Build the core user-facing experience with admin login (bcrypt + signed session cookies), profile switcher, workout day viewer with warmups and exercise cards, and HTMX-powered exercise browser with search/filter. - AuthService with bcrypt password verification and itsdangerous session tokens - Auth dependency redirects to /login (303) for unauthenticated requests - NavContextMiddleware injects admin/profiles/active_profile into all templates - Profile management (list, switch, edit) with cookie-based active profile - Workout day viewer shows warmups + exercises + per-user programming targets - Exercise browser with HTMX filter dropdowns (no page reloads) - Flash message partial for success/error feedback - 12 new tests (66 total passing) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
94 lines
3.2 KiB
Python
94 lines
3.2 KiB
Python
"""Tests for the AuthService class."""
|
|
|
|
import bcrypt
|
|
from sqlmodel import SQLModel, Session, create_engine
|
|
|
|
from app.models.user import User
|
|
from app.services.auth_service import AuthService
|
|
|
|
|
|
class TestAuthService:
|
|
"""Tests for admin authentication logic."""
|
|
|
|
def _setup(self):
|
|
"""Create an in-memory DB with an admin user."""
|
|
engine = create_engine("sqlite:///:memory:")
|
|
SQLModel.metadata.create_all(engine)
|
|
session = Session(engine)
|
|
|
|
# Create admin user with known password
|
|
pw_hash = bcrypt.hashpw(b"adminpass", bcrypt.gensalt()).decode("utf-8")
|
|
admin = User(
|
|
username="admin",
|
|
password_hash=pw_hash,
|
|
display_name="Admin",
|
|
is_admin=True,
|
|
)
|
|
session.add(admin)
|
|
session.commit()
|
|
|
|
service = AuthService(session, secret_key="test-secret-key")
|
|
return session, service
|
|
|
|
def test_authenticate_valid_credentials(self) -> None:
|
|
"""authenticate should return the User for valid credentials."""
|
|
session, service = self._setup()
|
|
user = service.authenticate("admin", "adminpass")
|
|
assert user is not None
|
|
assert user.username == "admin"
|
|
session.close()
|
|
|
|
def test_authenticate_wrong_password(self) -> None:
|
|
"""authenticate should return None for wrong password."""
|
|
session, service = self._setup()
|
|
user = service.authenticate("admin", "wrongpass")
|
|
assert user is None
|
|
session.close()
|
|
|
|
def test_authenticate_unknown_user(self) -> None:
|
|
"""authenticate should return None for unknown username."""
|
|
session, service = self._setup()
|
|
user = service.authenticate("nobody", "anything")
|
|
assert user is None
|
|
session.close()
|
|
|
|
def test_authenticate_non_admin(self) -> None:
|
|
"""authenticate should return None for non-admin users."""
|
|
session, service = self._setup()
|
|
pw_hash = bcrypt.hashpw(b"userpass", bcrypt.gensalt()).decode("utf-8")
|
|
non_admin = User(
|
|
username="regular",
|
|
password_hash=pw_hash,
|
|
display_name="Regular",
|
|
is_admin=False,
|
|
)
|
|
session.add(non_admin)
|
|
session.commit()
|
|
|
|
user = service.authenticate("regular", "userpass")
|
|
assert user is None
|
|
session.close()
|
|
|
|
def test_create_session_token(self) -> None:
|
|
"""create_session_token should return a non-empty signed string."""
|
|
session, service = self._setup()
|
|
token = service.create_session_token(user_id=1)
|
|
assert isinstance(token, str)
|
|
assert len(token) > 0
|
|
session.close()
|
|
|
|
def test_validate_session_token(self) -> None:
|
|
"""validate_session_token should return the user_id from a valid token."""
|
|
session, service = self._setup()
|
|
token = service.create_session_token(user_id=42)
|
|
user_id = service.validate_session_token(token)
|
|
assert user_id == 42
|
|
session.close()
|
|
|
|
def test_validate_session_token_invalid(self) -> None:
|
|
"""validate_session_token should return None for tampered tokens."""
|
|
session, service = self._setup()
|
|
user_id = service.validate_session_token("fake-token-value")
|
|
assert user_id is None
|
|
session.close()
|