Files
FabledSteward/tests/core/test_viewer_actions.py
T
bvandeusenandClaude Opus 5.5 3a772716ae
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 51s
CI / unit (push) Failing after 1m14s
CI / publish (push) Skipped
feat(ui): audit, alert rules and the rule form say things in words (#5212)
#5212 "Plain labels: no raw ids, scopes, metric keys or unexplained jargon",
batch 1:

- Audit log: each action reads as a sentence ("paused monitor"), with the
  code as its tooltip. Detail shows flags as yes/no, lists joined, a run's
  scope by name, spans as spans, and no ids the entity column already gives.
  The filter is now a picker of areas.
- Alert rules, history and Status say the condition in words: "is down",
  "Fullest disk >= 85%". Readings use metric_value, and history times use
  |ago.
- Rule form: sources and metrics by name, the threshold's unit, "fires
  when it is down/up" for an up/down metric instead of 0.5, and a metric
  reference built from the catalog.
- Schedules and Maintenance: viewers no longer see Run now, Pause, Edit,
  Delete or New. Intervals and next runs read as "every 2h" and "in 3h";
  a scope reads "Every alert on Host agent".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 00:23:02 -04:00

44 lines
2.2 KiB
Python

"""A viewer is not offered what they cannot do (#5212): Schedules and
Maintenance draw no Run now / Pause / Edit / Delete / New for them, and
say intervals and scopes in words."""
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace as NS
from quart import render_template, session
NOW = datetime.now(timezone.utc)
def _window(name, source, resource):
return NS(id=f"w-{name}", name=name, scope_source=source, scope_resource=resource,
start_at=NOW - timedelta(hours=1), end_at=NOW + timedelta(hours=1))
async def _maintenance(app, role):
windows = [_window("patching", "host_agent", None), _window("nas", "http", "nas web")]
async with app.test_request_context("/alerts/maintenance"):
session["user_role"] = role
return await render_template("alerts/maintenance.html", windows=windows, active=windows,
upcoming=[], expired=[])
async def test_maintenance_offers_changes_only_to_those_who_can_make_them(app):
viewer, operator = await _maintenance(app, "viewer"), await _maintenance(app, "operator")
assert "New window" not in viewer and "/delete" not in viewer
assert "New window" in operator and operator.count("/delete") == 2
assert "Every alert on Host agent" in viewer and "host_agent/*" not in viewer
assert "nas web</span>" in viewer and "HTTP monitors" in viewer
async def test_schedules_show_a_viewer_the_list_without_its_controls(app):
s = NS(id="s1", name="nightly", enabled=True, source_name="local", playbook_path="prune.yml",
inventory_scope="steward:all", interval_seconds=7200, last_error=None, last_run_at=None)
rows = [{"s": s, "last_run": None, "next_run": NOW + timedelta(hours=2), "scope_label": "all inventory targets"}]
async with app.test_request_context("/ansible/schedules"):
session["user_role"] = "viewer"
page = await render_template("ansible/schedules.html", rows=rows, editing=None)
assert "nightly" in page and "every 2h" in page and "7200s" not in page
assert ">in 2h</time>" in page or ">in 1h 59m</time>" in page
for control in ("Run now", "Pause", "?edit=", "/delete", "New schedule"):
assert control not in page, control