test(datetime): pin tz-aware contract for fixtures (red until UtcDateTime lands)

Drops .replace(tzinfo=None) from 8 fixture sites that mirrored the
production-side strip convention. Two of these (test_live_state_poller.py
test_new_player_opens_session_with_backfilled_join, test_models.py
test_user_has_password_changed_at_default) now fail with TypeError when
comparing aware in-memory values against naive DB reads -- that failure
is intentional and describes the contract commit 2 must satisfy:
DB-sourced datetimes return aware UTC.

The remaining 6 sites were already cosmetic (fixture-seed only, no
aware-vs-DB comparison) but are flipped here so future authors write
aware fixtures.

The three deliberately-naive sites in test_timeago.py (lines 67, 73,
113) are LEFT untouched -- they exercise _ensure_utc's normalize-up
path and are feature tests, not workarounds.

See docs/superpowers/specs/2026-05-16-tz-aware-datetime-design.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mwiegand 2026-05-16 11:55:48 +02:00
parent 99b528e563
commit a5436deaf0
No known key found for this signature in database
4 changed files with 8 additions and 8 deletions

View file

@ -159,7 +159,7 @@ def test_load_current_user_rejects_stale_marker(client) -> None:
db.flush() db.flush()
uid = u.id uid = u.id
stale = datetime.now(UTC).replace(tzinfo=None) - timedelta(minutes=5) stale = datetime.now(UTC) - timedelta(minutes=5)
with client.session_transaction() as sess: with client.session_transaction() as sess:
sess["user_id"] = uid sess["user_id"] = uid
sess["pw_changed_at"] = stale.isoformat() sess["pw_changed_at"] = stale.isoformat()

View file

@ -156,7 +156,7 @@ def test_new_player_opens_session_with_backfilled_join(tmp_path, monkeypatch) ->
assert s.min_ping == 60 assert s.min_ping == 60
assert s.max_ping == 60 assert s.max_ping == 60
# joined_at should be ~30s before now # joined_at should be ~30s before now
delta = (datetime.now(UTC).replace(tzinfo=None) - s.joined_at).total_seconds() delta = (datetime.now(UTC) - s.joined_at).total_seconds()
assert 25 <= delta <= 60 assert 25 <= delta <= 60
@ -275,7 +275,7 @@ def test_skips_enrichment_when_api_key_unset(tmp_path, monkeypatch) -> None:
def test_retention_trims_old_rows(tmp_path, monkeypatch) -> None: def test_retention_trims_old_rows(tmp_path, monkeypatch) -> None:
app, sid = _seed(tmp_path) app, sid = _seed(tmp_path)
app.config["LIVE_STATE_HISTORY_DAYS"] = 30 app.config["LIVE_STATE_HISTORY_DAYS"] = 30
long_ago = datetime.now(UTC).replace(tzinfo=None) - timedelta(days=45) long_ago = datetime.now(UTC) - timedelta(days=45)
with session_scope() as db: with session_scope() as db:
db.add(ServerLiveState( db.add(ServerLiveState(
server_id=sid, started_at=long_ago, last_seen_at=long_ago, server_id=sid, started_at=long_ago, last_seen_at=long_ago,
@ -300,7 +300,7 @@ def test_retention_trims_old_rows(tmp_path, monkeypatch) -> None:
def test_close_stuck_sessions_after_threshold(tmp_path, monkeypatch) -> None: def test_close_stuck_sessions_after_threshold(tmp_path, monkeypatch) -> None:
app, sid = _seed(tmp_path) app, sid = _seed(tmp_path)
app.config["STUCK_SESSION_SECONDS"] = 60 app.config["STUCK_SESSION_SECONDS"] = 60
way_back = datetime.now(UTC).replace(tzinfo=None) - timedelta(hours=2) way_back = datetime.now(UTC) - timedelta(hours=2)
with session_scope() as db: with session_scope() as db:
db.add(ServerPlayerSession( db.add(ServerPlayerSession(
server_id=sid, steam_id_64="76561197960828710", server_id=sid, steam_id_64="76561197960828710",
@ -353,7 +353,7 @@ def test_skips_enrichment_when_cache_is_fresh(tmp_path, monkeypatch) -> None:
steam_id_64="76561197960828710", steam_id_64="76561197960828710",
persona_name="cached", persona_name="cached",
avatar_url="cached.jpg", avatar_url="cached.jpg",
fetched_at=datetime.now(UTC).replace(tzinfo=None), fetched_at=datetime.now(UTC),
)) ))
monkeypatch.setattr( monkeypatch.setattr(
live_state_poller, "query_status", live_state_poller, "query_status",

View file

@ -40,7 +40,7 @@ def test_user_has_password_changed_at_default(tmp_path, monkeypatch):
create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"}) create_app({"TESTING": True, "DATABASE_URL": db_url, "SECRET_KEY": "test"})
init_db() init_db()
before = datetime.now(UTC).replace(tzinfo=None) before = datetime.now(UTC)
with session_scope() as db: with session_scope() as db:
db.add(User(username="alice", password_digest=hash_password("secret"))) db.add(User(username="alice", password_digest=hash_password("secret")))
with session_scope() as db: with session_scope() as db:

View file

@ -456,7 +456,7 @@ def test_servers_index_renders_live_state_badge(user_client_with_blueprints) ->
client, data = user_client_with_blueprints client, data = user_client_with_blueprints
# Seed one server with a recent snapshot, one without. # Seed one server with a recent snapshot, one without.
now = datetime.now(UTC).replace(tzinfo=None) now = datetime.now(UTC)
with session_scope() as db: with session_scope() as db:
s_active = Server( s_active = Server(
user_id=data["user_id"], user_id=data["user_id"],
@ -518,7 +518,7 @@ def test_live_state_fragment_renders_current_and_recent(user_client_with_bluepri
) )
client, data = user_client_with_blueprints client, data = user_client_with_blueprints
now = datetime.now(UTC).replace(tzinfo=None) now = datetime.now(UTC)
with session_scope() as db: with session_scope() as db:
srv = Server( srv = Server(
user_id=data["user_id"], blueprint_id=data["blueprint_id"], user_id=data["user_id"], blueprint_id=data["blueprint_id"],