Files
ledgrab/server/tests/e2e/conftest.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

115 lines
4.0 KiB
Python

"""Shared fixtures for end-to-end API tests.
Uses the real FastAPI app with a session-scoped TestClient.
All e2e tests run against an ISOLATED temporary database and assets
directory — never the production data.
"""
import shutil
import tempfile
from pathlib import Path
import pytest
# ---------------------------------------------------------------------------
# Isolate e2e tests from production data.
#
# 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.
# ---------------------------------------------------------------------------
_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 ledgrab.config as _config_mod # noqa: E402
# Build a Config that mirrors production settings but with isolated paths.
_original_config = _config_mod.Config.load()
_test_config = _original_config.model_copy(
update={
"storage": _config_mod.StorageConfig(database_file=_test_db_path),
"assets": _config_mod.AssetsConfig(
assets_dir=_test_assets_dir,
max_file_size_mb=_original_config.assets.max_file_size_mb,
),
},
)
# Install as the global singleton so all subsequent get_config() calls
# (including main.py module-level code) use isolated paths.
_config_mod.config = _test_config
API_KEY = next(iter(_test_config.auth.api_keys.values()), "")
AUTH_HEADERS = {"Authorization": f"Bearer {API_KEY}"}
@pytest.fixture(scope="session")
def _test_client():
"""Session-scoped TestClient to avoid lifespan re-entry issues.
The app's lifespan (MQTT, automation engine, health monitoring, etc.)
starts once for the entire e2e test session and shuts down after all
tests complete. The app uses the isolated test database set above.
"""
from fastapi.testclient import TestClient
from ledgrab.main import app
with TestClient(app, raise_server_exceptions=False) as c:
yield c
# Clean up temp directory after all e2e tests finish
shutil.rmtree(_e2e_tmp, ignore_errors=True)
@pytest.fixture
def client(_test_client):
"""Per-test client with auth headers and clean stores.
Clears all entity stores before each test so tests are independent.
"""
_clear_stores()
_test_client.headers["Authorization"] = f"Bearer {API_KEY}"
yield _test_client
# Clean up after test
_clear_stores()
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 ledgrab.storage.base_store import unfreeze_saves
unfreeze_saves()
import ledgrab.storage.database as _db_mod
_db_mod._writes_frozen = False
from ledgrab.api import dependencies as deps
store_clearers = [
(deps.get_device_store, "get_all_devices", "delete_device"),
(deps.get_output_target_store, "get_all_targets", "delete_target"),
(deps.get_color_strip_store, "get_all_sources", "delete_source"),
(deps.get_value_source_store, "get_all", "delete"),
(deps.get_sync_clock_store, "get_all", "delete"),
(deps.get_automation_store, "get_all", "delete"),
(deps.get_scene_preset_store, "get_all", "delete"),
(deps.get_asset_store, "get_all_assets", "delete_asset"),
]
for getter, list_method, delete_method in store_clearers:
try:
store = getter()
items = getattr(store, list_method)()
for item in items:
item_id = getattr(item, "id", getattr(item, "device_id", None))
if item_id:
try:
getattr(store, delete_method)(item_id)
except Exception:
pass
except RuntimeError:
pass # Store not initialized yet