Files
ledgrab/server/tests/core/test_lol_adapter.py
T
alexei.dolgolyov 02cd9d519c
Lint & Test / test (push) Successful in 1m56s
refactor: rename project to LedGrab, split HA integration into separate repo
- Rename Python package: wled_controller -> ledgrab
- Rename env var prefix: WLED_ -> LEDGRAB_ (with auto-migration for old vars)
- Rename localStorage key: wled_api_key -> ledgrab_api_key (with migration)
- Rename HA integration domain: wled_screen_controller -> ledgrab
- Update all imports, build scripts, Docker, installer, config, docs
- Remove HA integration (moved to ledgrab-haos-integration repo)
- Remove hacs.json (belongs in HA repo now)
- Add startup warning for users with old WLED_ env vars
- All tests pass (715/715), ruff clean, tsc clean, frontend builds
2026-04-12 22:45:28 +03:00

184 lines
6.5 KiB
Python

"""Tests for League of Legends Live Client Data API adapter."""
import threading
import pytest
from ledgrab.core.game_integration.adapters.lol_adapter import (
LoLAdapter,
LoLPoller,
)
def _make_lol_payload(
*,
current_health: float = 1000.0,
max_health: float = 1000.0,
resource_value: float = 500.0,
resource_max: float = 500.0,
level: int = 10,
summoner_name: str = "TestPlayer",
current_gold: float | None = None,
) -> dict:
"""Build a realistic LoL Live Client Data payload."""
payload: dict = {
"activePlayer": {
"summonerName": summoner_name,
"level": level,
"currentHealth": current_health,
"championStats": {
"currentHealth": current_health,
"maxHealth": max_health,
"resourceValue": resource_value,
"resourceMax": resource_max,
"attackDamage": 80.0,
"abilityPower": 0.0,
"armor": 60.0,
"magicResist": 40.0,
"moveSpeed": 345.0,
},
},
"allPlayers": [
{
"summonerName": summoner_name,
"championName": "Jinx",
"team": "ORDER",
"scores": {"kills": 5, "deaths": 2, "assists": 7},
},
],
"gameData": {
"gameMode": "CLASSIC",
"gameTime": 1200.5,
"mapName": "Map11",
"mapNumber": 11,
"mapTerrain": "Default",
},
}
if current_gold is not None:
payload["allPlayers"][0]["currentGold"] = current_gold
return payload
class TestLoLContinuousEvents:
def test_health_normalized(self) -> None:
payload = _make_lol_payload(current_health=500.0, max_health=1000.0)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, {})
hp = [e for e in events if e.event_type == "health"]
assert len(hp) == 1
assert hp[0].value == pytest.approx(0.5)
def test_mana_normalized(self) -> None:
payload = _make_lol_payload(resource_value=300.0, resource_max=600.0)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, {})
mp = [e for e in events if e.event_type == "mana"]
assert len(mp) == 1
assert mp[0].value == pytest.approx(0.5)
def test_level_normalized(self) -> None:
payload = _make_lol_payload(level=9)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, {})
lvl = [e for e in events if e.event_type == "speed"]
assert len(lvl) == 1
assert lvl[0].value == pytest.approx(9.0 / 18.0)
def test_level_max(self) -> None:
payload = _make_lol_payload(level=18)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, {})
lvl = [e for e in events if e.event_type == "speed"]
assert lvl[0].value == pytest.approx(1.0)
def test_gold(self) -> None:
payload = _make_lol_payload(current_gold=15000.0)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, {})
gold = [e for e in events if e.event_type == "gold"]
assert len(gold) == 1
assert gold[0].value == pytest.approx(15000.0 / 30000.0)
class TestLoLDeathRespawn:
def test_death_detected_when_health_drops_to_zero(self) -> None:
prev = {"alive": True}
payload = _make_lol_payload(current_health=0.0, max_health=1000.0)
events, state = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, prev)
deaths = [e for e in events if e.event_type == "death"]
assert len(deaths) == 1
assert state["alive"] is False
def test_no_death_when_already_dead(self) -> None:
prev = {"alive": False}
payload = _make_lol_payload(current_health=0.0, max_health=1000.0)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, prev)
deaths = [e for e in events if e.event_type == "death"]
assert len(deaths) == 0
def test_respawn_detected(self) -> None:
prev = {"alive": False}
payload = _make_lol_payload(current_health=800.0, max_health=1000.0)
events, state = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, prev)
respawns = [e for e in events if e.event_type == "objective_progress"]
assert len(respawns) == 1
assert state["alive"] is True
def test_no_respawn_when_already_alive(self) -> None:
prev = {"alive": True}
payload = _make_lol_payload(current_health=800.0, max_health=1000.0)
events, _ = LoLAdapter.parse_payload(payload, {"adapter_id": "lol"}, prev)
respawns = [e for e in events if e.event_type == "objective_progress"]
assert len(respawns) == 0
class TestLoLAuth:
def test_always_accepts(self) -> None:
assert LoLAdapter.validate_auth({}, {}, {}) is True
assert LoLAdapter.validate_auth({"X-Custom": "val"}, {}, {"auth_token": "x"}) is True
class TestLoLMetadata:
def test_adapter_type(self) -> None:
assert LoLAdapter.ADAPTER_TYPE == "lol"
def test_supported_events(self) -> None:
assert "health" in LoLAdapter.SUPPORTED_EVENTS
assert "mana" in LoLAdapter.SUPPORTED_EVENTS
assert "death" in LoLAdapter.SUPPORTED_EVENTS
def test_config_schema(self) -> None:
schema = LoLAdapter.get_config_schema()
assert "poll_interval_ms" in schema["properties"]
def test_setup_instructions(self) -> None:
instructions = LoLAdapter.get_setup_instructions()
assert "League of Legends" in instructions
class TestLoLPoller:
def test_start_stop(self) -> None:
"""Test that the poller starts and stops cleanly."""
called = threading.Event()
def callback(data: dict) -> None:
called.set()
poller = LoLPoller({"poll_interval_ms": 100}, callback)
assert not poller.is_running
poller.start()
assert poller.is_running
poller.stop()
assert not poller.is_running
def test_double_start_no_crash(self) -> None:
"""Starting twice should not create duplicate threads."""
poller = LoLPoller({"poll_interval_ms": 1000}, lambda d: None)
poller.start()
poller.start() # should warn but not crash
poller.stop()
def test_stop_without_start(self) -> None:
"""Stopping without starting should not crash."""
poller = LoLPoller({"poll_interval_ms": 1000}, lambda d: None)
poller.stop() # no-op