test: independent explicit oracle + true zero-change + child-scoped override (NQ-84-01..04)
This commit is contained in:
+193
-131
@@ -1113,99 +1113,158 @@ def test_regenerate_for_child_is_child_scoped(tmp_path) -> None:
|
|||||||
_with_db(tmp_path, "regenerate-child-scoped.db")(_body)
|
_with_db(tmp_path, "regenerate-child-scoped.db")(_body)
|
||||||
|
|
||||||
|
|
||||||
def _is_nth_weekday(day, *, weekday, nth) -> bool:
|
#: Fixed end-to-end anchor: 2026-06-01 is a Monday AND the 1st of its month,
|
||||||
"""True when ``day`` is the ``nth`` (1-based) ``weekday`` 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
|
||||||
|
#: (pin ``today=anchor`` throughout so the walk never clamps this fixed
|
||||||
|
#: calendar date up to the host clock).
|
||||||
|
_E2E_ANCHOR = "2026-06-01"
|
||||||
|
|
||||||
Independent calendar arithmetic (not ``occurs_on``): the month's
|
#: Hand-checked oracle over 2026-06-01 .. 2026-06-28 (69 entries). Each row
|
||||||
1st anchors the count, so the target day-of-month is
|
#: is one independent (definition, child, window, due_date, due_time); the
|
||||||
``1 + first_weekday_gap + (nth - 1) * 7``.
|
#: definition/child slugs are substituted with runtime ids, but the dates are
|
||||||
|
#: literal, reviewed by hand — no recurrence/presence math is re-run here.
|
||||||
|
#: Custody (2-week cycle anchored on the Monday): A present weeks 0 and 2,
|
||||||
|
#: B present weeks 1 and 3, C present every day.
|
||||||
|
_E2E_EXPECTED = [
|
||||||
|
# daily [A, C] morning 09:00, every day. A present weeks 0, 2.
|
||||||
|
("daily", "A", "morning", "2026-06-01", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-02", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-03", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-04", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-05", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-06", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-07", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-15", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-16", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-17", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-18", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-19", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-20", "09:00"),
|
||||||
|
("daily", "A", "morning", "2026-06-21", "09:00"),
|
||||||
|
# daily C is present every day: all 28 days.
|
||||||
|
("daily", "C", "morning", "2026-06-01", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-02", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-03", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-04", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-05", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-06", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-07", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-08", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-09", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-10", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-11", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-12", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-13", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-14", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-15", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-16", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-17", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-18", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-19", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-20", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-21", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-22", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-23", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-24", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-25", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-26", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-27", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-28", "09:00"),
|
||||||
|
# weekly [B, C] Mondays, morning 07:00 + evening 19:00.
|
||||||
|
# B present weeks 1, 3 -> Mondays 06-08 and 06-22.
|
||||||
|
("weekly", "B", "morning", "2026-06-08", "07:00"),
|
||||||
|
("weekly", "B", "evening", "2026-06-08", "19:00"),
|
||||||
|
("weekly", "B", "morning", "2026-06-22", "07:00"),
|
||||||
|
("weekly", "B", "evening", "2026-06-22", "19:00"),
|
||||||
|
# C present every Monday: 06-01, 06-08, 06-15, 06-22.
|
||||||
|
("weekly", "C", "morning", "2026-06-01", "07:00"),
|
||||||
|
("weekly", "C", "evening", "2026-06-01", "19:00"),
|
||||||
|
("weekly", "C", "morning", "2026-06-08", "07:00"),
|
||||||
|
("weekly", "C", "evening", "2026-06-08", "19:00"),
|
||||||
|
("weekly", "C", "morning", "2026-06-15", "07:00"),
|
||||||
|
("weekly", "C", "evening", "2026-06-15", "19:00"),
|
||||||
|
("weekly", "C", "morning", "2026-06-22", "07:00"),
|
||||||
|
("weekly", "C", "evening", "2026-06-22", "19:00"),
|
||||||
|
# month_day [C] the 15th, morning 08:00. 2026-06-15 is the 15th.
|
||||||
|
("month_day", "C", "morning", "2026-06-15", "08:00"),
|
||||||
|
# month_wd [C] 2nd Wednesday, morning 07:30. June 2026's 2nd
|
||||||
|
# Wednesday is 06-10 (the 1st is 06-03).
|
||||||
|
("month_wd", "C", "morning", "2026-06-10", "07:30"),
|
||||||
|
# yearly [C] 1st of June, afternoon 12:00. Fires on 06-01.
|
||||||
|
("yearly", "C", "afternoon", "2026-06-01", "12:00"),
|
||||||
|
# custom [A, C] Saturdays + Sundays, evening 20:00.
|
||||||
|
# A present weeks 0, 2 -> 06-06, 06-07, 06-20, 06-21.
|
||||||
|
("custom", "A", "evening", "2026-06-06", "20:00"),
|
||||||
|
("custom", "A", "evening", "2026-06-07", "20:00"),
|
||||||
|
("custom", "A", "evening", "2026-06-20", "20:00"),
|
||||||
|
("custom", "A", "evening", "2026-06-21", "20:00"),
|
||||||
|
# C present every weekend day.
|
||||||
|
("custom", "C", "evening", "2026-06-06", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-07", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-13", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-14", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-20", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-21", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-27", "20:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-28", "20:00"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The tuples that must disappear when Cleo (C) is overridden to ABSENT for
|
||||||
|
# 2026-06-04 .. 2026-06-06 (a mid-window stretch): her two daily chores and
|
||||||
|
# the Saturday-evening custom chore on 06-06.
|
||||||
|
_E2E_REMOVED = [
|
||||||
|
("daily", "C", "morning", "2026-06-04", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-05", "09:00"),
|
||||||
|
("daily", "C", "morning", "2026-06-06", "09:00"),
|
||||||
|
("custom", "C", "evening", "2026-06-06", "20:00"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_e2e(raw, def_ids, child_ids) -> set:
|
||||||
|
"""Substitute the oracle's definition/child slugs with runtime ids."""
|
||||||
|
return {
|
||||||
|
(def_ids[slug], child_ids[child], window, due_date, due_time)
|
||||||
|
for slug, child, window, due_date, due_time in raw
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def _collect_records(database, child_ids, start, end) -> set:
|
||||||
|
"""Return the full logical instance records over the children/range.
|
||||||
|
|
||||||
|
Each 7-tuple carries the primary key AND the ``generated_at`` snapshot
|
||||||
|
stamp, so comparing two of these sets proves true zero-change rather
|
||||||
|
than merely stable keys or an unchanged count.
|
||||||
"""
|
"""
|
||||||
first = datetime.date(day.year, day.month, 1)
|
|
||||||
first_gap = (weekday - first.weekday()) % 7
|
|
||||||
return day.day == 1 + first_gap + (nth - 1) * 7
|
|
||||||
|
|
||||||
|
|
||||||
def _e2e_expected(start_iso, def_ids, a_id, b_id, c_id) -> set:
|
|
||||||
"""Hand-derived expected (definition_id, child_id, window, due_date,
|
|
||||||
due_time) tuples over the 28-day window, one per
|
|
||||||
(definition, child, window, firing date).
|
|
||||||
|
|
||||||
Custody uses anchor-date arithmetic on the Monday ``start``: cycle
|
|
||||||
week = ``floor(offset / 7) % 2``. A is present cycle-weeks 0 and 2
|
|
||||||
(offsets 0..6, 14..20); B is present cycle-weeks 1 and 3 (offsets
|
|
||||||
7..13, 21..27); C has no schedule so is present every day.
|
|
||||||
|
|
||||||
Six definitions, all anchored at ``start``:
|
|
||||||
|
|
||||||
- daily DAILY [A, C] morning 09:00
|
|
||||||
- weekly WEEKLY Mondays [B, C] morning 07:00 + evening 19:00
|
|
||||||
- month_day MONTHLY_DAY 15 [C] morning 08:00
|
|
||||||
- month_wd MONTHLY_WEEKDAY [C] morning 07:30 (2nd Wednesday)
|
|
||||||
- yearly YEARLY day 1 of [C] afternoon 12:00
|
|
||||||
the start month
|
|
||||||
- custom CUSTOM_DAYS [A, C] evening 20:00
|
|
||||||
Saturdays + Sundays
|
|
||||||
"""
|
|
||||||
expected: set = set()
|
|
||||||
start_date = datetime.date.fromisoformat(start_iso)
|
|
||||||
start_month = start_date.month
|
|
||||||
for offset in range(28):
|
|
||||||
day = start_date + datetime.timedelta(days=offset)
|
|
||||||
iso = day.isoformat()
|
|
||||||
week = offset // 7
|
|
||||||
a_present = week % 2 == 0
|
|
||||||
b_present = week % 2 == 1
|
|
||||||
monday = day.weekday() == 0
|
|
||||||
fifteenth = day.day == 15
|
|
||||||
second_wednesday = _is_nth_weekday(day, weekday=2, nth=2)
|
|
||||||
yearly_firing = day.month == start_month and day.day == 1
|
|
||||||
weekend = day.weekday() in (5, 6)
|
|
||||||
|
|
||||||
# DAILY: every day.
|
|
||||||
if a_present:
|
|
||||||
expected.add((def_ids["daily"], a_id, "morning", iso, "09:00"))
|
|
||||||
expected.add((def_ids["daily"], c_id, "morning", iso, "09:00"))
|
|
||||||
|
|
||||||
# WEEKLY: Mondays only, two windows each.
|
|
||||||
if monday:
|
|
||||||
if b_present:
|
|
||||||
expected.add((def_ids["weekly"], b_id, "morning", iso, "07:00"))
|
|
||||||
expected.add((def_ids["weekly"], b_id, "evening", iso, "19:00"))
|
|
||||||
expected.add((def_ids["weekly"], c_id, "morning", iso, "07:00"))
|
|
||||||
expected.add((def_ids["weekly"], c_id, "evening", iso, "19:00"))
|
|
||||||
|
|
||||||
# MONTHLY_DAY: the 15th of the month.
|
|
||||||
if fifteenth:
|
|
||||||
expected.add((def_ids["month_day"], c_id, "morning", iso, "08:00"))
|
|
||||||
|
|
||||||
# MONTHLY_WEEKDAY: the 2nd Wednesday of the month.
|
|
||||||
if second_wednesday:
|
|
||||||
expected.add((def_ids["month_wd"], c_id, "morning", iso, "07:30"))
|
|
||||||
|
|
||||||
# YEARLY: day 1 of the start month, same year.
|
|
||||||
if yearly_firing:
|
|
||||||
expected.add((def_ids["yearly"], c_id, "afternoon", iso, "12:00"))
|
|
||||||
|
|
||||||
# CUSTOM_DAYS: Saturdays and Sundays.
|
|
||||||
if weekend:
|
|
||||||
if a_present:
|
|
||||||
expected.add((def_ids["custom"], a_id, "evening", iso, "20:00"))
|
|
||||||
expected.add((def_ids["custom"], c_id, "evening", iso, "20:00"))
|
|
||||||
|
|
||||||
return expected
|
|
||||||
|
|
||||||
|
|
||||||
async def _collect_ids(database, child_ids, start, end) -> set:
|
|
||||||
"""Return the set of instance primary-key ids for the children/range."""
|
|
||||||
dao = QuestInstancesDao(database)
|
dao = QuestInstancesDao(database)
|
||||||
ids = set()
|
records = set()
|
||||||
for child_id in child_ids:
|
for child_id in child_ids:
|
||||||
for record in await dao.list_by_date_range(child_id, start, end):
|
for record in await dao.list_by_date_range(child_id, start, end):
|
||||||
ids.add(record.id)
|
records.add(
|
||||||
return ids
|
(
|
||||||
|
record.id,
|
||||||
|
record.definition_id,
|
||||||
|
record.child_id,
|
||||||
|
record.window,
|
||||||
|
record.due_date,
|
||||||
|
record.due_time,
|
||||||
|
record.generated_at,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
def test_materialize_end_to_end_six_rule_types(tmp_path) -> None:
|
def test_materialize_end_to_end_six_rule_types(tmp_path, monkeypatch) -> None:
|
||||||
|
import custom_components.nestquest.materialize as materialize_module
|
||||||
|
|
||||||
|
# Pin the batch stamp so the idempotency assertion can compare full
|
||||||
|
# records (generated_at included) deterministically across re-runs.
|
||||||
|
monkeypatch.setattr(
|
||||||
|
materialize_module,
|
||||||
|
"_now_stamp",
|
||||||
|
lambda: "2026-06-01T00:00:00+00:00",
|
||||||
|
)
|
||||||
|
|
||||||
async def _body(database):
|
async def _body(database):
|
||||||
from custom_components.nestquest.dao_presence import (
|
from custom_components.nestquest.dao_presence import (
|
||||||
PresenceOverridesDao,
|
PresenceOverridesDao,
|
||||||
@@ -1220,15 +1279,15 @@ def test_materialize_end_to_end_six_rule_types(tmp_path) -> None:
|
|||||||
b = await children.create("Bo", NOW) # present weeks 1, 3
|
b = await children.create("Bo", NOW) # present weeks 1, 3
|
||||||
c = await children.create("Cleo", NOW) # no schedule: always present
|
c = await children.create("Cleo", NOW) # no schedule: always present
|
||||||
|
|
||||||
start = _future_monday()
|
anchor = datetime.date.fromisoformat(_E2E_ANCHOR)
|
||||||
start_iso = start.isoformat()
|
start_iso = anchor.isoformat()
|
||||||
end_iso = (start + datetime.timedelta(days=27)).isoformat()
|
end_iso = (anchor + datetime.timedelta(days=27)).isoformat()
|
||||||
|
|
||||||
# Two-week custody with OPPOSITE weeks, encoded through the
|
# Two-week custody with OPPOSITE weeks, encoded through the
|
||||||
# PresenceSchedule model so the anchor-date arithmetic is explicit.
|
# PresenceSchedule model on the fixed Monday anchor.
|
||||||
all_week = frozenset(range(7))
|
all_week = frozenset(range(7))
|
||||||
a_schedule = PresenceSchedule(a.id, 2, start, {0: all_week, 1: frozenset()})
|
a_schedule = PresenceSchedule(a.id, 2, anchor, {0: all_week, 1: frozenset()})
|
||||||
b_schedule = PresenceSchedule(b.id, 2, start, {0: frozenset(), 1: all_week})
|
b_schedule = PresenceSchedule(b.id, 2, anchor, {0: frozenset(), 1: all_week})
|
||||||
await schedules.upsert_by_child(a.id, 2, start_iso, a_schedule.encode())
|
await schedules.upsert_by_child(a.id, 2, start_iso, a_schedule.encode())
|
||||||
await schedules.upsert_by_child(b.id, 2, start_iso, b_schedule.encode())
|
await schedules.upsert_by_child(b.id, 2, start_iso, b_schedule.encode())
|
||||||
|
|
||||||
@@ -1276,10 +1335,10 @@ def test_materialize_end_to_end_six_rule_types(tmp_path) -> None:
|
|||||||
)).definition.id
|
)).definition.id
|
||||||
def_ids["yearly"] = (await create_quest_definition(
|
def_ids["yearly"] = (await create_quest_definition(
|
||||||
database,
|
database,
|
||||||
"Yearly first-of-month chore",
|
"Yearly first-of-June chore",
|
||||||
ScheduleRule(
|
ScheduleRule(
|
||||||
rule_type=RuleType.YEARLY,
|
rule_type=RuleType.YEARLY,
|
||||||
month=start.month,
|
month=anchor.month,
|
||||||
day_of_month=1,
|
day_of_month=1,
|
||||||
start_date=start_iso,
|
start_date=start_iso,
|
||||||
),
|
),
|
||||||
@@ -1298,58 +1357,61 @@ def test_materialize_end_to_end_six_rule_types(tmp_path) -> None:
|
|||||||
[("evening", "20:00")],
|
[("evening", "20:00")],
|
||||||
)).definition.id
|
)).definition.id
|
||||||
|
|
||||||
expected = _e2e_expected(start_iso, def_ids, a.id, b.id, c.id)
|
child_ids = {"A": a.id, "B": b.id, "C": c.id}
|
||||||
child_ids = (a.id, b.id, c.id)
|
expected = _resolve_e2e(_E2E_EXPECTED, def_ids, child_ids)
|
||||||
|
removed = _resolve_e2e(_E2E_REMOVED, def_ids, child_ids)
|
||||||
|
all_children = (a.id, b.id, c.id)
|
||||||
|
|
||||||
# First run: every (definition, child, window, date) tuple, exactly.
|
# First run: every (definition, child, window, date) tuple, exactly.
|
||||||
first = await materialize(database, start_iso, end_iso)
|
first = await materialize(database, start_iso, end_iso, today=anchor)
|
||||||
assert first == len(expected)
|
assert first == len(expected)
|
||||||
assert await _collect_instances(
|
assert await _collect_instances(
|
||||||
database, child_ids, start_iso, end_iso
|
database, all_children, start_iso, end_iso
|
||||||
) == expected
|
) == expected
|
||||||
|
|
||||||
# Second run: idempotent — same physical rows, same primary keys.
|
# True zero-change: a re-run leaves every column — primary key AND
|
||||||
first_ids = await _collect_ids(
|
# generated_at — byte-for-byte unchanged across all 69 records.
|
||||||
database, child_ids, start_iso, end_iso
|
first_records = await _collect_records(
|
||||||
|
database, all_children, start_iso, end_iso
|
||||||
)
|
)
|
||||||
first_a_ids = await _collect_ids(database, (a.id,), start_iso, end_iso)
|
second = await materialize(database, start_iso, end_iso, today=anchor)
|
||||||
first_b_ids = await _collect_ids(database, (b.id,), start_iso, end_iso)
|
|
||||||
|
|
||||||
second = await materialize(database, start_iso, end_iso)
|
|
||||||
assert second == len(expected)
|
assert second == len(expected)
|
||||||
assert await _collect_ids(
|
assert await _collect_records(
|
||||||
database, child_ids, start_iso, end_iso
|
database, all_children, start_iso, end_iso
|
||||||
) == first_ids
|
) == first_records
|
||||||
|
|
||||||
# Absent override over Cleo's mid-window stretch days 3..5, then
|
# Capture Ada and Bo's complete records BEFORE the override, so we
|
||||||
# regenerate. regenerate_for_child rebuilds only a 14-day rolling
|
# can prove they are untouched afterward.
|
||||||
# horizon and deletes the child's tail beyond it, so re-materialize
|
a_before = await _collect_records(database, (a.id,), start_iso, end_iso)
|
||||||
# the full window afterward to restore the horizon and isolate the
|
b_before = await _collect_records(database, (b.id,), start_iso, end_iso)
|
||||||
# override's effect on exactly those three due dates.
|
|
||||||
absent_start = start + datetime.timedelta(days=3)
|
# Cleo is ABSENT 2026-06-04 .. 2026-06-06 (mid-window).
|
||||||
absent_end = start + datetime.timedelta(days=5)
|
|
||||||
await overrides.create(
|
await overrides.create(
|
||||||
c.id, absent_start.isoformat(), absent_end.isoformat(),
|
c.id, "2026-06-04", "2026-06-06", False, note="away"
|
||||||
False, note="away",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
await regenerate_for_child(database, c.id, today=start)
|
# regenerate_for_child deletes + rebuilds CLEO ONLY (Ada and Bo are
|
||||||
await materialize(database, start_iso, end_iso, today=start)
|
# never touched). It rebuilds a 14-day rolling horizon though, so
|
||||||
|
# the child-scoped materialize below restores the remainder of the
|
||||||
|
# 28-day window without ever writing another child.
|
||||||
|
await regenerate_for_child(database, c.id, today=anchor)
|
||||||
|
await materialize(
|
||||||
|
database, start_iso, end_iso, child_ids=[c.id], today=anchor
|
||||||
|
)
|
||||||
|
|
||||||
absent_dates = {
|
# Ada and Bo are byte-for-byte untouched: id, snapshot columns and
|
||||||
(absent_start + datetime.timedelta(days=i)).isoformat()
|
# generated_at alike.
|
||||||
for i in range(3)
|
assert await _collect_records(
|
||||||
}
|
database, (a.id,), start_iso, end_iso
|
||||||
removed = {t for t in expected if t[1] == c.id and t[3] in absent_dates}
|
) == a_before
|
||||||
|
assert await _collect_records(
|
||||||
|
database, (b.id,), start_iso, end_iso
|
||||||
|
) == b_before
|
||||||
|
|
||||||
# Unrelated children are byte-for-byte untouched (same ids).
|
# Exactly Cleo's override-covered tuples vanish; every other tuple
|
||||||
assert await _collect_ids(database, (a.id,), start_iso, end_iso) == first_a_ids
|
# survives.
|
||||||
assert await _collect_ids(database, (b.id,), start_iso, end_iso) == first_b_ids
|
|
||||||
|
|
||||||
# Exactly Cleo's (window, date) tuples inside the override vanish;
|
|
||||||
# every other tuple survives.
|
|
||||||
remaining = await _collect_instances(
|
remaining = await _collect_instances(
|
||||||
database, child_ids, start_iso, end_iso
|
database, all_children, start_iso, end_iso
|
||||||
)
|
)
|
||||||
assert remaining == expected - removed
|
assert remaining == expected - removed
|
||||||
assert remaining.isdisjoint(removed)
|
assert remaining.isdisjoint(removed)
|
||||||
|
|||||||
Reference in New Issue
Block a user