feat: per-user filtering of settings sections
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NfPpSpccTWBfZg1FTveyaU
This commit is contained in:
@@ -24,7 +24,7 @@ def test_sections_have_section_mode():
|
||||
sections = settings_mod.get_settings_sections(CFG)
|
||||
for s in sections:
|
||||
assert "section_mode" in s, f"Section {s['id']} missing section_mode"
|
||||
assert s["section_mode"] in ("form", "yaml", "channels", "hosts")
|
||||
assert s["section_mode"] in ("form", "yaml", "channels", "hosts", "thresholds")
|
||||
|
||||
|
||||
def test_sections_have_api_section():
|
||||
@@ -42,15 +42,13 @@ def test_network_section_has_editable_fields():
|
||||
assert len(editable) >= 2 # hbd_port, ws_port at minimum
|
||||
|
||||
|
||||
def test_yaml_sections_have_correct_mode():
|
||||
def test_thresholds_and_dns_section_modes():
|
||||
sections = settings_mod.get_settings_sections(CFG)
|
||||
yaml_sections = {s["id"]: s for s in sections if s["section_mode"] == "yaml"}
|
||||
assert "channels" not in yaml_sections # now uses "channels" mode
|
||||
assert "hosts" not in yaml_sections # now uses "hosts" mode
|
||||
assert "thresholds" in yaml_sections
|
||||
assert "dns" in yaml_sections
|
||||
assert yaml_sections["thresholds"]["api_section"] == "thresholds"
|
||||
assert yaml_sections["dns"]["api_section"] == "dns"
|
||||
by_id = {s["id"]: s for s in sections}
|
||||
assert by_id["thresholds"]["section_mode"] == "thresholds"
|
||||
assert by_id["thresholds"]["api_section"] == "thresholds"
|
||||
assert by_id["dns"]["section_mode"] == "form"
|
||||
assert by_id["dns"]["api_section"] == "dns"
|
||||
|
||||
|
||||
def test_hosts_section_uses_hosts_mode():
|
||||
@@ -70,7 +68,7 @@ def test_channels_section_uses_channels_mode():
|
||||
assert ch["name"] == "pushover_ops"
|
||||
assert ch["type"] == "pushover"
|
||||
assert "owner" in ch
|
||||
assert "private" in ch
|
||||
assert "editable" in ch
|
||||
|
||||
|
||||
def test_channel_type_schemas_exported():
|
||||
@@ -112,3 +110,84 @@ def test_users_section_has_user_list():
|
||||
assert users_sec["users"][0]["username"] == "alice"
|
||||
# Password hash never exposed
|
||||
assert "password" not in users_sec["users"][0]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-user filtering (owners/managers on the settings page)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from types import SimpleNamespace # noqa: E402
|
||||
|
||||
|
||||
MULTI_CFG = {
|
||||
**CFG,
|
||||
"users": {
|
||||
"alice": {"full_name": "Alice", "admin": True, "password": "x"},
|
||||
"bob": {"full_name": "Bob", "admin": False, "password": "x"},
|
||||
},
|
||||
"notification_channels": {
|
||||
"global_ch": {"type": "pushover", "token": "t", "user": "u"},
|
||||
"bob_ch": {"type": "pushover", "token": "t", "user": "u", "owner": "bob"},
|
||||
"carol_ch": {"type": "pushover", "token": "t", "user": "u", "owner": "carol"},
|
||||
},
|
||||
"threshold_configs": {
|
||||
"bob_tc": {"owner": "bob", "thresholds": {}},
|
||||
"carol_tc": {"owner": "carol", "thresholds": {}},
|
||||
},
|
||||
"hosts": {
|
||||
"bobhost": {"owner": "bob"},
|
||||
"managedhost": {"owner": "carol", "managers": ["bob"]},
|
||||
"carolhost": {"owner": "carol"},
|
||||
},
|
||||
}
|
||||
|
||||
BOB = SimpleNamespace(username="bob", admin=False)
|
||||
ADMIN = SimpleNamespace(username="alice", admin=True)
|
||||
|
||||
|
||||
def test_nonadmin_sees_only_three_sections():
|
||||
sections = settings_mod.get_settings_sections(MULTI_CFG, user=BOB)
|
||||
assert [s["id"] for s in sections] == ["channels", "hosts", "thresholds"]
|
||||
|
||||
|
||||
def test_nonadmin_sections_hide_admin_fields():
|
||||
sections = settings_mod.get_settings_sections(MULTI_CFG, user=BOB)
|
||||
for s in sections:
|
||||
assert s["fields"] == []
|
||||
|
||||
|
||||
def test_nonadmin_hosts_filtered_with_is_owner():
|
||||
sections = settings_mod.get_settings_sections(MULTI_CFG, user=BOB)
|
||||
hosts = next(s for s in sections if s["id"] == "hosts")["hosts"]
|
||||
by_name = {h["name"]: h for h in hosts}
|
||||
assert set(by_name) == {"bobhost", "managedhost"}
|
||||
assert by_name["bobhost"]["is_owner"] is True
|
||||
assert by_name["managedhost"]["is_owner"] is False
|
||||
|
||||
|
||||
def test_nonadmin_channels_filtered_with_editable():
|
||||
sections = settings_mod.get_settings_sections(MULTI_CFG, user=BOB)
|
||||
chans = {c["name"]: c for c in next(s for s in sections if s["id"] == "channels")["channels"]}
|
||||
assert set(chans) == {"global_ch", "bob_ch"}
|
||||
assert chans["bob_ch"]["editable"] is True
|
||||
assert chans["global_ch"]["editable"] is False
|
||||
|
||||
|
||||
def test_admin_sees_everything_with_editable():
|
||||
sections = settings_mod.get_settings_sections(MULTI_CFG, user=ADMIN)
|
||||
ids = [s["id"] for s in sections]
|
||||
assert "network" in ids and "users" in ids
|
||||
chans = {c["name"]: c for c in next(s for s in sections if s["id"] == "channels")["channels"]}
|
||||
assert set(chans) == {"global_ch", "bob_ch", "carol_ch"}
|
||||
assert all(c["editable"] for c in chans.values())
|
||||
|
||||
|
||||
def test_settings_data_pickers_filtered_for_nonadmin():
|
||||
data = settings_mod.get_settings_data(MULTI_CFG, user=BOB)
|
||||
assert data["all_channel_names"] == ["bob_ch", "global_ch"]
|
||||
assert data["all_threshold_configs"] == ["bob_tc"]
|
||||
|
||||
|
||||
def test_no_user_means_admin_view():
|
||||
sections = settings_mod.get_settings_sections(MULTI_CFG) # auth disabled
|
||||
assert len(sections) > 3
|
||||
|
||||
Reference in New Issue
Block a user