From 14a1c5ceb6ffe10f8b78d3caf8eb6cf9f0590da6 Mon Sep 17 00:00:00 2001 From: "Brian S. Stephan" Date: Thu, 16 May 2024 22:01:12 -0500 Subject: [PATCH] get more IRC bot functionality under test remove a character, get character status, and the main level up check thread Signed-off-by: Brian S. Stephan --- idlerpg/ircplugin.py | 42 ++++++++++ tests/fixtures/simple_character.json | 46 +++++++++++ tests/test_idlerpg_ircplugin.py | 116 +++++++++++++++++++++++++++ 3 files changed, 204 insertions(+) diff --git a/idlerpg/ircplugin.py b/idlerpg/ircplugin.py index ea0484f..ba5aeb8 100644 --- a/idlerpg/ircplugin.py +++ b/idlerpg/ircplugin.py @@ -110,6 +110,29 @@ class IdleRPG(Plugin): explicit_target=character.game.channel.name) return self.bot.reply(event, f"{character} has been successfully logged in.") + def handle_remove(self, connection, event, match): + """Handle the disabling of a character.""" + hostmask = event.source + try: + character = Character.objects.get(enabled=True, hostmask=hostmask) + character.enabled = False + character.save() + return self.bot.reply(event, f"Character {character.name} has been disabled.") + except Character.DoesNotExist: + return self.bot.reply(event, "No character associated to your hostmask found (try logging in first).") + + def handle_status(self, connection, event, match): + """Handle the request for character/player status.""" + hostmask = event.source + try: + character = Character.objects.get(enabled=True, hostmask=hostmask) + self.bot.reply(event, f"{character}, is {hostmask}.") + self.bot.reply(event, f"{character.name} is {Character.CHARACTER_STATUSES[character.status]}.") + if character.status == Character.CHARACTER_STATUS_LOGGED_IN: + self.bot.reply(event, f"{character.name} will level up on {character.next_level_str()}.") + except Character.DoesNotExist: + self.bot.reply(event, "No character associated to your hostmask found (try logging in first).") + def _handle_generic_penalty_and_logout(self, hostmask: str, channel: str, penalty: int, reason: str, penalty_log_attr: str): """Penalize a character and log them out, for a provided reason. @@ -135,5 +158,24 @@ class IdleRPG(Plugin): except Character.DoesNotExist: logger.debug("no character found for %s", hostmask) + def level_thread(self): + """Check for characters who have leveled up, and log as such in the channel.""" + while self.check_for_level_ups: + time.sleep(self.SLEEP_BETWEEN_LEVEL_CHECKS) + self._level_up_games() + + def _level_up_games(self): + """Find games under management and level up characters within them, updating their channel.""" + logger.debug("checking for level ups in games") + for game in Game.objects.filter(active=True): + logger.debug("checking for level ups in %s", game) + for character in Character.objects.levelable(game): + logger.debug("going to try to level up %s", character) + character.level_up() + self.bot.reply(None, f"{character.name}, the {character.character_class}, has attained level " + f"{character.level}! Next level at {character.next_level_str()}.", + explicit_target=game.channel.name) + character.save() + plugin = IdleRPG diff --git a/tests/fixtures/simple_character.json b/tests/fixtures/simple_character.json index c300024..3c16394 100644 --- a/tests/fixtures/simple_character.json +++ b/tests/fixtures/simple_character.json @@ -33,6 +33,20 @@ "discord_bridge": "" } }, +{ + "model": "ircbot.ircchannel", + "pk": 2, + "fields": { + "name": "#level_test", + "server": 1, + "autojoin": false, + "topic_msg": "", + "topic_time": "2024-05-06T05:10:25.154Z", + "topic_by": "", + "markov_learn_from_channel": true, + "discord_bridge": "" + } +}, { "model": "idlerpg.game", "pk": 1, @@ -42,6 +56,15 @@ "channel": 1 } }, +{ + "model": "idlerpg.game", + "pk": 2, + "fields": { + "name": "level test", + "active": true, + "channel": 2 + } +}, { "model": "idlerpg.character", "pk": 1, @@ -64,5 +87,28 @@ "time_penalized_notice": 0, "game": 1 } +}, +{ + "model": "idlerpg.character", + "pk": 2, + "fields": { + "name": "bss2", + "password": "pbkdf2_sha256$720000$A941t4dL96zzqeldCFucrr$Pof137/IjT3p//ZR+iYNoBnGmYPG6jLbNqenwMA3hHY=", + "hostmask": "bss2!bss@bss", + "status": "OFFLINE", + "character_class": "tester", + "level": 0, + "next_level": "2024-05-05T05:20:45.437Z", + "created": "2024-05-05T05:10:45.438Z", + "last_login": "2024-05-05T05:10:45.437Z", + "time_penalized_nick_change": 0, + "time_penalized_part": 0, + "time_penalized_quit": 0, + "time_penalized_logout": 0, + "time_penalized_kicked": 0, + "time_penalized_privmsg": 0, + "time_penalized_notice": 0, + "game": 1 + } } ] diff --git a/tests/test_idlerpg_ircplugin.py b/tests/test_idlerpg_ircplugin.py index 2f76b1c..9200e59 100644 --- a/tests/test_idlerpg_ircplugin.py +++ b/tests/test_idlerpg_ircplugin.py @@ -3,6 +3,7 @@ SPDX-FileCopyrightText: © 2024 Brian S. Stephan SPDX-License-Identifier: AGPL-3.0-or-later """ +import datetime import logging import re import unittest.mock as mock @@ -255,3 +256,118 @@ class IrcPluginTest(TestCase): ) self.plugin.seen_hostmasks.remove('bss!bss@test_login') + + def test_remove(self): + """Test the remove command.""" + mock_event = mock.MagicMock() + mock_event.source = 'bss!bss@bss_remove' + mock_event.recursing = False + + game = Game.objects.get(pk=2) + test_char = Character.objects.register('test_remove', game, 'test', 'bss!bss@bss_remove', 'tester') + match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'REMOVEME') + self.plugin.handle_remove(self.mock_connection, mock_event, match) + + assert test_char.enabled is True + refetch = Character.objects.get(name='test_remove') + assert refetch.enabled is False + self.mock_bot.reply.assert_has_calls([ + mock.call(mock_event, "Character test_remove has been disabled."), + ]) + + def test_remove_no_match(self): + """Test the remove command doesn't do anything if the hostname isn't found.""" + mock_event = mock.MagicMock() + mock_event.source = 'bss2!bss@bss_remove' + mock_event.recursing = False + + game = Game.objects.get(pk=2) + test_char = Character.objects.register('test_remove', game, 'test', 'bss!bss@bss_remove', 'tester') + match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'REMOVEME') + self.plugin.handle_remove(self.mock_connection, mock_event, match) + + assert test_char.enabled is True + refetch = Character.objects.get(name='test_remove') + assert refetch.enabled is True + self.mock_bot.reply.assert_has_calls([ + mock.call(mock_event, "No character associated to your hostmask found (try logging in first)."), + ]) + + def test_status(self): + """Test the status command.""" + mock_event = mock.MagicMock() + mock_event.source = 'bss!bss@bss' + mock_event.recursing = False + + self.plugin.seen_hostmasks.add('bss!bss@bss') + match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'STATUS') + self.plugin.handle_status(self.mock_connection, mock_event, match) + + self.mock_bot.reply.assert_has_calls([ + mock.call(mock_event, "bss, the level 0 tester, is bss!bss@bss."), + mock.call(mock_event, "bss is logged in."), + mock.call(mock_event, "bss will level up on 2024-05-05 05:20:45 UTC."), + ]) + assert self.mock_bot.reply.call_count == 3 + + self.plugin.seen_hostmasks.remove('bss!bss@bss') + + def test_status_logged_out(self): + """Test the status command.""" + mock_event = mock.MagicMock() + mock_event.source = 'bss2!bss@bss' + mock_event.recursing = False + + self.plugin.seen_hostmasks.add('bss2!bss@bss') + match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'STATUS') + self.plugin.handle_status(self.mock_connection, mock_event, match) + + self.mock_bot.reply.assert_has_calls([ + mock.call(mock_event, "bss2, the level 0 tester, is bss2!bss@bss."), + mock.call(mock_event, "bss2 is offline."), + ]) + assert self.mock_bot.reply.call_count == 2 + + self.plugin.seen_hostmasks.remove('bss2!bss@bss') + + def test_status_no_match(self): + """Test the status command.""" + mock_event = mock.MagicMock() + mock_event.source = 'bss3!bss@bss' + mock_event.recursing = False + + self.plugin.seen_hostmasks.add('bss3!bss@bss') + match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'STATUS') + self.plugin.handle_status(self.mock_connection, mock_event, match) + + self.mock_bot.reply.assert_has_calls([ + mock.call(mock_event, "No character associated to your hostmask found (try logging in first)."), + ]) + assert self.mock_bot.reply.call_count == 1 + + self.plugin.seen_hostmasks.remove('bss3!bss@bss') + + def test_level_up_games(self): + """Test the walking of games and characters to level them up.""" + game = Game.objects.get(pk=2) + char_1 = Character.objects.register('test_level_1', game, 'test', 'test_1!test@test', 'tester') + char_2 = Character.objects.register('test_level_2', game, 'test', 'test_2!test@test', 'tester') + char_2.log_out() + level_time = datetime.datetime(year=2024, month=5, day=16, hour=00, minute=0, second=0) + char_1.next_level = level_time + char_2.next_level = level_time + char_1.save() + char_2.save() + # only one should level up, since char_2 isn't logged in + self.plugin._level_up_games() + + char_1 = Character.objects.get(pk=char_1.pk) + char_2 = Character.objects.get(pk=char_2.pk) + assert char_1.level == 1 + assert char_2.level == 0 + assert char_1.next_level != char_2.next_level + self.mock_bot.reply.assert_has_calls([ + mock.call(None, "test_level_1, the tester, has attained level 1! Next level at 2024-05-16 00:10:00 UTC.", + explicit_target=game.channel.name), + ]) + assert self.mock_bot.reply.call_count == 2