mirror of
https://github.com/kuhyx/wake-alarm.git
synced 2026-11-10 18:07:05 +01:00
Top-right always-on-top strip while the signed morning session is in_progress: title, "Next: <step>", "Locks return at HH:MM". wake-alarm-strip.path starts it on every file rewrite; it re-reads the file each second, asks for a refresh every 30 s, exits 0 when the session ends. No display/output: warning, exit 1. Bump gatelock pin to 0.12.0 (corner_strip). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWZd3smuxDGmEgXkYP8
104 lines
3.3 KiB
Python
104 lines
3.3 KiB
Python
"""Shared fakes for the corner-strip tests: signed entries, Tk and Popen."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timedelta, timezone
|
|
import json
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from gatelock.corner_strip import CornerStrip, StripContent
|
|
from gatelock.log_integrity import compute_entry_hmac
|
|
|
|
from wake_alarm import _session_strip
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
TZ = timezone(timedelta(hours=1))
|
|
NOW = datetime(2026, 11, 8, 8, 0, tzinfo=TZ)
|
|
STEP = "Shower, then tap the bathroom tag again"
|
|
|
|
|
|
def signed(**overrides: object) -> dict[str, object]:
|
|
"""A verified in-progress entry for NOW's day, with fields overridden."""
|
|
entry: dict[str, object] = {
|
|
"date": "2026-11-08",
|
|
"outcome": "in_progress",
|
|
"exempt_until": (NOW + timedelta(minutes=45)).isoformat(),
|
|
"next_step": STEP,
|
|
}
|
|
entry.update(overrides)
|
|
entry["hmac"] = compute_entry_hmac(entry)
|
|
return entry
|
|
|
|
|
|
class Procs:
|
|
"""A fake ``_popen`` recording starts; each proc finishes when told."""
|
|
|
|
def __init__(self) -> None:
|
|
self.started: list[list[str]] = []
|
|
self.procs: list[MagicMock] = []
|
|
self.error: OSError | None = None
|
|
|
|
def __call__(self, cmd: list[str]) -> MagicMock:
|
|
if self.error is not None:
|
|
raise self.error
|
|
self.started.append(cmd)
|
|
proc = MagicMock()
|
|
proc.poll.return_value = None
|
|
self.procs.append(proc)
|
|
return proc
|
|
|
|
|
|
class Display:
|
|
"""A fake Tk world: records the strip's tick callbacks and drives them."""
|
|
|
|
def __init__(self) -> None:
|
|
self.root = MagicMock(name="root")
|
|
self.window = MagicMock(name="window")
|
|
self.callbacks: list[Callable[[], None]] = []
|
|
self.window.after.side_effect = self._after
|
|
self.root.quit.side_effect = self.callbacks.clear
|
|
self.root.mainloop.side_effect = self._mainloop
|
|
self.ticks = 0
|
|
self.on_tick: Callable[[int], None] = lambda _n: None
|
|
self.rect: object = MagicMock(name="rect")
|
|
self.built: list[StripContent] = []
|
|
|
|
def _after(self, _ms: int, callback: Callable[[], None]) -> str:
|
|
self.callbacks.append(callback)
|
|
return "job"
|
|
|
|
def _mainloop(self) -> None:
|
|
while self.callbacks:
|
|
self.ticks += 1
|
|
self.on_tick(self.ticks)
|
|
self.callbacks.pop(0)()
|
|
if self.ticks > 50: # runaway-loop guard
|
|
msg = "strip never closed"
|
|
raise AssertionError(msg)
|
|
|
|
def build(
|
|
self, _root: object, _config: object, content: StripContent, **kwargs: object
|
|
) -> object:
|
|
|
|
assert kwargs["rect"] is self.rect
|
|
self.built.append(content)
|
|
return CornerStrip(self.window, MagicMock(), [MagicMock(), MagicMock()])
|
|
|
|
def patches(self) -> list[object]:
|
|
return [
|
|
patch("wake_alarm._session_strip._make_root", return_value=self.root),
|
|
patch("wake_alarm._session_strip.enumerate_outputs"),
|
|
patch(
|
|
"wake_alarm._session_strip.corner_rect",
|
|
side_effect=lambda _scan: self.rect,
|
|
),
|
|
patch("wake_alarm._session_strip.build_corner_strip", self.build),
|
|
]
|
|
|
|
|
|
def write(entry: dict[str, object]) -> None:
|
|
_session_strip.MORNING_SESSION_FILE.write_text(json.dumps(entry))
|