Compare commits

...

6 Commits

Author SHA1 Message Date
56b554f6f1
add scaffolding to load the bot and lightly test it
better tests might be coming some time in the future, but I have the
whole IRC framework mocked out at the moment

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-06-02 00:19:06 -05:00
731e434c8d
add a bot command to allow a user to register in a game
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-06-02 00:16:27 -05:00
f22ca875e3
requirements bumps, though they don't solve a jinja warning
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-06-01 23:30:57 -05:00
4a8babf39e
enforce exclusivity of only one active Game at a time
a prior commit made this determination for now, for simplicity's sake
(and also what are the odds of running two games at once on the same
codebase), but it was'n really enforced until now

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-06-01 23:26:49 -05:00
0ad687669e
LOGIN and LOGOUT ircbot tests
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-05-18 08:26:55 -05:00
e9b985bd79
change the level up time to a logarithmic curve
Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-05-17 12:46:11 -05:00
9 changed files with 365 additions and 63 deletions

View File

@ -32,6 +32,70 @@ class IdleRPG(Plugin):
self.seen_hostmasks = set()
super().__init__(bot, connection, event)
def start(self):
"""Set up the handlers and start processing game state."""
self.connection.add_global_handler('join', self.handle_join, -20)
self.connection.add_global_handler('kick', self.handle_kick_penalty, -20)
self.connection.add_global_handler('nick', self.handle_nick_penalty, -20)
self.connection.add_global_handler('part', self.handle_part_penalty, -20)
self.connection.add_global_handler('pubmsg', self.handle_message_penalty, -20)
self.connection.add_global_handler('pubnotice', self.handle_message_penalty, -20)
self.connection.add_global_handler('quit', self.handle_quit_penalty, -20)
self.connection.add_global_handler('whoreply', self.handle_whoreply_response, -20)
self.connection.reactor.add_global_regex_handler(['privmsg'], self.LOGIN_COMMAND_PATTERN,
self.handle_login, -20)
self.connection.reactor.add_global_regex_handler(['privmsg'], self.LOGOUT_COMMAND_PATTERN,
self.handle_logout, -20)
self.connection.reactor.add_global_regex_handler(['privmsg'], self.REGISTER_COMMAND_PATTERN,
self.handle_register, -20)
self.connection.reactor.add_global_regex_handler(['privmsg'], self.REMOVEME_COMMAND_PATTERN,
self.handle_remove, -20)
self.connection.reactor.add_global_regex_handler(['privmsg'], self.STATUS_COMMAND_PATTERN,
self.handle_status, -20)
# get a list of who is in the channel and auto-log them in
logger.info("Automatically logging in users already in the channel(s).")
for game in Game.objects.filter(active=True):
self.connection.who(game.channel.name)
# start the thread to check for level ups
self.check_for_level_ups = True
level_t = threading.Thread(target=self.level_thread)
level_t.daemon = True
level_t.start()
logger.info("Started up the level up checker thread.")
super(IdleRPG, self).start()
def stop(self):
"""Tear down handlers, stop checking for new levels, and return to a stable game state."""
self.check_for_level_ups = False
self.connection.remove_global_handler('join', self.handle_join)
self.connection.remove_global_handler('kick', self.handle_kick_penalty)
self.connection.remove_global_handler('nick', self.handle_nick_penalty)
self.connection.remove_global_handler('part', self.handle_part_penalty)
self.connection.remove_global_handler('pubmsg', self.handle_message_penalty)
self.connection.remove_global_handler('pubnotice', self.handle_message_penalty)
self.connection.remove_global_handler('quit', self.handle_quit_penalty)
self.connection.remove_global_handler('whoreply', self.handle_whoreply_response)
self.connection.reactor.remove_global_regex_handler(['privmsg'], self.handle_login)
self.connection.reactor.remove_global_regex_handler(['privmsg'], self.handle_logout)
self.connection.reactor.remove_global_regex_handler(['privmsg'], self.handle_register)
self.connection.reactor.remove_global_regex_handler(['privmsg'], self.handle_remove)
self.connection.reactor.remove_global_regex_handler(['privmsg'], self.handle_status)
# log everyone out (no penalty)
Character.objects.log_out_everyone()
# unset the hostmask tracking
self.seen_hostmasks = set()
logger.info("Reset the set of seen hostmasks.")
super(IdleRPG, self).stop()
def handle_join(self, connection, event):
"""Track a character as online if their player joins the game channel."""
self.seen_hostmasks.add(event.source)
@ -108,7 +172,49 @@ class IdleRPG(Plugin):
self.bot.reply(None, f"{character}, is now online from nickname {nick}. "
f"Next level at {character.next_level_str()}.",
explicit_target=character.game.channel.name)
return self.bot.reply(event, f"{character} has been successfully logged in.")
return self.bot.reply(event, f"{character}, has been successfully logged in.")
def handle_logout(self, connection, event, match):
"""Log out a character when requested."""
hostmask = event.source
try:
character = Character.objects.get(enabled=True, hostmask=hostmask)
except Character.DoesNotExist:
return self.bot.reply(event, "You do not have a character in a running game.")
if character.status != Character.CHARACTER_STATUS_LOGGED_IN:
return self.bot.reply(event, f"Cannot log out {character.name}, either they already are, "
"or they are in a state that cannot be modified.")
character.log_out()
penalty = character.penalize(20, "logging out")
character.time_penalized_logout += penalty
character.save()
return self.bot.reply(event, f"{character}, has been successfully logged out.")
def handle_register(self, connection, event, match):
"""Register a character for a user."""
hostmask = event.source
nick = irc.client.NickMask(hostmask).nick
try:
game = Game.objects.get(active=True)
except Game.DoesNotExist:
return self.bot.reply(event, "No active game exists.")
if hostmask not in self.seen_hostmasks:
return self.bot.reply(event, f"Please join {game.channel.name} before registering.")
try:
character = Character.objects.register(match.group('name'), game, match.group('password'),
hostmask, match.group('char_class'))
except IntegrityError:
return self.bot.reply(event, "Registration failed; either you already have a character, "
"or the character name is already taken.")
self.bot.reply(None, f"{nick}'s newest character, {character.name}, the {character.character_class}, "
f"has been summoned! Next level at {character.next_level_str()}.",
explicit_target=game.channel.name)
return self.bot.reply(event, f"You have registered {character}, and been automatically logged in.")
def handle_remove(self, connection, event, match):
"""Handle the disabling of a character."""
@ -162,20 +268,19 @@ class IdleRPG(Plugin):
"""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()
self._level_up_characters()
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()
def _level_up_characters(self):
"""Level up characters in the active game, updating their channel."""
game = Game.objects.get(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

View File

@ -0,0 +1,18 @@
# Generated by Django 5.0.5 on 2024-05-18 14:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('idlerpg', '0007_remove_character_one_player_character_per_game_and_more'),
('ircbot', '0020_alter_alias_id_alter_botuser_id_alter_ircchannel_id_and_more'),
]
operations = [
migrations.AddConstraint(
model_name='game',
constraint=models.UniqueConstraint(models.F('active'), condition=models.Q(('active', True)), name='one_enabled_at_a_time'),
),
]

View File

@ -26,7 +26,8 @@ class Game(models.Model):
"""Options for the Game and its objects."""
constraints = [
models.UniqueConstraint("active", "channel", name="one_game_per_channel"),
models.UniqueConstraint('active', 'channel', name='one_game_per_channel'),
models.UniqueConstraint('active', condition=models.Q(active=True), name='one_enabled_at_a_time'),
]
def __str__(self):
@ -47,10 +48,9 @@ class Game(models.Model):
"""
if not base_time:
base_time = timezone.now()
if current_level >= 60:
# "The exponent method code had simply gotten to that point that levels were taking too long to complete"
return base_time + timedelta(seconds=math.ceil((600*(1.16**59)) + ((60*60*24)*(current_level-59))))
return base_time + timedelta(seconds=math.ceil(600*(1.16**current_level)))
# this is an logarithmic curve that starts peaking at 10ish days per level
return base_time + timedelta(seconds=int(24*60*60*1.4*(5+math.log(0.01*(current_level+1)))))
class CharacterManager(models.Manager):

View File

@ -4,7 +4,7 @@
#
# pip-compile --extra=dev --output-file=requirements/requirements-dev.txt
#
annotated-types==0.6.0
annotated-types==0.7.0
# via pydantic
asgiref==3.8.1
# via django
@ -12,7 +12,7 @@ authlib==1.3.0
# via safety
autocommand==2.2.2
# via jaraco-text
backports-tarfile==1.1.1
backports-tarfile==1.2.0
# via jaraco-context
bandit==1.7.8
# via dr.botzo-idlerpg (pyproject.toml)
@ -26,7 +26,7 @@ build==1.2.1
# via pip-tools
cachetools==5.3.3
# via tox
certifi==2024.2.2
certifi==2024.6.2
# via requests
cffi==1.16.0
# via cryptography
@ -44,13 +44,13 @@ click==8.1.7
# typer
colorama==0.4.6
# via tox
coverage[toml]==7.5.1
coverage[toml]==7.5.3
# via pytest-cov
cryptography==42.0.7
# via authlib
distlib==0.3.8
# via virtualenv
django==5.0.5
django==5.0.6
# via dr.botzo-idlerpg (pyproject.toml)
dlint==0.14.1
# via dr.botzo-idlerpg (pyproject.toml)
@ -153,7 +153,7 @@ pbr==6.0.0
# via stevedore
pip-tools==7.4.1
# via dr.botzo-idlerpg (pyproject.toml)
platformdirs==4.2.1
platformdirs==4.2.2
# via
# tox
# virtualenv
@ -165,11 +165,11 @@ pycodestyle==2.11.1
# via flake8
pycparser==2.22
# via cffi
pydantic==2.7.1
pydantic==2.7.2
# via
# safety
# safety-schemas
pydantic-core==2.18.2
pydantic-core==2.18.3
# via pydantic
pydocstyle==6.3.0
# via flake8-docstrings
@ -183,7 +183,7 @@ pyproject-hooks==1.1.0
# via
# build
# pip-tools
pytest==8.2.0
pytest==8.2.1
# via
# dr.botzo-idlerpg (pyproject.toml)
# pytest-cov
@ -200,7 +200,7 @@ pytz==2024.1
# tempora
pyyaml==6.0.1
# via bandit
requests==2.31.0
requests==2.32.3
# via safety
reuse==3.0.2
# via dr.botzo-idlerpg (pyproject.toml)
@ -233,11 +233,11 @@ tempora==5.5.1
# jaraco-logging
tox==4.15.0
# via dr.botzo-idlerpg (pyproject.toml)
typeguard==4.2.1
typeguard==4.3.0
# via inflect
typer==0.12.3
# via safety
typing-extensions==4.11.0
typing-extensions==4.12.1
# via
# inflect
# pydantic
@ -250,7 +250,7 @@ urllib3==2.2.1
# via
# requests
# safety
virtualenv==20.26.1
virtualenv==20.26.2
# via tox
wheel==0.43.0
# via pip-tools

View File

@ -8,9 +8,9 @@ asgiref==3.8.1
# via django
autocommand==2.2.2
# via jaraco-text
backports-tarfile==1.1.1
backports-tarfile==1.2.0
# via jaraco-context
django==5.0.5
django==5.0.6
# via dr.botzo-idlerpg (pyproject.toml)
inflect==7.2.1
# via jaraco-text
@ -49,9 +49,9 @@ tempora==5.5.1
# via
# irc
# jaraco-logging
typeguard==4.2.1
typeguard==4.3.0
# via inflect
typing-extensions==4.11.0
typing-extensions==4.12.1
# via
# inflect
# typeguard

View File

@ -37,7 +37,7 @@
"model": "ircbot.ircchannel",
"pk": 2,
"fields": {
"name": "#level_test",
"name": "#duplicate_test",
"server": 1,
"autojoin": false,
"topic_msg": "",
@ -56,15 +56,6 @@
"channel": 1
}
},
{
"model": "idlerpg.game",
"pk": 2,
"fields": {
"name": "level test",
"active": true,
"channel": 2
}
},
{
"model": "idlerpg.character",
"pk": 1,

View File

@ -135,24 +135,22 @@ class CharacterTest(TestCase):
char = Character.objects.get(pk=1)
# level 0 -> 1
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=600)
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=47758)
# level 1 -> 2
char.level = 1
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=696)
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=131601)
# level 24 -> 25
char.level = 24
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=21142)
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=437113)
# level 59 -> 60
char.level = 59
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=3812174)
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=543010)
# level 60 -> 61
char.level = 60
assert char.calculate_datetime_to_next_level() == char.next_level + (timedelta(seconds=3812174) +
timedelta(seconds=86400))
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=545009)
# level 61 -> 62
char.level = 61
assert char.calculate_datetime_to_next_level() == char.next_level + (timedelta(seconds=3812174) +
timedelta(seconds=86400*2))
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=546976)
def test_calculate_datetime_to_next_level_not_time_yet(self):
"""Test that we just bail with the current next_level if it hasn't been reached yet."""
@ -169,7 +167,7 @@ class CharacterTest(TestCase):
new_char = Character.objects.register('new', game, 'pass', 'bss!bss@test_register', 'unit tester')
assert new_char.status == Character.CHARACTER_STATUS_LOGGED_IN
assert new_char.next_level == register_time + timedelta(seconds=600)
assert new_char.next_level == register_time + timedelta(seconds=47758)
assert new_char.last_login == register_time
assert new_char.password[0:13] == 'pbkdf2_sha256'
@ -203,7 +201,7 @@ class CharacterTest(TestCase):
char.level_up()
assert char.level == old_level + 1
assert char.next_level == old_next_level + timedelta(seconds=600)
assert char.next_level == old_next_level + timedelta(seconds=47758)
def test_level_up_fail_not_time(self):
"""Test the failure condition for trying to level up before the timestamp."""

View File

@ -5,7 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-or-later
"""
import logging
from django.db import transaction
from django.db.utils import IntegrityError
from django.test import TestCase
from ircbot.models import IrcChannel
from idlerpg.models import Game
@ -22,3 +25,16 @@ class GameTest(TestCase):
game = Game.objects.get(pk=1)
logger.debug(str(game))
assert str(game) == "test in #test on default (active)"
def test_cant_have_two_active(self):
"""Test that if we create another game, it's disabled, and can't be active until the first is disabled."""
channel = IrcChannel.objects.get(pk=2)
game = Game.objects.get(pk=1)
new_game = Game.objects.create(name='new one', channel=channel)
assert game.active is True
assert new_game.active is False
with self.assertRaises(IntegrityError):
with transaction.atomic():
new_game.active = True
new_game.save()

View File

@ -32,6 +32,18 @@ class IrcPluginTest(TestCase):
self.plugin = IdleRPG(self.mock_bot, self.mock_connection, mock.MagicMock())
self.game = Game.objects.get(pk=1)
def test_start_stop(self):
"""Test that handlers are registered, roughly, as expected."""
self.plugin.start()
self.plugin.stop()
assert self.mock_connection.add_global_handler.call_count == 8
assert (self.mock_connection.add_global_handler.call_count ==
self.mock_connection.remove_global_handler.call_count)
assert self.mock_connection.reactor.add_global_regex_handler.call_count == 5
assert (self.mock_connection.reactor.add_global_regex_handler.call_count ==
self.mock_connection.reactor.remove_global_regex_handler.call_count)
def test_kick_penalty(self):
"""Test that if a character is kicked from the game channel, they get penalized."""
mock_event = mock.MagicMock()
@ -257,13 +269,176 @@ class IrcPluginTest(TestCase):
self.plugin.seen_hostmasks.remove('bss!bss@test_login')
def test_login(self):
"""Check that the LOGIN command actually sets the character as logged in."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@bss_login'
mock_event.recursing = False
game = Game.objects.get(pk=1)
test_char = Character.objects.register('test_login', game, 'test', 'bss!bss@bss_login', 'tester')
test_char.log_out()
test_char.next_level = datetime.datetime.fromisoformat('2024-05-17 17:00:00-00:00')
test_char.last_login = test_char.next_level
test_char.save()
self.plugin.seen_hostmasks.add('bss!bss@bss_login')
# manipulate login so it doesn't add time to next_level and so we have a consistent
# value to test against below
with mock.patch('django.utils.timezone.now', return_value=test_char.next_level):
match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'LOGIN test_login test')
self.plugin.handle_login(self.mock_connection, mock_event, match)
# refetch
refetch = Character.objects.get(name='test_login')
# should have been penalized
assert refetch.status == Character.CHARACTER_STATUS_LOGGED_IN
# the ordering of this surprises me... keep an eye on it
self.mock_bot.reply.assert_has_calls([
mock.call(None, "test_login, the level 0 tester, is now online from nickname bss. "
"Next level at 2024-05-17 17:00:00 UTC.", explicit_target='#test'),
mock.call(mock_event, "test_login, the level 0 tester, has been successfully logged in."),
])
def test_logout_character_not_found(self):
"""Test the LOGOUT command sent to the bot."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_login'
mock_event.recursing = False
match = re.match(IdleRPG.LOGOUT_COMMAND_PATTERN, 'LOGOUT')
self.plugin.handle_logout(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_once_with(mock_event, "You do not have a character in a running game.")
def test_logout_character_not_logged_in(self):
"""Test the LOGOUT command sent to the bot."""
mock_event = mock.MagicMock()
mock_event.source = 'bss2!bss@bss'
mock_event.recursing = False
match = re.match(IdleRPG.LOGOUT_COMMAND_PATTERN, 'LOGOUT')
self.plugin.handle_logout(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_once_with(
mock_event,
"Cannot log out bss2, either they already are, or they are in a state that cannot be modified."
)
def test_logout(self):
"""Check that the LOGOUT command actually sets the character offline."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@bss_logout'
mock_event.recursing = False
game = Game.objects.get(pk=1)
test_char = Character.objects.register('test_logout', game, 'test', 'bss!bss@bss_logout', 'tester')
match = re.match(IdleRPG.LOGOUT_COMMAND_PATTERN, 'LOGOUT')
self.plugin.handle_logout(self.mock_connection, mock_event, match)
# refetch
refetch = Character.objects.get(name='test_logout')
# should have been penalized
assert refetch.next_level > test_char.next_level
assert refetch.status == Character.CHARACTER_STATUS_OFFLINE
self.mock_bot.reply.assert_called_once_with(
mock_event, "test_logout, the level 0 tester, has been successfully logged out."
)
def test_register(self):
"""Test the ability to register a new character."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@bss_register'
mock_event.recursing = False
self.plugin.seen_hostmasks.add('bss!bss@bss_register')
now = datetime.datetime.fromisoformat('2024-05-17 17:00:00-00:00')
with mock.patch('django.utils.timezone.now', return_value=now):
match = re.match(IdleRPG.REGISTER_COMMAND_PATTERN, 'REGISTER reg_test test tester')
self.plugin.handle_register(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_has_calls([
mock.call(None, "bss's newest character, reg_test, the tester, "
"has been summoned! Next level at 2024-05-18 06:15:58 UTC.",
explicit_target='#test'),
mock.call(mock_event, "You have registered reg_test, the level 0 tester, "
"and been automatically logged in."),
])
def test_register_no_active(self):
"""Test the behavior of a failed registration because there's no active game."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@bss_register'
mock_event.recursing = False
# manipulate the game temporarily
game = Game.objects.get(active=True)
game.active = False
game.save()
self.plugin.seen_hostmasks.add('bss!bss@bss_register')
now = datetime.datetime.fromisoformat('2024-05-17 17:00:00-00:00')
with mock.patch('django.utils.timezone.now', return_value=now):
match = re.match(IdleRPG.REGISTER_COMMAND_PATTERN, 'REGISTER reg_test test tester')
self.plugin.handle_register(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_has_calls([
mock.call(mock_event, "No active game exists."),
])
# undo manipulation
game.active = True
game.save()
def test_register_not_in_channel(self):
"""Test the behavior of a failed registration because the user isn't in the game channel."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@bss_register'
mock_event.recursing = False
now = datetime.datetime.fromisoformat('2024-05-17 17:00:00-00:00')
with mock.patch('django.utils.timezone.now', return_value=now):
match = re.match(IdleRPG.REGISTER_COMMAND_PATTERN, 'REGISTER reg_test test tester')
self.plugin.handle_register(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_has_calls([
mock.call(mock_event, "Please join #test before registering.")
])
def test_register_cant_double_register(self):
"""Test the ability to register a new character fails if you try to have two."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@bss_dupe_register'
mock_event.recursing = False
self.plugin.seen_hostmasks.add('bss!bss@bss_dupe_register')
now = datetime.datetime.fromisoformat('2024-05-17 17:00:00-00:00')
with mock.patch('django.utils.timezone.now', return_value=now):
match = re.match(IdleRPG.REGISTER_COMMAND_PATTERN, 'REGISTER reg_test test tester')
self.plugin.handle_register(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_has_calls([
mock.call(None, "bss's newest character, reg_test, the tester, "
"has been summoned! Next level at 2024-05-18 06:15:58 UTC.",
explicit_target='#test'),
mock.call(mock_event, "You have registered reg_test, the level 0 tester, "
"and been automatically logged in."),
])
with mock.patch('django.utils.timezone.now', return_value=now):
match = re.match(IdleRPG.REGISTER_COMMAND_PATTERN, 'REGISTER reg_test2 test tester')
self.plugin.handle_register(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_has_calls([
mock.call(mock_event, "Registration failed; either you already have a character, "
"or the character name is already taken.")
])
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)
game = Game.objects.get(pk=1)
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)
@ -281,7 +456,7 @@ class IrcPluginTest(TestCase):
mock_event.source = 'bss2!bss@bss_remove'
mock_event.recursing = False
game = Game.objects.get(pk=2)
game = Game.objects.get(pk=1)
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)
@ -347,9 +522,9 @@ class IrcPluginTest(TestCase):
self.plugin.seen_hostmasks.remove('bss3!bss@bss')
def test_level_up_games(self):
def test_level_up_characters(self):
"""Test the walking of games and characters to level them up."""
game = Game.objects.get(pk=2)
game = Game.objects.get(pk=1)
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()
@ -359,7 +534,7 @@ class IrcPluginTest(TestCase):
char_1.save()
char_2.save()
# only one should level up, since char_2 isn't logged in
self.plugin._level_up_games()
self.plugin._level_up_characters()
char_1 = Character.objects.get(pk=char_1.pk)
char_2 = Character.objects.get(pk=char_2.pk)
@ -367,7 +542,6 @@ class IrcPluginTest(TestCase):
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.",
mock.call(None, "test_level_1, the tester, has attained level 1! Next level at 2024-05-16 13:15:58 UTC.",
explicit_target=game.channel.name),
])
assert self.mock_bot.reply.call_count == 2