"""Offline test rig: a fake backend fed from JSON fixtures. Same idea as scripts/ha/tests/test_import_api_offline.sh — no network, no HA instance, no token. The fixtures under tests/fixtures/ are shaped exactly like real `/api/states` and WebSocket registry payloads (trimmed down); read_automation tests read the real repo automations, which is offline too. """ import json import sys from pathlib import Path import pytest SERVICE_DIR = Path(__file__).resolve().parents[1] REPO_ROOT = SERVICE_DIR.parents[1] FIXTURES = Path(__file__).resolve().parent / "fixtures" sys.path.insert(0, str(SERVICE_DIR / "src")) from ha_mcp import backend as backend_mod # noqa: E402 def load_fixture(name): with open(FIXTURES / name, encoding="utf-8") as f: return json.load(f) class FakeBackend: """Stands in for LiveBackend: canned states/config/registries, real repo files.""" def __init__(self, instance="ken", states=None, registries=None, config=None, area_source="websocket", automations_instance=None): self.instance = instance self.cfg = { "adapter": "api", "base_url": "http://ha.example:8123", "status": "active", "site": "ken", "token_path": "/nonexistent", } self._states = load_fixture("states.json") if states is None else states reg = load_fixture("registries.json") if registries is None else registries self._config = config or { "version": "2026.7.2", "location_name": "KEN", "time_zone": "Europe/Warsaw", } self._index = backend_mod._index_from_registries( reg.get("areas"), reg.get("entities"), reg.get("devices"), area_source ) self._automations_instance = automations_instance or instance @property def status(self): return self.cfg.get("status") or "unknown" def states(self): return self._states def ha_config(self): return self._config def area_index(self): return self._index def automation_files(self): return backend_mod.automation_files(self._automations_instance, REPO_ROOT) class UnreachableBackend(FakeBackend): """Live calls fail; repo-backed calls still work (read_automation offline path).""" def states(self): raise backend_mod.BackendError("instance 'ken' unreachable at http://ha.example:8123") def ha_config(self): raise backend_mod.BackendError("instance 'ken' unreachable at http://ha.example:8123") @pytest.fixture def fake_backend(): return FakeBackend() @pytest.fixture def unreachable_backend(): return UnreachableBackend() @pytest.fixture def repo_root(): return REPO_ROOT @pytest.fixture(scope="session") def real_ken_backend(): """FakeBackend over the newest committed /api/states snapshot of `ken`. Still offline (the fixture is a file in the repo), but ~1650 real entities instead of ten — ranking behaves differently at that scale. Areas come from the storage-export fallback, same as a live run with the WebSocket unavailable. """ import yaml from ha_mcp import backend as bm snapshots = sorted((REPO_ROOT / "services" / "home-assistant" / "fixtures").glob("ken-states-*.yaml")) if not snapshots: pytest.skip("no ken-states-*.yaml fixture in the repo") with open(snapshots[-1], encoding="utf-8") as f: states = yaml.safe_load(f) backend = FakeBackend(states=states) backend._index = bm.load_offline_area_index("ken", REPO_ROOT) return backend