#5290 "Host page: the health tiles span the card's width; even spacing above Ansible": the four tiles take equal quarters (halves on a phone) and their sparklines stretch across them; the Monitors + Agent row drops the Agent card's own margin, so the gap above Ansible is 1rem like every other. #5291 "Add host opens as a dialog over the Hosts list": a native <dialog> on /hosts/, opened by Add host or /hosts/?add=1. A name clash re-renders the list with it open, the error and what was typed. GET /hosts/new is gone; hosts/form.html is Edit only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
2.5 KiB
Python
81 lines
2.5 KiB
Python
"""Unit tests for the status-source registry + sparkline (no DB, no Quart).
|
|
|
|
The DB-backed sources (ping/dns/http) are exercised in the integration lane;
|
|
here we cover the registry contract, ordering, failure isolation, and the
|
|
pure sparkline helper.
|
|
"""
|
|
import asyncio
|
|
|
|
from steward.core import status as status_mod
|
|
from steward.core.status import (
|
|
StatusEntry,
|
|
clear_status_sources,
|
|
collect_status,
|
|
register_status_source,
|
|
sparkline_svg,
|
|
)
|
|
|
|
|
|
def _entry(name, st, kind="ping"):
|
|
return StatusEntry(kind=kind, key=name, name=name, status=st)
|
|
|
|
|
|
def test_collect_orders_down_then_pending_then_up_alphabetical():
|
|
clear_status_sources()
|
|
|
|
async def src(db):
|
|
return [_entry("zeta", "up"), _entry("alpha", "down"),
|
|
_entry("beta", "pending"), _entry("gamma", "up")]
|
|
|
|
register_status_source(src)
|
|
entries = asyncio.run(collect_status(None))
|
|
assert [e.status for e in entries] == ["down", "pending", "up", "up"]
|
|
# within the trailing "up" group, alphabetical by name
|
|
assert entries[2].name == "gamma"
|
|
assert entries[3].name == "zeta"
|
|
clear_status_sources()
|
|
|
|
|
|
def test_failing_source_is_skipped_not_fatal():
|
|
clear_status_sources()
|
|
|
|
async def good(db):
|
|
return [_entry("a", "up")]
|
|
|
|
async def bad(db):
|
|
raise RuntimeError("boom")
|
|
|
|
register_status_source(good)
|
|
register_status_source(bad)
|
|
entries = asyncio.run(collect_status(None))
|
|
assert [e.name for e in entries] == ["a"]
|
|
clear_status_sources()
|
|
|
|
|
|
def test_register_is_idempotent_on_same_callable():
|
|
clear_status_sources()
|
|
|
|
async def src(db):
|
|
return []
|
|
|
|
register_status_source(src)
|
|
register_status_source(src)
|
|
assert status_mod._SOURCES.count(src) == 1
|
|
clear_status_sources()
|
|
|
|
|
|
def test_sparkline_svg_handles_edge_cases():
|
|
assert "polyline" not in sparkline_svg([5]) # <2 points → empty svg
|
|
assert "polyline" in sparkline_svg([1, 2, 3])
|
|
assert "polyline" in sparkline_svg([4, 4, 4]) # flat series, no div-by-zero
|
|
assert "polyline" in sparkline_svg([1, None, 3]) # None values filtered
|
|
|
|
|
|
def test_a_sparkline_can_stretch_across_its_tile():
|
|
"""#5290: the host page's tiles are wide; the line fills them, stroke kept."""
|
|
wide = sparkline_svg([1, 2, 3], height=24, fill_width=True)
|
|
assert 'width="100%"' in wide and 'preserveAspectRatio="none"' in wide
|
|
assert 'vector-effect="non-scaling-stroke"' in wide
|
|
fixed = sparkline_svg([1, 2, 3])
|
|
assert 'width="80"' in fixed and "preserveAspectRatio" not in fixed
|