"""Invite status and lifetime rules, DB-free. Redemption itself is a conditional UPDATE and is tested against Postgres in test_integration.py.""" from datetime import datetime, timedelta, timezone import pytest from inkwell.invites import DEFAULT_DAYS, MAX_DAYS, lifetime_days, status from inkwell.models.invite import Invite NOW = datetime(2026, 10, 7, 12, 0, tzinfo=timezone.utc) def _invite(**fields) -> Invite: fields.setdefault("expires_at", NOW + timedelta(days=1)) return Invite(token_hash="x", **fields) def test_an_untouched_invite_in_date_is_pending(): assert status(_invite(), NOW) == "pending" def test_an_invite_is_expired_from_its_expiry_instant(): assert status(_invite(expires_at=NOW), NOW) == "expired" def test_revoked_beats_expired_and_redeemed_beats_both(): assert status(_invite(expires_at=NOW, revoked_at=NOW), NOW) == "revoked" assert status(_invite(expires_at=NOW, revoked_at=NOW, redeemed_at=NOW), NOW) == "redeemed" @pytest.mark.parametrize( "raw, days", [(None, DEFAULT_DAYS), (1, 1), ("14", 14), (MAX_DAYS, MAX_DAYS), (0, None), (MAX_DAYS + 1, None), ("soon", None), (True, None), (2.5, None)], ) def test_lifetimes(raw, days): assert lifetime_days(raw) == days