feat(charting): unify configurable time windows
This commit is contained in:
@@ -14,16 +14,33 @@ from media_library_viewer_api.widgets.prometheus_range import (
|
||||
|
||||
|
||||
class TestStepForWindow:
|
||||
"""SC-104: every preset must yield 100–300 points."""
|
||||
"""SC-104: presets preserve usable resolution without sub-15s steps."""
|
||||
|
||||
@pytest.mark.parametrize("preset", sorted(WINDOW_PRESETS))
|
||||
def test_presets_yield_in_band_point_counts(self, preset: str) -> None:
|
||||
def test_presets_yield_supported_point_counts(self, preset: str) -> None:
|
||||
window = WINDOW_PRESETS[preset]
|
||||
step = step_for_window(window)
|
||||
# Clamped minimum.
|
||||
# The Prometheus-safe 15-second floor limits the two short presets to
|
||||
# 20 and 60 points; all longer windows stay in the 100–300 target band.
|
||||
assert step >= 15
|
||||
point_count = window // step
|
||||
assert 100 <= point_count <= 300, f"{preset}: {point_count} points (step={step})"
|
||||
assert min(100, window // 15) <= point_count <= 300, f"{preset}: {point_count} points (step={step})"
|
||||
|
||||
def test_window_presets_cover_the_shared_chart_windows(self) -> None:
|
||||
assert WINDOW_PRESETS == {
|
||||
"5m": 300,
|
||||
"15m": 900,
|
||||
"30m": 1_800,
|
||||
"1h": 3_600,
|
||||
"3h": 10_800,
|
||||
"6h": 21_600,
|
||||
"12h": 43_200,
|
||||
"24h": 86_400,
|
||||
"2d": 172_800,
|
||||
"7d": 604_800,
|
||||
"14d": 1_209_600,
|
||||
"30d": 2_592_000,
|
||||
}
|
||||
|
||||
def test_floor_of_fifteen_seconds(self) -> None:
|
||||
# A tiny window that would otherwise produce a sub-15s step is clamped.
|
||||
|
||||
@@ -87,6 +87,32 @@ def test_scheduler_routes_expose_status_history_and_disabled_manual_run(schedule
|
||||
assert manual.status_code == 400
|
||||
|
||||
|
||||
def test_scheduler_samples_all_values_reads_all_retained_samples(scheduler_client):
|
||||
client, store = scheduler_client
|
||||
service = store.upsert_service(
|
||||
{
|
||||
"service_type": "qbittorrent",
|
||||
"name": "qbit",
|
||||
"config": {"base_url": "http://qbit:8080"},
|
||||
"secrets": {},
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
retained = [{"ts": 10, "dl_speed": 20, "up_speed": 30}]
|
||||
with patch("media_library_viewer_api.routers.scheduler.QbittorrentSampleStore") as store_cls:
|
||||
store_cls.return_value.window.return_value = retained
|
||||
response = client.get(f"/api/scheduler/services/{service['id']}/samples?all_values=true")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {
|
||||
"service_id": service["id"],
|
||||
"window_seconds": None,
|
||||
"all_values": True,
|
||||
"samples": retained,
|
||||
}
|
||||
store_cls.return_value.window.assert_called_once_with(service["id"])
|
||||
|
||||
|
||||
def test_sample_store_applies_time_and_row_limits(tmp_path):
|
||||
harness = ServiceDataHarness(tmp_path)
|
||||
harness.register(QBITTORRENT_CONCERN)
|
||||
|
||||
@@ -1224,6 +1224,21 @@ async def test_qbittorrent_speed_reads_samples_without_polling(tmp_path):
|
||||
assert dl_points[-1]["v"] == 500000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_qbittorrent_speed_all_values_reads_all_retained_samples():
|
||||
"""The all-values speed setting intentionally omits the time cutoff."""
|
||||
from media_library_viewer_api.widgets.sources import QbittorrentWidgetSource
|
||||
|
||||
adapter = QbittorrentWidgetSource()
|
||||
service = ServiceRecord(id="svc-speed", service_type="qbittorrent", name="qbit", config={}, secrets={})
|
||||
with patch("media_library_viewer_api.widgets.sources.QbittorrentSampleStore") as store_cls:
|
||||
store_cls.return_value.window.return_value = [{"ts": 10, "dl_speed": 20, "up_speed": 30}]
|
||||
result = await adapter.fetch(service, "speed", {"window_seconds": "all"})
|
||||
|
||||
store_cls.return_value.window.assert_called_once_with("svc-speed")
|
||||
assert result["series"][0]["points"] == [{"t": 10_000, "v": 20}]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_qbittorrent_adapter_missing_service():
|
||||
from media_library_viewer_api.widgets.sources import QbittorrentWidgetSource
|
||||
|
||||
Reference in New Issue
Block a user