drbotzo-idlerpg/tests/test_idlerpg_character.py
Brian S. Stephan d862aa16d8
implement the bones of character management
this provides character-level operations such as character creation,
logging in/out, leveling them, and penalizing them. this isn't a game,
yet, but it does implement and test a lot of the core functionality,
just without triggers or bulk operations

Signed-off-by: Brian S. Stephan <bss@incorporeal.org>
2024-05-06 00:16:16 -05:00

183 lines
7.3 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)
# retain some data for comparison after logging out
old_next_level = char.next_level
logout_time = timezone.now()
with patch('django.utils.timezone.now', return_value=logout_time):
char.log_out()
assert char.next_level == old_next_level + timedelta(seconds=20)
assert char.status == Character.CHARACTER_STATUS_OFFLINE
assert char.last_login == logout_time
assert char.time_penalized_logout == 20
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()