drbotzo-idlerpg/tests/test_idlerpg_character.py
Brian S. Stephan f452aab825
don't penalize character inside the log_out method
part, quit, kick are all going to have their own need to log out the
character without a penalty (because they apply their own), so to avoid
double penalties, the log out penalty should be moved into the bot
command and managed that way. this was the only place where an action
method was also applying a penalty, so hopefully this remains consistent
too

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-05-07 08:42:48 -05:00

179 lines
7.1 KiB
Python

"""Test IdleRPG character operations.
SPDX-FileCopyrightText: © 2024 Brian S. Stephan <bss@incorporeal.org>
SPDX-License-Identifier: AGPL-3.0-or-later
"""
import logging
from datetime import timedelta
from unittest.mock import patch
from django.test import TestCase
from django.utils import timezone
from idlerpg.models import Character, Game
logger = logging.getLogger(__name__)
class CharacterTest(TestCase):
"""Test the Character model."""
fixtures = ['tests/fixtures/simple_character.json']
def test_string_repr(self):
"""Test the basic string summary."""
char = Character.objects.get(pk=1)
logger.debug(str(char))
assert str(char) == "bss, level 0 tester"
def test_log_out(self):
"""Test basic log out functionality and end result."""
char = Character.objects.get(pk=1)
logout_time = timezone.now()
with patch('django.utils.timezone.now', return_value=logout_time):
char.log_out()
assert char.status == Character.CHARACTER_STATUS_OFFLINE
assert char.last_login == logout_time
def test_cant_log_out_offline(self):
"""Test that we error if trying to log out a character already offline."""
char = Character.objects.get(pk=1)
char.log_out()
with self.assertRaises(ValueError):
char.log_out()
def test_log_in(self):
"""Test the result of logging in."""
char = Character.objects.get(pk=1)
logout_time = timezone.now()
login_time = logout_time + timedelta(seconds=300)
# manipulate the time so that we log out and then log in 300 seconds later, which
# adds 20 seconds to the next level time
with patch('django.utils.timezone.now', return_value=logout_time):
char.log_out()
# logout has a penalty of its own, so this post-logout value is what will be altered
old_next_level = char.next_level
with patch('django.utils.timezone.now', return_value=login_time):
char.log_in('bss', 'bss!bss@test_log_in')
assert char.next_level == old_next_level + timedelta(seconds=300)
assert char.status == Character.CHARACTER_STATUS_ONLINE
assert char.hostmask == 'bss!bss@test_log_in'
def test_cant_log_in_when_already_online(self):
"""Test that we can't log in the character if they're already online."""
char = Character.objects.get(pk=1)
with self.assertRaises(ValueError):
char.log_in('bss', 'bss!bss@test_online')
def test_cant_log_in_bad_password(self):
"""Test that we can't log in the character if we don't have the right password."""
char = Character.objects.get(pk=1)
char.log_out()
with self.assertRaises(ValueError):
char.log_in('bad pass', 'bss!bss@test_bad_password')
def test_set_password(self):
"""Test that the password is actually changed when requested."""
char = Character.objects.get(pk=1)
old_password = char.password
char.set_password('something')
assert old_password != char.password
assert char.password[0:13] == 'pbkdf2_sha256'
def test_penalize(self):
"""Test that penalties apply properly, including level adjustments."""
char = Character.objects.get(pk=1)
next_level = char.next_level
# level 0
next_level += timedelta(seconds=20)
char.penalize(20, 'test')
assert char.next_level == next_level
# level 1
char.level = 1
next_level += timedelta(seconds=23)
char.penalize(20, 'test')
assert char.next_level == next_level
# level 5
char.level = 5
next_level += timedelta(seconds=39)
char.penalize(20, 'test')
assert char.next_level == next_level
# level 25
char.level = 25
next_level += timedelta(seconds=530)
char.penalize(20, 'test')
assert char.next_level == next_level
def test_calculate_datetime_to_next_level(self):
"""Test that the next level calculation behaves as expected in a number of situations."""
char = Character.objects.get(pk=1)
# level 0 -> 1
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=600)
# level 1 -> 2
char.level = 1
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=696)
# level 24 -> 25
char.level = 24
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=21142)
# level 59 -> 60
char.level = 59
assert char.calculate_datetime_to_next_level() == char.next_level + timedelta(seconds=3812174)
# level 60 -> 61
char.level = 60
assert char.calculate_datetime_to_next_level() == char.next_level + (timedelta(seconds=3812174) +
timedelta(seconds=86400))
# level 61 -> 62
char.level = 61
assert char.calculate_datetime_to_next_level() == char.next_level + (timedelta(seconds=3812174) +
timedelta(seconds=86400*2))
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."""
char = Character.objects.get(pk=1)
with patch('django.utils.timezone.now', return_value=char.next_level - timedelta(days=1)):
assert char.calculate_datetime_to_next_level() == char.next_level
def test_register(self):
"""Test the primary way to create a character with proper initial values."""
game = Game.objects.get(pk=1)
register_time = timezone.now()
with patch('django.utils.timezone.now', return_value=register_time):
new_char = Character.objects.register('new', game, 'pass', 'bss!bss@test_register', 'unit tester')
assert new_char.status == Character.CHARACTER_STATUS_ONLINE
assert new_char.next_level == register_time + timedelta(seconds=600)
assert new_char.last_login == register_time
assert new_char.password[0:13] == 'pbkdf2_sha256'
def test_level_up(self):
"""Test the level up functionality."""
char = Character.objects.get(pk=1)
old_level = char.level
old_next_level = char.next_level
char.level_up()
assert char.level == old_level + 1
assert char.next_level == old_next_level + timedelta(seconds=600)
def test_level_up_fail_not_time(self):
"""Test the failure condition for trying to level up before the timestamp."""
char = Character.objects.get(pk=1)
with patch('django.utils.timezone.now', return_value=char.next_level-timedelta(minutes=30)):
with self.assertRaises(ValueError):
char.level_up()
def test_level_up_fail_offline(self):
"""Test the failure condition for trying to level up a character not online."""
char = Character.objects.get(pk=1)
char.log_out()
with self.assertRaises(ValueError):
char.level_up()