Add multi-league foundation
This commit is contained in:
@@ -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
|
||||
@@ -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"] == []
|
||||
|
||||
Reference in New Issue
Block a user