"""Tests for CS2 Game State Integration adapter.""" import pytest from wled_controller.core.game_integration.adapters.cs2_adapter import CS2Adapter # ── Realistic CS2 GSI payload samples ──────────────────────────────────── def _make_cs2_payload( *, health: int = 100, armor: int = 100, money: int = 800, kills: int = 0, deaths: int = 0, round_phase: str | None = None, bomb: str | None = None, flashed: int = 0, team: str = "CT", ammo_clip: int | None = None, ammo_clip_max: int | None = None, auth_token: str | None = None, ) -> dict: """Build a realistic CS2 GSI payload.""" payload: dict = { "player": { "steamid": "76561198012345678", "name": "TestPlayer", "team": team, "state": { "health": health, "armor": armor, "helmet": True, "flashed": flashed, "smoked": 0, "burning": 0, "money": money, "round_kills": 0, "round_killhs": 0, "equip_value": 4400, }, "match_stats": { "kills": kills, "assists": 0, "deaths": deaths, "mvps": 0, "score": kills * 2, }, "weapons": {}, }, } if ammo_clip is not None and ammo_clip_max is not None: payload["player"]["weapons"]["weapon_0"] = { "name": "weapon_ak47", "paintkit": "default", "type": "Rifle", "ammo_clip": ammo_clip, "ammo_clip_max": ammo_clip_max, "ammo_reserve": 90, "state": "active", } if round_phase is not None: payload["round"] = {"phase": round_phase} if bomb is not None: payload.setdefault("round", {})["bomb"] = bomb if auth_token is not None: payload["auth"] = {"token": auth_token} return payload # ── Health/Armor/Money tests ───────────────────────────────────────────── class TestCS2ContinuousEvents: def test_health_normalized(self) -> None: payload = _make_cs2_payload(health=75) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "cs2_test"}, {}) health_events = [e for e in events if e.event_type == "health"] assert len(health_events) == 1 assert health_events[0].value == pytest.approx(0.75) def test_health_zero(self) -> None: payload = _make_cs2_payload(health=0) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "cs2_test"}, {}) health_events = [e for e in events if e.event_type == "health"] assert health_events[0].value == 0.0 def test_armor_normalized(self) -> None: payload = _make_cs2_payload(armor=50) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "cs2_test"}, {}) armor_events = [e for e in events if e.event_type == "armor"] assert len(armor_events) == 1 assert armor_events[0].value == pytest.approx(0.5) def test_money_normalized(self) -> None: payload = _make_cs2_payload(money=8000) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "cs2_test"}, {}) gold_events = [e for e in events if e.event_type == "gold"] assert len(gold_events) == 1 assert gold_events[0].value == pytest.approx(8000.0 / 16000.0) def test_ammo_normalized(self) -> None: payload = _make_cs2_payload(ammo_clip=15, ammo_clip_max=30) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "cs2_test"}, {}) ammo_events = [e for e in events if e.event_type == "ammo"] assert len(ammo_events) == 1 assert ammo_events[0].value == pytest.approx(0.5) def test_adapter_id_passed_through(self) -> None: payload = _make_cs2_payload(health=100) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "my_cs2"}, {}) assert all(e.adapter_id == "my_cs2" for e in events) # ── Kill/Death diff detection tests ────────────────────────────────────── class TestCS2DiffDetection: def test_kill_detected_on_increase(self) -> None: payload1 = _make_cs2_payload(kills=3) _, state1 = CS2Adapter.parse_payload(payload1, {"adapter_id": "t"}, {}) payload2 = _make_cs2_payload(kills=4) events, _ = CS2Adapter.parse_payload(payload2, {"adapter_id": "t"}, state1) kill_events = [e for e in events if e.event_type == "kill"] assert len(kill_events) == 1 def test_no_kill_on_first_payload(self) -> None: payload = _make_cs2_payload(kills=5) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, {}) kill_events = [e for e in events if e.event_type == "kill"] assert len(kill_events) == 0 def test_multiple_kills_detected(self) -> None: payload1 = _make_cs2_payload(kills=2) _, state1 = CS2Adapter.parse_payload(payload1, {"adapter_id": "t"}, {}) payload2 = _make_cs2_payload(kills=5) events, _ = CS2Adapter.parse_payload(payload2, {"adapter_id": "t"}, state1) kill_events = [e for e in events if e.event_type == "kill"] assert len(kill_events) == 3 def test_death_detected(self) -> None: payload1 = _make_cs2_payload(deaths=0) _, state1 = CS2Adapter.parse_payload(payload1, {"adapter_id": "t"}, {}) payload2 = _make_cs2_payload(deaths=1) events, _ = CS2Adapter.parse_payload(payload2, {"adapter_id": "t"}, state1) death_events = [e for e in events if e.event_type == "death"] assert len(death_events) == 1 def test_no_death_on_same_count(self) -> None: payload1 = _make_cs2_payload(deaths=2) _, state1 = CS2Adapter.parse_payload(payload1, {"adapter_id": "t"}, {}) payload2 = _make_cs2_payload(deaths=2) events, _ = CS2Adapter.parse_payload(payload2, {"adapter_id": "t"}, state1) death_events = [e for e in events if e.event_type == "death"] assert len(death_events) == 0 # ── Round/Bomb trigger tests ──────────────────────────────────────────── class TestCS2Triggers: def test_round_start(self) -> None: payload = _make_cs2_payload(round_phase="live") events, state = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, {}) rs = [e for e in events if e.event_type == "round_start"] assert len(rs) == 1 assert state["round_phase"] == "live" def test_round_end(self) -> None: prev = {"round_phase": "live"} payload = _make_cs2_payload(round_phase="over") events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, prev) re = [e for e in events if e.event_type == "round_end"] assert len(re) == 1 def test_no_round_event_on_same_phase(self) -> None: prev = {"round_phase": "live"} payload = _make_cs2_payload(round_phase="live") events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, prev) round_events = [e for e in events if e.event_type in ("round_start", "round_end")] assert len(round_events) == 0 def test_bomb_planted(self) -> None: payload = _make_cs2_payload(round_phase="live", bomb="planted") events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, {}) bomb = [e for e in events if e.event_type == "objective_captured"] assert len(bomb) == 1 def test_bomb_defused(self) -> None: prev = {"bomb": "planted"} payload = _make_cs2_payload(round_phase="live", bomb="defused") events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, prev) defuse = [e for e in events if e.event_type == "objective_lost"] assert len(defuse) == 1 def test_flashbang_detected(self) -> None: payload = _make_cs2_payload(flashed=200) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, {"flashed": 0}) flash = [e for e in events if e.event_type == "blinded"] assert len(flash) == 1 assert flash[0].value == pytest.approx(200.0 / 255.0) def test_no_flashbang_when_already_flashed(self) -> None: prev = {"flashed": 200} payload = _make_cs2_payload(flashed=150) events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, prev) flash = [e for e in events if e.event_type == "blinded"] # Already flashed (prev > 0), so no new flash event assert len(flash) == 0 def test_team_ct(self) -> None: payload = _make_cs2_payload(team="CT") events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, {}) team = [e for e in events if e.event_type == "team_a"] assert len(team) == 1 def test_team_t(self) -> None: payload = _make_cs2_payload(team="T") events, _ = CS2Adapter.parse_payload(payload, {"adapter_id": "t"}, {}) team = [e for e in events if e.event_type == "team_b"] assert len(team) == 1 # ── Auth validation tests ─────────────────────────────────────────────── class TestCS2Auth: def test_auth_valid(self) -> None: payload = _make_cs2_payload(auth_token="mysecret") result = CS2Adapter.validate_auth({}, payload, {"auth_token": "mysecret"}) assert result is True def test_auth_invalid(self) -> None: payload = _make_cs2_payload(auth_token="wrong") result = CS2Adapter.validate_auth({}, payload, {"auth_token": "mysecret"}) assert result is False def test_auth_no_token_configured_accepts_all(self) -> None: payload = _make_cs2_payload() result = CS2Adapter.validate_auth({}, payload, {}) assert result is True def test_auth_missing_payload_token(self) -> None: payload = _make_cs2_payload() # no auth in payload result = CS2Adapter.validate_auth({}, payload, {"auth_token": "mysecret"}) assert result is False # ── Metadata tests ────────────────────────────────────────────────────── class TestCS2Metadata: def test_adapter_type(self) -> None: assert CS2Adapter.ADAPTER_TYPE == "cs2" def test_supported_events(self) -> None: assert "health" in CS2Adapter.SUPPORTED_EVENTS assert "kill" in CS2Adapter.SUPPORTED_EVENTS assert "death" in CS2Adapter.SUPPORTED_EVENTS def test_config_schema(self) -> None: schema = CS2Adapter.get_config_schema() assert "auth_token" in schema["properties"] def test_setup_instructions(self) -> None: instructions = CS2Adapter.get_setup_instructions() assert "gamestate_integration" in instructions assert "CS2" in instructions