Story 1.1 (TDD, Suite gruen 19/19): - logic/model.py: frozen Dataclasses (NormField, RawForecast, NormForecast, Requirement, Item, ChosenItem, Gap, Outfit, Recommendation) + Enums RequirementKey/Category/Priority (AD-14/21/22). - const.py: normatives flaches Options-Schema + default_options()-Deepcopy, TO_REDACT, Timeouts, LLM-URLs (AD-5/6/23). - manifest.json/hacs.json (min HA 2025.3), Layering-Guard per AST (AD-1). - luna-pro-Story-Review: 4/6 Findings uebernommen, 2 verworfen (Evidenz im Ledger). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
69 lines
2.3 KiB
Python
69 lines
2.3 KiB
Python
"""Story 1.1 — the normative options schema lives once in const.py (AD-23)."""
|
|
from __future__ import annotations
|
|
|
|
from custom_components.what_to_wear import const
|
|
|
|
|
|
def test_domain() -> None:
|
|
assert const.DOMAIN == "what_to_wear"
|
|
|
|
|
|
def test_options_schema_keys_flat_english() -> None:
|
|
keys = set(const.OPTIONS_DEFAULTS)
|
|
expected = {
|
|
"warmth_band_limits",
|
|
"heat_threshold",
|
|
"rain_prob_should",
|
|
"rain_prob_must",
|
|
"rain_amount_should",
|
|
"rain_amount_must",
|
|
"gust_should",
|
|
"wind_proxy_should",
|
|
"uv_should",
|
|
"cold_sensitivity_offset",
|
|
"switchover_time",
|
|
"llm_enabled",
|
|
"llm_provider",
|
|
"llm_api_key",
|
|
"llm_model",
|
|
}
|
|
assert expected <= keys, f"missing option keys: {expected - keys}"
|
|
|
|
|
|
def test_warmth_band_limits_strictly_monotone_length_four() -> None:
|
|
limits = const.OPTIONS_DEFAULTS["warmth_band_limits"]
|
|
assert len(limits) == 4
|
|
assert all(a < b for a, b in zip(limits, limits[1:])), "must be strictly monotone"
|
|
|
|
|
|
def test_defaults_sane() -> None:
|
|
d = const.OPTIONS_DEFAULTS
|
|
assert d["cold_sensitivity_offset"] == 0
|
|
assert d["llm_enabled"] is False
|
|
assert d["switchover_time"] == "10:00:00"
|
|
assert d["heat_threshold"] == 28.0
|
|
|
|
|
|
def test_default_options_returns_independent_deep_copy() -> None:
|
|
a = const.default_options()
|
|
b = const.default_options()
|
|
a["warmth_band_limits"].append(99.0)
|
|
a["cold_sensitivity_offset"] = 2
|
|
# Mutating one copy must not affect another copy nor the template.
|
|
assert b["warmth_band_limits"] == [0.0, 8.0, 15.0, 22.0]
|
|
assert b["cold_sensitivity_offset"] == 0
|
|
assert const.OPTIONS_DEFAULTS["warmth_band_limits"] == [0.0, 8.0, 15.0, 22.0]
|
|
|
|
|
|
def test_to_redact_uses_real_key_names() -> None:
|
|
# AD-5: redaction constant must match the real stored key names.
|
|
assert const.TO_REDACT == {"llm_api_key", "weather_entity_id"}
|
|
|
|
|
|
def test_timeouts_and_urls_present() -> None:
|
|
assert const.FORECAST_TIMEOUT_S == 10
|
|
assert const.LLM_TIMEOUT_S == 18
|
|
assert const.LLM_MAX_RESPONSE_BYTES == 64 * 1024
|
|
assert const.OPENAI_URL.startswith("https://api.openai.com/")
|
|
assert const.ANTHROPIC_URL.startswith("https://api.anthropic.com/")
|
|
assert const.ANTHROPIC_VERSION == "2023-06-01"
|