from conftest import join_student def test_session_metadata_join_me_and_stats(client, sid): metadata = client.get(f"/api/session/{sid}").json() assert metadata["title"] == "Sample Quiz" assert metadata["state"] == "lobby" assert metadata["current_question_idx"] is None join = join_student(client, sid, "s1", "First Name") assert join["ok"] is True assert "qz_student" in client.cookies me = client.get(f"/api/session/{sid}/me") assert me.status_code == 200 assert me.json()["name"] == "First Name" stats = client.get(f"/api/session/{sid}/stats").json() assert stats["question_idx"] is None assert stats["top5"][0]["name"] == "First Name" def test_duplicate_student_id_join_is_rejected(client, sid): """First-claim-wins anti-hijack: a second join attempting the same student_id must 409 (without overwriting name or rotating the cookie). The original cookie keeps working; recovery is via admin clear-student.""" join_student(client, sid, "s1", "First Name") response = client.post(f"/api/session/{sid}/join", json={"student_id": "s1", "name": "Hijacker"}) assert response.status_code == 409 assert "already in use" in response.text.lower() me = client.get(f"/api/session/{sid}/me").json() assert me["name"] == "First Name" def test_root_without_sid_redirects_to_canonical(client, sid): response = client.get("/", follow_redirects=False) assert response.status_code == 302 assert response.headers["location"] == f"/?sid={sid}" def test_me_returns_401_and_clears_cookie_when_participant_is_gone(client, sid): """A stale signed cookie (e.g. after admin reset wiped participants) must return 401 with the cookie cleared, not 500. The client uses 401 to fall back to the join form.""" join_student(client, sid, "s1", "Student One") assert client.get(f"/api/session/{sid}/me").status_code == 200 # Simulate the post-reset state: cookie still valid by signature, # but the participant row is gone. rooms = client.app.state.rooms client.portal.call(rooms.reset, sid) response = client.get(f"/api/session/{sid}/me") assert response.status_code == 401 # Server should send a Set-Cookie that clears the qz_student cookie. assert any( h.lower() == "set-cookie" and "qz_student" in v and ('Max-Age=0' in v or 'expires=' in v.lower()) for h, v in response.headers.items() ), response.headers def test_leaderboard_marks_requesting_student_with_is_you(client, sid): """The student-facing top5 should mark only the requesting student's row with `is_you: true`, never include other students' ids.""" rooms = client.app.state.rooms join_student(client, sid, "s1", "Alice") join_student(client, sid, "s2", "Bob") client.portal.call(rooms.open_question, sid, 0, 5) client.portal.call(rooms.submit_answer, sid, "s1", 0, "B") client.portal.call(rooms.submit_answer, sid, "s2", 0, "B") client.portal.call(rooms.close_question, sid) # Stats endpoint reflects the requesting student's identity from cookie. stats = client.get(f"/api/session/{sid}/stats?question_idx=0").json() you_rows = [r for r in stats["top5"] if r.get("is_you")] other_rows = [r for r in stats["top5"] if not r.get("is_you")] assert len(you_rows) == 1 assert you_rows[0]["name"] in {"Alice", "Bob"} # Other students' ids are not exposed. assert all("student_id" not in r for r in other_rows) def test_invalid_session_and_missing_cookie_paths(client): response = client.get("/?sid=BAD") assert response.status_code == 404 assert "Ask your instructor" in response.text assert client.get("/api/session/BAD").status_code == 404 assert client.get("/api/session/BAD/me").status_code == 401