refactor: rename project to LedGrab, split HA integration into separate repo
Lint & Test / test (push) Successful in 1m56s
Lint & Test / test (push) Successful in 1m56s
- 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
This commit is contained in:
@@ -1 +1 @@
|
||||
"""Tests for WLED Screen Controller."""
|
||||
"""Tests for LedGrab."""
|
||||
|
||||
@@ -10,11 +10,11 @@ import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from wled_controller.api.routes.devices import router
|
||||
from wled_controller.storage.device_store import Device, DeviceStore
|
||||
from wled_controller.storage.output_target_store import OutputTargetStore
|
||||
from wled_controller.core.processing.processor_manager import ProcessorManager
|
||||
from wled_controller.api import dependencies as deps
|
||||
from ledgrab.api.routes.devices import router
|
||||
from ledgrab.storage.device_store import Device, DeviceStore
|
||||
from ledgrab.storage.output_target_store import OutputTargetStore
|
||||
from ledgrab.core.processing.processor_manager import ProcessorManager
|
||||
from ledgrab.api import dependencies as deps
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -31,7 +31,8 @@ def _make_app():
|
||||
|
||||
@pytest.fixture
|
||||
def _route_db(tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "test.db")
|
||||
yield db
|
||||
db.close()
|
||||
@@ -64,7 +65,8 @@ def client(device_store, output_target_store, processor_manager):
|
||||
app = _make_app()
|
||||
|
||||
# Override auth to always pass
|
||||
from wled_controller.api.auth import verify_api_key
|
||||
from ledgrab.api.auth import verify_api_key
|
||||
|
||||
app.dependency_overrides[verify_api_key] = lambda: "test-user"
|
||||
|
||||
# Override stores and manager
|
||||
|
||||
@@ -8,13 +8,13 @@ import pytest
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from wled_controller.api.routes.game_integration import router
|
||||
from wled_controller.core.game_integration.adapter_registry import AdapterRegistry
|
||||
from wled_controller.core.game_integration.base_adapter import GameAdapter
|
||||
from wled_controller.core.game_integration.event_bus import GameEventBus
|
||||
from wled_controller.core.game_integration.events import GameEvent
|
||||
from wled_controller.storage.game_integration_store import GameIntegrationStore
|
||||
from wled_controller.api import dependencies as deps
|
||||
from ledgrab.api.routes.game_integration import router
|
||||
from ledgrab.core.game_integration.adapter_registry import AdapterRegistry
|
||||
from ledgrab.core.game_integration.base_adapter import GameAdapter
|
||||
from ledgrab.core.game_integration.event_bus import GameEventBus
|
||||
from ledgrab.core.game_integration.events import GameEvent
|
||||
from ledgrab.storage.game_integration_store import GameIntegrationStore
|
||||
from ledgrab.api import dependencies as deps
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -75,7 +75,7 @@ def _make_app():
|
||||
|
||||
@pytest.fixture
|
||||
def _route_db(tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "test.db")
|
||||
yield db
|
||||
@@ -105,7 +105,7 @@ def client(game_store, event_bus):
|
||||
app = _make_app()
|
||||
|
||||
# Override auth to always pass
|
||||
from wled_controller.api.auth import verify_api_key
|
||||
from ledgrab.api.auth import verify_api_key
|
||||
|
||||
app.dependency_overrides[verify_api_key] = lambda: "test-user"
|
||||
app.dependency_overrides[deps.get_game_integration_store] = lambda: game_store
|
||||
|
||||
@@ -8,7 +8,7 @@ without setting up the full dependency injection.
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from wled_controller import __version__
|
||||
from ledgrab import __version__
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
@@ -18,7 +18,7 @@ def client():
|
||||
The app module initializes stores from the default config on import,
|
||||
which is acceptable for read-only endpoints tested here.
|
||||
"""
|
||||
from wled_controller.main import app
|
||||
from ledgrab.main import app
|
||||
|
||||
return TestClient(app, raise_server_exceptions=False)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import time
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.api.routes.webhooks import _check_rate_limit, _rate_hits
|
||||
from ledgrab.api.routes.webhooks import _check_rate_limit, _rate_hits
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -25,6 +25,7 @@ class TestRateLimiter:
|
||||
for _ in range(30):
|
||||
_check_rate_limit("1.2.3.4")
|
||||
from fastapi import HTTPException
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
_check_rate_limit("1.2.3.4")
|
||||
assert exc_info.value.status_code == 429
|
||||
@@ -50,7 +51,7 @@ class TestRateLimiter:
|
||||
|
||||
class TestWebhookPayload:
|
||||
def test_valid_payload_model(self):
|
||||
from wled_controller.api.routes.webhooks import WebhookPayload
|
||||
from ledgrab.api.routes.webhooks import WebhookPayload
|
||||
|
||||
p = WebhookPayload(action="activate")
|
||||
assert p.action == "activate"
|
||||
@@ -60,7 +61,7 @@ class TestWebhookPayload:
|
||||
|
||||
def test_arbitrary_action_accepted_by_model(self):
|
||||
"""The model accepts any string; validation is in the route handler."""
|
||||
from wled_controller.api.routes.webhooks import WebhookPayload
|
||||
from ledgrab.api.routes.webhooks import WebhookPayload
|
||||
|
||||
p = WebhookPayload(action="bogus")
|
||||
assert p.action == "bogus"
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.config import get_config
|
||||
from ledgrab.config import get_config
|
||||
|
||||
# Ensure audio filters registered
|
||||
import wled_controller.core.audio.filters # noqa: F401
|
||||
import ledgrab.core.audio.filters # noqa: F401
|
||||
|
||||
_config = get_config()
|
||||
_api_key = next(iter(_config.auth.api_keys.values()), "")
|
||||
@@ -16,7 +16,7 @@ AUTH = {"Authorization": f"Bearer {_api_key}"} if _api_key else {}
|
||||
def client():
|
||||
"""Provide a TestClient with lifespan (startup/shutdown) properly triggered."""
|
||||
from fastapi.testclient import TestClient
|
||||
from wled_controller.main import app
|
||||
from ledgrab.main import app
|
||||
|
||||
with TestClient(app) as c:
|
||||
yield c
|
||||
|
||||
+14
-14
@@ -1,7 +1,7 @@
|
||||
"""Pytest configuration and shared fixtures.
|
||||
|
||||
IMPORTANT: This conftest patches the global config singleton BEFORE any test
|
||||
module can import ``wled_controller.main``. ``main.py`` reads ``get_config()``
|
||||
module can import ``ledgrab.main``. ``main.py`` reads ``get_config()``
|
||||
at module level to open the database — if the singleton is not patched first,
|
||||
the REAL production database (``data/ledgrab.db``) is opened and tests
|
||||
read/write/delete production data.
|
||||
@@ -15,10 +15,10 @@ import pytest
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ISOLATE ALL TESTS FROM PRODUCTION DATA — must happen before any test module
|
||||
# imports ``wled_controller.main``.
|
||||
# imports ``ledgrab.main``.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
import wled_controller.config as _config_mod # noqa: E402
|
||||
import ledgrab.config as _config_mod # noqa: E402
|
||||
|
||||
_test_tmp = Path(tempfile.mkdtemp(prefix="wled_test_"))
|
||||
_test_db_path = str(_test_tmp / "test_ledgrab.db")
|
||||
@@ -38,15 +38,15 @@ _config_mod.config = _test_config
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from wled_controller.config import Config, StorageConfig, ServerConfig, AuthConfig # noqa: E402
|
||||
from wled_controller.storage.database import Database # noqa: E402
|
||||
from wled_controller.storage.device_store import Device, DeviceStore # noqa: E402
|
||||
from wled_controller.storage.sync_clock import SyncClock # noqa: E402
|
||||
from wled_controller.storage.sync_clock_store import SyncClockStore # noqa: E402
|
||||
from wled_controller.storage.output_target_store import OutputTargetStore # noqa: E402
|
||||
from wled_controller.storage.automation import Automation # noqa: E402
|
||||
from wled_controller.storage.automation_store import AutomationStore # noqa: E402
|
||||
from wled_controller.storage.value_source_store import ValueSourceStore # noqa: E402
|
||||
from ledgrab.config import Config, StorageConfig, ServerConfig, AuthConfig # noqa: E402
|
||||
from ledgrab.storage.database import Database # noqa: E402
|
||||
from ledgrab.storage.device_store import Device, DeviceStore # noqa: E402
|
||||
from ledgrab.storage.sync_clock import SyncClock # noqa: E402
|
||||
from ledgrab.storage.sync_clock_store import SyncClockStore # noqa: E402
|
||||
from ledgrab.storage.output_target_store import OutputTargetStore # noqa: E402
|
||||
from ledgrab.storage.automation import Automation # noqa: E402
|
||||
from ledgrab.storage.automation_store import AutomationStore # noqa: E402
|
||||
from ledgrab.storage.value_source_store import ValueSourceStore # noqa: E402
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -244,12 +244,12 @@ def authenticated_client(test_config, monkeypatch):
|
||||
|
||||
Patches global config so the app uses temp storage paths.
|
||||
"""
|
||||
import wled_controller.config as config_mod
|
||||
import ledgrab.config as config_mod
|
||||
|
||||
monkeypatch.setattr(config_mod, "config", test_config)
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from wled_controller.main import app
|
||||
from ledgrab.main import app
|
||||
|
||||
client = TestClient(app, raise_server_exceptions=False)
|
||||
client.headers["Authorization"] = "Bearer test-api-key-12345"
|
||||
|
||||
@@ -4,9 +4,9 @@ from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.adapter_registry import AdapterRegistry
|
||||
from wled_controller.core.game_integration.base_adapter import GameAdapter
|
||||
from wled_controller.core.game_integration.events import GameEvent
|
||||
from ledgrab.core.game_integration.adapter_registry import AdapterRegistry
|
||||
from ledgrab.core.game_integration.base_adapter import GameAdapter
|
||||
from ledgrab.core.game_integration.events import GameEvent
|
||||
|
||||
|
||||
class FakeAdapter(GameAdapter):
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.audio.analysis import NUM_BANDS, AudioAnalysis
|
||||
from wled_controller.core.audio.filters.base import AudioFilter
|
||||
from wled_controller.core.audio.filters.pipeline import AudioFilterPipeline
|
||||
from wled_controller.core.audio.filters.registry import AudioFilterRegistry
|
||||
from ledgrab.core.audio.analysis import NUM_BANDS, AudioAnalysis
|
||||
from ledgrab.core.audio.filters.base import AudioFilter
|
||||
from ledgrab.core.audio.filters.pipeline import AudioFilterPipeline
|
||||
from ledgrab.core.audio.filters.registry import AudioFilterRegistry
|
||||
|
||||
# Import the package to trigger auto-registration of all built-in filters
|
||||
import wled_controller.core.audio.filters # noqa: F401
|
||||
import ledgrab.core.audio.filters # noqa: F401
|
||||
|
||||
from wled_controller.core.filters.filter_instance import FilterInstance
|
||||
from ledgrab.core.filters.filter_instance import FilterInstance
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -5,8 +5,8 @@ from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.automations.automation_engine import AutomationEngine
|
||||
from wled_controller.storage.automation import (
|
||||
from ledgrab.core.automations.automation_engine import AutomationEngine
|
||||
from ledgrab.storage.automation import (
|
||||
ApplicationRule,
|
||||
Automation,
|
||||
DisplayStateRule,
|
||||
@@ -15,7 +15,7 @@ from wled_controller.storage.automation import (
|
||||
TimeOfDayRule,
|
||||
WebhookRule,
|
||||
)
|
||||
from wled_controller.storage.automation_store import AutomationStore
|
||||
from ledgrab.storage.automation_store import AutomationStore
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -43,7 +43,7 @@ def engine(mock_store, mock_manager) -> AutomationEngine:
|
||||
causes access violations in the test environment, so we replace it
|
||||
with a simple MagicMock.
|
||||
"""
|
||||
with patch("wled_controller.core.automations.automation_engine.PlatformDetector"):
|
||||
with patch("ledgrab.core.automations.automation_engine.PlatformDetector"):
|
||||
eng = AutomationEngine(
|
||||
automation_store=mock_store,
|
||||
processor_manager=mock_manager,
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.community_loader import (
|
||||
from ledgrab.core.game_integration.community_loader import (
|
||||
clear_community_adapters,
|
||||
get_community_adapter,
|
||||
get_community_adapter_info,
|
||||
@@ -213,11 +213,7 @@ class TestBuiltInYamlFiles:
|
||||
def test_load_bundled_adapters(self) -> None:
|
||||
"""Load the built-in game_adapters directory."""
|
||||
bundled_dir = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "src"
|
||||
/ "wled_controller"
|
||||
/ "data"
|
||||
/ "game_adapters"
|
||||
Path(__file__).parent.parent.parent / "src" / "ledgrab" / "data" / "game_adapters"
|
||||
)
|
||||
if not bundled_dir.exists():
|
||||
pytest.skip("Bundled adapter directory not found")
|
||||
@@ -232,11 +228,7 @@ class TestBuiltInYamlFiles:
|
||||
def test_minecraft_adapter_parses(self) -> None:
|
||||
"""Test that the Minecraft community adapter can parse a payload."""
|
||||
bundled_dir = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "src"
|
||||
/ "wled_controller"
|
||||
/ "data"
|
||||
/ "game_adapters"
|
||||
Path(__file__).parent.parent.parent / "src" / "ledgrab" / "data" / "game_adapters"
|
||||
)
|
||||
adapters = load_community_adapters(bundled_dir)
|
||||
mc = adapters.get("community_minecraft")
|
||||
@@ -260,11 +252,7 @@ class TestBuiltInYamlFiles:
|
||||
def test_rocket_league_adapter_parses(self) -> None:
|
||||
"""Test that the Rocket League community adapter can parse a payload."""
|
||||
bundled_dir = (
|
||||
Path(__file__).parent.parent.parent
|
||||
/ "src"
|
||||
/ "wled_controller"
|
||||
/ "data"
|
||||
/ "game_adapters"
|
||||
Path(__file__).parent.parent.parent / "src" / "ledgrab" / "data" / "game_adapters"
|
||||
)
|
||||
adapters = load_community_adapters(bundled_dir)
|
||||
rl = adapters.get("community_rocket_league")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.adapters.cs2_adapter import CS2Adapter
|
||||
from ledgrab.core.game_integration.adapters.cs2_adapter import CS2Adapter
|
||||
|
||||
|
||||
# ── Realistic CS2 GSI payload samples ────────────────────────────────────
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.adapters.dota2_adapter import Dota2Adapter
|
||||
from ledgrab.core.game_integration.adapters.dota2_adapter import Dota2Adapter
|
||||
|
||||
|
||||
def _make_dota2_payload(
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import threading
|
||||
|
||||
|
||||
from wled_controller.core.game_integration.event_bus import GameEventBus
|
||||
from wled_controller.core.game_integration.events import GameEvent
|
||||
from ledgrab.core.game_integration.event_bus import GameEventBus
|
||||
from ledgrab.core.game_integration.events import GameEvent
|
||||
|
||||
|
||||
def _make_event(event_type: str = "health", value: float = 0.5) -> GameEvent:
|
||||
|
||||
@@ -5,11 +5,11 @@ from datetime import datetime, timezone
|
||||
|
||||
import numpy as np
|
||||
|
||||
from wled_controller.core.game_integration.event_bus import GameEventBus
|
||||
from wled_controller.core.game_integration.events import GameEvent
|
||||
from wled_controller.core.processing.game_event_stream import GameEventColorStripStream
|
||||
from wled_controller.storage.bindable import BindableColor
|
||||
from wled_controller.storage.color_strip_source import (
|
||||
from ledgrab.core.game_integration.event_bus import GameEventBus
|
||||
from ledgrab.core.game_integration.events import GameEvent
|
||||
from ledgrab.core.processing.game_event_stream import GameEventColorStripStream
|
||||
from ledgrab.storage.bindable import BindableColor
|
||||
from ledgrab.storage.color_strip_source import (
|
||||
ColorStripSource,
|
||||
GameEventColorStripSource,
|
||||
)
|
||||
|
||||
@@ -5,10 +5,10 @@ import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.event_bus import GameEventBus
|
||||
from wled_controller.core.game_integration.events import GameEvent
|
||||
from wled_controller.core.value_sources.game_event_value_source import GameEventValueStream
|
||||
from wled_controller.storage.value_source import (
|
||||
from ledgrab.core.game_integration.event_bus import GameEventBus
|
||||
from ledgrab.core.game_integration.events import GameEvent
|
||||
from ledgrab.core.value_sources.game_event_value_source import GameEventValueStream
|
||||
from ledgrab.storage.value_source import (
|
||||
GameEventValueSource,
|
||||
ValueSource,
|
||||
)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.presets import (
|
||||
from ledgrab.core.game_integration.presets import (
|
||||
EffectPreset,
|
||||
get_all_presets,
|
||||
get_preset,
|
||||
)
|
||||
from wled_controller.storage.game_integration import EventMapping
|
||||
from ledgrab.storage.game_integration import EventMapping
|
||||
|
||||
|
||||
class TestPresetData:
|
||||
|
||||
@@ -4,8 +4,8 @@ Ensures that GameEventBus is properly threaded through to
|
||||
ColorStripStreamManager and ValueStreamManager.
|
||||
"""
|
||||
|
||||
from wled_controller.core.game_integration.event_bus import GameEventBus
|
||||
from wled_controller.core.processing.processor_manager import (
|
||||
from ledgrab.core.game_integration.event_bus import GameEventBus
|
||||
from ledgrab.core.processing.processor_manager import (
|
||||
ProcessorDependencies,
|
||||
ProcessorManager,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.adapters.generic_webhook_adapter import (
|
||||
from ledgrab.core.game_integration.adapters.generic_webhook_adapter import (
|
||||
GenericWebhookAdapter,
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import threading
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.adapters.lol_adapter import (
|
||||
from ledgrab.core.game_integration.adapters.lol_adapter import (
|
||||
LoLAdapter,
|
||||
LoLPoller,
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.game_integration.mapping_adapter import (
|
||||
from ledgrab.core.game_integration.mapping_adapter import (
|
||||
MappingAdapter,
|
||||
load_adapter_from_yaml,
|
||||
validate_adapter_yaml,
|
||||
|
||||
@@ -4,7 +4,7 @@ import threading
|
||||
import time
|
||||
|
||||
|
||||
from wled_controller.core.processing.sync_clock_runtime import SyncClockRuntime
|
||||
from ledgrab.core.processing.sync_clock_runtime import SyncClockRuntime
|
||||
|
||||
|
||||
class TestSyncClockRuntimeInit:
|
||||
|
||||
@@ -14,7 +14,7 @@ import pytest
|
||||
# ---------------------------------------------------------------------------
|
||||
# Isolate e2e tests from production data.
|
||||
#
|
||||
# We must set the config singleton BEFORE wled_controller.main is imported,
|
||||
# We must set the config singleton BEFORE ledgrab.main is imported,
|
||||
# because main.py reads get_config() at module level to create the DB and
|
||||
# all stores. By forcing the singleton here we guarantee the app opens a
|
||||
# throwaway SQLite file in a temp directory.
|
||||
@@ -24,7 +24,7 @@ _e2e_tmp = Path(tempfile.mkdtemp(prefix="wled_e2e_"))
|
||||
_test_db_path = str(_e2e_tmp / "test_ledgrab.db")
|
||||
_test_assets_dir = str(_e2e_tmp / "test_assets")
|
||||
|
||||
import wled_controller.config as _config_mod # noqa: E402
|
||||
import ledgrab.config as _config_mod # noqa: E402
|
||||
|
||||
# Build a Config that mirrors production settings but with isolated paths.
|
||||
_original_config = _config_mod.Config.load()
|
||||
@@ -54,7 +54,7 @@ def _test_client():
|
||||
tests complete. The app uses the isolated test database set above.
|
||||
"""
|
||||
from fastapi.testclient import TestClient
|
||||
from wled_controller.main import app
|
||||
from ledgrab.main import app
|
||||
|
||||
with TestClient(app, raise_server_exceptions=False) as c:
|
||||
yield c
|
||||
@@ -80,12 +80,14 @@ def _clear_stores():
|
||||
"""Remove all entities from all stores for test isolation."""
|
||||
# Reset frozen-writes flags that restore flows set.
|
||||
# Without this, subsequent tests can't persist data.
|
||||
from wled_controller.storage.base_store import unfreeze_saves
|
||||
from ledgrab.storage.base_store import unfreeze_saves
|
||||
|
||||
unfreeze_saves()
|
||||
import wled_controller.storage.database as _db_mod
|
||||
import ledgrab.storage.database as _db_mod
|
||||
|
||||
_db_mod._writes_frozen = False
|
||||
|
||||
from wled_controller.api import dependencies as deps
|
||||
from ledgrab.api import dependencies as deps
|
||||
|
||||
store_clearers = [
|
||||
(deps.get_device_store, "get_all_devices", "delete_device"),
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.filters.filter_instance import FilterInstance
|
||||
from wled_controller.storage.audio_processing_template_store import AudioProcessingTemplateStore
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.core.filters.filter_instance import FilterInstance
|
||||
from ledgrab.storage.audio_processing_template_store import AudioProcessingTemplateStore
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
# Ensure all built-in audio filters are registered
|
||||
import wled_controller.core.audio.filters # noqa: F401
|
||||
import ledgrab.core.audio.filters # noqa: F401
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.audio_source import CaptureAudioSource, ProcessedAudioSource
|
||||
from wled_controller.storage.audio_source_store import AudioSourceStore, ResolvedAudioSource
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.audio_source import CaptureAudioSource, ProcessedAudioSource
|
||||
from ledgrab.storage.audio_source_store import AudioSourceStore, ResolvedAudioSource
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
# Ensure audio filter registration for any template-related code
|
||||
import wled_controller.core.audio.filters # noqa: F401
|
||||
import ledgrab.core.audio.filters # noqa: F401
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.automation import (
|
||||
from ledgrab.storage.automation import (
|
||||
ApplicationRule,
|
||||
Automation,
|
||||
DisplayStateRule,
|
||||
@@ -13,7 +13,7 @@ from wled_controller.storage.automation import (
|
||||
TimeOfDayRule,
|
||||
WebhookRule,
|
||||
)
|
||||
from wled_controller.storage.automation_store import AutomationStore
|
||||
from ledgrab.storage.automation_store import AutomationStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -268,7 +268,7 @@ class TestAutomationNameUniqueness:
|
||||
|
||||
class TestAutomationPersistence:
|
||||
def test_persist_and_reload(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "auto_persist.db")
|
||||
s1 = AutomationStore(db)
|
||||
|
||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.base_store import BaseJsonStore, EntityNotFoundError
|
||||
from ledgrab.storage.base_store import BaseJsonStore, EntityNotFoundError
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -45,6 +45,7 @@ class _TestStore(BaseJsonStore[_Item]):
|
||||
|
||||
class _LegacyStore(BaseJsonStore[_Item]):
|
||||
"""Store that supports legacy JSON keys for migration testing."""
|
||||
|
||||
_json_key = "items_v2"
|
||||
_entity_name = "Item"
|
||||
_legacy_json_keys = ["items_v1", "old_items"]
|
||||
|
||||
@@ -4,7 +4,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.device_store import Device, DeviceStore
|
||||
from ledgrab.storage.device_store import Device, DeviceStore
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -14,7 +14,8 @@ from wled_controller.storage.device_store import Device, DeviceStore
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_db(tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "test.db")
|
||||
yield db
|
||||
db.close()
|
||||
@@ -95,8 +96,13 @@ class TestDeviceModel:
|
||||
|
||||
def test_to_dict_includes_non_defaults(self):
|
||||
d = Device(
|
||||
device_id="d", name="D", url="http://x", led_count=10,
|
||||
rgbw=True, tags=["a"], software_brightness=100,
|
||||
device_id="d",
|
||||
name="D",
|
||||
url="http://x",
|
||||
led_count=10,
|
||||
rgbw=True,
|
||||
tags=["a"],
|
||||
software_brightness=100,
|
||||
)
|
||||
data = d.to_dict()
|
||||
assert data["rgbw"] is True
|
||||
@@ -243,7 +249,8 @@ class TestDeviceNameUniqueness:
|
||||
|
||||
class TestDevicePersistence:
|
||||
def test_persistence_across_instances(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "persist.db")
|
||||
s1 = DeviceStore(db)
|
||||
d = s1.create_device(name="Persist", url="http://p", led_count=77)
|
||||
@@ -256,7 +263,8 @@ class TestDevicePersistence:
|
||||
db.close()
|
||||
|
||||
def test_update_persists(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "persist2.db")
|
||||
s1 = DeviceStore(db)
|
||||
d = s1.create_device(name="Before", url="http://x", led_count=10)
|
||||
@@ -274,7 +282,8 @@ class TestDevicePersistence:
|
||||
|
||||
class TestDeviceThreadSafety:
|
||||
def test_concurrent_creates(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "conc.db")
|
||||
s = DeviceStore(db)
|
||||
errors = []
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.output_target import OutputTarget
|
||||
from wled_controller.storage.output_target_store import OutputTargetStore
|
||||
from wled_controller.storage.wled_output_target import WledOutputTarget
|
||||
from ledgrab.storage.output_target import OutputTarget
|
||||
from ledgrab.storage.output_target_store import OutputTargetStore
|
||||
from ledgrab.storage.wled_output_target import WledOutputTarget
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -168,7 +168,7 @@ class TestOutputTargetQueries:
|
||||
|
||||
class TestOutputTargetPersistence:
|
||||
def test_persist_and_reload(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db_path = str(tmp_path / "ot_persist.db")
|
||||
db = Database(db_path)
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.sync_clock import SyncClock
|
||||
from wled_controller.storage.sync_clock_store import SyncClockStore
|
||||
from ledgrab.storage.sync_clock import SyncClock
|
||||
from ledgrab.storage.sync_clock_store import SyncClockStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -54,9 +54,7 @@ class TestSyncClockStoreCRUD:
|
||||
assert store.count() == 1
|
||||
|
||||
def test_create_clock_with_options(self, store):
|
||||
c = store.create_clock(
|
||||
name="Fast", speed=5.0, description="speedy", tags=["anim"]
|
||||
)
|
||||
c = store.create_clock(name="Fast", speed=5.0, description="speedy", tags=["anim"])
|
||||
assert c.speed == 5.0
|
||||
assert c.description == "speedy"
|
||||
assert c.tags == ["anim"]
|
||||
@@ -149,7 +147,8 @@ class TestSyncClockNameUniqueness:
|
||||
|
||||
class TestSyncClockPersistence:
|
||||
def test_persist_and_reload(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "sc_persist.db")
|
||||
s1 = SyncClockStore(db)
|
||||
c = s1.create_clock(name="Persist", speed=2.5)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.value_source import (
|
||||
from ledgrab.storage.value_source import (
|
||||
AdaptiveValueSource,
|
||||
AnimatedValueSource,
|
||||
AudioValueSource,
|
||||
@@ -10,7 +10,7 @@ from wled_controller.storage.value_source import (
|
||||
StaticValueSource,
|
||||
ValueSource,
|
||||
)
|
||||
from wled_controller.storage.value_source_store import ValueSourceStore
|
||||
from ledgrab.storage.value_source_store import ValueSourceStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -149,8 +149,10 @@ class TestValueSourceStoreCRUD:
|
||||
|
||||
def test_create_animated(self, store):
|
||||
s = store.create_source(
|
||||
name="A1", source_type="animated",
|
||||
waveform="sawtooth", speed=20.0,
|
||||
name="A1",
|
||||
source_type="animated",
|
||||
waveform="sawtooth",
|
||||
speed=20.0,
|
||||
)
|
||||
assert isinstance(s, AnimatedValueSource)
|
||||
assert s.waveform == "sawtooth"
|
||||
@@ -158,8 +160,10 @@ class TestValueSourceStoreCRUD:
|
||||
|
||||
def test_create_audio(self, store):
|
||||
s = store.create_source(
|
||||
name="Au1", source_type="audio",
|
||||
audio_source_id="as_1", mode="beat",
|
||||
name="Au1",
|
||||
source_type="audio",
|
||||
audio_source_id="as_1",
|
||||
mode="beat",
|
||||
)
|
||||
assert isinstance(s, AudioValueSource)
|
||||
assert s.mode == "beat"
|
||||
@@ -170,7 +174,9 @@ class TestValueSourceStoreCRUD:
|
||||
{"time": "20:00", "value": 1.0},
|
||||
]
|
||||
s = store.create_source(
|
||||
name="AT", source_type="adaptive_time", schedule=schedule,
|
||||
name="AT",
|
||||
source_type="adaptive_time",
|
||||
schedule=schedule,
|
||||
)
|
||||
assert isinstance(s, AdaptiveValueSource)
|
||||
assert len(s.schedule) == 2
|
||||
@@ -178,14 +184,18 @@ class TestValueSourceStoreCRUD:
|
||||
def test_create_adaptive_time_insufficient_schedule(self, store):
|
||||
with pytest.raises(ValueError, match="at least 2 points"):
|
||||
store.create_source(
|
||||
name="Bad", source_type="adaptive_time",
|
||||
name="Bad",
|
||||
source_type="adaptive_time",
|
||||
schedule=[{"time": "12:00", "value": 0.5}],
|
||||
)
|
||||
|
||||
def test_create_daylight(self, store):
|
||||
s = store.create_source(
|
||||
name="DL", source_type="daylight",
|
||||
speed=2.0, use_real_time=True, latitude=48.0,
|
||||
name="DL",
|
||||
source_type="daylight",
|
||||
speed=2.0,
|
||||
use_real_time=True,
|
||||
latitude=48.0,
|
||||
)
|
||||
assert isinstance(s, DaylightValueSource)
|
||||
assert s.use_real_time is True
|
||||
@@ -247,7 +257,8 @@ class TestValueSourceNameUniqueness:
|
||||
|
||||
class TestValueSourcePersistence:
|
||||
def test_persist_and_reload(self, tmp_path):
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
db = Database(tmp_path / "vs_persist.db")
|
||||
s1 = ValueSourceStore(db)
|
||||
src = s1.create_source("Persist", "static", value=0.42)
|
||||
|
||||
@@ -5,8 +5,8 @@ import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller import __version__
|
||||
from wled_controller.config import get_config
|
||||
from ledgrab import __version__
|
||||
from ledgrab.config import get_config
|
||||
|
||||
_has_display = bool(
|
||||
os.environ.get("DISPLAY") or sys.platform == "win32" or sys.platform == "darwin"
|
||||
@@ -23,7 +23,7 @@ AUTH_HEADERS = {"Authorization": f"Bearer {_api_key}"} if _api_key else {}
|
||||
def client():
|
||||
"""Provide a TestClient backed by the isolated test database."""
|
||||
from fastapi.testclient import TestClient
|
||||
from wled_controller.main import app
|
||||
from ledgrab.main import app
|
||||
|
||||
with TestClient(app, raise_server_exceptions=False) as c:
|
||||
yield c
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.capture.calibration import (
|
||||
from ledgrab.core.capture.calibration import (
|
||||
CalibrationSegment,
|
||||
CalibrationConfig,
|
||||
PixelMapper,
|
||||
@@ -13,7 +13,7 @@ from wled_controller.core.capture.calibration import (
|
||||
EDGE_ORDER,
|
||||
EDGE_REVERSE,
|
||||
)
|
||||
from wled_controller.core.capture.screen_capture import BorderPixels
|
||||
from ledgrab.core.capture.screen_capture import BorderPixels
|
||||
|
||||
|
||||
def test_calibration_segment():
|
||||
@@ -139,16 +139,19 @@ def test_build_segments_skips_zero_edges():
|
||||
assert "bottom" not in edges
|
||||
|
||||
|
||||
@pytest.mark.parametrize("start_position,layout", [
|
||||
("bottom_left", "clockwise"),
|
||||
("bottom_left", "counterclockwise"),
|
||||
("bottom_right", "clockwise"),
|
||||
("bottom_right", "counterclockwise"),
|
||||
("top_left", "clockwise"),
|
||||
("top_left", "counterclockwise"),
|
||||
("top_right", "clockwise"),
|
||||
("top_right", "counterclockwise"),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"start_position,layout",
|
||||
[
|
||||
("bottom_left", "clockwise"),
|
||||
("bottom_left", "counterclockwise"),
|
||||
("bottom_right", "clockwise"),
|
||||
("bottom_right", "counterclockwise"),
|
||||
("top_left", "clockwise"),
|
||||
("top_left", "counterclockwise"),
|
||||
("top_right", "clockwise"),
|
||||
("top_right", "counterclockwise"),
|
||||
],
|
||||
)
|
||||
def test_build_segments_all_combinations(start_position, layout):
|
||||
"""Test build_segments matches lookup tables for all 8 combinations."""
|
||||
config = CalibrationConfig(
|
||||
@@ -171,8 +174,9 @@ def test_build_segments_all_combinations(start_position, layout):
|
||||
# Verify reverse flags match EDGE_REVERSE table
|
||||
expected_reverse = EDGE_REVERSE[(start_position, layout)]
|
||||
for seg in segments:
|
||||
assert seg.reverse == expected_reverse[seg.edge], \
|
||||
f"Mismatch for {start_position}/{layout}/{seg.edge}: expected reverse={expected_reverse[seg.edge]}"
|
||||
assert (
|
||||
seg.reverse == expected_reverse[seg.edge]
|
||||
), f"Mismatch for {start_position}/{layout}/{seg.edge}: expected reverse={expected_reverse[seg.edge]}"
|
||||
|
||||
# Verify led_start values are cumulative
|
||||
expected_start = 0
|
||||
@@ -240,11 +244,11 @@ def test_pixel_mapper_test_calibration():
|
||||
|
||||
# Top edge should be lit (red)
|
||||
top_segment = config.get_segment_for_edge("top")
|
||||
top_leds = led_colors[top_segment.led_start:top_segment.led_start + top_segment.led_count]
|
||||
top_leds = led_colors[top_segment.led_start : top_segment.led_start + top_segment.led_count]
|
||||
assert all(color == (255, 0, 0) for color in top_leds)
|
||||
|
||||
# Other LEDs should be off
|
||||
other_leds = led_colors[:top_segment.led_start]
|
||||
other_leds = led_colors[: top_segment.led_start]
|
||||
assert all(color == (0, 0, 0) for color in other_leds)
|
||||
|
||||
|
||||
@@ -317,7 +321,6 @@ def test_calibration_from_dict():
|
||||
assert config.get_total_leds() == 140
|
||||
|
||||
|
||||
|
||||
def test_calibration_from_dict_missing_field():
|
||||
"""Test calibration from dict with missing field."""
|
||||
data = {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.color_strip_store import ColorStripStore, MAX_COMPOSITE_DEPTH
|
||||
from wled_controller.storage.database import Database
|
||||
from ledgrab.storage.color_strip_store import ColorStripStore, MAX_COMPOSITE_DEPTH
|
||||
from ledgrab.storage.database import Database
|
||||
|
||||
|
||||
# ── Fixtures ──────────────────────────────────────────────────────────
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"""Tests for configuration management."""
|
||||
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from wled_controller.config import (
|
||||
from ledgrab.config import (
|
||||
Config,
|
||||
ServerConfig,
|
||||
get_config,
|
||||
@@ -54,8 +53,8 @@ class TestFromYaml:
|
||||
|
||||
class TestEnvironmentVariables:
|
||||
def test_env_overrides(self, monkeypatch):
|
||||
monkeypatch.setenv("WLED_SERVER__HOST", "192.168.1.1")
|
||||
monkeypatch.setenv("WLED_SERVER__PORT", "7000")
|
||||
monkeypatch.setenv("LEDGRAB_SERVER__HOST", "192.168.1.1")
|
||||
monkeypatch.setenv("LEDGRAB_SERVER__PORT", "7000")
|
||||
config = Config()
|
||||
assert config.server.host == "192.168.1.1"
|
||||
assert config.server.port == 7000
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.database import Database
|
||||
from wled_controller.storage.device_store import Device, DeviceStore
|
||||
from ledgrab.storage.database import Database
|
||||
from ledgrab.storage.device_store import Device, DeviceStore
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from wled_controller.storage.database import Database
|
||||
from wled_controller.storage.device_store import Device, DeviceStore
|
||||
from ledgrab.storage.database import Database
|
||||
from ledgrab.storage.device_store import Device, DeviceStore
|
||||
|
||||
|
||||
# ── Fixtures ──────────────────────────────────────────────────────────
|
||||
@@ -240,7 +240,7 @@ class TestGroupLEDClient:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_connect_creates_children(self, mock_store):
|
||||
from wled_controller.core.devices.group_client import GroupLEDClient
|
||||
from ledgrab.core.devices.group_client import GroupLEDClient
|
||||
|
||||
store, devices = mock_store
|
||||
client = GroupLEDClient(
|
||||
@@ -257,7 +257,7 @@ class TestGroupLEDClient:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sequence_mode_slices(self, mock_store):
|
||||
from wled_controller.core.devices.group_client import GroupLEDClient
|
||||
from ledgrab.core.devices.group_client import GroupLEDClient
|
||||
|
||||
store, devices = mock_store
|
||||
client = GroupLEDClient(
|
||||
@@ -292,7 +292,7 @@ class TestGroupLEDClient:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_independent_mode_resamples(self, mock_store):
|
||||
from wled_controller.core.devices.group_client import GroupLEDClient
|
||||
from ledgrab.core.devices.group_client import GroupLEDClient
|
||||
|
||||
store, devices = mock_store
|
||||
client = GroupLEDClient(
|
||||
@@ -328,7 +328,7 @@ class TestGroupLEDClient:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_cleans_up(self, mock_store):
|
||||
from wled_controller.core.devices.group_client import GroupLEDClient
|
||||
from ledgrab.core.devices.group_client import GroupLEDClient
|
||||
|
||||
store, devices = mock_store
|
||||
client = GroupLEDClient(
|
||||
@@ -345,7 +345,7 @@ class TestGroupLEDClient:
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sequence_pads_short_pixels(self, mock_store):
|
||||
from wled_controller.core.devices.group_client import GroupLEDClient
|
||||
from ledgrab.core.devices.group_client import GroupLEDClient
|
||||
|
||||
store, devices = mock_store
|
||||
client = GroupLEDClient(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.processing.processor_manager import (
|
||||
from ledgrab.core.processing.processor_manager import (
|
||||
ProcessorDependencies,
|
||||
ProcessorManager,
|
||||
)
|
||||
@@ -233,7 +233,7 @@ def test_get_target_metrics(processor_manager):
|
||||
|
||||
def test_target_type_detection(processor_manager):
|
||||
"""Test target type detection via processor instances."""
|
||||
from wled_controller.core.processing.wled_target_processor import WledTargetProcessor
|
||||
from ledgrab.core.processing.wled_target_processor import WledTargetProcessor
|
||||
|
||||
processor_manager.add_device(
|
||||
device_id="test_device",
|
||||
|
||||
@@ -6,7 +6,7 @@ import sys
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from wled_controller.core.capture.screen_capture import (
|
||||
from ledgrab.core.capture.screen_capture import (
|
||||
get_available_displays,
|
||||
capture_display,
|
||||
extract_border_pixels,
|
||||
@@ -18,7 +18,9 @@ from wled_controller.core.capture.screen_capture import (
|
||||
)
|
||||
|
||||
# Skip tests that require a real display on headless CI
|
||||
_has_display = bool(os.environ.get("DISPLAY") or sys.platform == "win32" or sys.platform == "darwin")
|
||||
_has_display = bool(
|
||||
os.environ.get("DISPLAY") or sys.platform == "win32" or sys.platform == "darwin"
|
||||
)
|
||||
requires_display = pytest.mark.skipif(not _has_display, reason="No display available (headless CI)")
|
||||
|
||||
|
||||
@@ -66,12 +68,7 @@ def test_extract_border_pixels():
|
||||
"""Test extracting border pixels."""
|
||||
# Create a test screen capture
|
||||
test_image = np.random.randint(0, 256, (100, 200, 3), dtype=np.uint8)
|
||||
capture = ScreenCapture(
|
||||
image=test_image,
|
||||
width=200,
|
||||
height=100,
|
||||
display_index=0
|
||||
)
|
||||
capture = ScreenCapture(image=test_image, width=200, height=100, display_index=0)
|
||||
|
||||
border_width = 10
|
||||
borders = extract_border_pixels(capture, border_width)
|
||||
@@ -86,12 +83,7 @@ def test_extract_border_pixels():
|
||||
def test_extract_border_pixels_invalid_width():
|
||||
"""Test extracting borders with invalid width."""
|
||||
test_image = np.random.randint(0, 256, (100, 200, 3), dtype=np.uint8)
|
||||
capture = ScreenCapture(
|
||||
image=test_image,
|
||||
width=200,
|
||||
height=100,
|
||||
display_index=0
|
||||
)
|
||||
capture = ScreenCapture(image=test_image, width=200, height=100, display_index=0)
|
||||
|
||||
# Border width too small
|
||||
with pytest.raises(ValueError):
|
||||
|
||||
@@ -4,7 +4,7 @@ import pytest
|
||||
import respx
|
||||
from httpx import Response
|
||||
|
||||
from wled_controller.core.devices.wled_client import WLEDClient, WLEDInfo
|
||||
from ledgrab.core.devices.wled_client import WLEDClient, WLEDInfo
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -30,15 +30,7 @@ def mock_wled_info():
|
||||
@pytest.fixture
|
||||
def mock_wled_cfg():
|
||||
"""Provide mock WLED config response (needed by get_info)."""
|
||||
return {
|
||||
"hw": {
|
||||
"led": {
|
||||
"ins": [
|
||||
{"start": 0, "len": 150, "order": 1, "pin": [2], "type": 22}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
return {"hw": {"led": {"ins": [{"start": 0, "len": 150, "order": 1, "pin": [2], "type": 22}]}}}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -57,15 +49,9 @@ def _mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_s
|
||||
connect() calls get_info() which hits /json/info + /json/cfg,
|
||||
then snapshot_device_state() which hits /json/state.
|
||||
"""
|
||||
respx.get(f"{wled_url}/json/info").mock(
|
||||
return_value=Response(200, json=mock_wled_info)
|
||||
)
|
||||
respx.get(f"{wled_url}/json/cfg").mock(
|
||||
return_value=Response(200, json=mock_wled_cfg)
|
||||
)
|
||||
respx.get(f"{wled_url}/json/state").mock(
|
||||
return_value=Response(200, json=mock_wled_state)
|
||||
)
|
||||
respx.get(f"{wled_url}/json/info").mock(return_value=Response(200, json=mock_wled_info))
|
||||
respx.get(f"{wled_url}/json/cfg").mock(return_value=Response(200, json=mock_wled_cfg))
|
||||
respx.get(f"{wled_url}/json/state").mock(return_value=Response(200, json=mock_wled_state))
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -133,9 +119,7 @@ async def test_get_state(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_stat
|
||||
async def test_send_pixels(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
|
||||
"""Test sending pixel data."""
|
||||
_mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)
|
||||
respx.post(f"{wled_url}/json/state").mock(
|
||||
return_value=Response(200, json={"success": True})
|
||||
)
|
||||
respx.post(f"{wled_url}/json/state").mock(return_value=Response(200, json={"success": True}))
|
||||
|
||||
async with WLEDClient(wled_url) as client:
|
||||
pixels = [
|
||||
@@ -173,9 +157,7 @@ async def test_send_pixels_invalid_values(wled_url, mock_wled_info, mock_wled_cf
|
||||
async def test_set_power(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
|
||||
"""Test turning device on/off."""
|
||||
_mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)
|
||||
respx.post(f"{wled_url}/json/state").mock(
|
||||
return_value=Response(200, json={"success": True})
|
||||
)
|
||||
respx.post(f"{wled_url}/json/state").mock(return_value=Response(200, json={"success": True}))
|
||||
|
||||
async with WLEDClient(wled_url) as client:
|
||||
# Turn on
|
||||
@@ -192,9 +174,7 @@ async def test_set_power(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_stat
|
||||
async def test_set_brightness(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state):
|
||||
"""Test setting brightness."""
|
||||
_mock_connect_endpoints(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_state)
|
||||
respx.post(f"{wled_url}/json/state").mock(
|
||||
return_value=Response(200, json={"success": True})
|
||||
)
|
||||
respx.post(f"{wled_url}/json/state").mock(return_value=Response(200, json={"success": True}))
|
||||
|
||||
async with WLEDClient(wled_url) as client:
|
||||
success = await client.set_brightness(128)
|
||||
@@ -231,12 +211,8 @@ async def test_retry_logic(wled_url, mock_wled_info, mock_wled_cfg, mock_wled_st
|
||||
return Response(200, json=mock_wled_info)
|
||||
|
||||
respx.get(f"{wled_url}/json/info").mock(side_effect=mock_info_response)
|
||||
respx.get(f"{wled_url}/json/cfg").mock(
|
||||
return_value=Response(200, json=mock_wled_cfg)
|
||||
)
|
||||
respx.get(f"{wled_url}/json/state").mock(
|
||||
return_value=Response(200, json=mock_wled_state)
|
||||
)
|
||||
respx.get(f"{wled_url}/json/cfg").mock(return_value=Response(200, json=mock_wled_cfg))
|
||||
respx.get(f"{wled_url}/json/state").mock(return_value=Response(200, json=mock_wled_state))
|
||||
|
||||
client = WLEDClient(wled_url, retry_attempts=3, retry_delay=0.1)
|
||||
success = await client.connect()
|
||||
|
||||
Reference in New Issue
Block a user