from edge.events import Event, EventType, Severity from edge.queue import OfflineQueue def _event(n=1): return Event(EventType.FALL, Severity.HIGH, "cam-01", n, None, float(n), 3.0, {}, None) class _Client: def __init__(self, fail=False): self.fail = fail self.sent = [] def send(self, event, snapshots): if self.fail: return False self.sent.append(event.track_id) return True def test_try_send_success_not_queued(tmp_path): q = OfflineQueue(str(tmp_path / "q.db"), _Client()) assert q.try_send(_event(1), [b"x"]) is True assert q.pending() == 0 def test_failure_is_queued(tmp_path): q = OfflineQueue(str(tmp_path / "q.db"), _Client(fail=True)) assert q.try_send(_event(1), [b"x"]) is False assert q.pending() == 1 def test_flush_resends_when_recovered(tmp_path): client = _Client(fail=True) q = OfflineQueue(str(tmp_path / "q.db"), client) q.try_send(_event(1), [b"x"]) q.try_send(_event(2), [b"y"]) assert q.pending() == 2 client.fail = False assert q.flush() == 2 assert q.pending() == 0 assert client.sent == [1, 2] class _PoisonClient: """Rejects one specific event forever (e.g. a 400 the cloud will never accept).""" def __init__(self, poison_id): self.poison = poison_id self.sent = [] def send(self, event, snapshots): if event.track_id == self.poison: return False self.sent.append(event.track_id) return True def test_queue_is_bounded_and_keeps_newest(tmp_path): # An extended outage (or a permanent misconfig) must not fill the SD card. The queue caps # itself, evicting the OLDEST — a fresh fall alert matters more than a stale one. q = OfflineQueue(str(tmp_path / "q.db"), _Client(fail=True), max_rows=5) for n in range(20): q.try_send(_event(n), [b"x"]) assert q.pending() == 5 q.client.fail = False q.flush() assert q.client.sent == [15, 16, 17, 18, 19] # the newest 5 survived def test_poison_head_is_dropped_and_unblocks_the_queue(tmp_path): # A permanently-rejected event at the head must not wedge the whole queue forever; after # max_send_attempts it is dropped so the events behind it can drain. client = _PoisonClient(99) q = OfflineQueue(str(tmp_path / "q.db"), client, max_send_attempts=3) q.enqueue(_event(99), [b"x"]) # poison, at the head q.enqueue(_event(2), [b"y"]) # good, behind it for _ in range(3): q.flush() assert client.sent == [2] # good event delivered assert q.pending() == 0 # poison dropped, queue drained