Files
meshcore-stats/tests/charts/test_timeseries.py
Jorijn Schrijvershof 137bbe3c66 feat: add telemetry chart discovery and unit display (#109)
* add telemetry chart discovery and unit display

* fix: tests were failing
2026-02-09 13:12:44 +01:00

228 lines
7.7 KiB
Python

"""Tests for TimeSeries data class and loading."""
from datetime import datetime, timedelta
import pytest
from meshmon.charts import (
DataPoint,
TimeSeries,
load_timeseries_from_db,
)
from meshmon.db import insert_metrics
BASE_TIME = datetime(2024, 1, 1, 0, 0, 0)
class TestDataPoint:
"""Tests for DataPoint dataclass."""
def test_stores_timestamp_and_value(self):
"""Stores timestamp and value."""
ts = BASE_TIME
dp = DataPoint(timestamp=ts, value=3.85)
assert dp.timestamp == ts
assert dp.value == 3.85
def test_value_types(self):
"""Accepts float and int values."""
ts = BASE_TIME
dp_float = DataPoint(timestamp=ts, value=3.85)
assert dp_float.value == 3.85
dp_int = DataPoint(timestamp=ts, value=100)
assert dp_int.value == 100
class TestTimeSeries:
"""Tests for TimeSeries dataclass."""
def test_stores_metadata(self):
"""Stores metric, role, period metadata."""
ts = TimeSeries(
metric="bat",
role="repeater",
period="day",
)
assert ts.metric == "bat"
assert ts.role == "repeater"
assert ts.period == "day"
def test_empty_by_default(self):
"""Points list is empty by default."""
ts = TimeSeries(metric="bat", role="repeater", period="day")
assert ts.points == []
assert ts.is_empty is True
def test_timestamps_property(self, sample_timeseries):
"""timestamps property returns list of timestamps."""
timestamps = sample_timeseries.timestamps
assert len(timestamps) == len(sample_timeseries.points)
assert all(isinstance(t, datetime) for t in timestamps)
def test_values_property(self, sample_timeseries):
"""values property returns list of values."""
values = sample_timeseries.values
assert len(values) == len(sample_timeseries.points)
assert all(isinstance(v, float) for v in values)
def test_is_empty_false_with_data(self, sample_timeseries):
"""is_empty is False when points exist."""
assert sample_timeseries.is_empty is False
def test_is_empty_true_without_data(self, empty_timeseries):
"""is_empty is True when no points."""
assert empty_timeseries.is_empty is True
class TestLoadTimeseriesFromDb:
"""Tests for load_timeseries_from_db function."""
def test_loads_metric_data(self, initialized_db, configured_env):
"""Loads metric data from database."""
base_ts = 1704067200
insert_metrics(base_ts, "repeater", {"bat": 3850.0}, initialized_db)
insert_metrics(base_ts + 900, "repeater", {"bat": 3860.0}, initialized_db)
ts = load_timeseries_from_db(
role="repeater",
metric="bat",
end_time=datetime.fromtimestamp(base_ts + 1000),
lookback=timedelta(hours=1),
period="day",
)
assert len(ts.points) == 2
def test_filters_by_time_range(self, initialized_db, configured_env):
"""Only loads data within time range."""
base_ts = 1704067200
# Insert data outside and inside range
insert_metrics(base_ts - 7200, "repeater", {"bat": 3800.0}, initialized_db) # Outside
insert_metrics(base_ts, "repeater", {"bat": 3850.0}, initialized_db) # Inside
insert_metrics(base_ts + 7200, "repeater", {"bat": 3900.0}, initialized_db) # Outside
ts = load_timeseries_from_db(
role="repeater",
metric="bat",
end_time=datetime.fromtimestamp(base_ts + 1800),
lookback=timedelta(hours=1),
period="day",
)
assert len(ts.points) == 1
assert ts.points[0].value == pytest.approx(3.85) # Transformed to volts
def test_returns_correct_metadata(self, initialized_db, configured_env):
"""Returned TimeSeries has correct metadata."""
ts = load_timeseries_from_db(
role="repeater",
metric="bat",
end_time=BASE_TIME,
lookback=timedelta(hours=1),
period="week",
)
assert ts.metric == "bat"
assert ts.role == "repeater"
assert ts.period == "week"
def test_uses_prefetched_metrics(self, initialized_db, configured_env):
"""Can use pre-fetched metrics dict."""
base_ts = 1704067200
insert_metrics(base_ts, "repeater", {"bat": 3850.0}, initialized_db)
# Pre-fetch metrics
from meshmon.db import get_metrics_for_period
all_metrics = get_metrics_for_period("repeater", base_ts - 3600, base_ts + 3600)
ts = load_timeseries_from_db(
role="repeater",
metric="bat",
end_time=datetime.fromtimestamp(base_ts + 3600),
lookback=timedelta(hours=2),
period="day",
all_metrics=all_metrics,
)
assert len(ts.points) == 1
def test_handles_missing_metric(self, initialized_db, configured_env):
"""Returns empty TimeSeries for missing metric."""
ts = load_timeseries_from_db(
role="repeater",
metric="nonexistent_metric",
end_time=BASE_TIME,
lookback=timedelta(hours=1),
period="day",
)
assert ts.is_empty
def test_sorts_by_timestamp(self, initialized_db, configured_env):
"""Points are sorted by timestamp."""
base_ts = 1704067200
# Insert out of order
insert_metrics(base_ts + 300, "repeater", {"bat": 3860.0}, initialized_db)
insert_metrics(base_ts, "repeater", {"bat": 3850.0}, initialized_db)
insert_metrics(base_ts + 150, "repeater", {"bat": 3855.0}, initialized_db)
ts = load_timeseries_from_db(
role="repeater",
metric="bat",
end_time=datetime.fromtimestamp(base_ts + 600),
lookback=timedelta(hours=1),
period="day",
)
timestamps = [p.timestamp for p in ts.points]
assert timestamps == sorted(timestamps)
def test_telemetry_temperature_converts_to_imperial(self, initialized_db, configured_env, monkeypatch):
"""Telemetry temperature converts from C to F when DISPLAY_UNIT_SYSTEM=imperial."""
monkeypatch.setenv("DISPLAY_UNIT_SYSTEM", "imperial")
import meshmon.env
meshmon.env._config = None
base_ts = 1704067200
insert_metrics(base_ts, "repeater", {"telemetry.temperature.1": 0.0}, initialized_db)
insert_metrics(base_ts + 900, "repeater", {"telemetry.temperature.1": 10.0}, initialized_db)
ts = load_timeseries_from_db(
role="repeater",
metric="telemetry.temperature.1",
end_time=datetime.fromtimestamp(base_ts + 1000),
lookback=timedelta(hours=1),
period="day",
)
assert [p.value for p in ts.points] == pytest.approx([32.0, 50.0])
def test_telemetry_temperature_stays_metric(self, initialized_db, configured_env, monkeypatch):
"""Telemetry temperature remains Celsius when DISPLAY_UNIT_SYSTEM=metric."""
monkeypatch.setenv("DISPLAY_UNIT_SYSTEM", "metric")
import meshmon.env
meshmon.env._config = None
base_ts = 1704067200
insert_metrics(base_ts, "repeater", {"telemetry.temperature.1": 0.0}, initialized_db)
insert_metrics(base_ts + 900, "repeater", {"telemetry.temperature.1": 10.0}, initialized_db)
ts = load_timeseries_from_db(
role="repeater",
metric="telemetry.temperature.1",
end_time=datetime.fromtimestamp(base_ts + 1000),
lookback=timedelta(hours=1),
period="day",
)
assert [p.value for p in ts.points] == pytest.approx([0.0, 10.0])