fix(hands): watchdog seen moves only on a new peer beat, tracked by message id
This commit is contained in:
1 parent
a39e0b27af
commit
78a0293819
2 files changed
+94
-7
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import json
|
||||
import logging
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from types import SimpleNamespace
|
||||
@@ -113,6 +114,59 @@ def test_fresh_beat_arms_and_stays_quiet(tmp_path):
|
||||
assert w.seen == NOW and w.top == 100
|
||||
|
||||
|
||||
def test_same_beat_seen_twice_does_not_move_seen(tmp_path):
|
||||
w = state(tmp_path)
|
||||
old = hb(NOW - timedelta(hours=1), eid=200)
|
||||
first = look(w, [old], now=NOW, res=None, full=True)
|
||||
assert first.armed and first.alarm is None
|
||||
assert w.seen == NOW and w.beat_id == 200
|
||||
|
||||
# Тот же самый hb в окне следующего прогона - это не новое показание
|
||||
again = look(w, [old], now=NOW + timedelta(hours=6), res=None, full=True)
|
||||
assert w.seen == NOW and again.alarm is None
|
||||
assert "отметка та же" in again.note
|
||||
|
||||
# И через порог 36h та же отметка уже не выглядит живой цепью
|
||||
late = NOW + timedelta(hours=37)
|
||||
loud = look(w, [old], now=late, res=None, full=True)
|
||||
assert loud.alarm == "silence" and w.seen == NOW
|
||||
assert loud.age is not None and loud.age > timedelta(hours=36)
|
||||
|
||||
# Пришла новая отметка - новый id, и seen снова двигается
|
||||
fresh = look(w, [old, hb(late, eid=201)], now=late, res=None, full=True)
|
||||
assert fresh.alarm is None and w.seen == late and w.beat_id == 201
|
||||
|
||||
|
||||
def test_state_without_beat_id_trusts_its_own_seen(tmp_path):
|
||||
path = tmp_path / "сторож.json"
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"version": 1,
|
||||
"at": "2026-09-21T05:00:01Z",
|
||||
"seen": "2026-09-22T05:42:01Z",
|
||||
"every": 86400,
|
||||
"every_unknown": False,
|
||||
"top": 300,
|
||||
"alerted": None,
|
||||
"unarmed_since": None,
|
||||
"unarmed_told": False,
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
w = Watch.load(path)
|
||||
was = datetime(2026, 9, 22, 5, 42, 1, tzinfo=UTC)
|
||||
assert w.beat_id == 0 and w.seen == was
|
||||
|
||||
old = hb(datetime(2026, 9, 21, 5, 0, 1, tzinfo=UTC), eid=305)
|
||||
assert look(w, [old], now=NOW, res=None, full=True).alarm is None
|
||||
assert w.seen == was and w.beat_id == 305
|
||||
# Старое состояние не пере-вооружилось на старой отметке: порог придёт в срок
|
||||
over = datetime(2026, 9, 23, 20, 0, tzinfo=UTC)
|
||||
assert look(w, [old], now=over, res=None, full=True).alarm == "silence"
|
||||
|
||||
|
||||
def test_threshold_is_every_times_one_and_a_half(tmp_path):
|
||||
# 30 часов при every=24h - ещё не тревога: одна потерянная строка не в счёт
|
||||
quiet = state(tmp_path, seen=NOW - timedelta(hours=30), every=timedelta(hours=24))
|
||||
@@ -265,6 +319,7 @@ def test_state_survives_a_round_trip(tmp_path):
|
||||
seen=NOW,
|
||||
every=timedelta(hours=24),
|
||||
every_unknown=True,
|
||||
beat_id=329,
|
||||
top=42,
|
||||
alerted=NOW,
|
||||
unarmed_since=None,
|
||||
@@ -273,6 +328,7 @@ def test_state_survives_a_round_trip(tmp_path):
|
||||
w.save()
|
||||
back = Watch.load(w.path)
|
||||
assert back.at == NOW and back.seen == NOW and back.top == 42
|
||||
assert back.beat_id == 329
|
||||
assert back.every == timedelta(hours=24) and back.every_unknown
|
||||
assert back.alerted == NOW and back.unarmed_told
|
||||
assert Watch.load(tmp_path / "нет.json").seen is None
|
||||
|
||||
Reference in new issue
Block a user