"""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