- 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>
83 lines
3.9 KiB
Python
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"
|