Sign-in page redesign and Portal roles Super Admin / Admin / Viewer
Sign-in: brand panel, show / hide password, Caps Lock warning, moving background, tab icon. Roles: Super Admin manages Admins and Viewers, Admin manages Viewers only (and can Start / Stop, see Sign-ins), Viewer sees claims and Runs only. create_tables.sql moves an old table. 71 test files, 1220 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
120fd6808a
commit
ce9bdbd974
11 files changed
+408
-99
No files matched your search
+67
-11
@@ -74,7 +74,7 @@ class Clock:
|
||||
def _accounts():
|
||||
store, clock = FakeUserStore(), Clock()
|
||||
acc = users.Accounts(store, now=clock)
|
||||
acc.create_user("Asha", "Asha R", "Admin", "first-password-1")
|
||||
acc.create_user("Asha", "Asha R", "Super Admin", "first-password-1")
|
||||
return acc, store, clock
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ def a_good_sign_in_clears_the_wrong_count():
|
||||
@case
|
||||
def an_admin_made_user_must_change_and_a_reset_does_the_same():
|
||||
acc, store, _ = _accounts()
|
||||
acc.create_user("ravi", "Ravi", "Operator", "temp-password-1", by="asha")
|
||||
acc.create_user("ravi", "Ravi", "Viewer", "temp-password-1", by="asha")
|
||||
assert store.users["ravi"]["must_change"] is True
|
||||
assert store.users["ravi"]["created_by"] == "asha"
|
||||
acc.change_password("ravi", "temp-password-1", "ravis-own-pass")
|
||||
@@ -199,24 +199,80 @@ def a_disabled_user_cannot_sign_in_and_is_never_deleted():
|
||||
|
||||
|
||||
@case
|
||||
def the_last_admin_stays_and_nobody_disables_themselves():
|
||||
acc, _, _ = _accounts()
|
||||
for fn in (lambda: acc.set_role("asha", "Viewer", by="asha"),
|
||||
lambda: acc.set_disabled("asha", True, by="asha")):
|
||||
def an_admin_adds_and_changes_viewers_only():
|
||||
"""Operator, 2026-10-27: an Admin creates users, and users means
|
||||
Viewers."""
|
||||
acc, store, _ = _accounts()
|
||||
acc.create_user("ravi", "Ravi", "Admin", "temp-password-1", by="asha")
|
||||
acc.create_user("vee", "Vee", "Viewer", "temp-password-1", by="ravi")
|
||||
acc.reset_password("vee", "temp-password-2", by="ravi")
|
||||
acc.set_disabled("vee", True, by="ravi")
|
||||
acc.set_disabled("vee", False, by="ravi")
|
||||
acc.create_user("mani", "Mani", "Admin", "temp-password-1", by="asha")
|
||||
for fn in (lambda: acc.create_user("kim", "K", "Admin", "temp-password-1",
|
||||
by="ravi"),
|
||||
lambda: acc.set_role("vee", "Admin", by="ravi"),
|
||||
lambda: acc.set_disabled("mani", True, by="ravi"),
|
||||
lambda: acc.reset_password("mani", "temp-password-3",
|
||||
by="ravi"),
|
||||
lambda: acc.set_role("asha", "Viewer", by="ravi"),
|
||||
lambda: acc.set_disabled("asha", True, by="ravi")):
|
||||
try:
|
||||
fn()
|
||||
raise AssertionError("an Admin went past Viewers")
|
||||
except users.UserError:
|
||||
pass
|
||||
assert "kim" not in store.users and store.users["vee"]["role"] == "Viewer"
|
||||
|
||||
|
||||
@case
|
||||
def the_super_admin_runs_admins_and_nobody_touches_a_super_admin():
|
||||
acc, store, _ = _accounts()
|
||||
assert store.users["asha"]["role"] == "Super Admin"
|
||||
acc.create_user("ravi", "Ravi", "Admin", "temp-password-1", by="asha")
|
||||
acc.set_role("ravi", "Viewer", by="asha")
|
||||
acc.set_role("ravi", "Admin", by="asha")
|
||||
acc.set_disabled("ravi", True, by="asha")
|
||||
assert ("ravi", "role_admin", "asha") in store.events
|
||||
for fn in (lambda: acc.create_user("boss", "B", "Super Admin",
|
||||
"temp-password-1", by="asha"),
|
||||
lambda: acc.set_role("asha", "Viewer", by="asha"),
|
||||
lambda: acc.set_disabled("asha", True, by="asha"),
|
||||
lambda: acc.set_role("ravi", "Super Admin", by="asha"),
|
||||
lambda: acc.create_user("bob", "B", "Viewer",
|
||||
"temp-password-1", by="nobody")):
|
||||
try:
|
||||
fn()
|
||||
raise AssertionError("allowed")
|
||||
except users.UserError:
|
||||
pass
|
||||
acc.create_user("ravi", "Ravi", "Admin", "temp-password-1", by="asha")
|
||||
acc.set_role("asha", "Operator", by="ravi")
|
||||
assert store.users["asha"]["role"] == "Super Admin"
|
||||
acc.create_user("boss", "B", "Super Admin", "temp-password-1")
|
||||
assert store.users["boss"]["role"] == "Super Admin", "the PC tool may"
|
||||
|
||||
|
||||
@case
|
||||
def the_old_operator_role_is_gone():
|
||||
assert users.ROLES == ("Super Admin", "Admin", "Viewer"), users.ROLES
|
||||
assert users.CAN_RUN == ("Super Admin", "Admin")
|
||||
acc, _, _ = _accounts()
|
||||
try:
|
||||
acc.set_disabled("ravi", True, by="asha")
|
||||
raise AssertionError("disabled the last Admin")
|
||||
acc.create_user("olu", "Olu", "Operator", "temp-password-1")
|
||||
raise AssertionError("Operator accepted")
|
||||
except users.UserError:
|
||||
pass
|
||||
|
||||
|
||||
@case
|
||||
def the_sql_table_allows_the_same_roles_and_moves_old_ones():
|
||||
sql = (pathlib.Path(__file__).resolve().parent.parent / "tools" / "sql"
|
||||
/ "create_tables.sql").read_text(encoding="utf-8")
|
||||
check = "CHECK (role IN ('Super Admin', 'Admin', 'Viewer'))"
|
||||
assert sql.count(check) == 2, "the new table and the old one moved"
|
||||
first = sql.index("SET role = 'Super Admin' WHERE role = 'Admin'")
|
||||
assert first < sql.index("SET role = 'Admin' WHERE role = 'Operator'"), "Admins move up before Operators take their place"
|
||||
|
||||
|
||||
@case
|
||||
def sessions_time_out_after_thirty_idle_minutes():
|
||||
clock = Clock()
|
||||
@@ -234,7 +290,7 @@ def sessions_time_out_after_thirty_idle_minutes():
|
||||
@case
|
||||
def a_users_sessions_can_be_ended_and_refreshed():
|
||||
s = users.Sessions()
|
||||
t1 = s.new({"username": "ravi", "role": "Operator"})
|
||||
t1 = s.new({"username": "ravi", "role": "Admin"})
|
||||
t2 = s.new({"username": "asha", "role": "Admin"})
|
||||
s.refresh_user({"username": "ravi", "role": "Viewer"})
|
||||
assert s.get(t1)[0]["role"] == "Viewer"
|
||||
|
||||
Reference in new issue
Block a user