# retoor import requests from tests.api.quizzes._helpers import ( JSON, build_published, create_quiz, signup, ) from tests.conftest import BASE_URL def test_the_hub_is_public(app_server, seeded_db): response = requests.get(f"{BASE_URL}/quizzes", headers=JSON) assert response.status_code == 200 assert "quizzes" in response.json() def test_the_hub_renders_html_for_a_browser(app_server, seeded_db): response = requests.get(f"{BASE_URL}/quizzes") assert response.status_code == 200 assert "feed-layout" in response.text assert "sidebar-card" in response.text assert "feed-right" in response.text def test_the_hub_exposes_the_scoreboard_and_counts(app_server, seeded_db): payload = requests.get(f"{BASE_URL}/quizzes", headers=JSON).json() assert "scoreboard" in payload assert "counts" in payload assert "pagination" in payload def test_a_guest_cannot_create(app_server, seeded_db): payload = requests.get(f"{BASE_URL}/quizzes", headers=JSON).json() assert payload["viewer_can_create"] is False def test_a_member_can_create(app_server, seeded_db): session, _ = signup() payload = session.get(f"{BASE_URL}/quizzes", headers=JSON).json() assert payload["viewer_can_create"] is True def test_creating_a_quiz_returns_the_builder_url(app_server, seeded_db): session, _ = signup() response = session.post( f"{BASE_URL}/quizzes/create", data={"title": "My first quiz", "description": "d"}, headers=JSON, ) assert response.status_code == 200 data = response.json()["data"] assert data["slug"] assert data["url"].endswith("/edit") def test_creating_a_quiz_requires_auth(app_server, seeded_db): response = requests.post( f"{BASE_URL}/quizzes/create", data={"title": "Nope"}, headers=JSON ) assert response.status_code in (401, 303) def test_a_short_title_is_rejected(app_server, seeded_db): session, _ = signup() response = session.post(f"{BASE_URL}/quizzes/create", data={"title": "no"}, headers=JSON) assert response.status_code == 422 def test_a_new_quiz_starts_as_a_draft(app_server, seeded_db): session, _ = signup() slug = create_quiz(session) payload = session.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).json() assert payload["quiz"]["status"] == "draft" assert payload["quiz"]["viewer_can_edit"] is True def test_a_draft_is_hidden_from_a_guest(app_server, seeded_db): session, _ = signup() slug = create_quiz(session) assert requests.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).status_code == 404 def test_a_draft_is_hidden_from_another_member(app_server, seeded_db): owner, _ = signup() slug = create_quiz(owner) other, _ = signup() assert other.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).status_code == 404 def test_the_owner_sees_their_draft(app_server, seeded_db): session, _ = signup() slug = create_quiz(session) assert session.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).status_code == 200 def test_an_admin_sees_another_members_draft(app_server, seeded_db): owner, _ = signup() slug = create_quiz(owner) admin = requests.Session() admin.post( f"{BASE_URL}/auth/login", data={"email": seeded_db["alice"]["email"], "password": seeded_db["alice"]["password"]}, allow_redirects=True, ) assert admin.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).status_code == 200 def test_a_published_quiz_is_public(app_server, seeded_db): session, _ = signup() slug = build_published(session, title="Public quiz") response = requests.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON) assert response.status_code == 200 assert response.json()["quiz"]["status"] == "published" def test_editing_a_draft_changes_its_settings(app_server, seeded_db): session, _ = signup() slug = create_quiz(session) session.post( f"{BASE_URL}/quizzes/edit/{slug}", data={"title": "Renamed quiz", "description": "changed", "pass_percent": "80"}, headers=JSON, ) payload = session.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).json() assert payload["quiz"]["title"] == "Renamed quiz" assert payload["quiz"]["pass_percent"] == 80 def test_another_member_cannot_edit(app_server, seeded_db): owner, _ = signup() slug = build_published(owner, title="Not yours") other, _ = signup() response = other.post( f"{BASE_URL}/quizzes/edit/{slug}", data={"title": "Hijacked"}, headers=JSON ) assert response.status_code == 404 def test_the_owner_deletes_their_quiz(app_server, seeded_db): session, _ = signup() slug = build_published(session, title="Doomed quiz") assert session.post(f"{BASE_URL}/quizzes/delete/{slug}", headers=JSON).status_code == 200 assert requests.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).status_code == 404 def test_an_admin_deletes_another_members_quiz(app_server, seeded_db): owner, _ = signup() slug = build_published(owner, title="Moderated quiz") admin = requests.Session() admin.post( f"{BASE_URL}/auth/login", data={"email": seeded_db["alice"]["email"], "password": seeded_db["alice"]["password"]}, allow_redirects=True, ) assert admin.post(f"{BASE_URL}/quizzes/delete/{slug}", headers=JSON).status_code == 200 def test_deleting_cascades_to_the_questions(app_server, seeded_db): from devplacepy.database import get_table, refresh_snapshot session, _ = signup() slug = build_published(session, title="Cascade quiz") refresh_snapshot() quiz = get_table("quizzes").find_one(slug=slug) session.post(f"{BASE_URL}/quizzes/delete/{slug}", headers=JSON) refresh_snapshot() for table in ("quiz_questions", "quiz_options"): assert get_table(table).count(quiz_uid=quiz["uid"], deleted_at=None) == 0 def test_the_delete_cascade_shares_one_stamp(app_server, seeded_db): from devplacepy.database import get_table, refresh_snapshot session, _ = signup() slug = build_published(session, title="Stamped quiz") attempt_owner, _ = signup() attempt_owner.post(f"{BASE_URL}/quizzes/{slug}/attempts", headers=JSON) refresh_snapshot() quiz = get_table("quizzes").find_one(slug=slug) session.post(f"{BASE_URL}/quizzes/delete/{slug}", headers=JSON) refresh_snapshot() stamps = set() for table in ("quiz_questions", "quiz_options", "quiz_attempts", "quiz_answers"): for row in get_table(table).find(quiz_uid=quiz["uid"]): stamps.add(row["deleted_at"]) assert len(stamps) == 1 assert next(iter(stamps)) def test_the_todo_filter_excludes_a_completed_quiz(app_server, seeded_db): from tests.api.quizzes._helpers import ( answer_correctly, attempt_state, start_attempt, ) owner, _ = signup() slug = build_published(owner, title="Todo filter quiz") player, _ = signup() before = player.get(f"{BASE_URL}/quizzes?filter=todo", headers=JSON).json() assert any(item["slug"] == slug for item in before["quizzes"]) attempt_uid = start_attempt(player, slug) state = attempt_state(player, slug, attempt_uid) for question in state["attempt"]["questions"]: answer_correctly(player, slug, attempt_uid, question) player.post(f"{BASE_URL}/quizzes/{slug}/attempts/{attempt_uid}/finish", headers=JSON) after = player.get(f"{BASE_URL}/quizzes?filter=todo", headers=JSON).json() assert not any(item["slug"] == slug for item in after["quizzes"]) done = player.get(f"{BASE_URL}/quizzes?filter=done", headers=JSON).json() assert any(item["slug"] == slug for item in done["quizzes"]) def test_the_mine_filter_lists_only_the_viewers_quizzes(app_server, seeded_db): session, _ = signup() slug = build_published(session, title="Mine filter quiz") payload = session.get(f"{BASE_URL}/quizzes?filter=mine", headers=JSON).json() assert all(item["author"]["username"] for item in payload["quizzes"]) assert any(item["slug"] == slug for item in payload["quizzes"]) def test_the_drafts_filter_lists_only_drafts(app_server, seeded_db): session, _ = signup() draft_slug = create_quiz(session, "Draft filter quiz") published_slug = build_published(session, title="Published filter quiz") payload = session.get(f"{BASE_URL}/quizzes?filter=drafts", headers=JSON).json() slugs = [item["slug"] for item in payload["quizzes"]] assert draft_slug in slugs assert published_slug not in slugs def test_a_guest_gets_no_personal_filters(app_server, seeded_db): payload = requests.get(f"{BASE_URL}/quizzes?filter=todo", headers=JSON).json() assert payload["quizzes"] == [] def test_search_matches_the_title(app_server, seeded_db): session, _ = signup() slug = build_published(session, title="Highly searchable haddock quiz") payload = requests.get(f"{BASE_URL}/quizzes?search=haddock", headers=JSON).json() assert any(item["slug"] == slug for item in payload["quizzes"]) def test_search_matches_the_author_username(app_server, seeded_db): session, name = signup("searchable") build_published(session, title="Author search quiz") payload = requests.get(f"{BASE_URL}/quizzes?search={name}", headers=JSON).json() assert any(item["author"]["username"] == name for item in payload["quizzes"]) def test_an_unknown_filter_falls_back_to_all(app_server, seeded_db): payload = requests.get(f"{BASE_URL}/quizzes?filter=nonsense", headers=JSON).json() assert payload["filter"] == "all" def test_the_listing_carries_the_viewer_state(app_server, seeded_db): session, _ = signup() slug = build_published(session, title="Viewer state quiz") other, _ = signup() payload = other.get(f"{BASE_URL}/quizzes", headers=JSON).json() row = next(item for item in payload["quizzes"] if item["slug"] == slug) assert row["viewer_state"] == "todo" assert row["question_count"] == 2 assert row["total_points"] == 3 def test_a_quiz_accepts_a_comment_and_counts_it(app_server, seeded_db): from devplacepy.database import get_table, refresh_snapshot session, _ = signup() slug = build_published(session, title="Commented quiz") refresh_snapshot() quiz = get_table("quizzes").find_one(slug=slug) session.post( f"{BASE_URL}/comments/create", data={"content": "Nice quiz", "target_uid": quiz["uid"], "target_type": "quiz"}, headers=JSON, ) payload = requests.get(f"{BASE_URL}/quizzes", headers=JSON).json() row = next(item for item in payload["quizzes"] if item["slug"] == slug) assert row["comment_count"] == 1 def test_a_quiz_accepts_a_star_vote(app_server, seeded_db): from devplacepy.database import get_table, refresh_snapshot session, _ = signup() slug = build_published(session, title="Voted quiz") refresh_snapshot() quiz = get_table("quizzes").find_one(slug=slug) voter, _ = signup() voter.post(f"{BASE_URL}/votes/quiz/{quiz['uid']}", data={"value": "1"}, headers=JSON) payload = requests.get(f"{BASE_URL}/quizzes/{slug}", headers=JSON).json() assert payload["star_count"] == 1 def test_the_bare_uid_resolves_like_the_slug(app_server, seeded_db): from devplacepy.database import get_table, refresh_snapshot session, _ = signup() slug = build_published(session, title="Uid lookup quiz") refresh_snapshot() quiz = get_table("quizzes").find_one(slug=slug) response = requests.get(f"{BASE_URL}/quizzes/{quiz['uid']}", headers=JSON) assert response.status_code == 200 def test_an_unknown_quiz_is_a_404(app_server, seeded_db): assert requests.get(f"{BASE_URL}/quizzes/nope-nope", headers=JSON).status_code == 404 def test_the_new_quiz_page_requires_auth(app_server, seeded_db): response = requests.get(f"{BASE_URL}/quizzes/new", headers=JSON, allow_redirects=False) assert response.status_code in (401, 303) def test_the_new_quiz_page_renders_for_a_member(app_server, seeded_db): session, _ = signup() response = session.get(f"{BASE_URL}/quizzes/new") assert response.status_code == 200 assert "quiz-form" in response.text def test_the_hub_nav_entry_is_visible_to_a_guest(app_server, seeded_db): response = requests.get(f"{BASE_URL}/quizzes") assert 'href="/quizzes"' in response.text assert "Quizzes" in response.text