Source code for regstack.auth.clock

from __future__ import annotations

from datetime import UTC, datetime, timedelta
from typing import Protocol


[docs] class Clock(Protocol): """Source of "now" — the seam that makes time-sensitive code testable. JWT issuance, JWT expiry validation, lockout window calculations, and bulk-revoke comparisons all read time through this protocol rather than calling ``datetime.now()`` directly. Production passes :class:`SystemClock`; tests pass :class:`FrozenClock` so a single test can deterministically assert "this token expires in exactly 7200 seconds" without sleeping. """
[docs] def now(self) -> datetime: """Return the current tz-aware UTC datetime.""" ...
[docs] class SystemClock: """Production :class:`Clock` — wraps ``datetime.now(UTC)``."""
[docs] def now(self) -> datetime: """Return the current wall-clock time as a tz-aware UTC datetime.""" return datetime.now(UTC)
[docs] class FrozenClock: """Test :class:`Clock` — returns a fixed timestamp until advanced. Pin the clock to a known instant for the lifetime of a test, then advance it explicitly to step over expiry boundaries:: clock = FrozenClock() token, _ = codec.encode("user-1") clock.advance(timedelta(seconds=7201)) # past exp with pytest.raises(TokenError): codec.decode(token) """ def __init__(self, start: datetime | None = None) -> None: """Pin the clock at ``start`` (default 2025-01-01 UTC). Args: start: The initial timestamp. Should be tz-aware. Defaults to ``2025-01-01T00:00:00Z`` so test datetimes are memorable. """ self._now = start or datetime(2025, 1, 1, tzinfo=UTC)
[docs] def now(self) -> datetime: """Return the currently-pinned timestamp.""" return self._now
[docs] def advance(self, delta: timedelta) -> None: """Move the clock forward by ``delta``. Args: delta: How far to advance. Negative values are accepted but rarely useful. """ self._now += delta
[docs] def set(self, when: datetime) -> None: """Reset the clock to an absolute instant. Args: when: The new "now". Should be tz-aware. """ self._now = when