netsleuth/tests/test_trend.py
Andrew Pimenov 245533f904 add Prometheus/CSV export, auto-diff vs previous run, and --trend
New metrics.py: collect_metrics() does the per-section extraction a flat
dict walk can't (latency needs label/host/method, TLS label/host, path hops
target/ttl/ip/asn), producing a flat Metric list that render_prometheus()
and render_csv() each format in about 15 lines. Simpler than treating them
as two separate renderers. FORMAT_EXTENSIONS gained "prom"/"csv" as the
single wiring point both parse_formats() and Output._normalize_formats()
already key off of.

New history.py finds a previous JSON report for the same ASN by reusing
the exact filename convention exporter.report_filename() already writes,
so the lookup key can't drift from what's on disk. Wired into diagnose()
before write_report() - a compact vs-previous-run summary
(compare.py::render_diff_brief(), new) prints automatically whenever this
run writes JSON and an earlier one exists for the same network; silent on
every other case (first run, prior run wrote no JSON prints one note
instead of guessing). New --no-auto-diff escapes it per run,
history.auto_diff in config disables it entirely.

New trend.py aggregates N saved JSON reports into per-label sparkline
series via --trend N - read-only, and it doesn't touch the network to find
"this network's" history either: history.latest_key() just reads it off
the newest report filename already on disk.

Growing FORMAT_EXTENSIONS to 5 entries broke three pre-existing tests that
hardcoded the old 3-format set; fixed in this commit.

Live-tested: prom/csv artifacts render correctly, auto-diff fired on a
second run, --trend 3 produced readable output, --compare unaffected.

Part of plans/zesty-zooming-wilkinson.md (gitignored).
2026-08-12 22:10:21 +03:00

76 lines
2.6 KiB
Python

from __future__ import annotations
from netsleuth.trend import build_trend, render_trend, series_summary
def _report(started_at: str, avg_ms: float | None, download: float | None, score: int | None) -> dict:
return {
"meta": {"started_at": started_at},
"latency": {"data": [{"label": "cloudflare-dns", "avg_ms": avg_ms}] if avg_ms is not None else []},
"speed": {"data": {"download_mbps": download}},
"interpretation": {"overall_score": score},
}
def test_build_trend_over_a_single_report():
trend = build_trend([_report("2026-01-01T00:00:00Z", 10.0, 100.0, 90)])
assert trend.timestamps == ["2026-01-01T00:00:00Z"]
assert trend.latency[0].label == "cloudflare-dns"
assert trend.latency[0].values == [10.0]
assert trend.download_mbps == [100.0]
assert trend.overall_score == [90]
def test_build_trend_over_two_reports():
reports = [
_report("2026-01-01T00:00:00Z", 10.0, 100.0, 90),
_report("2026-01-02T00:00:00Z", 12.0, 90.0, 85),
]
trend = build_trend(reports)
assert trend.latency[0].values == [10.0, 12.0]
assert trend.download_mbps == [100.0, 90.0]
def test_build_trend_handles_a_hole_when_a_label_is_missing_from_one_report():
reports = [
_report("2026-01-01T00:00:00Z", 10.0, 100.0, 90),
{"meta": {"started_at": "2026-01-02T00:00:00Z"}, "latency": {"data": []}, "speed": {"data": {}}, "interpretation": {}},
]
trend = build_trend(reports)
assert trend.latency[0].values == [10.0, None]
def test_build_trend_a_label_appearing_only_in_a_later_report():
reports = [
{"meta": {"started_at": "2026-01-01T00:00:00Z"}, "latency": {"data": []}, "speed": {"data": {}}, "interpretation": {}},
_report("2026-01-02T00:00:00Z", 8.0, 100.0, 90),
]
trend = build_trend(reports)
assert trend.latency[0].values == [None, 8.0]
def test_series_summary_basic_stats():
lo, median, hi, delta = series_summary([10.0, 20.0, 30.0])
assert lo == 10.0
assert median == 20.0
assert hi == 30.0
assert delta == 20.0
def test_series_summary_ignores_holes_for_min_max_median():
lo, median, hi, delta = series_summary([None, 10.0, None, 30.0, None])
assert lo == 10.0
assert hi == 30.0
assert delta == 20.0
def test_series_summary_all_none():
assert series_summary([None, None]) == (None, None, None, None)
def test_render_trend_produces_readable_output():
trend = build_trend([_report("2026-01-01T00:00:00Z", 10.0, 100.0, 90)])
text = render_trend(trend)
assert "Latency: cloudflare-dns" in text
assert "Download" in text
assert "Overall score" in text