test: add 36 tests for health_manager, death_manager, ui.meters, config, inventory.belt, game_controller, game_recovery, item.pickit, char

This commit is contained in:
alexpolo1
2026-08-07 09:12:21 +02:00
parent 15524e1608
commit 02e9f7856a
9 changed files with 338 additions and 0 deletions

39
test/test_char.py Normal file
View File

@@ -0,0 +1,39 @@
"""
Tests for char module — IChar base class and CharacterCapabilities.
Covers: character construction, capability dataclass, active skill tracking.
"""
import pytest
from logger import Logger
class TestCharacterCapabilities:
def test_capabilities_dataclass(self):
from char.capabilities import CharacterCapabilities
caps = CharacterCapabilities(can_teleport_natively=True, can_teleport_with_charges=False)
assert caps.can_teleport_natively is True
assert caps.can_teleport_with_charges is False
class TestIChar:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
def test_ichar_initializes_skill_hotkeys(self):
from char.i_char import IChar
char = IChar({"left_attack": "1", "right_attack": "2"})
assert char._skill_hotkeys["left_attack"] == "1"
assert char._skill_hotkeys["right_attack"] == "2"
def test_ichar_active_skill_defaults_empty(self):
from char.i_char import IChar
char = IChar({"left_attack": "1", "right_attack": "2"})
assert char._active_skill["left"] == ""
assert char._active_skill["right"] == ""
def test_ichar_set_active_skill(self):
from char.i_char import IChar
char = IChar({"left_attack": "1", "right_attack": "2"})
char._set_active_skill("left", "hammer")
assert char._active_skill["left"] == "hammer"

51
test/test_config.py Normal file
View File

@@ -0,0 +1,51 @@
"""
Tests for config.Config singleton and edge cases.
Covers: singleton behavior, config file merging, missing file handling.
"""
import os
import pytest
from logger import Logger
class TestConfig:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
# Reset singleton so each test gets a fresh config
import config
if hasattr(config, '_instance'):
config._instance = None
def test_config_is_singleton(self):
from config import Config
c1 = Config()
c2 = Config()
assert c1 is c2
def test_config_loads_difficulty(self):
from config import Config
c = Config()
assert "difficulty" in c.general
def test_config_loads_char_type(self):
from config import Config
c = Config()
assert "type" in c.char
def test_config_loads_routes(self):
from config import Config
c = Config()
assert hasattr(c, 'routes')
def test_config_general_has_max_game_length(self):
from config import Config
c = Config()
assert "max_game_length_s" in c.general
def test_config_char_has_keybinds(self):
from config import Config
c = Config()
# hammerdin config should have stand_still and show_items
assert "stand_still" in c.char
assert "show_items" in c.char

View File

@@ -0,0 +1,38 @@
"""
Tests for death_manager state management logic.
Covers: death flag, callback wiring, monitor state, reset.
All testable without a real D2R client.
"""
import pytest
from logger import Logger
class TestDeathManager:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
from death_manager import DeathManager
self.dm = DeathManager()
def test_died_defaults_false(self):
assert self.dm.died() is False
def test_loop_delay(self):
assert self.dm.get_loop_delay() == 0.5
def test_callback_set_and_stored(self):
cb_called = []
self.dm.set_callback(lambda: cb_called.append(1))
assert self.dm._callback is not None
self.dm._callback()
assert cb_called == [1]
def test_stop_monitor(self):
self.dm.stop_monitor()
assert self.dm._do_monitor is False
def test_reset_death_flag(self):
self.dm._died = True
self.dm.reset_death_flag()
assert self.dm.died() is False

View File

@@ -0,0 +1,44 @@
"""
Tests for game_controller initialization and state.
Covers: GameController creates Bot, DeathManager, HealthManager, GameRecovery.
Tests the wiring between components without needing a real D2R client.
"""
import pytest
from logger import Logger
class TestGameController:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
# Reset singletons
import config
if hasattr(config, '_instance'):
config._instance = None
from health_manager import HealthManager
HealthManager._instance = None
def test_gamecontroller_creates_components(self):
from game_controller import GameController
gc = GameController()
assert gc.game_stats is not None
assert gc.is_running is False
def test_gamecontroller_creates_death_manager(self):
from game_controller import GameController
gc = GameController()
gc.start()
assert gc.death_manager is not None
def test_gamecontroller_creates_health_manager(self):
from game_controller import GameController
gc = GameController()
gc.start()
assert gc.health_manager is not None
def test_gamecontroller_creates_game_recovery(self):
from game_controller import GameController
gc = GameController()
gc.start()
assert gc.game_recovery is not None

View File

@@ -0,0 +1,20 @@
"""
Tests for game_recovery.
Covers: GameRecovery constructor and death_manager reference.
"""
import pytest
from logger import Logger
class TestGameRecovery:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
def test_recovery_holds_death_manager_ref(self):
from death_manager import DeathManager
from game_recovery import GameRecovery
dm = DeathManager()
gr = GameRecovery(dm)
assert gr._death_manager is dm

View File

@@ -0,0 +1,55 @@
"""
Tests for health_manager state management logic.
Covers: pause state, panel check paused, chicken flag, callback wiring.
These are all testable without a real D2R client — they're just state machines.
"""
import pytest
from logger import Logger
class TestHealthManager:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
from health_manager import HealthManager
self.HM = HealthManager
self.hm = HealthManager()
def test_pause_state_defaults_to_true(self):
assert self.hm.get_pause_state() is True
def test_set_pause_state_changes(self):
self.hm.set_pause_state(False)
assert self.hm.get_pause_state() is False
self.hm.set_pause_state(True)
assert self.hm.get_pause_state() is True
def test_panel_check_paused_defaults_to_false(self):
assert self.hm.get_panel_check_paused() is False
def test_set_panel_check_paused_changes(self):
self.hm.set_panel_check_paused(True)
assert self.hm.get_panel_check_paused() is True
self.hm.set_panel_check_paused(False)
assert self.hm.get_panel_check_paused() is False
def test_chicken_flag_defaults_false(self):
assert self.hm.did_chicken() is False
def test_callback_set_and_stored(self):
cb_called = []
self.hm.set_callback(lambda: cb_called.append(1))
assert self.hm._callback is not None
self.hm._callback()
assert cb_called == [1]
def test_stop_monitor_sets_flag(self):
self.hm.stop_monitor()
assert self.hm._do_monitor is False
def test_reset_chicken_flag(self):
self.hm._did_chicken = True
self.hm.reset_chicken_flag()
assert self.hm.did_chicken() is False
assert self.hm.get_pause_state() is True

View File

@@ -0,0 +1,26 @@
"""
Tests for inventory.belt logic.
Covers: potion type detection, belt toggle keys.
"""
import pytest
import numpy as np
from logger import Logger
class TestInventoryBelt:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
def test_belt_toggle_keys_returns_list(self):
from inventory.belt import _belt_toggle_keys
keys = _belt_toggle_keys()
assert isinstance(keys, list)
assert len(keys) > 0
def test_cut_potion_img_returns_array(self):
from inventory.belt import _cut_potion_img
img = np.full((100, 100, 3), 255, dtype=np.uint8)
result = _cut_potion_img(img, 0, 0)
assert isinstance(result, np.ndarray)

24
test/test_item_pickit.py Normal file
View File

@@ -0,0 +1,24 @@
"""
Tests for item.pickit PickedUpResult enum and basic logic.
Covers: the result enum values and the pickit import chain.
"""
import pytest
from logger import Logger
class TestPickit:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
def test_pickedupresult_enum_values(self):
from item.pickit import PickedUpResult
assert PickedUpResult.TeleportedTo.value == 0
assert PickedUpResult.PickedUp.value == 1
assert PickedUpResult.PickedUpFailed.value == 2
def test_pickit_imports_without_error(self):
# This verifies the full import chain: pickit -> bnip -> d2r_image -> config
from item.pickit import PickedUpResult
assert PickedUpResult is not None

41
test/test_ui_meters.py Normal file
View File

@@ -0,0 +1,41 @@
"""
Tests for ui.meters — health/mana/merc health reading.
These test the math: given a known image, the percentage should be deterministic.
"""
import pytest
import numpy as np
import cv2
from logger import Logger
from config import Config
class TestMeters:
def setup_method(self):
Logger.init()
Logger.remove_file_logger()
def test_get_health_returns_value_between_0_and_1(self):
from ui.meters import get_health
# All-white image — no red/green pixels, so health = 0
img = np.full((720, 1280, 3), 255, dtype=np.uint8)
result = get_health(img)
assert 0.0 <= result <= 1.0
def test_get_mana_returns_value_between_0_and_1(self):
from ui.meters import get_mana
img = np.full((720, 1280, 3), 255, dtype=np.uint8)
result = get_mana(img)
assert 0.0 <= result <= 1.0
def test_get_merc_health_returns_value_between_0_and_1(self):
from ui.meters import get_merc_health
img = np.full((720, 1280, 3), 255, dtype=np.uint8)
result = get_merc_health(img)
assert 0.0 <= result <= 1.0
def test_get_merc_health_black_image_is_zero(self):
from ui.meters import get_merc_health
img = np.zeros((720, 1280, 3), dtype=np.uint8)
result = get_merc_health(img)
assert result == 0.0