mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-03 12:03:04 +02:00
117 lines
4.1 KiB
Python
117 lines
4.1 KiB
Python
"""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())
|