mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-11-11 13:47:38 +01:00
Stop asking for meals on a shared free day
Third app onto freedays (~/utils/freedays), the shared pool of pre-declared days when every gate app stands down. due_slots() returns nothing on a free day, so no slot is ever due, the lock never opens and nothing is said about the missing meals. Read from one local file, never the network, so this cannot hang the systemd-fired gate -- and an unreadable pool reads as "not free", which means the normal rules apply rather than the gate quietly switching itself off. Tested explicitly: fail closed, not open. The conftest redirect is load-bearing. The pool lives under ~/.local/share, outside every diet_guard path the suite already isolates, so without it a real free day on this machine would fail every "the gate is due" test here for a reason that looks nothing like the cause. gatelock moves v0.7.2 -> v0.7.3, and the pylint hook's own pins (which had drifted further, to gatelock v0.7.1 and crdt-sync v0.6.0) come with it. Not cosmetic: pip refuses two different direct URLs for one distribution, so any stale pin alongside freedays -- which requires v0.7.3 -- is an unresolvable environment rather than a warning. requirements.txt also picks up pylint 4.0.8, pytest-randomly 5.0.0 and ruff 0.16.6, which the freshness gate flagged once this file was touched. 1043 tests pass at 100% coverage across the pytest-randomly major bump. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
862a114909
commit
b77145010c
@ -102,8 +102,13 @@ repos:
|
||||
additional_dependencies:
|
||||
- pytest
|
||||
- requests
|
||||
- gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.1#subdirectory=gatelock
|
||||
- crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.6.0#subdirectory=crdt-sync
|
||||
# Kept in step with pyproject.toml on purpose: pip refuses two
|
||||
# different direct URLs for one distribution, so a stale pin here
|
||||
# becomes an unresolvable hook environment the moment freedays --
|
||||
# which requires gatelock v0.7.3 -- joins the list.
|
||||
- gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.3#subdirectory=gatelock
|
||||
- crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync
|
||||
- freedays @ git+https://github.com/kuhyx/utils@freedays-v0.1.0#subdirectory=freedays
|
||||
- mcp>=2,<3
|
||||
- pydantic
|
||||
# Test suites intentionally use patterns (protected-access, magic-value
|
||||
|
||||
@ -2,7 +2,7 @@ name: diet_guard_app
|
||||
description: "Companion phone app for diet_guard: log meals on the go."
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.0.76+1
|
||||
version: 1.0.77+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
@ -17,6 +17,8 @@ from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import freedays
|
||||
|
||||
from diet_guard._meal_schedule_store import current_schedule
|
||||
from diet_guard._slots import missing_slots, slot_label
|
||||
from diet_guard._state import (
|
||||
@ -38,8 +40,15 @@ def due_slots(now: datetime | None = None) -> tuple[int, ...]:
|
||||
|
||||
Returns:
|
||||
The slot hours that still need a meal logged (empty == nothing due).
|
||||
|
||||
Empty on a shared free day, whatever the schedule says. The pool is read
|
||||
from a single local file and never the network, so this cannot hang the
|
||||
systemd-fired gate, and an unreadable pool reads as "not free" -- the
|
||||
normal rules apply rather than the gate switching itself off.
|
||||
"""
|
||||
reference = now if now is not None else now_local()
|
||||
if freedays.is_free_day(reference.date()):
|
||||
return ()
|
||||
return missing_slots(reference, logged_slots_today(), current_schedule())
|
||||
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ from contextlib import ExitStack
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
|
||||
import freedays
|
||||
import pytest
|
||||
|
||||
from diet_guard._estimator import Nutrition
|
||||
@ -103,6 +104,14 @@ def _isolate_state(tmp_path: Path) -> Iterator[None]:
|
||||
~20 redirects CPython refuses to compile it.
|
||||
"""
|
||||
redirects = [
|
||||
# The shared free-day pool lives under ~/.local/share, outside every
|
||||
# diet_guard path below. Without this a real free day on the
|
||||
# developer's machine would make every "the gate is due" test here
|
||||
# fail, for a reason that looks nothing like the cause.
|
||||
patch(
|
||||
"freedays._api.resolve_paths",
|
||||
lambda paths: paths or freedays.Paths.under(tmp_path / "freedays"),
|
||||
),
|
||||
patch(
|
||||
"diet_guard._budget.BUDGET_FILE",
|
||||
tmp_path / ".budget",
|
||||
|
||||
@ -6,11 +6,17 @@ here the logged set is mocked and ``now`` is injected to drive each decision.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from datetime import UTC, date, datetime
|
||||
from typing import TYPE_CHECKING
|
||||
from unittest.mock import patch
|
||||
|
||||
import freedays
|
||||
|
||||
from diet_guard._gate import due_slots, gate_is_due, gate_message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _at(hour: int) -> datetime:
|
||||
"""Return a fixed local datetime at ``hour``."""
|
||||
@ -77,3 +83,36 @@ class TestGateMessage:
|
||||
with _logged(set()):
|
||||
message = gate_message(_at(17))
|
||||
assert message == "Log your meals for 08:00, 12:00, 16:00 to unlock."
|
||||
|
||||
|
||||
class TestFreeDays:
|
||||
"""The shared pool stands the gate down entirely."""
|
||||
|
||||
def test_a_free_day_leaves_nothing_due(self, tmp_path: Path) -> None:
|
||||
with _logged(set()):
|
||||
assert due_slots(_at(20)), "precondition: slots are due without a free day"
|
||||
freedays.mark(
|
||||
_at(20).date(),
|
||||
paths=freedays.Paths.under(tmp_path / "freedays"),
|
||||
now=_at(20).date(),
|
||||
)
|
||||
assert due_slots(_at(20)) == ()
|
||||
assert not gate_is_due(_at(20))
|
||||
|
||||
def test_a_free_day_on_another_date_changes_nothing(self, tmp_path: Path) -> None:
|
||||
with _logged(set()):
|
||||
freedays.mark(
|
||||
date(2026, 12, 24),
|
||||
paths=freedays.Paths.under(tmp_path / "freedays"),
|
||||
now=_at(20).date(),
|
||||
)
|
||||
assert due_slots(_at(20))
|
||||
assert gate_is_due(_at(20))
|
||||
|
||||
def test_an_unreadable_pool_leaves_the_gate_armed(self, tmp_path: Path) -> None:
|
||||
"""Fail closed: a corrupt pool must not switch the gate off."""
|
||||
pool_dir = tmp_path / "freedays"
|
||||
pool_dir.mkdir(exist_ok=True)
|
||||
(pool_dir / "free_days.json").write_text("{ not json", encoding="utf-8")
|
||||
with _logged(set()):
|
||||
assert gate_is_due(_at(20))
|
||||
|
||||
@ -4,8 +4,9 @@ version = "1.0.0"
|
||||
description = "Tkinter/systemd log-to-unlock meal gate with calorie/macro budget tracking"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock",
|
||||
"gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.3#subdirectory=gatelock",
|
||||
"crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync",
|
||||
"freedays @ git+https://github.com/kuhyx/utils@freedays-v0.1.0#subdirectory=freedays",
|
||||
"requests==2.34.2",
|
||||
]
|
||||
|
||||
|
||||
@ -4,16 +4,17 @@ bandit==1.9.4
|
||||
codespell==2.4.3
|
||||
coverage==7.16.0
|
||||
crdt-sync @ git+https://github.com/kuhyx/utils@crdt-sync-v0.9.1#subdirectory=crdt-sync
|
||||
gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.2#subdirectory=gatelock
|
||||
freedays @ git+https://github.com/kuhyx/utils@freedays-v0.1.0#subdirectory=freedays
|
||||
gatelock @ git+https://github.com/kuhyx/utils@gatelock-v0.7.3#subdirectory=gatelock
|
||||
mcp==2.1.1
|
||||
mypy==2.3.1
|
||||
pre-commit==4.6.2
|
||||
pydantic==2.13.5
|
||||
pylint==4.0.7
|
||||
pylint==4.0.8
|
||||
pytest==9.1.1
|
||||
pytest-cov==7.1.0
|
||||
pytest-randomly==4.1.0
|
||||
pytest-randomly==5.0.0
|
||||
pytest-sugar==1.1.1
|
||||
pytest-xdist==3.8.0
|
||||
requests==2.34.2
|
||||
ruff==0.16.5
|
||||
ruff==0.16.6
|
||||
|
||||
Loading…
Reference in New Issue
Block a user