Files
ledgrab/server/tests/storage/test_game_integration_store.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

275 lines
9.8 KiB
Python

"""Tests for GameIntegrationStore — CRUD, validation, uniqueness."""
import pytest
from ledgrab.storage.base_store import EntityNotFoundError
from ledgrab.storage.game_integration import EventMapping, GameIntegrationConfig
from ledgrab.storage.game_integration_store import GameIntegrationStore
@pytest.fixture
def store(tmp_db) -> GameIntegrationStore:
return GameIntegrationStore(tmp_db)
# ---------------------------------------------------------------------------
# Dataclass model tests
# ---------------------------------------------------------------------------
class TestEventMapping:
def test_round_trip(self):
m = EventMapping(
event_type="health",
effect="pulse",
color=[0, 255, 0],
duration_ms=1000,
intensity=0.8,
priority=5,
)
data = m.to_dict()
restored = EventMapping.from_dict(data)
assert restored.event_type == "health"
assert restored.effect == "pulse"
assert restored.color == [0, 255, 0]
assert restored.duration_ms == 1000
assert restored.intensity == 0.8
assert restored.priority == 5
def test_defaults(self):
m = EventMapping(event_type="kill")
assert m.effect == "flash"
assert m.color == [255, 0, 0]
assert m.duration_ms == 500
assert m.intensity == 1.0
assert m.priority == 0
def test_from_dict_defaults(self):
m = EventMapping.from_dict({"event_type": "death"})
assert m.effect == "flash"
assert m.color == [255, 0, 0]
class TestGameIntegrationConfig:
def test_round_trip(self):
config = GameIntegrationConfig.create_from_kwargs(
name="CS2 Integration",
adapter_type="cs2_gsi",
enabled=True,
adapter_config={"token": "secret123"},
event_mappings=[
EventMapping(event_type="health", effect="gradient", color=[255, 0, 0]),
EventMapping(event_type="kill", effect="flash", color=[0, 255, 0]),
],
description="Counter-Strike 2 game state integration",
tags=["fps", "cs2"],
)
data = config.to_dict()
restored = GameIntegrationConfig.from_dict(data)
assert restored.id == config.id
assert restored.name == "CS2 Integration"
assert restored.adapter_type == "cs2_gsi"
assert restored.enabled is True
assert restored.adapter_config == {"token": "secret123"}
assert len(restored.event_mappings) == 2
assert restored.event_mappings[0].event_type == "health"
assert restored.event_mappings[1].event_type == "kill"
assert restored.description == "Counter-Strike 2 game state integration"
assert restored.tags == ["fps", "cs2"]
def test_create_from_kwargs_generates_id(self):
config = GameIntegrationConfig.create_from_kwargs(name="Test", adapter_type="test")
assert config.id.startswith("gi_")
assert len(config.id) == 11 # gi_ + 8 hex chars
def test_apply_update_immutable(self):
original = GameIntegrationConfig.create_from_kwargs(
name="Original", adapter_type="test", enabled=True
)
updated = original.apply_update(name="Updated", enabled=False)
# Original unchanged
assert original.name == "Original"
assert original.enabled is True
# Updated has new values
assert updated.name == "Updated"
assert updated.enabled is False
assert updated.id == original.id
assert updated.created_at == original.created_at
assert updated.updated_at >= original.updated_at
def test_apply_update_partial(self):
original = GameIntegrationConfig.create_from_kwargs(
name="Test", adapter_type="test", description="original desc"
)
updated = original.apply_update(description="new desc")
assert updated.name == "Test"
assert updated.adapter_type == "test"
assert updated.description == "new desc"
# ---------------------------------------------------------------------------
# Store CRUD tests
# ---------------------------------------------------------------------------
class TestGameIntegrationStoreCRUD:
def test_create_and_get(self, store):
config = store.create_integration(
name="My Integration",
adapter_type="webhook",
description="Test integration",
)
assert config.id.startswith("gi_")
assert config.name == "My Integration"
assert config.adapter_type == "webhook"
assert config.enabled is True
fetched = store.get_integration(config.id)
assert fetched.name == "My Integration"
def test_list_all(self, store):
store.create_integration(name="Int 1", adapter_type="webhook")
store.create_integration(name="Int 2", adapter_type="cs2_gsi")
all_configs = store.get_all_integrations()
assert len(all_configs) == 2
names = {c.name for c in all_configs}
assert names == {"Int 1", "Int 2"}
def test_update(self, store):
config = store.create_integration(
name="Old Name",
adapter_type="webhook",
)
updated = store.update_integration(
config.id,
name="New Name",
enabled=False,
description="Updated description",
)
assert updated.name == "New Name"
assert updated.enabled is False
assert updated.description == "Updated description"
assert updated.updated_at > config.updated_at
def test_update_event_mappings(self, store):
config = store.create_integration(
name="Test",
adapter_type="webhook",
event_mappings=[EventMapping(event_type="health")],
)
new_mappings = [
EventMapping(event_type="kill", effect="flash", color=[0, 255, 0]),
EventMapping(event_type="death", effect="pulse", color=[255, 0, 0]),
]
updated = store.update_integration(config.id, event_mappings=new_mappings)
assert len(updated.event_mappings) == 2
assert updated.event_mappings[0].event_type == "kill"
assert updated.event_mappings[1].event_type == "death"
def test_delete(self, store):
config = store.create_integration(name="ToDelete", adapter_type="webhook")
store.delete_integration(config.id)
with pytest.raises(EntityNotFoundError):
store.get_integration(config.id)
def test_delete_nonexistent(self, store):
with pytest.raises(EntityNotFoundError):
store.delete_integration("gi_nonexist")
def test_get_nonexistent(self, store):
with pytest.raises(EntityNotFoundError):
store.get_integration("gi_nonexist")
def test_create_with_adapter_config(self, store):
config = store.create_integration(
name="CS2",
adapter_type="cs2_gsi",
adapter_config={"auth_token": "secret", "port": 3000},
)
fetched = store.get_integration(config.id)
assert fetched.adapter_config == {"auth_token": "secret", "port": 3000}
# ---------------------------------------------------------------------------
# Name uniqueness tests
# ---------------------------------------------------------------------------
class TestNameUniqueness:
def test_duplicate_name_rejected(self, store):
store.create_integration(name="Unique Name", adapter_type="webhook")
with pytest.raises(ValueError, match="already exists"):
store.create_integration(name="Unique Name", adapter_type="webhook")
def test_empty_name_rejected(self, store):
with pytest.raises(ValueError, match="required"):
store.create_integration(name="", adapter_type="webhook")
def test_whitespace_name_rejected(self, store):
with pytest.raises(ValueError, match="required"):
store.create_integration(name=" ", adapter_type="webhook")
def test_update_same_name_allowed(self, store):
config = store.create_integration(name="Same", adapter_type="webhook")
updated = store.update_integration(config.id, name="Same")
assert updated.name == "Same"
def test_update_to_existing_name_rejected(self, store):
store.create_integration(name="Name A", adapter_type="webhook")
config_b = store.create_integration(name="Name B", adapter_type="webhook")
with pytest.raises(ValueError, match="already exists"):
store.update_integration(config_b.id, name="Name A")
# ---------------------------------------------------------------------------
# Persistence tests
# ---------------------------------------------------------------------------
class TestPersistence:
def test_survives_reload(self, tmp_db):
store1 = GameIntegrationStore(tmp_db)
config = store1.create_integration(
name="Persistent",
adapter_type="webhook",
event_mappings=[EventMapping(event_type="health", effect="gradient")],
tags=["test"],
)
# Create a new store instance (simulates restart)
store2 = GameIntegrationStore(tmp_db)
fetched = store2.get_integration(config.id)
assert fetched.name == "Persistent"
assert fetched.adapter_type == "webhook"
assert len(fetched.event_mappings) == 1
assert fetched.event_mappings[0].event_type == "health"
assert fetched.tags == ["test"]
# ---------------------------------------------------------------------------
# get_references tests
# ---------------------------------------------------------------------------
class TestGetReferences:
def test_returns_empty(self, store):
config = store.create_integration(name="Test", adapter_type="webhook")
refs = store.get_references(config.id)
assert refs == []