"""Story 4.4 — diagnostics whitelist + redaction (AD-5/NFR-6).""" from __future__ import annotations import json from datetime import timedelta from homeassistant.components.weather import WeatherEntityFeature from homeassistant.config_entries import ConfigSubentryData from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse from homeassistant.util import dt as dt_util from pytest_homeassistant_custom_component.common import MockConfigEntry from custom_components.what_to_wear.const import CONF_WEATHER_ENTITY, DOMAIN, default_options from custom_components.what_to_wear.diagnostics import async_get_config_entry_diagnostics ENTITY = "weather.home_muellerstrasse" SECRET_KEY = "sk-supersecret-12345" ITEM_NAME = "Michaels Arbeitshose" def _set_weather(hass) -> None: hass.states.async_set( ENTITY, "cloudy", {"temperature_unit": "°C", "wind_speed_unit": "km/h", "precipitation_unit": "mm", "supported_features": WeatherEntityFeature.FORECAST_DAILY | WeatherEntityFeature.FORECAST_HOURLY}, ) def _register_forecast(hass) -> None: async def handler(call) -> ServiceResponse: base = dt_util.now() entries = [{"datetime": (base + timedelta(days=o)).replace(hour=12), "temperature": 8.0, "templow": 5.0} for o in range(-1, 4)] return {call.data["entity_id"]: {"forecast": entries}} hass.services.async_register("weather", "get_forecasts", handler, supports_response=SupportsResponse.ONLY) def _item(name, cat): return ConfigSubentryData(data={"name": name, "category": cat, "warmth": 3}, subentry_type="item", title=name, unique_id=None) async def _setup(hass): _set_weather(hass) _register_forecast(hass) opts = default_options() opts.update({"llm_enabled": True, "llm_api_key": SECRET_KEY}) entry = MockConfigEntry( domain=DOMAIN, data={CONF_WEATHER_ENTITY: ENTITY}, options=opts, subentries_data=[_item(ITEM_NAME, "bottom"), _item("Shirt", "top"), _item("Boots", "shoes")], ) entry.add_to_hass(hass) assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() return entry async def test_diagnostics_never_leak_key_entity_or_names(hass: HomeAssistant) -> None: entry = await _setup(hass) diag = await async_get_config_entry_diagnostics(hass, entry) blob = json.dumps(diag, default=str) # the API key value must never appear assert SECRET_KEY not in blob # the weather entity id (a location hint) must never appear assert ENTITY not in blob # item names must never appear assert ITEM_NAME not in blob async def test_diagnostics_contains_useful_whitelist(hass: HomeAssistant) -> None: entry = await _setup(hass) diag = await async_get_config_entry_diagnostics(hass, entry) # per-category counts (not names) assert diag["item_counts"] == {"bottom": 1, "top": 1, "shoes": 1} assert "recommendation" in diag assert diag["recommendation"]["status"] == "ok" assert "metrics" in diag["recommendation"] # options present as a whitelist without the secret key assert "llm_api_key" not in diag["options"] assert "data" not in diag # entry.data (weather entity id) not dumped at all assert "source" not in diag["recommendation"] # entity id never included