Files
FabledSteward/tests/integration/test_account_time_zone.py
T
bvandeusenandClaude Opus 5.5 6a81cf4aac
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m0s
CI / unit (push) Failing after 1m23s
CI / publish (push) Skipped
feat: times in the reader's own zone; the audit log shows exact moments (milestone 490)
- users.time_zone (0034). An account without one takes the browser's zone
  on its first page (POST /account/time-zone/adopt, never over a choice made
  by hand); the session follows the account on every request.
- /account: who you are and the zone every page writes its times in,
  reached from your name in the sidebar.
- formatting.when: 'Today 06:58', 'Yesterday 23:41', 'Tue 6 Oct 14:12',
  '6 Oct 2025 14:12', 24-hour, in the reader's zone; |ago's hover is the
  exact local moment instead of UTC; tz_label() for column headers.
- Audit log: When (EDT) with exact times, one detail fact a line, on classes.
- tzdata in the image, and the airgapped smoke checks a zone resolves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:56:27 -04:00

83 lines
3.9 KiB
Python

"""Integration: the zone a user reads times in (milestone 490), against real
Postgres. The browser's zone is saved for an account without one and never
over one set by hand; the Account page shows and saves it, refuses a zone
Steward does not know, and a change reaches the session on the next request.
Requires STEWARD_DATABASE_URL."""
from __future__ import annotations
import asyncio
import os
import time
import uuid
import pytest
pytestmark = pytest.mark.integration
@pytest.fixture
def app():
if not os.environ.get("STEWARD_DATABASE_URL"):
pytest.skip("needs Postgres")
from steward.app import create_app
return create_app(testing=False)
def test_the_browser_zone_is_adopted_once_and_the_account_page_changes_it(app):
from steward.models.users import User, UserRole
async def _go():
tag = uuid.uuid4().hex[:8]
uid = str(uuid.uuid4())
async with app.db_sessionmaker() as s:
async with s.begin():
s.add(User(id=uid, username=f"tz-{tag}", email=f"tz-{tag}@test",
password_hash="x", role=UserRole.viewer, is_active=True))
client = app.test_client()
async with client.session_transaction() as sess:
sess.update(user_id=uid, user_role="viewer", username=f"tz-{tag}",
login_at=time.time(), _csrf="test-token")
csrf = {"X-CSRF-Token": "test-token"}
async def zone():
async with app.db_sessionmaker() as s:
return (await s.get(User, uid)).time_zone
out = {}
# No zone yet: every page carries the script that reports the browser's.
out["before"] = await (await client.get("/account/")).get_data(as_text=True)
out["bogus"] = (await client.post("/account/time-zone/adopt", json={"time_zone": "Mars/Olympus"},
headers=csrf)).status_code
out["adopt"] = await (await client.post("/account/time-zone/adopt",
json={"time_zone": "America/New_York"},
headers=csrf)).get_json()
out["after_adopt"] = await zone()
# A second browser elsewhere does not replace it.
out["again"] = await (await client.post("/account/time-zone/adopt",
json={"time_zone": "Europe/Berlin"},
headers=csrf)).get_json()
out["page"] = await (await client.get("/account/")).get_data(as_text=True)
resp = await client.post("/account/", form={"time_zone": "Europe/London"}, headers=csrf)
out["save"] = (resp.status_code, resp.headers.get("Location"))
out["after_save"] = await zone()
resp = await client.post("/account/", form={"time_zone": "Nowhere"}, headers=csrf)
out["refused"] = (resp.status_code, await resp.get_data(as_text=True))
out["after_refused"] = await zone()
async with client.session_transaction() as sess:
out["session_tz"] = sess.get("tz")
return out
out = asyncio.run(_go())
assert "/account/time-zone/adopt" in out["before"]
assert out["bogus"] == 400
assert out["adopt"] == {"adopted": True} and out["after_adopt"] == "America/New_York"
assert out["again"] == {"adopted": False}
page = out["page"]
assert "times show in <strong>America/New_York</strong> (E" in page # EDT or EST
assert '<option value="America/New_York" selected>New York</option>' in page
assert "/account/time-zone/adopt" not in page # nothing left to adopt
assert out["save"] == (302, "/account/") and out["after_save"] == "Europe/London"
status, body = out["refused"]
assert status == 400 and "Nowhere is not a time zone Steward knows." in body
assert out["after_refused"] == "Europe/London" and out["session_tz"] == "Europe/London"