mirror of
https://github.com/zvx-echo6/meshai.git
synced 2026-08-26 17:31:34 +00:00
125 lines
4.6 KiB
Python
125 lines
4.6 KiB
Python
|
|
"""Tests for MeshContext.update_settings — live hot-reload without restart.
|
||
|
|
|
||
|
|
Covers:
|
||
|
|
- update_settings applies max_age, observe_channels, ignore_nodes in place.
|
||
|
|
- Normalization mirrors __init__ exactly (empty list → None for
|
||
|
|
observe_channels; empty list/None → set() for ignore_nodes).
|
||
|
|
- An observation older than a newly-shortened max_age is pruned and
|
||
|
|
subsequently excluded by get_context_block.
|
||
|
|
- Omitting a parameter leaves the current value unchanged.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import time
|
||
|
|
|
||
|
|
from meshai.context import MeshContext, MeshObservation
|
||
|
|
|
||
|
|
|
||
|
|
def _make_context(**kwargs) -> MeshContext:
|
||
|
|
return MeshContext(**kwargs)
|
||
|
|
|
||
|
|
|
||
|
|
class TestUpdateSettingsNormalization:
|
||
|
|
"""update_settings mirrors __init__ normalization rules."""
|
||
|
|
|
||
|
|
def test_max_age_updated(self):
|
||
|
|
ctx = _make_context(max_age=3600)
|
||
|
|
ctx.update_settings(max_age=7200)
|
||
|
|
assert ctx._max_age == 7200
|
||
|
|
|
||
|
|
def test_observe_channels_non_empty_becomes_set(self):
|
||
|
|
ctx = _make_context()
|
||
|
|
ctx.update_settings(observe_channels=[1, 2, 3])
|
||
|
|
assert ctx._observe_channels == {1, 2, 3}
|
||
|
|
|
||
|
|
def test_observe_channels_empty_list_becomes_none(self):
|
||
|
|
"""Empty list → None (observe all), mirroring constructor behaviour."""
|
||
|
|
ctx = _make_context(observe_channels=[1, 2])
|
||
|
|
ctx.update_settings(observe_channels=[])
|
||
|
|
assert ctx._observe_channels is None
|
||
|
|
|
||
|
|
def test_observe_channels_none_arg_leaves_unchanged(self):
|
||
|
|
"""Passing None as argument leaves _observe_channels unchanged."""
|
||
|
|
ctx = _make_context(observe_channels=[5])
|
||
|
|
ctx.update_settings(observe_channels=None)
|
||
|
|
assert ctx._observe_channels == {5}
|
||
|
|
|
||
|
|
def test_ignore_nodes_non_empty_becomes_set(self):
|
||
|
|
ctx = _make_context()
|
||
|
|
ctx.update_settings(ignore_nodes=["!abc", "!def"])
|
||
|
|
assert ctx._ignore_nodes == {"!abc", "!def"}
|
||
|
|
|
||
|
|
def test_ignore_nodes_empty_list_becomes_empty_set(self):
|
||
|
|
"""Empty list → set(), mirroring constructor behaviour."""
|
||
|
|
ctx = _make_context(ignore_nodes=["!abc"])
|
||
|
|
ctx.update_settings(ignore_nodes=[])
|
||
|
|
assert ctx._ignore_nodes == set()
|
||
|
|
|
||
|
|
def test_ignore_nodes_none_arg_leaves_unchanged(self):
|
||
|
|
"""Passing None as argument leaves _ignore_nodes unchanged."""
|
||
|
|
ctx = _make_context(ignore_nodes=["!abc"])
|
||
|
|
ctx.update_settings(ignore_nodes=None)
|
||
|
|
assert ctx._ignore_nodes == {"!abc"}
|
||
|
|
|
||
|
|
def test_omitted_params_unchanged(self):
|
||
|
|
"""Only supplied params are modified; others stay as-is."""
|
||
|
|
ctx = _make_context(max_age=100, observe_channels=[3], ignore_nodes=["!x"])
|
||
|
|
ctx.update_settings(max_age=999)
|
||
|
|
assert ctx._max_age == 999
|
||
|
|
assert ctx._observe_channels == {3}
|
||
|
|
assert ctx._ignore_nodes == {"!x"}
|
||
|
|
|
||
|
|
|
||
|
|
class TestPruneAfterMaxAgeShortened:
|
||
|
|
"""After update_settings shortens max_age, prune() removes old entries
|
||
|
|
and get_context_block returns only the surviving observations."""
|
||
|
|
|
||
|
|
def _insert_obs(self, ctx: MeshContext, timestamp: float, text: str = "msg"):
|
||
|
|
"""Bypass observe() to inject an observation with an arbitrary timestamp."""
|
||
|
|
obs = MeshObservation(
|
||
|
|
timestamp=timestamp,
|
||
|
|
sender_name="TestNode",
|
||
|
|
sender_id="!test1",
|
||
|
|
channel=0,
|
||
|
|
is_dm=False,
|
||
|
|
text=text,
|
||
|
|
)
|
||
|
|
ctx._buffer.append(obs)
|
||
|
|
|
||
|
|
def test_old_observation_pruned_after_max_age_shortened(self):
|
||
|
|
now = time.time()
|
||
|
|
|
||
|
|
# Start with a generous max_age (1 hour).
|
||
|
|
ctx = _make_context(max_age=3600)
|
||
|
|
|
||
|
|
# Insert one observation that is 30 minutes old.
|
||
|
|
self._insert_obs(ctx, now - 1800, text="old message")
|
||
|
|
assert ctx.count == 1
|
||
|
|
assert "old message" in ctx.get_context_block()
|
||
|
|
|
||
|
|
# Shorten max_age to 10 minutes — the 30-minute-old observation is now stale.
|
||
|
|
ctx.update_settings(max_age=600)
|
||
|
|
assert ctx._max_age == 600
|
||
|
|
|
||
|
|
# prune() is the documented mechanism to remove expired observations.
|
||
|
|
pruned = ctx.prune()
|
||
|
|
assert pruned == 1
|
||
|
|
assert ctx.count == 0
|
||
|
|
assert ctx.get_context_block() == ""
|
||
|
|
|
||
|
|
def test_recent_observation_survives_after_max_age_shortened(self):
|
||
|
|
now = time.time()
|
||
|
|
|
||
|
|
ctx = _make_context(max_age=3600)
|
||
|
|
|
||
|
|
# Insert one old and one recent observation.
|
||
|
|
self._insert_obs(ctx, now - 1800, text="old message")
|
||
|
|
self._insert_obs(ctx, now - 60, text="recent message")
|
||
|
|
|
||
|
|
# Shorten max_age to 10 minutes — only the old one is stale.
|
||
|
|
ctx.update_settings(max_age=600)
|
||
|
|
ctx.prune()
|
||
|
|
|
||
|
|
assert ctx.count == 1
|
||
|
|
block = ctx.get_context_block()
|
||
|
|
assert "recent message" in block
|
||
|
|
assert "old message" not in block
|