"""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) 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("")] assert 'Restarting' in card assert 'href="/plugins/docker/container/h1/api">api' in card assert "is restarting and keeps dying · exit 1 · since 8m ago" in card assert "cache" not in card assert page.index("Needs attention") < page.index("Restarting1' in page assert 'Stale1' in page assert 'class="badge badge-warn" title="Last report' in page # the stale row assert '95.0%' in page # CPU through Settings → Thresholds assert "history" not in page.lower() and "" not in page and "No containers reported yet" in page