"""Shared fixtures for end-to-end API tests. Uses the real FastAPI app with a module-scoped TestClient to avoid repeated lifespan startup/shutdown issues. Each test function gets fresh, empty stores via the _clear_stores helper. """ import pytest from wled_controller.config import get_config # Resolve the API key from the real config (same key used in production tests) _config = get_config() API_KEY = next(iter(_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. """ from fastapi.testclient import TestClient from wled_controller.main import app with TestClient(app, raise_server_exceptions=False) as c: yield c @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.""" from wled_controller.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"), ] 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