mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-08-08 01:33:01 +02:00
Offer multiple timing windows for repeater telemetry pickup. Closes #192.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
"""Tests for the telemetry interval math helpers.
|
||||
|
||||
These helpers back both the PATCH validation and the scheduler clamping,
|
||||
so regressions here silently corrupt cadence for every operator. Keep this
|
||||
suite fast, pure, and focused on the boundary values in the N=1..8 table.
|
||||
"""
|
||||
|
||||
from datetime import UTC, datetime, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from app.telemetry_interval import (
|
||||
DAILY_CHECK_CEILING,
|
||||
DEFAULT_TELEMETRY_INTERVAL_HOURS,
|
||||
TELEMETRY_INTERVAL_OPTIONS_HOURS,
|
||||
clamp_telemetry_interval,
|
||||
legal_interval_options,
|
||||
next_run_timestamp_utc,
|
||||
shortest_legal_interval_hours,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("n", "expected_hours"),
|
||||
[
|
||||
(1, 1),
|
||||
(2, 2),
|
||||
(3, 3),
|
||||
(4, 4),
|
||||
(5, 6),
|
||||
(6, 6),
|
||||
(7, 8),
|
||||
(8, 8),
|
||||
],
|
||||
)
|
||||
def test_shortest_legal_interval_table(n: int, expected_hours: int):
|
||||
"""The N=1..8 table must match the user-facing design exactly."""
|
||||
assert shortest_legal_interval_hours(n) == expected_hours
|
||||
|
||||
|
||||
def test_shortest_legal_interval_above_ceiling_falls_back_to_24h():
|
||||
# Not reachable today (max 8 tracked), but verify the math terminates
|
||||
# gracefully if the limit is ever raised above DAILY_CHECK_CEILING.
|
||||
assert shortest_legal_interval_hours(DAILY_CHECK_CEILING + 1) == 24
|
||||
|
||||
|
||||
def test_shortest_legal_interval_zero_returns_default():
|
||||
# No repeaters tracked: loop skips the cycle regardless, but the math
|
||||
# must terminate with a sane value (otherwise div-by-zero).
|
||||
assert shortest_legal_interval_hours(0) == DEFAULT_TELEMETRY_INTERVAL_HOURS
|
||||
|
||||
|
||||
def test_clamp_respects_user_pref_when_legal():
|
||||
# User picks 2h with N=2 tracked -> 2h is the shortest legal, keep it.
|
||||
assert clamp_telemetry_interval(2, 2) == 2
|
||||
|
||||
|
||||
def test_clamp_pushes_up_when_pref_illegal():
|
||||
# User picked 1h, then grew to 5 tracked. 5 repeaters' shortest legal is
|
||||
# 6h, so the scheduler should be using 6h while the saved pref is still 1.
|
||||
assert clamp_telemetry_interval(1, 5) == 6
|
||||
|
||||
|
||||
def test_clamp_unrecognized_value_falls_back_to_default():
|
||||
# A malformed saved value (e.g. from a hand-edited DB row) should default,
|
||||
# not error. Default 8h still gets clamped up if illegal for N.
|
||||
assert clamp_telemetry_interval(99, 1) == DEFAULT_TELEMETRY_INTERVAL_HOURS
|
||||
|
||||
|
||||
def test_clamp_preserves_longer_than_shortest_legal():
|
||||
# 24h is always legal at any N.
|
||||
assert clamp_telemetry_interval(24, 8) == 24
|
||||
|
||||
|
||||
def test_legal_options_filters_menu():
|
||||
assert legal_interval_options(5) == [6, 8, 12, 24]
|
||||
assert legal_interval_options(1) == list(TELEMETRY_INTERVAL_OPTIONS_HOURS)
|
||||
assert legal_interval_options(8) == [8, 12, 24]
|
||||
|
||||
|
||||
def test_next_run_is_strictly_future_even_on_boundary():
|
||||
# Exactly at a matching top-of-hour (8:00 UTC with interval=8), we want
|
||||
# the *next* one (16:00), never "now". Prevents a double-run in the same
|
||||
# minute if code mishandles equality.
|
||||
now = datetime(2026, 4, 16, 8, 0, 0, tzinfo=UTC)
|
||||
result = next_run_timestamp_utc(8, now=now)
|
||||
expected = datetime(2026, 4, 16, 16, 0, 0, tzinfo=UTC)
|
||||
assert result == int(expected.timestamp())
|
||||
|
||||
|
||||
def test_next_run_rounds_up_from_mid_hour():
|
||||
# 14:37 UTC with interval=8 -> next matching hour is 16:00.
|
||||
now = datetime(2026, 4, 16, 14, 37, 0, tzinfo=UTC)
|
||||
result = next_run_timestamp_utc(8, now=now)
|
||||
expected = datetime(2026, 4, 16, 16, 0, 0, tzinfo=UTC)
|
||||
assert result == int(expected.timestamp())
|
||||
|
||||
|
||||
def test_next_run_crosses_midnight():
|
||||
# 23:12 UTC with interval=8 -> midnight (00:00 next day) is legal.
|
||||
now = datetime(2026, 4, 16, 23, 12, 0, tzinfo=UTC)
|
||||
result = next_run_timestamp_utc(8, now=now)
|
||||
expected = datetime(2026, 4, 17, 0, 0, 0, tzinfo=UTC)
|
||||
assert result == int(expected.timestamp())
|
||||
|
||||
|
||||
def test_next_run_accepts_non_utc_input():
|
||||
# Non-UTC input should be normalized internally.
|
||||
from datetime import timedelta
|
||||
|
||||
pst = timezone(timedelta(hours=-8))
|
||||
# 08:00 PST == 16:00 UTC, a matching boundary for interval=8 -> next is 00:00 UTC.
|
||||
now = datetime(2026, 4, 16, 8, 0, 0, tzinfo=pst)
|
||||
result = next_run_timestamp_utc(8, now=now)
|
||||
expected = datetime(2026, 4, 17, 0, 0, 0, tzinfo=UTC)
|
||||
assert result == int(expected.timestamp())
|
||||
Reference in New Issue
Block a user