Operator review of the Account page: two sections with two different save buttons, one stuck to the next card and one beside a squeezed hint. - Account: time zone and Email are one form; POST /account/ saves both, nothing unless all of it is valid. /account/email is gone. - Users › account: Name and Email are one form; the rename route also saves the email switches. /settings/users/<id>/email is gone. - The Email section is a plain section now (email_section); its hint sits under the switches, names the zone as the picker does, and links Notifications only for an admin. - apply_email_form leaves a switch the form doesn't carry as it is. 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>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"
|