Files
FabledSteward/tests/plugins/docker/test_docker_page.py
T
bvandeusenandClaude Opus 5.5 d8bacc7149 test(docker): #5152's tests read rows with SQL and the clock they render against
- The status test imported the plugin's models after the app had mapped
  them ("already defined"); it reads docker_host_status with SQL.
- The swarm-expiry count awaited inside a generator expression.
- The page test's since came from the module's NOW, so |ago said "5m 4s";
  it is built at test time and the assertion checks the minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 18:45:57 -04:00

107 lines
6.2 KiB
Python

"""The Docker page leads with what needs attention, ranked the way the MCP
tools rank it, and marks a container whose report is old as stale (#5123)."""
import re
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace as NS
from plugins.docker import service as svc
from plugins.docker.routes import _is_stale
from tests._templates import render_plugin_template
from tests.test_style_tokens import hex_colours
NOW = datetime.now(timezone.utc)
def _c(name, **kw):
base = dict(name=name, host_id="h1", image="nginx:1", status="running", health=None, exit_code=None,
oom_killed=False, restart_count=0, cpu_pct=5.0, mem_pct=10.0, compose_project=None,
service_name=None, ports_json=None, started_at=NOW - timedelta(hours=2),
scraped_at=NOW - timedelta(seconds=20))
base.update(kw)
return NS(**base)
def test_needs_attention_ranks_the_worst_problem_first():
ranked = [{"container": _c(n), "problems": p, "since": None, "replaced": False} for n, p in [
("fine", []), ("oom", ["oom_killed"]), ("sick", ["unhealthy"]),
("loop", ["crash_looping"]), ("exit", ["exited_nonzero"]), ("bounce", ["restarting", "crash_looping"])]]
assert [r["container"].name for r in svc.needs_attention(ranked)] == ["bounce", "loop", "sick", "oom", "exit"]
def test_stale_is_older_than_the_agents_window():
assert not _is_stale(_c("a"), NOW, 180)
assert _is_stale(_c("a", scraped_at=NOW - timedelta(minutes=10)), NOW, 180)
assert _is_stale(_c("a", scraped_at=None), NOW, 180)
# A fresh row on a host whose Docker stopped answering is stale too (#5152).
assert _is_stale(_c("a"), NOW, 180, {"h1": object()})
assert not _is_stale(_c("a"), NOW, 180, {"h2": object()})
assert svc.stale_after_seconds({"PLUGINS": {"host_agent": {"stale_after_seconds": 60}}}) == 60
assert svc.stale_after_seconds({}) == 180
async def test_a_crash_loop_comes_first_and_a_stale_row_says_so(app):
loop = _c("api", status="restarting", exit_code=1, restart_count=7)
old = _c("cache", scraped_at=NOW - timedelta(minutes=30), cpu_pct=95.0)
attention = [{"container": loop, "problems": ["restarting", "crash_looping"], "replaced": False,
"since": datetime.now(timezone.utc) - timedelta(minutes=8), "stale": False}]
groups = [{"host": None, "host_id": "h1", "host_name": "nas", "running": 1, "stopped": 1, "grouped": False,
"subgroups": [{"label": None, "containers": [
{"container": loop, "ports": [], "uptime": None, "stale": False},
{"container": old, "ports": [], "uptime": "2h", "stale": True}]}]}]
async with app.test_request_context("/plugins/docker/rows"):
page = await render_plugin_template(
app, "docker", "docker/rows.html", attention=attention, says=svc.PROBLEM_SAYS, severity=svc.PROBLEM_SEVERITY,
unhealthy=0, restarting=1, stale=1, host_groups=groups, swarm_services=[],
running=1, stopped=1, total=2)
page = re.sub(r">\s+<", "><", " ".join(page.split()))
card = page[page.index("Needs attention"):page.index("</section>")]
assert '<span class="badge badge-crit">Restarting</span>' in card
assert 'href="/plugins/docker/container/h1/api">api</a>' in card
assert "is restarting and keeps dying · exit 1 · since <time" in card and ">8m ago</time>" in card
assert "cache" not in card
assert page.index("Needs attention") < page.index("<table")
assert '<span class="stat-tile-label">Restarting</span><span class="stat-tile-value">1</span>' in page
assert '<span class="stat-tile-label">Stale</span><span class="stat-tile-value">1</span>' in page
assert 'class="badge badge-warn" title="Last report' in page # the stale row
assert '<span class="tone-crit">95.0%</span>' in page # CPU through Settings → Thresholds
assert "history" not in page.lower() and "<svg" not in page # no sparklines on the overview
assert not hex_colours(page), hex_colours(page)
async def test_a_healthy_board_has_no_attention_card(app):
async with app.test_request_context("/plugins/docker/rows"):
page = await render_plugin_template(
app, "docker", "docker/rows.html", attention=[], says=svc.PROBLEM_SAYS, severity=svc.PROBLEM_SEVERITY,
unhealthy=0, restarting=0, stale=0, host_groups=[], swarm_services=[], running=0, stopped=0, total=0)
assert "Needs attention</h2>" not in page and "No containers reported yet" in page
async def test_a_host_whose_docker_stopped_answering_says_so(app):
now = datetime.now(timezone.utc) # not the module's NOW: |ago reads the clock
down = [{"host_id": "h1", "host_name": "nas", "status": "unavailable",
"detail": "no Docker socket at /var/run/docker.sock", "since": now - timedelta(minutes=5)},
{"host_id": "h2", "host_name": "web-1", "status": "error",
"detail": "permission denied on /var/run/docker.sock", "since": now - timedelta(minutes=1)}]
async with app.test_request_context("/plugins/docker/rows"):
page = await render_plugin_template(
app, "docker", "docker/rows.html", docker_down=down, attention=[], says=svc.PROBLEM_SAYS,
severity=svc.PROBLEM_SEVERITY, unhealthy=0, restarting=0, stale=0, host_groups=[],
swarm_services=[], running=0, stopped=0, total=0)
page = re.sub(r">\s+<", "><", " ".join(page.split()))
card = page[page.index("Docker not answering"):page.index("</section>")]
assert '<span class="badge badge-warn">No Docker</span><a href="/hosts/h1">nas</a>' in card
assert "no Docker socket at /var/run/docker.sock · since <time" in card and ">5m " in card
assert '<span class="badge badge-crit">Error</span><a href="/hosts/h2">web-1</a>' in card
async def test_no_card_while_every_docker_answers(app):
async with app.test_request_context("/plugins/docker/rows"):
page = await render_plugin_template(
app, "docker", "docker/rows.html", docker_down=[], attention=[], says=svc.PROBLEM_SAYS,
severity=svc.PROBLEM_SEVERITY, unhealthy=0, restarting=0, stale=0, host_groups=[],
swarm_services=[], running=0, stopped=0, total=0)
assert "Docker not answering" not in page