"""Integration tests for SystemHealthCheck using aioresponses. Uses real aiosqlite Storage + EventEmitter + mocked HTTP. Marked 'integration' because it exercises the full stack end-to-end. """ from __future__ import annotations import json from pathlib import Path from typing import AsyncGenerator import pytest import pytest_asyncio from aioresponses import aioresponses from ha_diag.checks.system_health import SystemHealthCheck from ha_diag.config import Settings from ha_diag.event_emitter import EventEmitter from ha_diag.ha_client import HAClient, make_session from ha_diag.models import HAEventType from ha_diag.storage import Storage HA_URL = "http://ha-test-ken:8123" def _settings(**overrides) -> Settings: defaults: dict = { "ha_url": HA_URL, "ha_token": "test-token", "node_name": "piha", "location_tag": "ken", "alert_cooldown_hours": 0.0, "check_interval": 60, "check_interval_unavailable": 3600, } defaults.update(overrides) return Settings(**defaults) @pytest_asyncio.fixture async def storage(tmp_path: Path) -> AsyncGenerator[Storage, None]: s = Storage(tmp_path / "integration_test.db") await s.open() yield s await s.close() @pytest.fixture def events_dir(tmp_path: Path) -> Path: d = tmp_path / "events" d.mkdir() return d @pytest.mark.integration async def test_system_health_ok_components_no_event( storage: Storage, events_dir: Path ): """All components healthy on first run → no events emitted.""" health = { "homeassistant": {"type": "result", "data": {"version": "2025.5.0"}}, "recorder": {"type": "result", "data": {"backlog": 0}}, } emitter = EventEmitter(events_dir, node_name="piha", location_tag="ken") with aioresponses() as m: m.get(f"{HA_URL}/api/system_health", payload=health) async with make_session("test-token") as session: client = HAClient(HA_URL, session) check = SystemHealthCheck(client, storage, _settings()) results = await check.run() assert results == [] assert not list(events_dir.glob("*.json")) @pytest.mark.integration async def test_system_health_degraded_emits_event_and_writes_file( storage: Storage, events_dir: Path ): """Component degrades: event emitted + file written with correct structure.""" # First run: all ok health_ok = {"cloud": {"type": "result", "data": {}}} health_err = {"cloud": {"type": "error", "error": "Cloud connection lost"}} emitter = EventEmitter(events_dir, node_name="piha", location_tag="ken") with aioresponses() as m: m.get(f"{HA_URL}/api/system_health", payload=health_ok) async with make_session("test-token") as session: client = HAClient(HA_URL, session) await SystemHealthCheck(client, storage, _settings()).run() # Second run: cloud errors with aioresponses() as m: m.get(f"{HA_URL}/api/system_health", payload=health_err) async with make_session("test-token") as session: client = HAClient(HA_URL, session) check = SystemHealthCheck(client, storage, _settings()) results = await check.run() assert len(results) == 1 assert results[0].event_type == HAEventType.ha_system_health_degraded emitter.emit( event_type=results[0].event_type, severity=results[0].severity.value, service="homeassistant", message=results[0].message, payload=results[0].payload, ) files = list(events_dir.glob("*.json")) assert len(files) == 1 data = json.loads(files[0].read_text()) assert data["type"] == "ha_system_health_degraded" assert data["payload"]["component"] == "cloud" assert data["payload"]["location_tag"] == "ken" @pytest.mark.integration async def test_system_health_recovery_and_re_degradation(storage: Storage): """Full ok→error→ok→error cycle: events fire on degradation, not on recovery.""" def _run(health): with aioresponses() as m: m.get(f"{HA_URL}/api/system_health", payload=health) return make_session("test-token"), health settings = _settings() async def run_once(health): with aioresponses() as m: m.get(f"{HA_URL}/api/system_health", payload=health) async with make_session("test-token") as session: return await SystemHealthCheck( HAClient(HA_URL, session), storage, settings ).run() ok_h = {"cloud": {"type": "result", "data": {}}} err_h = {"cloud": {"type": "error", "error": "timeout"}} r1 = await run_once(ok_h) # baseline ok r2 = await run_once(err_h) # first degradation r3 = await run_once(err_h) # sustained error (no dup) r4 = await run_once(ok_h) # recovery r5 = await run_once(err_h) # second degradation assert r1 == [] assert len(r2) == 1 assert r3 == [] assert r4 == [] assert len(r5) == 1