|
|
|
|
|
""" |
|
|
Unit tests for the Wrdler Leaderboard System. |
|
|
|
|
|
Tests cover: |
|
|
- UserEntry and LeaderboardSettings dataclasses |
|
|
- Qualification logic |
|
|
- Sorting functions |
|
|
- Date/week ID generation |
|
|
- File ID generation and parsing |
|
|
- GameSettings matching |
|
|
""" |
|
|
|
|
|
import pytest |
|
|
from datetime import datetime, timezone, timedelta |
|
|
from unittest.mock import patch, MagicMock |
|
|
|
|
|
from wrdler.leaderboard import ( |
|
|
UserEntry, |
|
|
LeaderboardSettings, |
|
|
GameSettings, |
|
|
get_current_daily_id, |
|
|
get_current_weekly_id, |
|
|
get_daily_leaderboard_path, |
|
|
get_weekly_leaderboard_path, |
|
|
_sort_users, |
|
|
check_qualification, |
|
|
create_user_entry, |
|
|
_sanitize_wordlist_source, |
|
|
_build_file_id, |
|
|
_parse_file_id, |
|
|
MAX_DISPLAY_ENTRIES, |
|
|
) |
|
|
|
|
|
|
|
|
class TestUserEntry: |
|
|
"""Tests for UserEntry dataclass.""" |
|
|
|
|
|
def test_create_entry(self): |
|
|
"""Test basic UserEntry creation.""" |
|
|
entry = UserEntry( |
|
|
uid="test-uid-123", |
|
|
username="TestPlayer", |
|
|
word_list=["WORD", "TEST", "GAME", "PLAY", "SCORE", "FINAL"], |
|
|
score=42, |
|
|
time=180, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
assert entry.uid == "test-uid-123" |
|
|
assert entry.username == "TestPlayer" |
|
|
assert len(entry.word_list) == 6 |
|
|
assert entry.score == 42 |
|
|
assert entry.time == 180 |
|
|
assert entry.word_list_difficulty is None |
|
|
assert entry.source_challenge_id is None |
|
|
|
|
|
def test_to_dict_basic(self): |
|
|
"""Test to_dict without optional fields.""" |
|
|
entry = UserEntry( |
|
|
uid="test-uid", |
|
|
username="Player", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=30, |
|
|
time=120, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
d = entry.to_dict() |
|
|
|
|
|
assert d["uid"] == "test-uid" |
|
|
assert d["username"] == "Player" |
|
|
assert d["score"] == 30 |
|
|
assert d["time"] == 120 |
|
|
assert "word_list_difficulty" not in d |
|
|
assert "source_challenge_id" not in d |
|
|
|
|
|
def test_to_dict_with_optional_fields(self): |
|
|
"""Test to_dict with optional fields.""" |
|
|
entry = UserEntry( |
|
|
uid="test-uid", |
|
|
username="Player", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=30, |
|
|
time=120, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
word_list_difficulty=117.5, |
|
|
source_challenge_id="challenge-123", |
|
|
) |
|
|
d = entry.to_dict() |
|
|
|
|
|
assert d["word_list_difficulty"] == 117.5 |
|
|
assert d["source_challenge_id"] == "challenge-123" |
|
|
|
|
|
def test_from_dict_roundtrip(self): |
|
|
"""Test to_dict/from_dict roundtrip.""" |
|
|
original = UserEntry( |
|
|
uid="test-uid", |
|
|
username="Player", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=30, |
|
|
time=120, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
word_list_difficulty=100.0, |
|
|
) |
|
|
d = original.to_dict() |
|
|
restored = UserEntry.from_dict(d) |
|
|
|
|
|
assert restored.uid == original.uid |
|
|
assert restored.username == original.username |
|
|
assert restored.score == original.score |
|
|
assert restored.time == original.time |
|
|
assert restored.word_list_difficulty == original.word_list_difficulty |
|
|
|
|
|
def test_from_dict_legacy_time_seconds(self): |
|
|
"""Test from_dict handles legacy 'time_seconds' field.""" |
|
|
data = { |
|
|
"uid": "test", |
|
|
"username": "Player", |
|
|
"word_list": ["A", "B", "C", "D", "E", "F"], |
|
|
"score": 30, |
|
|
"time_seconds": 150, |
|
|
"timestamp": "2025-01-27T12:00:00+00:00", |
|
|
} |
|
|
entry = UserEntry.from_dict(data) |
|
|
assert entry.time == 150 |
|
|
|
|
|
|
|
|
class TestLeaderboardSettings: |
|
|
"""Tests for LeaderboardSettings dataclass.""" |
|
|
|
|
|
def test_create_leaderboard(self): |
|
|
"""Test basic LeaderboardSettings creation.""" |
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="2025-01-27/classic-classic-0", |
|
|
entry_type="daily", |
|
|
) |
|
|
assert lb.challenge_id == "2025-01-27/classic-classic-0" |
|
|
assert lb.entry_type == "daily" |
|
|
assert lb.game_mode == "classic" |
|
|
assert lb.grid_size == 8 |
|
|
assert len(lb.users) == 0 |
|
|
assert lb.max_display_entries == MAX_DISPLAY_ENTRIES |
|
|
|
|
|
def test_entry_type_values(self): |
|
|
"""Test valid entry_type values.""" |
|
|
for entry_type in ["daily", "weekly", "challenge"]: |
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type=entry_type, |
|
|
) |
|
|
assert lb.entry_type == entry_type |
|
|
|
|
|
def test_get_display_users_limit(self): |
|
|
"""Test get_display_users respects max_display_entries.""" |
|
|
users = [ |
|
|
UserEntry( |
|
|
uid=f"uid-{i}", |
|
|
username=f"Player{i}", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=100 - i, |
|
|
time=60 + i, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
for i in range(25) |
|
|
] |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type="daily", |
|
|
users=users, |
|
|
) |
|
|
|
|
|
display_users = lb.get_display_users() |
|
|
assert len(display_users) == MAX_DISPLAY_ENTRIES |
|
|
|
|
|
assert display_users[0].uid == "uid-0" |
|
|
|
|
|
def test_to_dict_and_from_dict(self): |
|
|
"""Test LeaderboardSettings serialization roundtrip.""" |
|
|
user = UserEntry( |
|
|
uid="test-uid", |
|
|
username="Player", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=50, |
|
|
time=100, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="2025-01-27/easy-easy-0", |
|
|
entry_type="daily", |
|
|
game_mode="easy", |
|
|
users=[user], |
|
|
wordlist_source="test.txt", |
|
|
) |
|
|
|
|
|
d = lb.to_dict() |
|
|
restored = LeaderboardSettings.from_dict(d) |
|
|
|
|
|
assert restored.challenge_id == lb.challenge_id |
|
|
assert restored.entry_type == lb.entry_type |
|
|
assert restored.game_mode == lb.game_mode |
|
|
assert len(restored.users) == 1 |
|
|
assert restored.wordlist_source == lb.wordlist_source |
|
|
|
|
|
def test_format_matches_challenge_structure(self): |
|
|
"""Test that leaderboard format matches challenge settings.json structure.""" |
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="2025-01-27/classic-classic-0", |
|
|
entry_type="daily", |
|
|
game_mode="classic", |
|
|
grid_size=8, |
|
|
wordlist_source="classic.txt", |
|
|
) |
|
|
d = lb.to_dict() |
|
|
|
|
|
|
|
|
assert "challenge_id" in d |
|
|
assert "entry_type" in d |
|
|
assert "game_mode" in d |
|
|
assert "grid_size" in d |
|
|
assert "puzzle_options" in d |
|
|
assert "users" in d |
|
|
assert "created_at" in d |
|
|
assert "version" in d |
|
|
assert "show_incorrect_guesses" in d |
|
|
assert "enable_free_letters" in d |
|
|
assert "wordlist_source" in d |
|
|
|
|
|
|
|
|
class TestGameSettings: |
|
|
"""Tests for GameSettings dataclass.""" |
|
|
|
|
|
def test_create_default_settings(self): |
|
|
"""Test default GameSettings creation.""" |
|
|
settings = GameSettings() |
|
|
assert settings.game_mode == "classic" |
|
|
assert settings.wordlist_source == "classic.txt" |
|
|
assert settings.show_incorrect_guesses is True |
|
|
assert settings.enable_free_letters is True |
|
|
|
|
|
def test_settings_matching_same(self): |
|
|
"""Test that identical settings match.""" |
|
|
s1 = GameSettings(game_mode="classic", wordlist_source="classic.txt") |
|
|
s2 = GameSettings(game_mode="classic", wordlist_source="classic.txt") |
|
|
assert s1.matches(s2) is True |
|
|
|
|
|
def test_settings_matching_different_mode(self): |
|
|
"""Test that different game modes don't match.""" |
|
|
s1 = GameSettings(game_mode="classic", wordlist_source="classic.txt") |
|
|
s2 = GameSettings(game_mode="easy", wordlist_source="classic.txt") |
|
|
assert s1.matches(s2) is False |
|
|
|
|
|
def test_settings_matching_different_wordlist(self): |
|
|
"""Test that different wordlists don't match.""" |
|
|
s1 = GameSettings(game_mode="classic", wordlist_source="classic.txt") |
|
|
s2 = GameSettings(game_mode="classic", wordlist_source="easy.txt") |
|
|
assert s1.matches(s2) is False |
|
|
|
|
|
def test_settings_matching_txt_extension_ignored(self): |
|
|
"""Test that .txt extension is ignored in comparison.""" |
|
|
s1 = GameSettings(game_mode="classic", wordlist_source="classic.txt") |
|
|
s2 = GameSettings(game_mode="classic", wordlist_source="classic") |
|
|
|
|
|
assert s1._get_sanitized_source() == s2._get_sanitized_source() |
|
|
|
|
|
def test_get_file_id_prefix(self): |
|
|
"""Test file_id prefix generation.""" |
|
|
settings = GameSettings(game_mode="classic", wordlist_source="classic.txt") |
|
|
assert settings.get_file_id_prefix() == "classic-classic" |
|
|
|
|
|
settings2 = GameSettings(game_mode="too easy", wordlist_source="easy.txt") |
|
|
assert settings2.get_file_id_prefix() == "easy-too_easy" |
|
|
|
|
|
|
|
|
class TestFileIdFunctions: |
|
|
"""Tests for file ID generation and parsing.""" |
|
|
|
|
|
def test_sanitize_wordlist_source_removes_txt(self): |
|
|
"""Test that .txt extension is removed.""" |
|
|
assert _sanitize_wordlist_source("classic.txt") == "classic" |
|
|
assert _sanitize_wordlist_source("easy.txt") == "easy" |
|
|
assert _sanitize_wordlist_source("my_words.txt") == "my_words" |
|
|
|
|
|
def test_sanitize_wordlist_source_lowercase(self): |
|
|
"""Test that output is lowercase.""" |
|
|
assert _sanitize_wordlist_source("CLASSIC.txt") == "classic" |
|
|
assert _sanitize_wordlist_source("MyWords.TXT") == "mywords" |
|
|
|
|
|
def test_sanitize_wordlist_source_no_extension(self): |
|
|
"""Test sources without .txt extension.""" |
|
|
assert _sanitize_wordlist_source("classic") == "classic" |
|
|
|
|
|
def test_build_file_id(self): |
|
|
"""Test file_id building.""" |
|
|
assert _build_file_id("classic.txt", "classic", 0) == "classic-classic-0" |
|
|
assert _build_file_id("easy.txt", "easy", 1) == "easy-easy-1" |
|
|
assert _build_file_id("classic.txt", "too easy", 2) == "classic-too_easy-2" |
|
|
|
|
|
def test_parse_file_id(self): |
|
|
"""Test file_id parsing.""" |
|
|
source, mode, seq = _parse_file_id("classic-classic-0") |
|
|
assert source == "classic" |
|
|
assert mode == "classic" |
|
|
assert seq == 0 |
|
|
|
|
|
source, mode, seq = _parse_file_id("easy-too_easy-5") |
|
|
assert source == "easy" |
|
|
assert mode == "too easy" |
|
|
assert seq == 5 |
|
|
|
|
|
def test_parse_file_id_invalid(self): |
|
|
"""Test file_id parsing with invalid format.""" |
|
|
with pytest.raises(ValueError): |
|
|
_parse_file_id("invalid") |
|
|
|
|
|
with pytest.raises(ValueError): |
|
|
_parse_file_id("classic-classic-notanumber") |
|
|
|
|
|
|
|
|
class TestQualification: |
|
|
"""Tests for qualification logic.""" |
|
|
|
|
|
def test_qualify_empty_leaderboard(self): |
|
|
"""Test that any score qualifies for empty leaderboard.""" |
|
|
assert check_qualification(None, 1, 999) is True |
|
|
|
|
|
def test_qualify_not_full(self): |
|
|
"""Test qualification when leaderboard is not full.""" |
|
|
users = [ |
|
|
UserEntry( |
|
|
uid=f"uid-{i}", |
|
|
username=f"Player{i}", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=50, |
|
|
time=100, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
for i in range(10) |
|
|
] |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type="daily", |
|
|
users=users, |
|
|
) |
|
|
|
|
|
|
|
|
assert check_qualification(lb, 1, 999) is True |
|
|
|
|
|
def test_qualify_by_score(self): |
|
|
"""Test qualification by higher score.""" |
|
|
users = [ |
|
|
UserEntry( |
|
|
uid=f"uid-{i}", |
|
|
username=f"Player{i}", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=50 - i, |
|
|
time=100, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
for i in range(20) |
|
|
] |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type="daily", |
|
|
users=users, |
|
|
) |
|
|
|
|
|
|
|
|
assert check_qualification(lb, 32, 100) is True |
|
|
|
|
|
assert check_qualification(lb, 31, 99) is True |
|
|
|
|
|
assert check_qualification(lb, 30, 100) is False |
|
|
|
|
|
def test_qualify_by_time_tiebreaker(self): |
|
|
"""Test qualification using time as tiebreaker.""" |
|
|
users = [ |
|
|
UserEntry( |
|
|
uid=f"uid-{i}", |
|
|
username=f"Player{i}", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=50, |
|
|
time=100 + i, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
for i in range(20) |
|
|
] |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type="daily", |
|
|
users=users, |
|
|
) |
|
|
|
|
|
|
|
|
assert check_qualification(lb, 50, 118) is True |
|
|
|
|
|
assert check_qualification(lb, 50, 120) is False |
|
|
|
|
|
def test_qualify_by_difficulty_tiebreaker(self): |
|
|
"""Test qualification using difficulty as final tiebreaker.""" |
|
|
users = [ |
|
|
UserEntry( |
|
|
uid=f"uid-{i}", |
|
|
username=f"Player{i}", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=50, |
|
|
time=100, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
word_list_difficulty=100.0 - i, |
|
|
) |
|
|
for i in range(20) |
|
|
] |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type="daily", |
|
|
users=users, |
|
|
) |
|
|
|
|
|
|
|
|
assert check_qualification(lb, 50, 100, 82.0) is True |
|
|
|
|
|
assert check_qualification(lb, 50, 100, 80.0) is False |
|
|
|
|
|
def test_not_qualify_lower_score(self): |
|
|
"""Test that lower score doesn't qualify for full leaderboard.""" |
|
|
users = [ |
|
|
UserEntry( |
|
|
uid=f"uid-{i}", |
|
|
username=f"Player{i}", |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
score=100, |
|
|
time=60, |
|
|
timestamp="2025-01-27T12:00:00+00:00", |
|
|
) |
|
|
for i in range(20) |
|
|
] |
|
|
|
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type="daily", |
|
|
users=users, |
|
|
) |
|
|
|
|
|
|
|
|
assert check_qualification(lb, 50, 60) is False |
|
|
|
|
|
|
|
|
class TestDateIds: |
|
|
"""Tests for date/week ID generation.""" |
|
|
|
|
|
def test_daily_id_format(self): |
|
|
"""Test daily ID format is YYYY-MM-DD.""" |
|
|
daily_id = get_current_daily_id() |
|
|
|
|
|
assert len(daily_id) == 10 |
|
|
assert daily_id[4] == "-" |
|
|
assert daily_id[7] == "-" |
|
|
|
|
|
|
|
|
date = datetime.strptime(daily_id, "%Y-%m-%d") |
|
|
assert date is not None |
|
|
|
|
|
def test_weekly_id_format(self): |
|
|
"""Test weekly ID format is YYYY-Www.""" |
|
|
weekly_id = get_current_weekly_id() |
|
|
|
|
|
assert "-W" in weekly_id |
|
|
parts = weekly_id.split("-W") |
|
|
assert len(parts) == 2 |
|
|
assert len(parts[0]) == 4 |
|
|
assert len(parts[1]) == 2 |
|
|
|
|
|
def test_daily_path(self): |
|
|
"""Test daily leaderboard path generation with new folder structure.""" |
|
|
path = get_daily_leaderboard_path("2025-01-27", "classic-classic-0") |
|
|
assert path == "games/leaderboards/daily/2025-01-27/classic-classic-0/settings.json" |
|
|
|
|
|
def test_weekly_path(self): |
|
|
"""Test weekly leaderboard path generation with new folder structure.""" |
|
|
path = get_weekly_leaderboard_path("2025-W04", "easy-easy-1") |
|
|
assert path == "games/leaderboards/weekly/2025-W04/easy-easy-1/settings.json" |
|
|
|
|
|
|
|
|
class TestSorting: |
|
|
"""Tests for user sorting.""" |
|
|
|
|
|
def test_sort_by_score_desc(self): |
|
|
"""Test users are sorted by score descending.""" |
|
|
users = [ |
|
|
UserEntry(uid="1", username="A", word_list=[], score=30, time=100, timestamp="" ), |
|
|
UserEntry(uid="2", username="B", word_list=[], score=50, time=100, timestamp="" ), |
|
|
UserEntry(uid="3", username="C", word_list=[], score=40, time=100, timestamp="" ), |
|
|
] |
|
|
|
|
|
sorted_users = _sort_users(users) |
|
|
|
|
|
assert sorted_users[0].score == 50 |
|
|
assert sorted_users[1].score == 40 |
|
|
assert sorted_users[2].score == 30 |
|
|
|
|
|
def test_sort_by_time_asc_for_equal_score(self): |
|
|
"""Test users with equal score are sorted by time ascending.""" |
|
|
users = [ |
|
|
UserEntry(uid="1", username="A", word_list=[], score=50, time=120, timestamp="" ), |
|
|
UserEntry(uid="2", username="B", word_list=[], score=50, time=80, timestamp="" ), |
|
|
UserEntry(uid="3", username="C", word_list=[], score=50, time=100, timestamp="" ), |
|
|
] |
|
|
|
|
|
sorted_users = _sort_users(users) |
|
|
|
|
|
assert sorted_users[0].time == 80 |
|
|
assert sorted_users[1].time == 100 |
|
|
assert sorted_users[2].time == 120 |
|
|
|
|
|
def test_sort_by_difficulty_desc_for_equal_score_and_time(self): |
|
|
"""Test users with equal score and time are sorted by difficulty descending.""" |
|
|
users = [ |
|
|
UserEntry(uid="1", username="A", word_list=[], score=50, time=100, timestamp="", word_list_difficulty=80.0), |
|
|
UserEntry(uid="2", username="B", word_list=[], score=50, time=100, timestamp="", word_list_difficulty=120.0), |
|
|
UserEntry(uid="3", username="C", word_list=[], score=50, time=100, timestamp="", word_list_difficulty=100.0), |
|
|
] |
|
|
|
|
|
sorted_users = _sort_users(users) |
|
|
|
|
|
assert sorted_users[0].word_list_difficulty == 120.0 |
|
|
assert sorted_users[1].word_list_difficulty == 100.0 |
|
|
assert sorted_users[2].word_list_difficulty == 80.0 |
|
|
|
|
|
|
|
|
class TestCreateUserEntry: |
|
|
"""Tests for create_user_entry helper.""" |
|
|
|
|
|
def test_create_user_entry_basic(self): |
|
|
"""Test creating a user entry with basic fields.""" |
|
|
entry = create_user_entry( |
|
|
username="TestPlayer", |
|
|
score=45, |
|
|
time_seconds=150, |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
) |
|
|
|
|
|
assert entry.username == "TestPlayer" |
|
|
assert entry.score == 45 |
|
|
assert entry.time == 150 |
|
|
assert len(entry.word_list) == 6 |
|
|
assert entry.uid is not None |
|
|
assert entry.timestamp is not None |
|
|
|
|
|
def test_create_user_entry_with_optional_fields(self): |
|
|
"""Test creating a user entry with optional fields.""" |
|
|
entry = create_user_entry( |
|
|
username="TestPlayer", |
|
|
score=45, |
|
|
time_seconds=150, |
|
|
word_list=["A", "B", "C", "D", "E", "F"], |
|
|
word_list_difficulty=110.5, |
|
|
source_challenge_id="challenge-xyz", |
|
|
) |
|
|
|
|
|
assert entry.word_list_difficulty == 110.5 |
|
|
assert entry.source_challenge_id == "challenge-xyz" |
|
|
|
|
|
|
|
|
class TestUnifiedFormat: |
|
|
"""Tests for unified format consistency.""" |
|
|
|
|
|
def test_leaderboard_matches_challenge_structure(self): |
|
|
"""Test leaderboard to_dict matches expected challenge structure.""" |
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="2025-01-27/classic-classic-0", |
|
|
entry_type="daily", |
|
|
) |
|
|
d = lb.to_dict() |
|
|
|
|
|
|
|
|
required_fields = [ |
|
|
"challenge_id", |
|
|
"entry_type", |
|
|
"game_mode", |
|
|
"grid_size", |
|
|
"puzzle_options", |
|
|
"users", |
|
|
"created_at", |
|
|
"version", |
|
|
"show_incorrect_guesses", |
|
|
"enable_free_letters", |
|
|
"wordlist_source", |
|
|
"game_title", |
|
|
"max_display_entries", |
|
|
] |
|
|
|
|
|
for field in required_fields: |
|
|
assert field in d, f"Missing field: {field}" |
|
|
|
|
|
def test_entry_type_field_present(self): |
|
|
"""Test entry_type is always present in serialized output.""" |
|
|
for entry_type in ["daily", "weekly", "challenge"]: |
|
|
lb = LeaderboardSettings( |
|
|
challenge_id="test", |
|
|
entry_type=entry_type, |
|
|
) |
|
|
d = lb.to_dict() |
|
|
assert d["entry_type"] == entry_type |
|
|
|
|
|
def test_challenge_id_as_primary_identifier(self): |
|
|
"""Test challenge_id serves as primary identifier for all types.""" |
|
|
|
|
|
daily = LeaderboardSettings(challenge_id="2025-01-27/classic-classic-0", entry_type="daily") |
|
|
assert daily.challenge_id == "2025-01-27/classic-classic-0" |
|
|
|
|
|
|
|
|
weekly = LeaderboardSettings(challenge_id="2025-W04/easy-easy-0", entry_type="weekly") |
|
|
assert weekly.challenge_id == "2025-W04/easy-easy-0" |
|
|
|
|
|
|
|
|
challenge = LeaderboardSettings(challenge_id="20251130T190249Z-ABCDEF", entry_type="challenge") |
|
|
assert challenge.challenge_id == "20251130T190249Z-ABCDEF" |
|
|
|