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
+13 -4
View File
@@ -14,6 +14,7 @@ from types import SimpleNamespace
from zoneinfo import ZoneInfo
from custom_components.nestquest.const import DEFAULT_HORIZON_DAYS
from custom_components.nestquest.core.settings import NestQuestSettings
from custom_components.nestquest.dao_children import ChildrenDao
from custom_components.nestquest.dao_instances import QuestInstancesDao
from custom_components.nestquest.db import NestQuestDatabase
@@ -147,7 +148,9 @@ def test_horizon_uses_newyork_local_date_after_local_midnight(
import custom_components.nestquest as nestquest
child_id = await _seed_daily(database, local_today.isoformat())
await nestquest._run_horizon_materialization(hass, database)
await nestquest._run_horizon_materialization(
hass, database, NestQuestSettings()
)
records = await QuestInstancesDao(database).list_by_date_range(
child_id,
local_today.isoformat(),
@@ -187,7 +190,9 @@ def test_horizon_uses_local_date_not_utc_when_host_ahead(
import custom_components.nestquest as nestquest
child_id = await _seed_daily(database, local_today.isoformat())
await nestquest._run_horizon_materialization(hass, database)
await nestquest._run_horizon_materialization(
hass, database, NestQuestSettings()
)
records = await QuestInstancesDao(database).list_by_date_range(
child_id,
local_today.isoformat(),
@@ -231,7 +236,9 @@ def test_horizon_resolves_spring_forward_day_without_off_by_one(
requested_batches.append(requested)
local_today = instant.date()
child_id = await _seed_daily(database, local_today.isoformat())
await nestquest._run_horizon_materialization(hass, database)
await nestquest._run_horizon_materialization(
hass, database, NestQuestSettings()
)
records = await QuestInstancesDao(database).list_by_date_range(
child_id,
local_today.isoformat(),
@@ -280,7 +287,9 @@ def test_horizon_resolves_fall_back_day_without_off_by_one(
requested_batches.append(requested)
local_today = instant.date()
child_id = await _seed_daily(database, local_today.isoformat())
await nestquest._run_horizon_materialization(hass, database)
await nestquest._run_horizon_materialization(
hass, database, NestQuestSettings()
)
records = await QuestInstancesDao(database).list_by_date_range(
child_id,
local_today.isoformat(),