Spaces:
Runtime error
Runtime error
| """Test functions for matchmaking module.""" | |
| from unittest.mock import Mock | |
| from lib.matchmaking import game_category, Matchmaking | |
| from lib.config import Configuration | |
| from lib.timer import Timer, seconds | |
| from lib.lichess_types import UserProfileType | |
| def test_game_category_standard_bullet() -> None: | |
| """Test bullet time control with config values.""" | |
| # challenge_initial_time: 60 (1 min), challenge_increment: 1 | |
| # 60 + 1*40 = 100 seconds < 179 = bullet | |
| assert game_category("standard", 60, 1, 0) == "bullet" | |
| # challenge_initial_time: 60, challenge_increment: 2 | |
| # 60 + 2*40 = 140 seconds < 179 = bullet | |
| assert game_category("standard", 60, 2, 0) == "bullet" | |
| def test_game_category_standard_blitz() -> None: | |
| """Test blitz time control with config values.""" | |
| # challenge_initial_time: 180 (3 min), challenge_increment: 1 | |
| # 180 + 1*40 = 220 seconds, 179 <= 220 < 479 = blitz | |
| assert game_category("standard", 180, 1, 0) == "blitz" | |
| # challenge_initial_time: 180, challenge_increment: 2 | |
| # 180 + 2*40 = 260 seconds, 179 <= 260 < 479 = blitz | |
| assert game_category("standard", 180, 2, 0) == "blitz" | |
| def test_game_category_standard_rapid() -> None: | |
| """Test rapid time control.""" | |
| # 10 minutes + 5 seconds increment | |
| # 600 + 5*40 = 800 seconds, 479 <= 800 < 1499 = rapid | |
| assert game_category("standard", 600, 5, 0) == "rapid" | |
| # 15 minutes no increment | |
| # 900 + 0*40 = 900 seconds, 479 <= 900 < 1499 = rapid | |
| assert game_category("standard", 900, 0, 0) == "rapid" | |
| def test_game_category_standard_classical() -> None: | |
| """Test classical time control with max config values.""" | |
| # max_base: 1800 (30 min), max_increment: 20 | |
| # 1800 + 20*40 = 2600 seconds >= 1499 = classical | |
| assert game_category("standard", 1800, 20, 0) == "classical" | |
| # 25 minutes no increment | |
| # 1500 + 0*40 = 1500 seconds >= 1499 = classical | |
| assert game_category("standard", 1500, 0, 0) == "classical" | |
| def test_game_category_correspondence() -> None: | |
| """Test correspondence games with config values.""" | |
| # min_days: 1 | |
| assert game_category("standard", 0, 0, 1) == "correspondence" | |
| # challenge_days: 2 | |
| assert game_category("standard", 0, 0, 2) == "correspondence" | |
| # max_days: 14 | |
| assert game_category("standard", 0, 0, 14) == "correspondence" | |
| def test_game_category_variants() -> None: | |
| """Test chess variants from config.""" | |
| assert game_category("atomic", 60, 1, 0) == "atomic" | |
| assert game_category("chess960", 180, 2, 0) == "chess960" | |
| assert game_category("crazyhouse", 600, 5, 0) == "crazyhouse" | |
| assert game_category("horde", 60, 0, 0) == "horde" | |
| assert game_category("kingOfTheHill", 180, 1, 0) == "kingOfTheHill" | |
| assert game_category("racingKings", 600, 0, 0) == "racingKings" | |
| assert game_category("threeCheck", 60, 1, 0) == "threeCheck" | |
| assert game_category("antichess", 180, 2, 0) == "antichess" | |
| def test_game_category_time_boundaries() -> None: | |
| """Test edge cases at time control boundaries.""" | |
| # Exactly at bullet/blitz boundary | |
| # 179 seconds should be blitz (179 < 179 is False) | |
| assert game_category("standard", 179, 0, 0) == "blitz" | |
| # Just below boundary | |
| assert game_category("standard", 178, 0, 0) == "bullet" | |
| # Exactly at blitz/rapid boundary | |
| assert game_category("standard", 479, 0, 0) == "rapid" | |
| # Just below | |
| assert game_category("standard", 478, 0, 0) == "blitz" | |
| # Exactly at rapid/classical boundary | |
| assert game_category("standard", 1499, 0, 0) == "classical" | |
| # Just below | |
| assert game_category("standard", 1498, 0, 0) == "rapid" | |
| def test_game_category_min_config_values() -> None: | |
| """Test minimum config values.""" | |
| # min_base: 0, min_increment: 0 | |
| # This is an edge case: 0 + 0*40 = 0 < 179 = bullet | |
| assert game_category("standard", 0, 0, 0) == "bullet" | |
| # min_base: 0, min_increment: 0, min_days: 1 | |
| assert game_category("standard", 0, 0, 1) == "correspondence" | |
| def test_game_category_correspondence_overrides_time() -> None: | |
| """Test that correspondence takes precedence over time controls.""" | |
| # If both days and time controls are set, days takes precedence | |
| assert game_category("standard", 1800, 20, 1) == "correspondence" | |
| assert game_category("standard", 60, 1, 2) == "correspondence" | |
| def test_game_category_variant_overrides_time() -> None: | |
| """Test that variants override time control categorization.""" | |
| # Variants are returned regardless of time control | |
| # Even if time would be "classical", variant name is returned | |
| assert game_category("atomic", 1800, 20, 0) == "atomic" | |
| assert game_category("horde", 60, 1, 0) == "horde" | |
| # Variants override correspondence too | |
| assert game_category("chess960", 0, 0, 14) == "chess960" | |
| def test_game_category_negative_values() -> None: | |
| """Test edge case with negative values (should not happen in practice).""" | |
| # Negative base time | |
| assert game_category("standard", -100, 5, 0) == "bullet" | |
| # Negative increment results in negative duration | |
| result = game_category("standard", 100, -10, 0) | |
| # 100 + (-10)*40 = -300, which is < 179, so bullet | |
| assert result == "bullet" | |
| def test_game_category_realistic_scenarios() -> None: | |
| """Test realistic game scenarios from actual lichess games.""" | |
| # 1+0 bullet | |
| assert game_category("standard", 60, 0, 0) == "bullet" | |
| # 2+1 bullet | |
| assert game_category("standard", 120, 1, 0) == "bullet" | |
| # 3+0 blitz | |
| assert game_category("standard", 180, 0, 0) == "blitz" | |
| # 3+2 blitz | |
| assert game_category("standard", 180, 2, 0) == "blitz" | |
| # 5+0 blitz | |
| assert game_category("standard", 300, 0, 0) == "blitz" | |
| # 5+3 blitz | |
| assert game_category("standard", 300, 3, 0) == "blitz" | |
| # 10+0 rapid | |
| assert game_category("standard", 600, 0, 0) == "rapid" | |
| # 15+5 rapid | |
| assert game_category("standard", 900, 5, 0) == "rapid" | |
| # 15+10 rapid | |
| assert game_category("standard", 900, 10, 0) == "rapid" | |
| # 30+0 classical | |
| assert game_category("standard", 1800, 0, 0) == "classical" | |
| # 30+20 classical | |
| assert game_category("standard", 1800, 20, 0) == "classical" | |
| def test_get_random_config_value__returns_specific_value() -> None: | |
| """Test that get_random_config_value returns the config value when it's not 'random'.""" | |
| # Create mock objects | |
| mock_li = Mock() | |
| mock_config = Configuration({ | |
| "challenge": {"variants": ["standard"]}, | |
| "matchmaking": { | |
| "allow_matchmaking": False, | |
| "block_list": [], | |
| "online_block_list": [], | |
| "challenge_timeout": 30 | |
| } | |
| }) | |
| mock_user_profile: UserProfileType = {"username": "testbot", "perfs": {}} | |
| # Create matchmaking instance | |
| matchmaking = Matchmaking(mock_li, mock_config, mock_user_profile) | |
| # Create config with a specific value | |
| test_config = Configuration({"challenge_variant": "atomic"}) | |
| # Test that it returns the specific value, not a random choice | |
| choices = ["standard", "chess960", "atomic", "horde"] | |
| result = matchmaking.get_random_config_value(test_config, "challenge_variant", choices) | |
| assert result == "atomic", f"Expected 'atomic' but got '{result}'" | |
| def test_should_create_challenge_respects_rate_limit_when_previous_challenge_expired() -> None: | |
| """An expired previous challenge should not bypass the challenge rate-limit timer.""" | |
| mock_li = Mock() | |
| mock_config = Configuration({ | |
| "challenge": {"variants": ["standard"]}, | |
| "matchmaking": { | |
| "allow_matchmaking": True, | |
| "block_list": [], | |
| "online_block_list": [], | |
| "challenge_timeout": 0 | |
| } | |
| }) | |
| mock_user_profile: UserProfileType = {"username": "testbot", "perfs": {}} | |
| matchmaking = Matchmaking(mock_li, mock_config, mock_user_profile) | |
| matchmaking.challenge_id = "challenge-id" | |
| matchmaking.last_challenge_created_delay.starting_time -= 100 | |
| matchmaking.rate_limit_timer = Timer(seconds(60)) | |
| assert matchmaking.should_create_challenge() is False | |
| def test_get_random_config_value__returns_from_choices_when_random() -> None: | |
| """Test that get_random_config_value returns a value from choices when config value is 'random'.""" | |
| # Create mock objects | |
| mock_li = Mock() | |
| mock_config = Configuration({ | |
| "challenge": {"variants": ["standard"]}, | |
| "matchmaking": { | |
| "allow_matchmaking": False, | |
| "block_list": [], | |
| "online_block_list": [], | |
| "challenge_timeout": 30 | |
| } | |
| }) | |
| mock_user_profile: UserProfileType = {"username": "testbot", "perfs": {}} | |
| # Create matchmaking instance | |
| matchmaking = Matchmaking(mock_li, mock_config, mock_user_profile) | |
| # Create config with "random" value | |
| test_config = Configuration({"challenge_mode": "random"}) | |
| # Test that it returns one of the choices | |
| choices = ["casual", "rated"] | |
| result = matchmaking.get_random_config_value(test_config, "challenge_mode", choices) | |
| assert result in choices, f"Expected result to be in {choices} but got '{result}'" | |