# retoor from datetime import datetime, timezone from devplacepy.database import get_table from devplacepy.services.quiz import store from devplacepy.utils import generate_uid, make_combined_slug _counter = [0] def _user(prefix): _counter[0] += 1 uid = generate_uid() get_table("users").insert( { "uid": uid, "username": f"{prefix}{_counter[0]}", "email": f"{prefix}{_counter[0]}@t.dev", "role": "Member", "api_key": generate_uid(), "created_at": datetime.now(timezone.utc).isoformat(), } ) return get_table("users").find_one(uid=uid) def _published(owner, points=10): uid = generate_uid() get_table("quizzes").insert( store.born_live( { "uid": uid, "user_uid": owner["uid"], "slug": make_combined_slug("Board", uid), "title": "Board", "description": "", "status": "published", "published_at": datetime.now(timezone.utc).isoformat(), "shuffle_questions": 0, "shuffle_options": 0, "reveal_answers": 0, "allow_review": 1, "time_limit_seconds": 0, "pass_percent": 0, "question_count": 1, "total_points": points, "attempt_count": 0, "stars": 0, } ) ) return store.get_quiz(uid) def _completed(player, quiz, points, percent): uid = generate_uid() get_table("quiz_attempts").insert( store.born_live( { "uid": uid, "quiz_uid": quiz["uid"], "user_uid": player["uid"], "status": "completed", "question_order": "[]", "started_at": datetime.now(timezone.utc).isoformat(), "expires_at": "", "completed_at": datetime.now(timezone.utc).isoformat(), "answered_count": 1, "score_points": float(points), "max_points": int(quiz["total_points"]), "score_percent": float(percent), "passed": 0, } ) ) store.clear_cache() return uid def _in_progress(player, quiz): uid = generate_uid() get_table("quiz_attempts").insert( store.born_live( { "uid": uid, "quiz_uid": quiz["uid"], "user_uid": player["uid"], "status": "in_progress", "question_order": "[]", "started_at": datetime.now(timezone.utc).isoformat(), "expires_at": "", "completed_at": "", "answered_count": 0, "score_points": 0.0, "max_points": int(quiz["total_points"]), "score_percent": 0.0, "passed": 0, } ) ) store.clear_cache() return uid def _entry_for(player): for entry in store.scoreboard(100): if entry["user"]["uid"] == player["uid"]: return entry return None def test_a_completed_attempt_puts_a_member_on_the_board(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 8, 80.0) entry = _entry_for(player) assert entry["total_points"] == 8.0 assert entry["quizzes_completed"] == 1 def test_only_the_best_attempt_per_quiz_counts(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 4, 40.0) _completed(player, quiz, 9, 90.0) _completed(player, quiz, 2, 20.0) entry = _entry_for(player) assert entry["total_points"] == 9.0 assert entry["quizzes_completed"] == 1 def test_replaying_a_quiz_can_never_multiply_a_score(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 7, 70.0) before = _entry_for(player)["total_points"] for _ in range(5): _completed(player, quiz, 7, 70.0) assert _entry_for(player)["total_points"] == before def test_a_worse_attempt_never_lowers_the_contribution(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 9, 90.0) _completed(player, quiz, 1, 10.0) assert _entry_for(player)["total_points"] == 9.0 def test_a_better_attempt_raises_by_exactly_the_difference(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 4, 40.0) _completed(player, quiz, 10, 100.0) assert _entry_for(player)["total_points"] == 10.0 def test_two_quizzes_sum_their_best_attempts(local_db): owner = _user("sb") player = _user("sb") first = _published(owner) second = _published(owner) _completed(player, first, 6, 60.0) _completed(player, second, 3, 30.0) entry = _entry_for(player) assert entry["total_points"] == 9.0 assert entry["quizzes_completed"] == 2 def test_an_author_scores_on_their_own_quiz(local_db): owner = _user("sb") quiz = _published(owner) _completed(owner, quiz, 10, 100.0) entry = _entry_for(owner) assert entry["total_points"] == 10.0 assert entry["quizzes_completed"] == 1 assert entry["perfect_count"] == 1 def test_an_in_progress_attempt_is_not_counted(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _in_progress(player, quiz) assert _entry_for(player) is None def test_a_draft_quiz_is_not_counted(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) get_table("quizzes").update({"uid": quiz["uid"], "status": "draft"}, ["uid"]) _completed(player, quiz, 10, 100.0) assert _entry_for(player) is None def test_a_deleted_attempt_is_not_counted(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) attempt_uid = _completed(player, quiz, 10, 100.0) get_table("quiz_attempts").update( {"uid": attempt_uid, "deleted_at": datetime.now(timezone.utc).isoformat()}, ["uid"] ) store.clear_cache() assert _entry_for(player) is None def test_ranks_are_a_gapless_total_order(local_db): owner = _user("sb") quiz = _published(owner) for score in (3, 9, 6): _completed(_user("sb"), quiz, score, score * 10.0) ranks = [entry["rank"] for entry in store.scoreboard(100)] assert ranks == list(range(1, len(ranks) + 1)) def test_equal_totals_resolve_deterministically(local_db): owner = _user("sb") quiz = _published(owner) for _ in range(3): _completed(_user("sb"), quiz, 5, 50.0) first = [entry["user"]["uid"] for entry in store.scoreboard(100)] store.clear_cache() assert [entry["user"]["uid"] for entry in store.scoreboard(100)] == first def test_the_limit_bounds_the_board(local_db): owner = _user("sb") quiz = _published(owner) for score in range(1, 6): _completed(_user("sb"), quiz, score, score * 10.0) assert len(store.scoreboard(3)) == 3 def test_perfect_attempts_are_counted(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 10, 100.0) assert _entry_for(player)["perfect_count"] == 1 def test_standing_for_finds_a_member_outside_the_top(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 2, 20.0) standing = store.standing_for(player["uid"]) assert standing["user"]["uid"] == player["uid"] assert standing["rank"] >= 1 def test_standing_for_is_none_without_attempts(local_db): assert store.standing_for(_user("sb")["uid"]) is None def test_attempt_states_reports_done_with_the_best_percentage(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 4, 40.0) _completed(player, quiz, 9, 90.0) states = store.attempt_states_for(player["uid"], [quiz["uid"]]) assert states[quiz["uid"]]["state"] == "done" assert states[quiz["uid"]]["best_percent"] == 90.0 def test_attempt_states_reports_in_progress(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _in_progress(player, quiz) states = store.attempt_states_for(player["uid"], [quiz["uid"]]) assert states[quiz["uid"]]["state"] == "in_progress" assert states[quiz["uid"]]["attempt_uid"] def test_attempt_states_is_empty_for_a_guest(local_db): owner = _user("sb") quiz = _published(owner) assert store.attempt_states_for("", [quiz["uid"]]) == {} def test_attempt_states_is_empty_without_quiz_uids(local_db): assert store.attempt_states_for(_user("sb")["uid"], []) == {} def test_completed_quiz_uids_lists_only_finished_quizzes(local_db): owner = _user("sb") player = _user("sb") done = _published(owner) open_quiz = _published(owner) _completed(player, done, 5, 50.0) _in_progress(player, open_quiz) completed = store.completed_quiz_uids(player["uid"]) assert done["uid"] in completed assert open_quiz["uid"] not in completed def test_progress_for_summarises_the_viewer(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 10, 100.0) progress = store.progress_for(player["uid"]) assert progress["completed"] == 1 assert progress["avg_percent"] == 100.0 assert progress["perfect_count"] == 1 def test_the_quiz_leaderboard_ranks_by_percentage(local_db): owner = _user("sb") quiz = _published(owner) _completed(_user("sb"), quiz, 3, 30.0) _completed(_user("sb"), quiz, 9, 90.0) entries = store.quiz_leaderboard(quiz["uid"]) assert entries[0]["score_percent"] == 90.0 assert entries[0]["rank"] == 1 def test_the_quiz_leaderboard_ignores_unfinished_attempts(local_db): owner = _user("sb") quiz = _published(owner) _in_progress(_user("sb"), quiz) assert store.quiz_leaderboard(quiz["uid"]) == [] def test_progress_agrees_with_the_scoreboard_for_an_author_only_completion(local_db): author = _user("sb") quiz = _published(author) _completed(author, quiz, 10, 100.0) progress = store.progress_for(author["uid"]) entry = _entry_for(author) assert progress["completed"] == entry["quizzes_completed"] == 1 assert progress["avg_percent"] == entry["avg_percent"] == 100.0 assert progress["total_points"] == entry["total_points"] == 10.0 assert progress["perfect_count"] == entry["perfect_count"] == 1 assert progress["rank"] == entry["rank"] def test_progress_counts_a_ranked_completion(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) _completed(player, quiz, 8, 80.0) progress = store.progress_for(player["uid"]) entry = _entry_for(player) assert progress["completed"] == entry["quizzes_completed"] == 1 assert progress["avg_percent"] == entry["avg_percent"] == 80.0 assert progress["total_points"] == entry["total_points"] == 8.0 def test_completing_your_own_quiz_clears_it_from_todo(local_db): author = _user("sb") before = store.progress_for(author["uid"])["todo"] quiz = _published(author) assert store.progress_for(author["uid"])["todo"] == before + 1 _completed(author, quiz, 10, 100.0) assert store.progress_for(author["uid"])["todo"] == before def test_progress_todo_counts_every_published_quiz(local_db): author = _user("sb") other = _user("sb") before = store.progress_for(author["uid"])["todo"] _published(author) _published(other) _published(other) assert store.progress_for(author["uid"])["todo"] == before + 3 def test_completing_a_quiz_moves_it_from_todo_to_completed(local_db): owner = _user("sb") player = _user("sb") quiz = _published(owner) before = store.progress_for(player["uid"]) _completed(player, quiz, 5, 50.0) after = store.progress_for(player["uid"]) assert after["completed"] == before["completed"] + 1 assert after["todo"] == before["todo"] - 1