Apply PR #144 review round 2: settings-based service kwargs, required settings, shim removal, hardened AST scan

P2: services._generation_kwargs now reads runtime.settings.horizon_days
instead of re-implementing the bool/non-int/<1 fallback against
runtime.options; drop the now-unused CONF_HORIZON_DAYS/DEFAULT_HORIZON_DAYS
imports. Remove NestQuestRuntimeData.options (kept only for that read; the
admin-id path reads entry.options directly) and the matching test assertion.

P3: _run_horizon_materialization's settings param is now REQUIRED (drop the
'| None = None' default + the NestQuestSettings() substitution branch);
update the test_timezones callers to pass NestQuestSettings() explicitly.

P3: __post_init__ normalises notify_target via _resolve_optional_str (strip)
so a hand-built NestQuestSettings(notify_target=' x ') behaves like the
options flow; keep the docstring accurate.

P3: delete the custom_components/nestquest/settings.py shim (settings is a
new module with no legacy import path) and import NestQuestSettings from
.core.settings directly in __init__.py, in alphabetical position.

P3: harden the AST scan for importlib.import_module('homeassistant...')
(string-based import) and call keyword names like hass= (ast.keyword);
add negative-control cases for each; explain in the failure message why
.config/.options/.data are banned (HA config-entry attributes with no
legitimate core use).

P3: drive from_options and from_options_resilient from one _FIELDS table of
(field, key, default, validator) to avoid three-way drift.

uv run pytest -q: 1573 passed.
This commit is contained in:
2026-09-21 15:42:26 -04:00
parent 33102f8806
commit d75ddc19bd
7 changed files with 221 additions and 176 deletions
+3 -3
View File
@@ -36,12 +36,12 @@ async def test_setup_stores_runtime_data_in_hass_data(hass, make_entry) -> None:
assert hass.data[DOMAIN][entry.entry_id] is entry.runtime_data
async def test_setup_runtime_data_has_entry_id_and_options(hass, make_entry) -> None:
async def test_setup_runtime_data_has_entry_id_and_settings(hass, make_entry) -> None:
entry = _wire(make_entry(options={"horizon_days": 7}), hass.registry)
await async_setup_entry(hass, entry)
runtime = entry.runtime_data
assert runtime.entry_id == entry.entry_id
assert runtime.options == {"horizon_days": 7}
assert runtime.settings.horizon_days == 7
assert callable(runtime.remove_update_listener)
@@ -534,7 +534,7 @@ def test_from_options_horizon_validates_and_defaults() -> None:
a malformed value RAISES (the integration's ``from_options_resilient``
handles the per-field fall-back at setup, not this strict path).
"""
from custom_components.nestquest.settings import NestQuestSettings
from custom_components.nestquest.core.settings import NestQuestSettings
assert NestQuestSettings.from_options({CONF_HORIZON_DAYS: 5}).horizon_days == 5
assert (