Add multi-league foundation

This commit is contained in:
Christian Krakau-Louis
2026-05-22 20:47:28 +02:00
parent e032994628
commit 15e1dc227b
23 changed files with 775 additions and 205 deletions
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import pytest
from datetime import datetime
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from app.league_context import get_default_league, parse_league_id, qr_code_league_id, resolve_selected_league
from app.models import Base, Event, League, QRCode, QRSet, Team
@pytest.fixture()
def db_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(bind=engine)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
def test_get_default_league_creates_compatible_default(db_session):
league = get_default_league(db_session)
assert league.name == "Default League"
assert league.slug == "default"
assert league.is_active is True
def test_resolve_selected_league_uses_active_requested_league(db_session):
get_default_league(db_session)
requested = League(name="Rover Pub League", slug="rover-pub", is_active=True)
inactive = League(name="Archived Pub League", slug="archived-pub", is_active=False)
db_session.add_all([requested, inactive])
db_session.commit()
assert resolve_selected_league(db_session, requested.id).id == requested.id
assert resolve_selected_league(db_session, inactive.id).slug == "default"
assert resolve_selected_league(db_session, 9999).slug == "default"
def test_parse_league_id_handles_invalid_values():
assert parse_league_id("42") == 42
assert parse_league_id("") is None
assert parse_league_id(None) is None
assert parse_league_id("not-a-number") is None
def test_qr_code_league_id_prefers_direct_then_set_then_event(db_session):
direct = League(name="Direct League", slug="direct", is_active=True)
via_set = League(name="Set League", slug="set", is_active=True)
via_event = League(name="Event League", slug="event", is_active=True)
db_session.add_all([direct, via_set, via_event])
db_session.commit()
qr_set = QRSet(name="Weekly Set", league_id=via_set.id)
event = Event(name="Quiz Night", league_id=via_event.id, event_date=datetime(2026, 5, 22))
team = Team(name="Quiz Team", league_id=direct.id)
db_session.add_all([qr_set, event, team])
db_session.commit()
direct_qr = QRCode(code="direct-code", points=10, league_id=direct.id)
set_qr = QRCode(code="set-code", points=10, qr_set_id=qr_set.id)
event_qr = QRCode(code="event-code", points=10, event_id=event.id)
db_session.add_all([direct_qr, set_qr, event_qr])
db_session.commit()
assert qr_code_league_id(direct_qr) == direct.id
assert qr_code_league_id(set_qr) == via_set.id
assert qr_code_league_id(event_qr) == via_event.id
+121 -159
View File
@@ -1,170 +1,132 @@
#!/usr/bin/env python3
import pytest
from unittest import mock
from sqlalchemy.orm import Session
from fastapi.testclient import TestClient
from datetime import datetime, timedelta
from app.main import app
from app.models import User, Team, Event, QRCode
from app.views.admin import get_user_statistics, get_team_statistics
from app.views.admin import get_event_statistics, get_system_health
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# Fixture for mocking the database session
@pytest.fixture
def mock_db():
"""Create a mock database session for testing."""
mock_session = mock.MagicMock(spec=Session)
return mock_session
from app.models import Base, Event, EventAttendee, Team, TeamMembership, User
from app.views.admin import (
get_event_statistics,
get_system_health,
get_team_statistics,
get_user_statistics,
)
# Test cases for user statistics
def test_get_user_statistics(mock_db):
"""Test getting user statistics."""
# Setup mock query results
mock_db.query().count.side_effect = [100, 80, 20]
mock_db.query().filter().count.return_value = 10
# Get last 30 days
thirty_days_ago = datetime.now() - timedelta(days=30)
mock_db.query().filter().filter().count.return_value = 15
# Get the statistics
stats = get_user_statistics(mock_db)
# Assert the expected results
assert stats["total_users"] == 100
assert stats["active_users"] == 80
assert stats["verified_users"] == 20
assert stats["admin_users"] == 10
assert stats["new_registrations_30d"] == 15
# Test cases for team statistics
def test_get_team_statistics(mock_db):
"""Test getting team statistics."""
# Setup mock query results
mock_db.query().count.side_effect = [50, 45]
mock_db.query().filter().count.return_value = 5
# Team distribution mock
mock_team_distribution = [
{"member_count": 0, "count": 5},
{"member_count": 1, "count": 10},
{"member_count": 2, "count": 15},
{"member_count": 3, "count": 10},
{"member_count": 4, "count": 7},
{"member_count": 5, "count": 3}
@pytest.fixture()
def db_session():
engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(bind=engine)
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
def seed_users(db):
now = datetime.utcnow()
users = [
User(
username="admin",
email="admin@example.com",
hashed_password="hash",
is_active=True,
is_verified=True,
is_admin=True,
created_at=now - timedelta(days=2),
),
User(
username="verified",
email="verified@example.com",
hashed_password="hash",
is_active=True,
is_verified=True,
is_admin=False,
created_at=now - timedelta(days=10),
),
User(
username="inactive",
email="inactive@example.com",
hashed_password="hash",
is_active=False,
is_verified=False,
is_admin=False,
created_at=now - timedelta(days=60),
),
]
mock_db.query().group_by().all.return_value = mock_team_distribution
# Get the statistics
stats = get_team_statistics(mock_db)
# Assert the expected results
assert stats["total_teams"] == 50
assert stats["active_teams"] == 45
assert stats["public_teams"] == 5
assert stats["team_distribution"] == mock_team_distribution
db.add_all(users)
db.commit()
return users
# Test cases for event statistics
def test_get_event_statistics(mock_db):
"""Test getting event statistics."""
# Setup mock query results
mock_db.query().count.side_effect = [30, 25, 5]
# Setup mock for upcoming events
today = datetime.now().date()
upcoming_events = [
mock.MagicMock(name="Event 1", event_date=today + timedelta(days=1), location="Location 1"),
mock.MagicMock(name="Event 2", event_date=today + timedelta(days=3), location="Location 2"),
mock.MagicMock(name="Event 3", event_date=today + timedelta(days=7), location="Location 3")
]
mock_db.query().filter().order_by().limit().all.return_value = upcoming_events
# Setup mock for attendance rates
mock_attendance_rates = [
{"event_id": 1, "event_name": "Event A", "attendee_count": 25},
{"event_id": 2, "event_name": "Event B", "attendee_count": 18},
{"event_id": 3, "event_name": "Event C", "attendee_count": 30}
]
mock_db.query().join().group_by().order_by().limit().all.return_value = mock_attendance_rates
# Get the statistics
stats = get_event_statistics(mock_db)
# Assert the expected results
assert stats["total_events"] == 30
assert stats["past_events"] == 25
assert stats["upcoming_events_count"] == 5
assert len(stats["upcoming_events"]) == 3
assert stats["attendance_rates"] == mock_attendance_rates
# Test cases for system health
def test_get_system_health(mock_db):
"""Test getting system health information."""
# Mock database status
mock_db.execute().fetchall.return_value = [{"status": "online"}]
# Get the health information
health_info = get_system_health(mock_db)
# Assert expected results
def test_get_user_statistics(db_session):
seed_users(db_session)
stats = get_user_statistics(db_session)
assert stats["total_users"] == 3
assert stats["active_users"] == 2
assert stats["verified_users"] == 2
assert stats["admin_users"] == 1
assert stats["new_registrations_30d"] == 2
assert len(stats["monthly_registrations"]) == 6
assert len(stats["month_labels"]) == 6
def test_get_team_statistics(db_session):
users = seed_users(db_session)
teams = [
Team(name="Open Team", is_active=True, is_public=True),
Team(name="Private Team", is_active=True, is_public=False),
Team(name="Archived Team", is_active=False, is_public=False),
]
db_session.add_all(teams)
db_session.commit()
db_session.add_all([
TeamMembership(user_id=users[0].id, team_id=teams[0].id),
TeamMembership(user_id=users[1].id, team_id=teams[0].id),
TeamMembership(user_id=users[2].id, team_id=teams[1].id),
])
db_session.commit()
stats = get_team_statistics(db_session)
assert stats["total_teams"] == 3
assert stats["active_teams"] == 2
assert stats["public_teams"] == 1
assert {"member_count": 2, "count": 1} in stats["team_distribution"]
assert {"member_count": 1, "count": 1} in stats["team_distribution"]
def test_get_event_statistics(db_session):
users = seed_users(db_session)
now = datetime.now()
past_event = Event(name="Past Quiz", event_date=now - timedelta(days=1))
future_event = Event(name="Future Quiz", event_date=now + timedelta(days=7))
db_session.add_all([past_event, future_event])
db_session.commit()
db_session.add_all([
EventAttendee(event_id=past_event.id, user_id=users[0].id),
EventAttendee(event_id=past_event.id, user_id=users[1].id),
])
db_session.commit()
stats = get_event_statistics(db_session)
assert stats["total_events"] == 2
assert stats["past_events"] == 1
assert stats["upcoming_events_count"] == 1
assert [event.name for event in stats["upcoming_events"]] == ["Future Quiz"]
assert stats["attendance_rates"][0].event_name == "Past Quiz"
assert stats["attendance_rates"][0].attendee_count == 2
def test_get_system_health(db_session):
health_info = get_system_health(db_session)
assert health_info["database_status"] == "online"
assert "uptime" in health_info
assert "recent_errors" in health_info
# Integration test for admin dashboard endpoint
@mock.patch("app.views.admin.get_db")
def test_admin_dashboard_endpoint(mock_get_db, mock_db):
"""Test the admin dashboard endpoint."""
# Setup mock DB to be returned from get_db
mock_get_db.return_value = mock_db
# Mock user stats
mock_user_stats = {
"total_users": 100,
"active_users": 80,
"verified_users": 20,
"admin_users": 10,
"new_registrations_30d": 15
}
# Mock team stats
mock_team_stats = {
"total_teams": 50,
"active_teams": 45,
"public_teams": 5,
"team_distribution": []
}
# Mock event stats
mock_event_stats = {
"total_events": 30,
"past_events": 25,
"upcoming_events_count": 5,
"upcoming_events": [],
"attendance_rates": []
}
# Mock system health
mock_system_health = {
"database_status": "online",
"uptime": "3 days, 2 hours",
"recent_errors": []
}
# Setup mock return values for our statistics functions
with mock.patch("app.views.admin.get_user_statistics", return_value=mock_user_stats), \
mock.patch("app.views.admin.get_team_statistics", return_value=mock_team_stats), \
mock.patch("app.views.admin.get_event_statistics", return_value=mock_event_stats), \
mock.patch("app.views.admin.get_system_health", return_value=mock_system_health), \
mock.patch("app.views.admin.require_admin", return_value=lambda f: f):
client = TestClient(app)
response = client.get("/admin/dashboard")
# Assert the response
assert response.status_code == 200
assert "user_stats" in response.context
assert "team_stats" in response.context
assert "event_stats" in response.context
assert "system_health" in response.context
assert health_info["recent_errors"] == []