"""Integration tests for UpdatesAvailableCheck and UpdatesDigestCheck. Uses real aiosqlite Storage + EventEmitter + mocked HTTP. """ 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.updates_available import UpdatesAvailableCheck, UpdatesDigestCheck 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, "updates_cooldown_days": 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 def _update_states(*entity_ids: str) -> list[dict]: return [ { "entity_id": eid, "state": "on", "attributes": { "title": eid.split(".")[-1].replace("_", " ").title(), "installed_version": "1.0.0", "latest_version": "2.0.0", "in_progress": False, "auto_update": False, }, } for eid in entity_ids ] @pytest.mark.integration async def test_individual_updates_written_to_disk(storage: Storage, events_dir: Path): """2 pending updates → 2 event files with correct structure.""" states = _update_states("update.ha_core", "update.mosquitto") emitter = EventEmitter(events_dir, node_name="piha", location_tag="ken") with aioresponses() as m: m.get(f"{HA_URL}/api/states", payload=states) async with make_session("test-token") as session: client = HAClient(HA_URL, session) check = UpdatesAvailableCheck(client, storage, _settings()) results = await check.run() assert len(results) == 2 for r in results: assert r.event_type == HAEventType.ha_update_available emitter.emit( event_type=r.event_type, severity=r.severity.value, service="homeassistant", message=r.message, payload=r.payload, ) files = list(events_dir.glob("*.json")) assert len(files) == 2 for f in files: data = json.loads(f.read_text()) assert data["type"] == "ha_update_available" assert data["payload"]["location_tag"] == "ken" assert "entity_id" in data["payload"] @pytest.mark.integration async def test_digest_writes_single_event_file(storage: Storage, events_dir: Path): """Sunday digest → single event file with digest=True payload.""" states = _update_states("update.ha_core", "update.mosquitto", "update.esphome") emitter = EventEmitter(events_dir, node_name="piha", location_tag="ken") with aioresponses() as m: m.get(f"{HA_URL}/api/states", payload=states) async with make_session("test-token") as session: client = HAClient(HA_URL, session) check = UpdatesDigestCheck(client, storage, _settings()) results = await check.run() assert len(results) == 1 r = results[0] assert r.payload["digest"] is True assert r.payload["count"] == 3 emitter.emit( event_type=r.event_type, severity=r.severity.value, service="homeassistant", message=r.message, payload=r.payload, ) files = list(events_dir.glob("*.json")) assert len(files) == 1 data = json.loads(files[0].read_text()) assert data["payload"]["digest"] is True assert len(data["payload"]["updates"]) == 3 @pytest.mark.integration async def test_dedup_across_daily_and_digest_independent(storage: Storage): """Daily dedup key doesn't suppress digest, and vice versa.""" states = _update_states("update.ha_core") settings = _settings(updates_cooldown_days=7) # Daily check with aioresponses() as m: m.get(f"{HA_URL}/api/states", payload=states) async with make_session("test-token") as session: r1 = await UpdatesAvailableCheck( HAClient(HA_URL, session), storage, settings ).run() assert len(r1) == 1 # Daily again — cooldown active with aioresponses() as m: m.get(f"{HA_URL}/api/states", payload=states) async with make_session("test-token") as session: r2 = await UpdatesAvailableCheck( HAClient(HA_URL, session), storage, settings ).run() assert r2 == [] # Digest — different key, should still fire with aioresponses() as m: m.get(f"{HA_URL}/api/states", payload=states) async with make_session("test-token") as session: r3 = await UpdatesDigestCheck( HAClient(HA_URL, session), storage, settings ).run() assert len(r3) == 1 assert r3[0].payload["digest"] is True