test: correct zero-change contract (distinct stamps, stable keys/ids)

This commit is contained in:
2026-09-17 16:37:51 -04:00
parent 6024eac529
commit a0c3d21742
+46 -17
View File
@@ -1229,12 +1229,36 @@ def _resolve_e2e(raw, def_ids, child_ids) -> set:
} }
async def _collect_records(database, child_ids, start, end) -> set: async def _collect_keyed(database, child_ids, start, end) -> dict:
"""Return the full logical instance records over the children/range. """Return {(definition_id, child_id, window, due_date): (id, due_time)}.
Each 7-tuple carries the primary key AND the ``generated_at`` snapshot The stable parts of an instance — its widened key, its physical id and
stamp, so comparing two of these sets proves true zero-change rather its due_time snapshot — with the refreshable ``generated_at`` stamp
than merely stable keys or an unchanged count. deliberately excluded. Comparing two of these captures the real
re-run contract: no rows added/removed, ids stable (upsert refreshes
in place, never delete + reinsert), and due_time untouched.
"""
dao = QuestInstancesDao(database)
keyed = {}
for child_id in child_ids:
for record in await dao.list_by_date_range(child_id, start, end):
key = (
record.definition_id,
record.child_id,
record.window,
record.due_date,
)
keyed[key] = (record.id, record.due_time)
return keyed
async def _collect_records(database, child_ids, start, end) -> set:
"""Return the full 7-field instance records over the children/range.
Includes the ``generated_at`` stamp, so this is only used for children
that should NEVER be written (Ada/Bo during Cleo's override): their
records are truly byte-for-byte untouched, unlike the idempotency
re-run where the stamp legitimately refreshes.
""" """
dao = QuestInstancesDao(database) dao = QuestInstancesDao(database)
records = set() records = set()
@@ -1257,13 +1281,17 @@ async def _collect_records(database, child_ids, start, end) -> set:
def test_materialize_end_to_end_six_rule_types(tmp_path, monkeypatch) -> None: def test_materialize_end_to_end_six_rule_types(tmp_path, monkeypatch) -> None:
import custom_components.nestquest.materialize as materialize_module import custom_components.nestquest.materialize as materialize_module
# Pin the batch stamp so the idempotency assertion can compare full # Give each materialize run a DISTINCT batch stamp: production upserts
# records (generated_at included) deterministically across re-runs. # rewrite generated_at on every re-run, so a shared pinned stamp would
monkeypatch.setattr( # merely hide that rewrite. The idempotency assertion below therefore
materialize_module, # ignores generated_at and checks the stable columns only.
"_now_stamp", stamps = {"n": 0}
lambda: "2026-06-01T00:00:00+00:00",
) def _distinct_stamp() -> str:
stamps["n"] += 1
return f"2026-06-01T00:00:{stamps['n']:02d}+00:00"
monkeypatch.setattr(materialize_module, "_now_stamp", _distinct_stamp)
async def _body(database): async def _body(database):
from custom_components.nestquest.dao_presence import ( from custom_components.nestquest.dao_presence import (
@@ -1369,16 +1397,17 @@ def test_materialize_end_to_end_six_rule_types(tmp_path, monkeypatch) -> None:
database, all_children, start_iso, end_iso database, all_children, start_iso, end_iso
) == expected ) == expected
# True zero-change: a re-run leaves every column — primary key AND # Re-run contract: the upsert refreshes each existing row IN PLACE,
# generated_at — byte-for-byte unchanged across all 69 records. # so the widened key set, the physical ids and each due_time are all
first_records = await _collect_records( # stable (only generated_at may refresh to the new run's stamp).
first_keyed = await _collect_keyed(
database, all_children, start_iso, end_iso database, all_children, start_iso, end_iso
) )
second = await materialize(database, start_iso, end_iso, today=anchor) second = await materialize(database, start_iso, end_iso, today=anchor)
assert second == len(expected) assert second == len(expected)
assert await _collect_records( assert await _collect_keyed(
database, all_children, start_iso, end_iso database, all_children, start_iso, end_iso
) == first_records ) == first_keyed
# Capture Ada and Bo's complete records BEFORE the override, so we # Capture Ada and Bo's complete records BEFORE the override, so we
# can prove they are untouched afterward. # can prove they are untouched afterward.