Compare commits
6 Commits
14a1c5ceb6
...
56b554f6f1
Author | SHA1 | Date | |
---|---|---|---|
56b554f6f1 | |||
731e434c8d | |||
f22ca875e3 | |||
4a8babf39e | |||
0ad687669e | |||
e9b985bd79 |
@ -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
|
||||
|
18
idlerpg/migrations/0008_game_one_enabled_at_a_time.py
Normal file
18
idlerpg/migrations/0008_game_one_enabled_at_a_time.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
11
tests/fixtures/simple_character.json
vendored
11
tests/fixtures/simple_character.json
vendored
@ -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,
|
||||
|
@ -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."""
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user