test(traefik): the page and widget routes read normals and anomalies (#5741)
#5741 "Traefik routers are graded against their own normal". The rows fragment and the Routers widget had no test that ran them against Postgres, so router_norms and the anomaly list were covered only by template renders. This one seeds a router answering 60% 4xx with a learned normal of 58% and an open p95 anomaly, and checks both routes answer 200, the page says the share is usual, and lists the anomaly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -177,3 +177,56 @@ def test_status_codes_sum_each_routers_codes_in_the_window_most_first(app):
|
||||
|
||||
got = asyncio.run(go())
|
||||
assert got == {a: [("502", 50), ("404", 42)], b: [("401", 4)]}
|
||||
|
||||
|
||||
def test_the_page_grades_a_router_against_its_normal_and_lists_its_anomalies(app):
|
||||
"""#5741: the rows fragment and the Routers widget read each router's
|
||||
normal for the hour and the routers' metric anomalies. A router that
|
||||
answers 60% 4xx every day is healthy and says it is usual; its open p95
|
||||
anomaly is listed on the page."""
|
||||
import time
|
||||
from steward.core.alerts import MetricBatch
|
||||
from steward.models.norms import MetricAnomaly, MetricNorm
|
||||
from steward.models.users import User, UserRole
|
||||
from plugins.traefik.models import TraefikMetric
|
||||
from plugins.traefik.routes import ROUTER_METRICS
|
||||
|
||||
now = datetime.now(timezone.utc).replace(microsecond=0)
|
||||
tag = uuid.uuid4().hex[:8]
|
||||
router, uid = f"errors-{tag}@docker", str(uuid.uuid4())
|
||||
figures = {"request_rate": 5.0, "error_rate_4xx_pct": 60.0, "error_rate_5xx_pct": 0.0,
|
||||
"latency_p50_ms": 5.0, "latency_p95_ms": 20.0, "latency_p99_ms": 30.0,
|
||||
"response_bytes_rate": 100.0}
|
||||
|
||||
async def go():
|
||||
async with app.db_sessionmaker() as s:
|
||||
async with s.begin():
|
||||
s.add(User(id=uid, username=f"tr-{tag}", email=f"tr-{tag}@test", password_hash="x",
|
||||
role=UserRole.viewer, is_active=True))
|
||||
s.add_all([TraefikMetric(router_name=router, scraped_at=now - timedelta(minutes=m),
|
||||
**figures) for m in range(1, 6)])
|
||||
batch = MetricBatch()
|
||||
for metric in ROUTER_METRICS:
|
||||
batch.add("traefik", router, metric, figures[metric], recorded_at=now)
|
||||
await batch.record(s)
|
||||
s.add(MetricNorm(source_module="traefik", resource_name=router,
|
||||
metric_name="error_rate_4xx_pct", slot=now.hour, kind="day",
|
||||
usual_hold=58.0, usual_peak=70.0, days_seen=7, updated_at=now))
|
||||
s.add(MetricAnomaly(source_module="traefik", resource_name=router,
|
||||
metric_name="latency_p95_ms", started_at=now - timedelta(minutes=20),
|
||||
last_started_at=now - timedelta(minutes=20), occurrences=1,
|
||||
peak_level=900.0, peak_at=now, usual_hold=20.0, usual_peak=40.0,
|
||||
review="to_review"))
|
||||
client = app.test_client()
|
||||
async with client.session_transaction() as sess:
|
||||
sess.update(user_id=uid, user_role="viewer", username=f"tr-{tag}", login_at=time.time())
|
||||
rows = await client.get("/plugins/traefik/rows?range=1h")
|
||||
widget = await client.get("/plugins/traefik/widget")
|
||||
return (rows.status_code, await rows.get_data(as_text=True),
|
||||
widget.status_code, await widget.get_data(as_text=True))
|
||||
|
||||
rows_status, page, widget_status, widget = asyncio.run(go())
|
||||
assert rows_status == 200 and widget_status == 200
|
||||
assert "4xx 60.0% is usual 58.0% at this hour" in page # healthy, and says why
|
||||
assert "Out of its normal now" in page and router in page # the open anomaly's card
|
||||
assert "4xx 60.0% is usual" not in widget # the widget's why is for problems
|
||||
|
||||
Reference in New Issue
Block a user