feat: game integration system
Receive real-time events from games (CS2, Dota 2, LoL, etc.) and drive LED effects through the existing color strip and value source pipelines. Core: - GameEventBus (thread-safe pub/sub) with standardized 23-type event vocabulary - GameAdapter ABC + AdapterRegistry + MappingAdapter (YAML-driven) - Built-in adapters: CS2 GSI, Dota 2 GSI, LoL Live Client, Generic Webhook - Community YAML adapters: Minecraft, Valorant, Rocket League - GameEventColorStripStream with 5 effects (flash/pulse/sweep/color_shift/breathing) - GameEventValueSource with EMA smoothing and timeout - 4 built-in effect presets (FPS Combat, MOBA Health, Racing, Generic Alert) - Auto-setup for Valve GSI games (Steam path detection, cfg file writing) - Demo capture engine exposed to non-demo mode Frontend: - Game tab in Streams tree navigation with integration cards - Game integration editor modal with adapter picker, config fields, event mappings - game_event source type in CSS and ValueSource editors - Setup instructions overlay (markdown rendered) - Live event monitor and connection test API: - Full CRUD for game integrations - Event ingestion endpoint (adapter-level auth) - Adapter metadata, presets, auto-setup, status/diagnostics endpoints
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
"""Tests for AutomationStore — CRUD, conditions, name uniqueness."""
|
||||
"""Tests for AutomationStore — CRUD, rules, name uniqueness."""
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.automation import (
|
||||
AlwaysCondition,
|
||||
ApplicationCondition,
|
||||
ApplicationRule,
|
||||
Automation,
|
||||
Condition,
|
||||
DisplayStateCondition,
|
||||
MQTTCondition,
|
||||
StartupCondition,
|
||||
SystemIdleCondition,
|
||||
TimeOfDayCondition,
|
||||
WebhookCondition,
|
||||
DisplayStateRule,
|
||||
MQTTRule,
|
||||
Rule,
|
||||
StartupRule,
|
||||
SystemIdleRule,
|
||||
TimeOfDayRule,
|
||||
WebhookRule,
|
||||
)
|
||||
from wled_controller.storage.automation_store import AutomationStore
|
||||
|
||||
@@ -23,72 +22,78 @@ def store(tmp_db) -> AutomationStore:
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Condition models
|
||||
# Rule models
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestConditionModels:
|
||||
def test_always_round_trip(self):
|
||||
c = AlwaysCondition()
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, AlwaysCondition)
|
||||
|
||||
class TestRuleModels:
|
||||
def test_application_round_trip(self):
|
||||
c = ApplicationCondition(apps=["chrome.exe", "firefox.exe"], match_type="topmost")
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, ApplicationCondition)
|
||||
r = ApplicationRule(apps=["chrome.exe", "firefox.exe"], match_type="topmost")
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, ApplicationRule)
|
||||
assert restored.apps == ["chrome.exe", "firefox.exe"]
|
||||
assert restored.match_type == "topmost"
|
||||
|
||||
def test_time_of_day_round_trip(self):
|
||||
c = TimeOfDayCondition(start_time="22:00", end_time="06:00")
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, TimeOfDayCondition)
|
||||
r = TimeOfDayRule(start_time="22:00", end_time="06:00")
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, TimeOfDayRule)
|
||||
assert restored.start_time == "22:00"
|
||||
assert restored.end_time == "06:00"
|
||||
|
||||
def test_system_idle_round_trip(self):
|
||||
c = SystemIdleCondition(idle_minutes=10, when_idle=False)
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, SystemIdleCondition)
|
||||
r = SystemIdleRule(idle_minutes=10, when_idle=False)
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, SystemIdleRule)
|
||||
assert restored.idle_minutes == 10
|
||||
assert restored.when_idle is False
|
||||
|
||||
def test_display_state_round_trip(self):
|
||||
c = DisplayStateCondition(state="off")
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, DisplayStateCondition)
|
||||
r = DisplayStateRule(state="off")
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, DisplayStateRule)
|
||||
assert restored.state == "off"
|
||||
|
||||
def test_mqtt_round_trip(self):
|
||||
c = MQTTCondition(topic="home/tv", payload="on", match_mode="contains")
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, MQTTCondition)
|
||||
r = MQTTRule(topic="home/tv", payload="on", match_mode="contains")
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, MQTTRule)
|
||||
assert restored.topic == "home/tv"
|
||||
assert restored.match_mode == "contains"
|
||||
|
||||
def test_webhook_round_trip(self):
|
||||
c = WebhookCondition(token="abc123")
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, WebhookCondition)
|
||||
r = WebhookRule(token="abc123")
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, WebhookRule)
|
||||
assert restored.token == "abc123"
|
||||
|
||||
def test_startup_round_trip(self):
|
||||
c = StartupCondition()
|
||||
data = c.to_dict()
|
||||
restored = Condition.from_dict(data)
|
||||
assert isinstance(restored, StartupCondition)
|
||||
r = StartupRule()
|
||||
data = r.to_dict()
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, StartupRule)
|
||||
|
||||
def test_unknown_condition_type_raises(self):
|
||||
with pytest.raises(ValueError, match="Unknown condition type"):
|
||||
Condition.from_dict({"condition_type": "nonexistent"})
|
||||
def test_unknown_rule_type_raises(self):
|
||||
with pytest.raises(ValueError, match="Unknown rule type"):
|
||||
Rule.from_dict({"rule_type": "nonexistent"})
|
||||
|
||||
def test_legacy_condition_type_migration(self):
|
||||
"""Legacy data with condition_type should still deserialize."""
|
||||
data = {"condition_type": "startup"}
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, StartupRule)
|
||||
|
||||
def test_legacy_always_maps_to_startup(self):
|
||||
"""Legacy 'always' condition_type should map to StartupRule."""
|
||||
data = {"condition_type": "always"}
|
||||
restored = Rule.from_dict(data)
|
||||
assert isinstance(restored, StartupRule)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -100,7 +105,7 @@ class TestAutomationModel:
|
||||
def test_round_trip(self, make_automation):
|
||||
auto = make_automation(
|
||||
name="Test Auto",
|
||||
conditions=[AlwaysCondition(), WebhookCondition(token="tok1")],
|
||||
rules=[StartupRule(), WebhookRule(token="tok1")],
|
||||
scene_preset_id="sp_123",
|
||||
deactivation_mode="revert",
|
||||
)
|
||||
@@ -109,21 +114,21 @@ class TestAutomationModel:
|
||||
|
||||
assert restored.id == auto.id
|
||||
assert restored.name == "Test Auto"
|
||||
assert len(restored.conditions) == 2
|
||||
assert isinstance(restored.conditions[0], AlwaysCondition)
|
||||
assert isinstance(restored.conditions[1], WebhookCondition)
|
||||
assert len(restored.rules) == 2
|
||||
assert isinstance(restored.rules[0], StartupRule)
|
||||
assert isinstance(restored.rules[1], WebhookRule)
|
||||
assert restored.scene_preset_id == "sp_123"
|
||||
assert restored.deactivation_mode == "revert"
|
||||
|
||||
def test_from_dict_skips_unknown_conditions(self):
|
||||
def test_from_dict_skips_unknown_rules(self):
|
||||
data = {
|
||||
"id": "a1",
|
||||
"name": "Skip",
|
||||
"enabled": True,
|
||||
"condition_logic": "or",
|
||||
"conditions": [
|
||||
{"condition_type": "always"},
|
||||
{"condition_type": "future_unknown"},
|
||||
"rule_logic": "or",
|
||||
"rules": [
|
||||
{"rule_type": "startup"},
|
||||
{"rule_type": "future_unknown"},
|
||||
],
|
||||
"scene_preset_id": None,
|
||||
"deactivation_mode": "none",
|
||||
@@ -132,7 +137,30 @@ class TestAutomationModel:
|
||||
"updated_at": "2025-01-01T00:00:00+00:00",
|
||||
}
|
||||
auto = Automation.from_dict(data)
|
||||
assert len(auto.conditions) == 1 # unknown was skipped
|
||||
assert len(auto.rules) == 1 # unknown was skipped
|
||||
|
||||
def test_legacy_conditions_field_migration(self):
|
||||
"""Legacy data with 'conditions' and 'condition_logic' should migrate."""
|
||||
data = {
|
||||
"id": "a2",
|
||||
"name": "Legacy",
|
||||
"enabled": True,
|
||||
"condition_logic": "and",
|
||||
"conditions": [
|
||||
{"condition_type": "startup"},
|
||||
{"condition_type": "application", "apps": ["test.exe"], "match_type": "running"},
|
||||
],
|
||||
"scene_preset_id": None,
|
||||
"deactivation_mode": "none",
|
||||
"deactivation_scene_preset_id": None,
|
||||
"created_at": "2025-01-01T00:00:00+00:00",
|
||||
"updated_at": "2025-01-01T00:00:00+00:00",
|
||||
}
|
||||
auto = Automation.from_dict(data)
|
||||
assert auto.rule_logic == "and"
|
||||
assert len(auto.rules) == 2
|
||||
assert isinstance(auto.rules[0], StartupRule)
|
||||
assert isinstance(auto.rules[1], ApplicationRule)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -146,27 +174,27 @@ class TestAutomationStoreCRUD:
|
||||
assert a.id.startswith("auto_")
|
||||
assert a.name == "Auto A"
|
||||
assert a.enabled is True
|
||||
assert a.condition_logic == "or"
|
||||
assert a.rule_logic == "or"
|
||||
assert store.count() == 1
|
||||
|
||||
def test_create_with_conditions(self, store):
|
||||
conditions = [
|
||||
AlwaysCondition(),
|
||||
WebhookCondition(token="secret123"),
|
||||
def test_create_with_rules(self, store):
|
||||
rules = [
|
||||
StartupRule(),
|
||||
WebhookRule(token="secret123"),
|
||||
]
|
||||
a = store.create_automation(
|
||||
name="Full",
|
||||
enabled=False,
|
||||
condition_logic="and",
|
||||
conditions=conditions,
|
||||
rule_logic="and",
|
||||
rules=rules,
|
||||
scene_preset_id="sp_001",
|
||||
deactivation_mode="fallback_scene",
|
||||
deactivation_scene_preset_id="sp_002",
|
||||
tags=["test"],
|
||||
)
|
||||
assert a.enabled is False
|
||||
assert a.condition_logic == "and"
|
||||
assert len(a.conditions) == 2
|
||||
assert a.rule_logic == "and"
|
||||
assert len(a.rules) == 2
|
||||
assert a.scene_preset_id == "sp_001"
|
||||
assert a.tags == ["test"]
|
||||
|
||||
@@ -195,12 +223,12 @@ class TestAutomationStoreCRUD:
|
||||
assert updated.name == "New"
|
||||
assert updated.enabled is False
|
||||
|
||||
def test_update_conditions(self, store):
|
||||
a = store.create_automation(name="Conds")
|
||||
new_conds = [ApplicationCondition(apps=["notepad.exe"])]
|
||||
updated = store.update_automation(a.id, conditions=new_conds)
|
||||
assert len(updated.conditions) == 1
|
||||
assert isinstance(updated.conditions[0], ApplicationCondition)
|
||||
def test_update_rules(self, store):
|
||||
a = store.create_automation(name="Rules")
|
||||
new_rules = [ApplicationRule(apps=["notepad.exe"])]
|
||||
updated = store.update_automation(a.id, rules=new_rules)
|
||||
assert len(updated.rules) == 1
|
||||
assert isinstance(updated.rules[0], ApplicationRule)
|
||||
|
||||
def test_update_scene_preset_id_clear(self, store):
|
||||
a = store.create_automation(name="SP", scene_preset_id="sp_1")
|
||||
@@ -241,17 +269,18 @@ class TestAutomationNameUniqueness:
|
||||
class TestAutomationPersistence:
|
||||
def test_persist_and_reload(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "auto_persist.db")
|
||||
s1 = AutomationStore(db)
|
||||
a = s1.create_automation(
|
||||
name="Persist",
|
||||
conditions=[WebhookCondition(token="t1")],
|
||||
rules=[WebhookRule(token="t1")],
|
||||
)
|
||||
aid = a.id
|
||||
|
||||
s2 = AutomationStore(db)
|
||||
loaded = s2.get_automation(aid)
|
||||
assert loaded.name == "Persist"
|
||||
assert len(loaded.conditions) == 1
|
||||
assert isinstance(loaded.conditions[0], WebhookCondition)
|
||||
assert len(loaded.rules) == 1
|
||||
assert isinstance(loaded.rules[0], WebhookRule)
|
||||
db.close()
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
"""Tests for GameIntegrationStore — CRUD, validation, uniqueness."""
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.base_store import EntityNotFoundError
|
||||
from wled_controller.storage.game_integration import EventMapping, GameIntegrationConfig
|
||||
from wled_controller.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 == []
|
||||
Reference in New Issue
Block a user