Files
ledgrab/server/tests/core/test_dota2_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

205 lines
7.3 KiB
Python

"""Tests for Dota 2 Game State Integration adapter."""
import pytest
from ledgrab.core.game_integration.adapters.dota2_adapter import Dota2Adapter
def _make_dota2_payload(
*,
health: int = 1000,
max_health: int = 1000,
mana: float = 500.0,
max_mana: float = 500.0,
gold: int = 625,
kills: int = 0,
deaths: int = 0,
game_state: str | None = None,
auth_token: str | None = None,
) -> dict:
"""Build a realistic Dota 2 GSI payload."""
payload: dict = {
"hero": {
"xpos": -6624,
"ypos": -6592,
"id": 1,
"name": "npc_dota_hero_antimage",
"level": 12,
"alive": True,
"respawn_seconds": 0,
"buyback_cost": 874,
"buyback_cooldown": 0,
"health": health,
"max_health": max_health,
"health_percent": int(100.0 * health / max_health) if max_health else 0,
"mana": mana,
"max_mana": max_mana,
"mana_percent": int(100.0 * mana / max_mana) if max_mana else 0,
},
"player": {
"steamid": "76561198012345678",
"name": "TestPlayer",
"activity": "playing",
"kills": kills,
"deaths": deaths,
"assists": 0,
"last_hits": 120,
"denies": 10,
"kill_streak": 0,
"gold": gold,
"gold_reliable": 200,
"gold_unreliable": gold - 200,
"gpm": 450,
"xpm": 520,
},
}
if game_state is not None:
payload["map"] = {
"name": "start",
"matchid": "12345",
"game_time": 1200,
"clock_time": 1200,
"daytime": True,
"game_state": game_state,
"win_team": "none",
}
if auth_token is not None:
payload["auth"] = {"token": auth_token}
return payload
class TestDota2ContinuousEvents:
def test_health_normalized(self) -> None:
payload = _make_dota2_payload(health=750, max_health=1500)
events, _ = Dota2Adapter.parse_payload(payload, {"adapter_id": "d2"}, {})
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_dota2_payload(mana=300.0, max_mana=600.0)
events, _ = Dota2Adapter.parse_payload(payload, {"adapter_id": "d2"}, {})
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_gold_normalized(self) -> None:
payload = _make_dota2_payload(gold=5000)
events, _ = Dota2Adapter.parse_payload(payload, {"adapter_id": "d2"}, {})
g = [e for e in events if e.event_type == "gold"]
assert len(g) == 1
assert g[0].value == pytest.approx(5000.0 / 99999.0)
def test_gold_custom_max(self) -> None:
payload = _make_dota2_payload(gold=5000)
events, _ = Dota2Adapter.parse_payload(
payload,
{"adapter_id": "d2", "max_gold": 10000},
{},
)
g = [e for e in events if e.event_type == "gold"]
assert g[0].value == pytest.approx(0.5)
class TestDota2DiffDetection:
def test_kill_detected(self) -> None:
p1 = _make_dota2_payload(kills=3)
_, s1 = Dota2Adapter.parse_payload(p1, {"adapter_id": "d2"}, {})
p2 = _make_dota2_payload(kills=4)
events, _ = Dota2Adapter.parse_payload(p2, {"adapter_id": "d2"}, s1)
kills = [e for e in events if e.event_type == "kill"]
assert len(kills) == 1
def test_no_kill_on_first_payload(self) -> None:
p = _make_dota2_payload(kills=5)
events, _ = Dota2Adapter.parse_payload(p, {"adapter_id": "d2"}, {})
kills = [e for e in events if e.event_type == "kill"]
assert len(kills) == 0
def test_death_detected(self) -> None:
p1 = _make_dota2_payload(deaths=1)
_, s1 = Dota2Adapter.parse_payload(p1, {"adapter_id": "d2"}, {})
p2 = _make_dota2_payload(deaths=2)
events, _ = Dota2Adapter.parse_payload(p2, {"adapter_id": "d2"}, s1)
deaths = [e for e in events if e.event_type == "death"]
assert len(deaths) == 1
def test_multiple_kills(self) -> None:
p1 = _make_dota2_payload(kills=0)
_, s1 = Dota2Adapter.parse_payload(p1, {"adapter_id": "d2"}, {})
p2 = _make_dota2_payload(kills=3)
events, _ = Dota2Adapter.parse_payload(p2, {"adapter_id": "d2"}, s1)
kills = [e for e in events if e.event_type == "kill"]
assert len(kills) == 3
class TestDota2MatchFlow:
def test_match_start_pre_game(self) -> None:
p = _make_dota2_payload(game_state="DOTA_GAMERULES_STATE_PRE_GAME")
events, _ = Dota2Adapter.parse_payload(p, {"adapter_id": "d2"}, {})
ms = [e for e in events if e.event_type == "match_start"]
assert len(ms) == 1
def test_match_start_in_progress(self) -> None:
prev = {"game_state": "DOTA_GAMERULES_STATE_PRE_GAME"}
p = _make_dota2_payload(game_state="DOTA_GAMERULES_STATE_GAME_IN_PROGRESS")
events, _ = Dota2Adapter.parse_payload(p, {"adapter_id": "d2"}, prev)
ms = [e for e in events if e.event_type == "match_start"]
assert len(ms) == 1
def test_match_end(self) -> None:
prev = {"game_state": "DOTA_GAMERULES_STATE_GAME_IN_PROGRESS"}
p = _make_dota2_payload(game_state="DOTA_GAMERULES_STATE_POST_GAME")
events, _ = Dota2Adapter.parse_payload(p, {"adapter_id": "d2"}, prev)
me = [e for e in events if e.event_type == "match_end"]
assert len(me) == 1
def test_no_event_on_same_state(self) -> None:
prev = {"game_state": "DOTA_GAMERULES_STATE_GAME_IN_PROGRESS"}
p = _make_dota2_payload(game_state="DOTA_GAMERULES_STATE_GAME_IN_PROGRESS")
events, _ = Dota2Adapter.parse_payload(p, {"adapter_id": "d2"}, prev)
flow = [e for e in events if e.event_type in ("match_start", "match_end")]
assert len(flow) == 0
class TestDota2Auth:
def test_auth_valid(self) -> None:
p = _make_dota2_payload(auth_token="secret")
result = Dota2Adapter.validate_auth({}, p, {"auth_token": "secret"})
assert result is True
def test_auth_invalid(self) -> None:
p = _make_dota2_payload(auth_token="wrong")
result = Dota2Adapter.validate_auth({}, p, {"auth_token": "secret"})
assert result is False
def test_auth_no_config_accepts_all(self) -> None:
p = _make_dota2_payload()
result = Dota2Adapter.validate_auth({}, p, {})
assert result is True
class TestDota2Metadata:
def test_adapter_type(self) -> None:
assert Dota2Adapter.ADAPTER_TYPE == "dota2"
def test_supported_events(self) -> None:
assert "health" in Dota2Adapter.SUPPORTED_EVENTS
assert "mana" in Dota2Adapter.SUPPORTED_EVENTS
assert "gold" in Dota2Adapter.SUPPORTED_EVENTS
def test_config_schema(self) -> None:
schema = Dota2Adapter.get_config_schema()
assert "auth_token" in schema["properties"]
assert "max_gold" in schema["properties"]
def test_setup_instructions(self) -> None:
instructions = Dota2Adapter.get_setup_instructions()
assert "Dota 2" in instructions