"""Test IdleRPG character operations. SPDX-FileCopyrightText: © 2024 Brian S. Stephan 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()