what_to_wear/tests/test_example_set.py
Nora 7d2f434828 feat(2.2): Beispiel-Kleiderschrank (12 §8-Stücke beim Setup)
Story 2.2 (TDD, phcc, Suite gruen 118/118, stabil):
- logic/texts.py: example_items(lang) — 12 §8-Stücke, Namen de/en lokalisiert,
  englisches Schema, Handschuhe bewusst fehlend (J4-Luecke, FR-1.5).
- config_flow.py: create_example-Checkbox -> async_create_entry(subentries=),
  nur im initialen user-Step (single_config_entry -> kein Duplikat).
- tests: §8-Frost/Regen-Abnahmetest mit echtem Set; conftest entlaedt Entries
  (kein Coordinator-Timer-Flake unter Randomisierung).
- luna-pro-Story-Review: 2/2 Findings uebernommen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:36:43 +00:00

98 lines
4.4 KiB
Python

"""Story 2.2 — example wardrobe (§8) at setup."""
from __future__ import annotations
from datetime import date, datetime, timedelta, timezone
from homeassistant.components.weather import WeatherEntityFeature
from homeassistant.config_entries import SOURCE_USER
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.util import dt as dt_util
from custom_components.what_to_wear.const import CONF_WEATHER_ENTITY, DOMAIN
from custom_components.what_to_wear.logic import matcher, pipeline, rules, texts
from custom_components.what_to_wear.logic.model import Category, Item, NormField, NormForecast, RequirementKey
from custom_components.what_to_wear import const
ENTITY = "weather.home"
UTC = timezone.utc
def test_example_items_count_and_gloves_absent() -> None:
de = texts.example_items("de")
en = texts.example_items("en")
assert len(de) == 12 and len(en) == 12
# no gloves (hands) — the deliberate J4 learning gap (§8)
assert all(i["category"] != "hands" for i in de)
assert {i["name"] for i in de} >= {"Thermoshirt", "Regenjacke", "Wasserdichte Stiefel"}
# localized names differ
assert de[0]["name"] != en[0]["name"]
def test_example_set_frost_rain_acceptance() -> None:
# §8 acceptance: example set + frost/rain -> full outfit, only gap gloves.
items = [
Item(id=str(i), name=d["name"], category=Category(d["category"]), warmth=d["warmth"],
waterproof=d.get("waterproof", False), windproof=d.get("windproof", False),
temp_min=d.get("temp_min"), temp_max=d.get("temp_max"))
for i, d in enumerate(texts.example_items("de"))
]
nf = NormForecast(
feels_like_morning=NormField(-1.0, "hourly", None),
temp_morning=NormField(-1.0, "hourly", None),
temp_min=NormField(-2.0, "daily", None), temp_max=NormField(1.0, "daily", None),
feels_like_min=NormField(None, None, None), feels_like_max=NormField(None, None, None),
rain_probability=NormField(70.0, "daily", None), rain_amount=NormField(None, None, None),
wind=NormField(10.0, "daily", None), gust=NormField(None, None, None),
uv_index=NormField(None, None, None), condition="regen",
)
result = rules.evaluate(nf, const.default_options())
outfit = matcher.compose(result.requirements, items, -1.0, 1.0)
names = {ci.item.name for ci in outfit.items}
assert names == {"Thermoshirt", "Wollpullover", "Regenjacke", "Jeans",
"Wasserdichte Stiefel", "Mütze", "Schal"}
assert {g.key for g in outfit.gaps} == {RequirementKey.GLOVES}
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)
async def test_flow_creates_example_subentries(hass: HomeAssistant) -> None:
_set_weather(hass)
_register_forecast(hass)
result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_USER})
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_WEATHER_ENTITY: ENTITY, "create_example": True}
)
assert result2["type"] == FlowResultType.CREATE_ENTRY
entry = result2["result"]
items = [s for s in entry.subentries.values() if s.subentry_type == "item"]
assert len(items) == 12
async def test_flow_without_example_has_no_items(hass: HomeAssistant) -> None:
_set_weather(hass)
_register_forecast(hass)
result = await hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_USER})
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_WEATHER_ENTITY: ENTITY, "create_example": False}
)
entry = result2["result"]
items = [s for s in entry.subentries.values() if s.subentry_type == "item"]
assert len(items) == 0