- 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>
90 lines
5.2 KiB
Python
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
|