Compute was_on_time at completion (Feature 08, task 5) (#92)
This commit was merged in pull request #92.
This commit is contained in:
@@ -687,6 +687,207 @@ def test_complete_instance_concurrent_second_call_is_noop(tmp_path) -> None:
|
||||
_run(_main())
|
||||
|
||||
|
||||
_HA_TZ = datetime.timezone(datetime.timedelta(hours=-8))
|
||||
|
||||
|
||||
def _ha_local(day: datetime.date, hour: int, minute: int = 0) -> datetime.datetime:
|
||||
return datetime.datetime(
|
||||
day.year, day.month, day.day, hour, minute, 0,
|
||||
tzinfo=_HA_TZ,
|
||||
)
|
||||
|
||||
|
||||
def test_complete_instance_on_due_date_before_due_time_is_on_time(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
instance = await QuestInstancesDao(database).upsert(
|
||||
instance.definition_id,
|
||||
child.id,
|
||||
instance.due_date,
|
||||
_now_stamp(),
|
||||
window=instance.window,
|
||||
due_time="12:00",
|
||||
)
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
now=_ha_local(due_date, 11, 0),
|
||||
today=due_date,
|
||||
)
|
||||
rows = await CompletionEventsDao(database).list_by_instance(
|
||||
instance.id
|
||||
)
|
||||
assert rows[0].was_on_time is True
|
||||
|
||||
_with_db(tmp_path, "ontime-before-due-time.db")(_body)
|
||||
|
||||
|
||||
def test_complete_instance_on_due_date_after_due_time_is_late(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
instance = await QuestInstancesDao(database).upsert(
|
||||
instance.definition_id,
|
||||
child.id,
|
||||
instance.due_date,
|
||||
_now_stamp(),
|
||||
window=instance.window,
|
||||
due_time="12:00",
|
||||
)
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
now=_ha_local(due_date, 12, 1),
|
||||
today=due_date,
|
||||
)
|
||||
rows = await CompletionEventsDao(database).list_by_instance(
|
||||
instance.id
|
||||
)
|
||||
assert rows[0].was_on_time is False
|
||||
|
||||
_with_db(tmp_path, "late-after-due-time.db")(_body)
|
||||
|
||||
|
||||
def test_complete_instance_microsecond_after_due_time_is_late(tmp_path) -> None:
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
instance = await QuestInstancesDao(database).upsert(
|
||||
instance.definition_id,
|
||||
child.id,
|
||||
instance.due_date,
|
||||
_now_stamp(),
|
||||
window=instance.window,
|
||||
due_time="12:00",
|
||||
)
|
||||
now = datetime.datetime(
|
||||
due_date.year, due_date.month, due_date.day, 12, 0, 0, 500,
|
||||
tzinfo=_HA_TZ,
|
||||
)
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
now=now,
|
||||
today=due_date,
|
||||
)
|
||||
rows = await CompletionEventsDao(database).list_by_instance(
|
||||
instance.id
|
||||
)
|
||||
assert rows[0].was_on_time is False
|
||||
|
||||
_with_db(tmp_path, "late-microsecond-after-due-time.db")(_body)
|
||||
|
||||
|
||||
def test_complete_instance_no_due_time_on_due_date_is_on_time(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
assert instance.due_time is None
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
now=_ha_local(due_date, 23, 59),
|
||||
today=due_date,
|
||||
)
|
||||
rows = await CompletionEventsDao(database).list_by_instance(
|
||||
instance.id
|
||||
)
|
||||
assert rows[0].was_on_time is True
|
||||
|
||||
_with_db(tmp_path, "ontime-no-due-time.db")(_body)
|
||||
|
||||
|
||||
def test_complete_instance_no_due_time_after_due_date_is_late(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
later = due_date + datetime.timedelta(days=1)
|
||||
assert instance.due_time is None
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
now=_ha_local(later, 8, 0),
|
||||
today=later,
|
||||
)
|
||||
rows = await CompletionEventsDao(database).list_by_instance(
|
||||
instance.id
|
||||
)
|
||||
assert rows[0].was_on_time is False
|
||||
|
||||
_with_db(tmp_path, "late-after-due-date.db")(_body)
|
||||
|
||||
|
||||
def test_complete_instance_ha_local_wall_clock_not_utc(tmp_path) -> None:
|
||||
"""HA-local 11:00 with due_time 12:00 is on time even when UTC is 19:00.
|
||||
|
||||
The helper's zone is UTC-8, so 11:00 local is 19:00 UTC. Converting
|
||||
to UTC before comparing would mark this late.
|
||||
"""
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
instance = await QuestInstancesDao(database).upsert(
|
||||
instance.definition_id,
|
||||
child.id,
|
||||
instance.due_date,
|
||||
_now_stamp(),
|
||||
window=instance.window,
|
||||
due_time="12:00",
|
||||
)
|
||||
now = _ha_local(due_date, 11, 0)
|
||||
assert now.astimezone(datetime.timezone.utc).hour == 19
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
now=now,
|
||||
today=due_date,
|
||||
)
|
||||
rows = await CompletionEventsDao(database).list_by_instance(
|
||||
instance.id
|
||||
)
|
||||
assert rows[0].was_on_time is True
|
||||
|
||||
_with_db(tmp_path, "ontime-ha-local-not-utc.db")(_body)
|
||||
|
||||
|
||||
def test_complete_instance_due_time_requires_now_on_due_date(tmp_path) -> None:
|
||||
async def _body(database, child, instance):
|
||||
due_date = datetime.date.fromisoformat(instance.due_date)
|
||||
instance = await QuestInstancesDao(database).upsert(
|
||||
instance.definition_id,
|
||||
child.id,
|
||||
instance.due_date,
|
||||
_now_stamp(),
|
||||
window=instance.window,
|
||||
due_time="12:00",
|
||||
)
|
||||
with pytest.raises(ValueError, match="now is required to compare due_time"):
|
||||
await complete_instance(
|
||||
database,
|
||||
instance.id,
|
||||
actor_source="user",
|
||||
actor_user_id="user-1",
|
||||
today=due_date,
|
||||
)
|
||||
|
||||
_with_db(tmp_path, "due-time-requires-now.db")(_body)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# uncomplete_instance: append uncompleted, no-op if already open
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user