from __future__ import annotations from pathlib import Path from fastapi.testclient import TestClient from sqlalchemy.orm import Session, sessionmaker from app.config import Settings, get_settings from app.database import Base, build_engine, get_db from app.main import app from tests.conftest import create_onboarded_student def test_favourites_add_reorder_and_delete(client: TestClient) -> None: token, _ = create_onboarded_student(client, "fav_tiger", "tiger", "avatar-3") headers = {"Authorization": f"Bearer {token}"} quiz_ids: list[int] = [] for title in ("Quiz Alpha", "Quiz Beta", "Quiz Gamma"): r = client.post( "/api/v1/quizzes", headers=headers, json={"title": title, "is_public": True, "questions": []}, ) assert r.status_code == 201 quiz_ids.append(r.json()["id"]) for qid in quiz_ids: add_r = client.post("/api/v1/favourites", headers=headers, json={"quiz_id": qid}) assert add_r.status_code == 201 # Reorder to Gamma, Alpha, Beta reordered = [quiz_ids[2], quiz_ids[0], quiz_ids[1]] put_r = client.put( "/api/v1/favourites/reorder", headers=headers, json={"quiz_ids": reordered}, ) assert put_r.status_code == 200 assert [item["quiz"]["id"] for item in put_r.json()] == reordered # Delete Alpha (simulating recycle bin drop) del_r = client.delete(f"/api/v1/favourites/{quiz_ids[0]}", headers=headers) assert del_r.status_code == 200 assert [item["quiz"]["id"] for item in del_r.json()] == [quiz_ids[2], quiz_ids[1]] def test_moderator_claim_ban_and_remove_quiz(client: TestClient) -> None: mod_token, _ = create_onboarded_student(client, "chief_mod", "bear", "avatar-6") mod_headers = {"Authorization": f"Bearer {mod_token}"} bad_token, bad_user = create_onboarded_student(client, "spammer_99", "frog", "avatar-7") bad_headers = {"Authorization": f"Bearer {bad_token}"} quiz_r = client.post( "/api/v1/quizzes", headers=bad_headers, json={"title": "Unwanted Quiz", "is_public": True, "questions": []}, ) assert quiz_r.status_code == 201 quiz_id = quiz_r.json()["id"] # Claim initial moderator when ADMIN_USER env var is unset claim_r = client.post("/api/v1/admin/claim", headers=mod_headers) assert claim_r.status_code == 200 assert claim_r.json()["is_admin"] is True # Second claim must fail with 409 second_claim = client.post("/api/v1/admin/claim", headers=bad_headers) assert second_claim.status_code == 409 # Moderator removes quiz rem_r = client.delete(f"/api/v1/admin/quizzes/{quiz_id}", headers=mod_headers) assert rem_r.status_code == 204 assert client.get(f"/api/v1/quizzes/{quiz_id}").status_code == 404 # Moderator bans spammer ban_r = client.post( f"/api/v1/admin/users/{bad_user['id']}/ban", headers=mod_headers, json={"reason": "Posting spam quizzes"}, ) assert ban_r.status_code == 200 assert ban_r.json()["is_banned"] is True # Banned user cannot create quizzes blocked = client.post( "/api/v1/quizzes", headers=bad_headers, json={"title": "Blocked Quiz", "is_public": True, "questions": []}, ) assert blocked.status_code == 403 def test_admin_user_container_env_var(tmp_path: Path) -> None: env_settings = Settings( database_url=f"sqlite:///{(tmp_path / 'env_admin.db').as_posix()}", dev_auth_enabled=True, admin_user="launch_moderator", session_secret="env-admin-secret", ) eng = build_engine(env_settings.database_url) Base.metadata.create_all(bind=eng) LocalSession = sessionmaker(bind=eng, autoflush=False, autocommit=False, expire_on_commit=False) def _override_db(): db: Session = LocalSession() try: yield db finally: db.close() app.dependency_overrides[get_db] = _override_db app.dependency_overrides[get_settings] = lambda: env_settings try: with TestClient(app) as c: login_r = c.post("/api/v1/auth/dev-login", json={"nickname": "launch_moderator"}) assert login_r.status_code == 200 assert login_r.json()["user"]["is_admin"] is True finally: app.dependency_overrides.clear() eng.dispose()