feat: thread configured horizon_days through materialization + regeneration

This commit is contained in:
2026-09-17 16:51:03 -04:00
parent c001aca62d
commit 0900a29220
5 changed files with 231 additions and 32 deletions
+76
View File
@@ -10,6 +10,7 @@ from conftest import make_config_entry, make_hass, wire_entry_to_registry
from custom_components.nestquest import DOMAIN, async_setup_entry, async_unload_entry
from custom_components.nestquest.const import (
CONF_DAY_ROLLOVER_TIME,
CONF_HORIZON_DAYS,
DEFAULT_HORIZON_DAYS,
SERVICE_REGENERATE,
DOMAIN as DOMAIN_CONST,
@@ -447,6 +448,55 @@ async def test_setup_backfills_instances_over_horizon(hass, make_entry) -> None:
assert len(records) == DEFAULT_HORIZON_DAYS + 1
async def test_setup_backfills_configured_horizon_days(hass, make_entry) -> None:
"""Setup backfills only the configured (non-default) horizon window."""
import datetime
from custom_components.nestquest.dao_instances import QuestInstancesDao
from custom_components.nestquest.db import NestQuestDatabase
from custom_components.nestquest.migrations import apply_migrations
from custom_components.nestquest.store import async_get_db_path
db_path = await async_get_db_path(hass)
pre = NestQuestDatabase(hass)
await pre.open(db_path)
await apply_migrations(pre)
child_id = await _seed_daily_child_and_definition(pre, _local_today(hass))
await pre.close()
entry = _wire(
make_entry(options={CONF_HORIZON_DAYS: 5}), hass.registry
)
assert await async_setup_entry(hass, entry) is True
today = _local_today(hass)
end = (today + datetime.timedelta(days=5)).isoformat()
records = await QuestInstancesDao(entry.runtime_data.database).list_by_date_range(
child_id, today.isoformat(), end
)
assert len(records) == 6
def test_configured_horizon_days_validates_and_falls_back() -> None:
"""The configured horizon is validated; a malformed value uses the default."""
import custom_components.nestquest as nestquest
assert nestquest._configured_horizon_days({CONF_HORIZON_DAYS: 5}) == 5
assert nestquest._configured_horizon_days({}) == DEFAULT_HORIZON_DAYS
assert (
nestquest._configured_horizon_days({CONF_HORIZON_DAYS: True})
== DEFAULT_HORIZON_DAYS
)
assert (
nestquest._configured_horizon_days({CONF_HORIZON_DAYS: "5"})
== DEFAULT_HORIZON_DAYS
)
assert (
nestquest._configured_horizon_days({CONF_HORIZON_DAYS: 0})
== DEFAULT_HORIZON_DAYS
)
async def test_regenerate_service_registered_and_materializes(
hass, make_entry
) -> None:
@@ -474,6 +524,32 @@ async def test_regenerate_service_registered_and_materializes(
assert len(records) == DEFAULT_HORIZON_DAYS + 1
async def test_regenerate_service_uses_configured_horizon_days(
hass, make_entry
) -> None:
"""``nestquest.regenerate`` regenerates only the configured horizon."""
import datetime
from custom_components.nestquest.dao_instances import QuestInstancesDao
entry = _wire(
make_entry(options={CONF_HORIZON_DAYS: 5}), hass.registry
)
assert await async_setup_entry(hass, entry) is True
database = entry.runtime_data.database
child_id = await _seed_daily_child_and_definition(database, _local_today(hass))
await hass.services.call(DOMAIN, SERVICE_REGENERATE)
today = _local_today(hass)
end = (today + datetime.timedelta(days=5)).isoformat()
records = await QuestInstancesDao(database).list_by_date_range(
child_id, today.isoformat(), end
)
assert len(records) == 6
async def test_unload_deregisters_regenerate_service(hass, make_entry) -> None:
"""Unload removes the ``regenerate`` service so no stale entry leaks."""
entry = _wire(make_entry(), hass.registry)
+64
View File
@@ -1113,6 +1113,70 @@ def test_regenerate_for_child_is_child_scoped(tmp_path) -> None:
_with_db(tmp_path, "regenerate-child-scoped.db")(_body)
def test_regenerate_for_definition_respects_horizon_days(tmp_path) -> None:
"""A non-default horizon_days sizes the definition's rebuild window."""
async def _body(database):
children = ChildrenDao(database)
child = await children.create("Ada", NOW)
today = datetime.date.today()
start_iso = today.isoformat()
created = await create_quest_definition(
database,
"Daily chore",
ScheduleRule(rule_type=RuleType.DAILY, start_date=start_iso),
[child.id],
[("morning", "09:00")],
)
# ``create`` does not materialize; regeneration over a 5-day horizon
# must build exactly today..today+5 (six daily rows).
await regenerate_for_definition(
database, created.definition.id, horizon_days=5
)
dao = QuestInstancesDao(database)
horizon_end = today + datetime.timedelta(days=5)
records = await dao.list_by_date_range(
child.id, start_iso, horizon_end.isoformat()
)
assert len(records) == 6
return None
_with_db(tmp_path, "regenerate-def-horizon.db")(_body)
def test_regenerate_for_child_respects_horizon_days(tmp_path) -> None:
"""A non-default horizon_days sizes the child's rebuild window."""
async def _body(database):
children = ChildrenDao(database)
child = await children.create("Ada", NOW)
today = datetime.date.today()
start_iso = today.isoformat()
await create_quest_definition(
database,
"Daily chore",
ScheduleRule(rule_type=RuleType.DAILY, start_date=start_iso),
[child.id],
[("morning", "09:00")],
)
await regenerate_for_child(database, child.id, horizon_days=5)
dao = QuestInstancesDao(database)
horizon_end = today + datetime.timedelta(days=5)
records = await dao.list_by_date_range(
child.id, start_iso, horizon_end.isoformat()
)
assert len(records) == 6
return None
_with_db(tmp_path, "regenerate-child-horizon.db")(_body)
#: Fixed end-to-end anchor: 2026-06-01 is a Monday AND the 1st of its month,
#: so the two-week custody cycle is week-aligned and the yearly rule's
#: "1st of the anchor month" is GUARANTEED to fire inside the 28-day window