diff --git a/lib/model.py b/lib/model.py index 04bc1c082..98651069e 100644 --- a/lib/model.py +++ b/lib/model.py @@ -71,7 +71,7 @@ def is_supported_mode(self, challenge_cfg: Configuration) -> bool: return ("rated" if self.rated else "casual") in challenge_cfg.modes def is_supported_recent(self, config: Configuration, recent_bot_challenges: defaultdict[str, list[Timer]]) -> bool: - """Check whether we have played a lot of games with this opponent recently. Only used when the oppoennt is a BOT.""" + """Check whether we have played a lot of games with this opponent recently. Only used when the opponent is a BOT.""" # Filter out old challenges recent_bot_challenges[self.challenger.name] = [timer for timer in recent_bot_challenges[self.challenger.name] diff --git a/lib/types.py b/lib/types.py index 931ecc313..758752295 100644 --- a/lib/types.py +++ b/lib/types.py @@ -30,6 +30,7 @@ class PerfType(TypedDict, total=False): rd: int sd: int prov: bool + prog: int class ProfileType(TypedDict, total=False): @@ -69,6 +70,7 @@ class UserProfileType(TypedDict, total=False): following: bool blocking: bool followsYou: bool + count: dict[str, int] class ReadableType(TypedDict): @@ -121,7 +123,7 @@ class InfoStrDict(TypedDict, total=False): class PlayerType(TypedDict, total=False): """Type hint for information on a player.""" - title: str + title: Optional[str] rating: int provisional: bool aiLevel: int diff --git a/test_bot/test_lichess.py b/test_bot/test_lichess.py new file mode 100644 index 000000000..89dd30133 --- /dev/null +++ b/test_bot/test_lichess.py @@ -0,0 +1,49 @@ +"""Tests for the lichess communication.""" + +from lib import lichess +import logging +import os +import pytest + + +def test_lichess() -> None: + """Test the lichess communication.""" + token = os.getenv("LICHESS_BOT_TEST_TOKEN") + if token is None: + pytest.skip("Lichess-bot test token must be set.") + li = lichess.Lichess(token, "https://lichess.org/", "0.0.0", logging.DEBUG, 3) + assert len(li.get_online_bots()) > 20 + profile = li.get_profile() + profile['seenAt'] = 1700000000000 + assert profile == {'blocking': False, + 'count': {'ai': 3, 'all': 12, 'bookmark': 0, 'draw': 1, 'drawH': 1, 'import': 0, + 'loss': 8, 'lossH': 5, 'me': 0, 'playing': 0, 'rated': 0, 'win': 3, 'winH': 3}, + 'createdAt': 1627834995597, 'followable': True, 'following': False, 'id': 'badsunfish', + 'perfs': {'blitz': {'games': 0, 'prog': 0, 'prov': True, 'rating': 1500, 'rd': 500}, + 'bullet': {'games': 0, 'prog': 0, 'prov': True, 'rating': 1500, 'rd': 500}, + 'classical': {'games': 0, 'prog': 0, 'prov': True, 'rating': 1500, 'rd': 500}, + 'correspondence': {'games': 0, 'prog': 0, 'prov': True, 'rating': 1500, 'rd': 500}, + 'rapid': {'games': 0, 'prog': 0, 'prov': True, 'rating': 1500, 'rd': 500}}, + 'playTime': {'total': 1873, 'tv': 0}, 'seenAt': 1700000000000, 'title': 'BOT', + 'url': 'https://lichess.org/@/BadSunfish', 'username': 'BadSunfish'} + assert li.get_ongoing_games() == [] + assert li.is_online("NNWithSF") is False + assert li.get_public_data("lichapibot") == {'blocking': False, + 'count': {'ai': 1, 'all': 15774, 'bookmark': 0, 'draw': 3009, 'drawH': 3009, + 'import': 0, 'loss': 6423, 'lossH': 6423, + 'me': 0, 'playing': 0, 'rated': 15121, 'win': 6342, 'winH': 6341}, + 'createdAt': 1524037267522, 'followable': True, 'following': False, + 'id': 'lichapibot', + 'perfs': {'blitz': {'games': 2430, 'prog': 3, 'prov': True, 'rating': 2388, + 'rd': 142}, + 'bullet': {'games': 7293, 'prog': 9, 'prov': True, 'rating': 2298, + 'rd': 133}, + 'classical': {'games': 0, 'prog': 0, 'prov': True, 'rating': 1500, + 'rd': 500}, + 'correspondence': {'games': 0, 'prog': 0, 'prov': True, + 'rating': 1500, 'rd': 500}, + 'rapid': {'games': 993, 'prog': -80, 'prov': True, 'rating': 2363, + 'rd': 149}}, + 'playTime': {'total': 4111502, 'tv': 1582068}, 'profile': {}, + 'seenAt': 1669272254317, 'title': 'BOT', 'tosViolation': True, + 'url': 'https://lichess.org/@/lichapibot', 'username': 'lichapibot'} diff --git a/test_bot/test_model.py b/test_bot/test_model.py new file mode 100644 index 000000000..bd0c8d505 --- /dev/null +++ b/test_bot/test_model.py @@ -0,0 +1,85 @@ +"""Tests for the models.""" + +import datetime +from lib import model +import yaml +from lib import config +from collections import defaultdict +from lib.timer import Timer +from lib.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"] = [] + configuration = config.Configuration(CONFIG).challenge + recent_challenges: defaultdict[str, list[Timer]] = defaultdict() + recent_challenges["c"] = [] + + 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" + assert challenge_model.is_supported(configuration, recent_challenges) == (True, "") + + CONFIG["challenge"]["min_base"] = 120 + assert challenge_model.is_supported(configuration, recent_challenges) == (False, "timeControl") + + +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 + + +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)"