Files
terra-view/tests/test_operator_gate.py
T
serversdown 68161298a4 fix(auth): hide /admin/users when flag off; pass OPTIONS preflight through gate
- operator_users router now depends on _require_auth_enabled, which raises
  404 when OPERATOR_AUTH_ENABLED is false — prevents world-open pre-seeding
  of a superadmin while the flag is off (the default).  Flag is read as a
  live module attribute (operator_auth.OPERATOR_AUTH_ENABLED) so monkeypatching
  in tests and a runtime flip both take effect.
- operator_gate passes OPTIONS requests through immediately before the exempt-
  path check, so CORS preflight reaches CORSMiddleware rather than being
  303/401'd by the gate.
- Two new tests: test_admin_surface_404s_when_flag_off (test_operator_users)
  and test_options_preflight_passes_through_gate (test_operator_gate).
  Full suite: 90 passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 20:27:01 +00:00

77 lines
3.2 KiB
Python

# tests/test_operator_gate.py
import uuid
from tests.conftest import wire_operator_auth
from backend.models import OperatorUser
from backend.operator_auth import make_operator_cookie, COOKIE_NAME
from backend.auth_passwords import hash_password
def _make_user(db, role="admin", **kw):
u = OperatorUser(id=str(uuid.uuid4()), email=kw.pop("email", "u@x.com"),
display_name="U", password_hash=hash_password("pw"), role=role, **kw)
db.add(u)
db.commit()
return u
def test_flag_off_passes_everything(client, db_session, monkeypatch):
wire_operator_auth(monkeypatch, db_session, enabled=False)
assert client.get("/", follow_redirects=False).status_code == 200
def test_gated_html_redirects_to_login_when_unauth(client, db_session, monkeypatch):
wire_operator_auth(monkeypatch, db_session, enabled=True)
r = client.get("/", follow_redirects=False)
assert r.status_code == 303
assert r.headers["location"].startswith("/login?next=")
def test_gated_api_returns_401_json_when_unauth(client, db_session, monkeypatch):
wire_operator_auth(monkeypatch, db_session, enabled=True)
r = client.get("/api/status-snapshot", follow_redirects=False)
assert r.status_code == 401
def test_valid_session_passes(client, db_session, monkeypatch):
u = _make_user(db_session)
wire_operator_auth(monkeypatch, db_session, enabled=True)
client.cookies.set(COOKIE_NAME, make_operator_cookie(u.id))
assert client.get("/", follow_redirects=False).status_code == 200
def test_must_change_password_user_routed_to_change_password(client, db_session, monkeypatch):
u = _make_user(db_session, must_change_password=True)
wire_operator_auth(monkeypatch, db_session, enabled=True)
client.cookies.set(COOKIE_NAME, make_operator_cookie(u.id))
r = client.get("/", follow_redirects=False)
assert r.status_code == 303
assert r.headers["location"] == "/change-password"
def test_exempt_paths_pass_without_cookie(client, db_session, monkeypatch):
wire_operator_auth(monkeypatch, db_session, enabled=True)
assert client.get("/health", follow_redirects=False).status_code == 200
def test_portal_paths_are_exempt(client, db_session, monkeypatch):
wire_operator_auth(monkeypatch, db_session, enabled=True)
# /portal/p/<bad> hits the portal's own gate (403/404), never the operator login.
r = client.get("/portal/p/nope", follow_redirects=False)
assert r.status_code in (403, 404)
def test_must_change_user_on_api_gets_403_json_not_redirect(client, db_session, monkeypatch):
u = _make_user(db_session, must_change_password=True)
wire_operator_auth(monkeypatch, db_session, enabled=True)
client.cookies.set(COOKIE_NAME, make_operator_cookie(u.id))
r = client.get("/api/status-snapshot", follow_redirects=False)
assert r.status_code == 403
assert r.json()["detail"] == "Password change required"
def test_options_preflight_passes_through_gate(client, db_session, monkeypatch):
wire_operator_auth(monkeypatch, db_session, enabled=True)
# CORS preflight has no cookie; the gate must not 303/401 it.
r = client.options("/api/status-snapshot", follow_redirects=False)
assert r.status_code not in (303, 401)