drbotzo-idlerpg/tests/test_idlerpg_ircplugin.py
Brian S. Stephan 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

373 lines
16 KiB
Python

"""Test IdleRPG character operations.
SPDX-FileCopyrightText: © 2024 Brian S. Stephan <bss@incorporeal.org>
SPDX-License-Identifier: AGPL-3.0-or-later
"""
import datetime
import logging
import re
import unittest.mock as mock
from django.test import TestCase
from django.utils import timezone
from ircbot.models import IrcChannel, IrcServer
from idlerpg.ircplugin import IdleRPG
from idlerpg.models import Character, Game
class IrcPluginTest(TestCase):
"""Test the IRC integration."""
fixtures = ['tests/fixtures/simple_character.json']
def setUp(self):
"""Create common objects."""
self.mock_bot = mock.MagicMock()
self.mock_connection = mock.MagicMock()
self.mock_connection.get_nickname.return_value = 'test_bot'
self.mock_connection.server_config = IrcServer.objects.get(pk=1)
self.plugin = IdleRPG(self.mock_bot, self.mock_connection, mock.MagicMock())
self.game = Game.objects.get(pk=1)
def test_kick_penalty(self):
"""Test that if a character is kicked from the game channel, they get penalized."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_kick_penalty'
mock_event.target = '#test'
mock_event.recursing = False
# make a character so as to not disturb other tests
test_char = Character.objects.register('testkickpen', self.game, 'test',
'bss!bss@test_kick_penalty', 'tester')
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
with mock.patch('idlerpg.models.Character.log_out') as mock_log_out:
self.plugin.handle_kick_penalty(self.mock_connection, mock_event)
mock_penalize.assert_called_with(250, "getting kicked from the game channel")
mock_log_out.assert_called()
test_char = Character.objects.get(name='testkickpen')
assert test_char.time_penalized_kicked == 5
test_char.delete()
def test_generic_penalty_no_penalty_for_unknown(self):
"""Test that a kick from an unknown hostmask is ignored."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_kick_penalty'
mock_event.target = '#test'
mock_event.recursing = False
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
with mock.patch('idlerpg.models.Character.log_out') as mock_log_out:
self.plugin.handle_kick_penalty(self.mock_connection, mock_event)
mock_penalize.assert_not_called()
mock_log_out.assert_not_called()
def test_nick_penalty(self):
"""Test that if a user changes their nick, they get penalized."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_nick_penalty'
mock_event.target = '#test'
mock_event.recursing = False
# make a character so as to not disturb other tests
test_char = Character.objects.register('testnickpen', self.game, 'test',
'bss!bss@test_nick_penalty', 'tester')
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
with mock.patch('idlerpg.models.Character.log_out') as mock_log_out:
self.plugin.handle_nick_penalty(self.mock_connection, mock_event)
mock_penalize.assert_called_with(30, "changing their nick")
mock_log_out.assert_called()
test_char = Character.objects.get(name='testnickpen')
assert test_char.time_penalized_nick_change == 5
test_char.delete()
def test_part_penalty(self):
"""Test that if a character parts from the game channel, they get penalized."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_part_penalty'
mock_event.target = '#test'
mock_event.recursing = False
# make a character so as to not disturb other tests
test_char = Character.objects.register('testpartpen', self.game, 'test',
'bss!bss@test_part_penalty', 'tester')
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
with mock.patch('idlerpg.models.Character.log_out') as mock_log_out:
self.plugin.handle_part_penalty(self.mock_connection, mock_event)
mock_penalize.assert_called_with(200, "parting the game channel")
mock_log_out.assert_called()
test_char = Character.objects.get(name='testpartpen')
assert test_char.time_penalized_part == 5
test_char.delete()
def test_pubmsg_penalty(self):
"""Test that if a character speaks in the game channel, they get penalized."""
mock_event = mock.MagicMock()
mock_event.arguments = ['this is a test message']
mock_event.source = 'bss!bss@test_pubmsg_penalty'
mock_event.target = '#test'
mock_event.recursing = False
# make a character so as to not disturb other tests
test_char = Character.objects.register('testpubmsgpen', self.game, 'test',
'bss!bss@test_pubmsg_penalty', 'tester')
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
self.plugin.handle_message_penalty(self.mock_connection, mock_event)
# 22 is the len of the message
mock_penalize.assert_called_with(22, "sending a privmsg to the game channel")
test_char = Character.objects.get(name='testpubmsgpen')
assert test_char.time_penalized_privmsg == 5
test_char.delete()
def test_pubmsg_no_penalty_for_unknown(self):
"""Test that a speakerwith an unknown hostmask is ignored."""
mock_event = mock.MagicMock()
mock_event.arguments = ['this is a test message']
mock_event.source = 'bss!bss@some_user'
mock_event.target = '#test'
mock_event.recursing = False
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
self.plugin.handle_message_penalty(self.mock_connection, mock_event)
mock_penalize.assert_not_called()
def test_join(self):
"""Test that joiners update the seen hostmasks."""
mock_event = mock.MagicMock()
mock_event.arguments = ['this is a test message']
mock_event.source = 'test!test@test_join'
mock_event.target = '#test'
mock_event.recursing = False
assert 'test!test@test_join' not in self.plugin.seen_hostmasks
self.plugin.handle_join(self.mock_connection, mock_event)
assert 'test!test@test_join' in self.plugin.seen_hostmasks
def test_quit_penalty(self):
"""Test that if a character quits from the game channel, they get penalized."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_quit_penalty'
mock_event.target = '#test'
mock_event.recursing = False
# make a character so as to not disturb other tests
test_char = Character.objects.register('testquitpen', self.game, 'test',
'bss!bss@test_quit_penalty', 'tester')
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
with mock.patch('idlerpg.models.Character.log_out') as mock_log_out:
self.plugin.handle_quit_penalty(self.mock_connection, mock_event)
mock_penalize.assert_called_with(20, "quitting IRC")
mock_log_out.assert_called()
test_char = Character.objects.get(name='testquitpen')
assert test_char.time_penalized_quit == 5
test_char.delete()
def test_quit_penalty_even_if_logged_out(self):
"""Test that the penalty applies even if the character is already logged out."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_quit_penalty'
mock_event.target = '#test'
mock_event.recursing = False
# make a character so as to not disturb other tests
test_char = Character.objects.register('testquitpen', self.game, 'test',
'bss!bss@test_quit_penalty', 'tester')
with mock.patch('idlerpg.models.Character.penalize', return_value=5) as mock_penalize:
with mock.patch('idlerpg.models.Character.log_out', side_effect=ValueError) as mock_log_out:
self.plugin.handle_quit_penalty(self.mock_connection, mock_event)
mock_penalize.assert_called_with(20, "quitting IRC")
mock_log_out.assert_called()
test_char = Character.objects.get(name='testquitpen')
assert test_char.time_penalized_quit == 5
test_char.delete()
def test_login_character_not_found(self):
"""Test the LOGIN command sent to the bot."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_login'
mock_event.target = '#test'
mock_event.recursing = False
match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'LOGIN nope bss')
self.plugin.handle_login(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_once_with(
mock_event,
"The requested character does not exist, or has been disabled, or your password does not match."
)
def test_login_bad_password(self):
"""Test the LOGIN command sent to the bot."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_login'
mock_event.target = '#test'
mock_event.recursing = False
match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'LOGIN bss nope')
self.plugin.handle_login(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_once_with(
mock_event,
"The requested character does not exist, or has been disabled, or your password does not match."
)
def test_login_not_seen(self):
"""Test the LOGIN command sent to the bot."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_login'
mock_event.target = '#test'
mock_event.recursing = False
match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'LOGIN bss bss')
self.plugin.handle_login(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_once_with(mock_event, "Please join #test before logging in.")
def test_login_not_logged_off(self):
"""Test the LOGIN command sent to the bot."""
mock_event = mock.MagicMock()
mock_event.source = 'bss!bss@test_login'
mock_event.target = '#test'
mock_event.recursing = False
self.plugin.seen_hostmasks.add('bss!bss@test_login')
match = re.match(IdleRPG.LOGIN_COMMAND_PATTERN, 'LOGIN bss bss')
self.plugin.handle_login(self.mock_connection, mock_event, match)
self.mock_bot.reply.assert_called_once_with(
mock_event,
"Cannot log in bss, either they already are, or they are in a state that cannot be modified."
)
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 13:15:58 UTC.",
explicit_target=game.channel.name),
])