"""Tests for FrameDispatcher.""" from __future__ import annotations from unittest.mock import MagicMock, patch from app.pipeline.frame_dispatcher import FrameDispatcher def _make_frame(): frame = MagicMock() frame.isValid.return_value = True return frame class TestFrameDispatcher: def setup_method(self): # FrameDispatcher is a QObject — needs QApplication # Use minimal mock to avoid Qt dependency in unit tests with patch("app.pipeline.frame_dispatcher.QObject.__init__", return_value=None): self.dispatcher = FrameDispatcher.__new__(FrameDispatcher) self.dispatcher._subscribers = [] self.dispatcher._frame_count = 0 self.dispatcher._last_dispatch_time = 0.0 def test_subscribe_adds_subscriber(self): cb = MagicMock() self.dispatcher.subscribe(cb) assert self.dispatcher.subscriber_count() == 1 def test_subscribe_same_callback_twice_is_noop(self): cb = MagicMock() self.dispatcher.subscribe(cb) self.dispatcher.subscribe(cb) assert self.dispatcher.subscriber_count() == 1 def test_unsubscribe_removes_subscriber(self): cb = MagicMock() self.dispatcher.subscribe(cb) self.dispatcher.unsubscribe(cb) assert self.dispatcher.subscriber_count() == 0 def test_unsubscribe_nonexistent_does_not_raise(self): cb = MagicMock() self.dispatcher.unsubscribe(cb) # should not raise def test_dispatch_calls_all_subscribers(self): cb1 = MagicMock() cb2 = MagicMock() self.dispatcher.subscribe(cb1) self.dispatcher.subscribe(cb2) frame = _make_frame() self.dispatcher.dispatch(frame) cb1.assert_called_once_with(frame) cb2.assert_called_once_with(frame) def test_dispatch_increments_frame_count(self): frame = _make_frame() self.dispatcher.dispatch(frame) self.dispatcher.dispatch(frame) assert self.dispatcher.frame_count == 2 def test_dispatch_with_no_subscribers_does_not_raise(self): frame = _make_frame() self.dispatcher.dispatch(frame) # should not raise def test_subscriber_exception_does_not_stop_others(self): def bad_cb(f): raise RuntimeError("boom") good_cb = MagicMock() self.dispatcher.subscribe(bad_cb) self.dispatcher.subscribe(good_cb) frame = _make_frame() self.dispatcher.dispatch(frame) # should not raise good_cb.assert_called_once_with(frame)