"""Tests for the models.""" import datetime from lib import model import yaml from lib import config from collections import defaultdict, Counter from lib.blocklist import OnlineBlocklist from lib.timer import Timer from lib.lichess_types import ChallengeType, UserProfileType, GameEventType, PlayerType def test_challenge() -> None: """Test the challenge model.""" challenge: ChallengeType = {"id": "zzzzzzzz", "url": "https://lichess.org/zzzzzzzz", "status": "created", "challenger": {"id": "c", "name": "c", "rating": 2000, "title": None, "online": True}, "destUser": {"id": "b", "name": "b", "rating": 3000, "title": "BOT", "online": True}, "variant": {"key": "standard", "name": "Standard", "short": "Std"}, "rated": False, "speed": "bullet", "timeControl": {"type": "clock", "limit": 90, "increment": 1, "show": "1.5+1"}, "color": "random", "finalColor": "white", "perf": {"icon": "\ue032", "name": "Bullet"}} user_profile: UserProfileType = {"id": "b", "username": "b", "perfs": {"bullet": {"games": 100, "rating": 3000, "rd": 150, "prog": -10}, "blitz": {"games": 100, "rating": 3000, "rd": 150, "prog": -10}, "rapid": {"games": 100, "rating": 3000, "rd": 150, "prog": -10}, "classical": {"games": 100, "rating": 3000, "rd": 150, "prog": -10}, "correspondence": {"games": 100, "rating": 3000, "rd": 150, "prog": -10}, "antichess": {"games": 100, "rating": 3000, "rd": 150, "prog": -10, "prov": True}}, "title": "BOT", "createdAt": 1500000000000, "profile": {"bio": "This is my bio", "links": "https://github.com/lichess-bot-devs/lichess-bot"}, "seenAt": 1700000000000, "playTime": {"total": 1000000, "tv": 10000}, "url": "https://lichess.org/@/b", "count": {"all": 600, "rated": 500, "ai": 50, "draw": 200, "drawH": 50, "loss": 50, "lossH": 50, "win": 250, "winH": 200, "bookmark": 0, "playing": 0, "import": 0, "me": 0}, "followable": True, "following": False, "blocking": False} with open("./config.yml.default") as file: CONFIG = yaml.safe_load(file) CONFIG["token"] = "" CONFIG["challenge"]["allow_list"] = [] CONFIG["challenge"]["block_list"] = [] CONFIG["challenge"]["min_rating"] = 0 CONFIG["challenge"]["max_rating"] = 4000 CONFIG["challenge"]["rating_difference"] = None configuration = config.Configuration(CONFIG).challenge recent_challenges: defaultdict[str, list[Timer]] = defaultdict() recent_challenges["c"] = [] online_block_list = OnlineBlocklist([]) challenge_model = model.Challenge(challenge, user_profile) assert challenge_model.id == "zzzzzzzz" assert challenge_model.rated is False assert challenge_model.variant == "standard" assert challenge_model.speed == "bullet" assert challenge_model.time_control["show"] == "1.5+1" assert challenge_model.color == "white" supported = challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) assert supported == (True, "") CONFIG["challenge"]["min_base"] = 120 assert challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) == ( False, "timeControl", ) def test_challenge_rating_filters() -> None: """Test challenge rating filtering for incoming challenges.""" challenge: ChallengeType = {"id": "zzzzzzzz", "url": "https://lichess.org/zzzzzzzz", "status": "created", "challenger": {"id": "c", "name": "c", "rating": 2000, "title": None, "online": True}, "destUser": {"id": "b", "name": "b", "rating": 3000, "title": "BOT", "online": True}, "variant": {"key": "standard", "name": "Standard", "short": "Std"}, "rated": False, "speed": "bullet", "timeControl": {"type": "clock", "limit": 90, "increment": 1, "show": "1.5+1"}, "color": "random", "finalColor": "white", "perf": {"icon": "\ue032", "name": "Bullet"}} user_profile: UserProfileType = {"id": "b", "username": "b", "perfs": {"bullet": {"games": 100, "rating": 3000, "rd": 150, "prog": -10}}, "title": "BOT"} with open("./config.yml.default") as file: CONFIG = yaml.safe_load(file) CONFIG["token"] = "" CONFIG["challenge"]["allow_list"] = [] CONFIG["challenge"]["block_list"] = [] CONFIG["challenge"]["min_rating"] = 0 CONFIG["challenge"]["max_rating"] = 4000 CONFIG["challenge"]["rating_difference"] = None configuration = config.Configuration(CONFIG).challenge recent_challenges: defaultdict[str, list[Timer]] = defaultdict() recent_challenges["c"] = [] online_block_list = OnlineBlocklist([]) challenge_model = model.Challenge(challenge, user_profile) # Default config should accept all ratings supported = challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) assert supported == (True, "") # Test max_rating filter CONFIG["challenge"]["max_rating"] = 1500 assert challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) == ( False, "generic") # Test min_rating filter CONFIG["challenge"]["max_rating"] = 4000 CONFIG["challenge"]["min_rating"] = 2500 assert challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) == ( False, "generic") # Test rating_difference filter (bot is 3000, challenger is 2000, diff is 1000) CONFIG["challenge"]["min_rating"] = 0 CONFIG["challenge"]["rating_difference"] = 500 assert challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) == ( False, "generic") # Rating difference large enough to accept CONFIG["challenge"]["rating_difference"] = 1500 supported = challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) assert supported == (True, "") # Test that rating_difference narrows the range # min_rating=0, max_rating=4000, but diff=500 from bot rating 3000 CONFIG["challenge"]["rating_difference"] = 500 CONFIG["challenge"]["min_rating"] = 0 CONFIG["challenge"]["max_rating"] = 4000 assert challenge_model.is_supported(configuration, recent_challenges, Counter(), online_block_list, user_profile) == ( False, "generic") # Test with AI opponent (no rating) - should always accept CONFIG["challenge"]["rating_difference"] = None CONFIG["challenge"]["max_rating"] = 1000 ai_challenge: ChallengeType = {**challenge, "challenger": {"id": "ai", "name": "AI level 5", "aiLevel": 5}} ai_challenge_model = model.Challenge(ai_challenge, user_profile) assert ai_challenge_model.is_supported_rating(configuration, user_profile) is True def test_game() -> None: """Test the game model.""" game: GameEventType = {"id": "zzzzzzzz", "variant": {"key": "standard", "name": "Standard", "short": "Std"}, "speed": "bullet", "perf": {"name": "Bullet"}, "rated": False, "createdAt": 1700000000000, "white": {"id": "c", "name": "c", "title": None, "rating": 2000}, "black": {"id": "b", "name": "b", "title": "BOT", "rating": 3000}, "initialFen": "startpos", "clock": {"initial": 90000, "increment": 1000}, "type": "gameFull", "state": {"type": "gameState", "moves": "", "wtime": 90000, "btime": 90000, "winc": 1000, "binc": 1000, "status": "started"}} username = "b" base_url = "https://lichess.org/" abort_time = datetime.timedelta(seconds=30) game_model = model.Game(game, username, base_url, abort_time) assert game_model.id == "zzzzzzzz" assert game_model.mode == "casual" assert game_model.is_white is False assert game_model.my_color == "black" assert game_model.url() == "https://lichess.org/zzzzzzzz/black" assert game_model.short_url() == "https://lichess.org/zzzzzzzz" assert game_model.pgn_event() == "Casual Bullet game" assert game_model.time_control() == "90+1" assert game_model.is_abortable() is True def test_player() -> None: """Test the player model.""" player: PlayerType = {"id": "b", "name": "b", "rating": 3000, "title": "BOT", "online": True} player_model = model.Player(player) assert player_model.is_bot is True assert str(player_model) == "BOT b (3000)" def test_is_chess_960() -> None: """Test the is_chess_960 function.""" items = [ {"fen": "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1", "type": "standard", "is_960": False}, {"fen": "brnkrqnb/pppppppp/8/8/8/8/PPPPPPPP/BRNKRQNB w KQkq - 0 1", "type": "960", "is_960": True}, # pos1 {"fen": "nrbbnkqr/pppppppp/8/8/8/8/PPPPPPPP/NRBBNKQR w KQkq - 0 1", "type": "960", "is_960": True}, # pos2 ] for item in items: fen = str(item["fen"]) expected = item["is_960"] result = model.is_chess_960(fen) assert result == expected