Files
FabledSteward/tests/core/test_ansible_schedules_page.py
T
bvandeusenandClaude Opus 5.5 0c72892f58
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 59s
CI / unit (push) Failing after 1m22s
CI / publish (push) Skipped
feat(ansible): schedules read as sentences, each with its own page (#5332)
- The Schedules tab: a verdict line (failing first, paused, next due),
  each schedule as one line (what runs where, how often, when next, how it
  last went); the row opens its page; Run now and Pause stay in the row.
- /ansible/schedules/new and /ansible/schedules/<id>: the form on classes,
  dry run beside the button, the playbook's variables rendered with the
  page; at the foot Delete behind the confirm dialog. ?source=&playbook=
  start a new one on a playbook (Schedule it on the playbook's page).
- A run started by a schedule links to that schedule's page.
- The run page's id guard allows the Run again link's ?from=.

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

90 lines
5.2 KiB
Python

"""Schedules read as sentences and each has its own page (#5332): the list's
row opens it, Run now and Pause stay in the row, and Delete is on the page
behind the confirm dialog."""
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace as NS
from quart import render_template, session
from steward.models.ansible import AnsibleRunStatus
from tests.test_style_tokens import hex_colours
NOW = datetime.now(timezone.utc)
def _sched(sid="s1", name="Nightly prune", enabled=True, last_error=None, check=False):
return NS(id=sid, name=name, enabled=enabled, source_name="steward-builtin",
playbook_path="maintenance/docker_prune.yml", inventory_scope="steward:group:g1",
interval_seconds=86400, last_error=last_error, last_run_at=NOW - timedelta(hours=22),
last_run_id="r1", params={"check": True} if check else None)
def _last(status=AnsibleRunStatus.success):
return NS(id="r1", status=status, started_at=NOW - timedelta(hours=22))
async def test_the_schedules_list_reads_as_sentences(app):
ok, broken = _sched(), _sched("s2", "Weekly update", last_error="Playbook not found")
rows = [{"s": broken, "last_run": None, "scope_label": "group Docker Swarm", "next": None},
{"s": ok, "last_run": _last(), "scope_label": "group Docker Swarm",
"next": {"id": "s1", "name": "Nightly prune", "at": NOW + timedelta(hours=2)}}]
async with app.test_request_context("/ansible/schedules"):
session["user_role"] = "operator"
page = await render_template("ansible/schedules.html", rows=rows, failing=1, paused=0,
next=rows[1]["next"])
main = page[page.index("<main"):]
assert '<span class="tone-crit">1 of 2 failing</span>' in main
assert main.count('<tr class="row-link">') == 2 and 'href="/ansible/schedules/s1"' in main
assert "maintenance/docker_prune.yml</span> on group Docker Swarm" in main and "daily" in main
assert '<div class="cell-sub tone-crit" title="Playbook not found">Playbook not found' in main
assert main.count(">Run now</button>") == 2 and ">Pause</button>" in main
assert "/delete" not in main and 'href="/ansible/schedules/new"' in main # Delete is on the schedule's page
assert '<a href="/ansible/schedules" aria-current="page">Schedules</a>' in main
assert not hex_colours(main), hex_colours(main)
def _page_ctx(editing):
return {"editing": editing, "groups": [NS(id="g1", name="Docker Swarm")], "targets": [],
"source_data": [{"name": "steward-builtin", "playbooks": ["maintenance/docker_prune.yml"]}],
"sel_source": "steward-builtin", "sel_playbook": "maintenance/docker_prune.yml",
"sel_playbooks": ["maintenance/docker_prune.yml"], "interval_presets": [(86400, "Daily")],
"edit_variables": [], "edit_description": "", "edit_category": "", "edit_confirm": False,
"last_run": _last() if editing else None, "scope_label": "group Docker Swarm" if editing else None,
"next": {"id": "s1", "name": "Nightly prune", "at": NOW + timedelta(hours=2)} if editing else None}
async def test_a_schedule_page_says_what_it_does_and_deletes_behind_the_dialog(app):
async with app.test_request_context("/ansible/schedules/s1"):
session["user_role"] = "operator"
page = await render_template("ansible/schedule.html", **_page_ctx(_sched(check=True)))
main = page[page.index("<main"):]
assert 'href="/ansible/schedules"' in page # crumbs back to the list
verdict = main[main.index('<p class="page-verdict">'):]
verdict = verdict[:verdict.index("</p>")]
assert 'href="/ansible/playbooks/steward-builtin/maintenance/docker_prune.yml"' in verdict
assert "on group Docker Swarm · daily" in verdict and "dry run" in verdict and "next <time" in verdict
assert 'last: <a href="/ansible/runs/r1"><span class="badge badge-ok">Succeeded</span></a>' in verdict
assert 'action="/ansible/schedules/s1"' in main and 'value="Nightly prune"' in main
assert 'name="check" checked' in main
assert 'data-confirm="Delete schedule Nightly prune?' in main and 'action="/ansible/schedules/s1/delete"' in main
assert ">Run now</button>" in main
assert 'style="' not in main[main.index('id="form"'):] # the form on classes
assert not hex_colours(main), hex_colours(main)
async def test_a_new_schedule_starts_on_the_chosen_playbook(app):
async with app.test_request_context("/ansible/schedules/new"):
session["user_role"] = "operator"
page = await render_template("ansible/schedule.html", **_page_ctx(None))
assert "<title>New schedule — Steward</title>" in page
assert 'action="/ansible/schedules"' in page and "/delete" not in page
assert '<option value="maintenance/docker_prune.yml" selected>' in page
async def test_a_viewer_reads_a_schedule_without_its_form(app):
async with app.test_request_context("/ansible/schedules/s1"):
session["user_role"] = "viewer"
page = await render_template("ansible/schedule.html", **_page_ctx(_sched()))
assert "on group Docker Swarm" in page and 'id="form"' not in page and "/delete" not in page
assert "Run now" not in page