"""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}'"