from conftest import join_student def test_full_lifecycle_via_advance_and_close(client, sid): """End-to-end: 3 students, instructor drives via advance_to_next which closes the open question and opens the next in a single step.""" rooms = client.app.state.rooms sockets = [] try: for idx in range(3): join_student(client, sid, f"s{idx}", f"Student {idx}") ws = client.websocket_connect(f"/ws/student/{sid}").__enter__() sockets.append(ws) assert ws.receive_json()["state"] == "lobby" # Start: opens Q0. client.portal.call(rooms.advance_to_next, sid) for ws in sockets: assert ws.receive_json()["type"] == "question_open" for idx, ws in enumerate(sockets): ws.send_json({"type": "submit", "question_idx": 0, "answer": "B" if idx < 2 else "A"}) assert ws.receive_json()["type"] == "submit_ack" # Advance: closes Q0 and opens Q1 in one step. client.portal.call(rooms.advance_to_next, sid) for ws in sockets: assert ws.receive_json()["type"] == "question_closed" assert ws.receive_json()["type"] == "question_open" assert client.portal.call(rooms.get_session, sid)["state"] == "question_open" assert client.portal.call(rooms.get_session, sid)["current_question_idx"] == 1 # End the session early. client.portal.call(rooms.end_session, sid) for ws in sockets: first = ws.receive_json() # end_session closes the open question, then sends session_ended. if first["type"] == "question_closed": assert ws.receive_json()["type"] == "session_ended" else: assert first["type"] == "session_ended" assert client.portal.call(rooms.get_session, sid)["state"] == "finished" finally: for ws in sockets: ws.__exit__(None, None, None) def test_explicit_close_then_advance_skips_redundant_close(client, sid): """If the instructor closes manually first, the next advance just opens the following question (no double-close broadcast).""" rooms = client.app.state.rooms join_student(client, sid, "s1", "Solo") with client.websocket_connect(f"/ws/student/{sid}") as ws: assert ws.receive_json()["state"] == "lobby" client.portal.call(rooms.open_question, sid, 0, 2) assert ws.receive_json()["type"] == "question_open" client.portal.call(rooms.close_question, sid) assert ws.receive_json()["type"] == "question_closed" client.portal.call(rooms.advance_to_next, sid) assert ws.receive_json()["type"] == "question_open" assert client.portal.call(rooms.get_session, sid)["current_question_idx"] == 1 def test_reset_clears_participants_and_returns_to_lobby(client, sid): rooms = client.app.state.rooms join_student(client, sid, "s1", "First") join_student(client, sid, "s2", "Second") client.portal.call(rooms.open_question, sid, 0, 2) client.portal.call(rooms.submit_answer, sid, "s1", 0, "B") client.portal.call(rooms.close_question, sid) client.portal.call(rooms.reset, sid) session = client.portal.call(rooms.get_session, sid) assert session["state"] == "lobby" assert session["current_question_idx"] is None # Participants and submissions are wiped. board = client.portal.call(rooms.leaderboard, sid) assert board == []