import logging from unittest.mock import Mock import pytest from target_simulator.gui import main_view as mv def _make_minimal_mainview_with_comm(use_json: bool): # Bypass __init__ to avoid Tkinter initialization in tests inst = mv.MainView.__new__(mv.MainView) # Provide a simple logger inst.logger = logging.getLogger("test.main_view") # Mock communicator comm = Mock() comm.is_open = True # Emulate the internal flag used by MainView to decide JSON vs legacy setattr(comm, "_use_json_protocol", use_json) comm.send_commands.return_value = True inst.target_communicator = comm # Mock simulation hub: immediate empty state so reset returns quickly hub = Mock() hub.has_active_real_targets.return_value = False inst.simulation_hub = hub # Attach a lightweight stub SimulationController so the test calls # delegate to a controller instance (the real app routes through # SimulationController.reset_radar_state). This avoids complex # initialization while reflecting current production behavior. class _StubSimulationController: def __init__(self, comm): self._comm = comm def reset_radar_state(self, main_view): # Mirror the behavior expected by the tests: choose JSON # or legacy reset depending on communicator flag. target_comm = getattr(main_view, "target_communicator", None) or self._comm if not target_comm or not getattr(target_comm, "is_open", False): return False use_json = getattr(target_comm, "_use_json_protocol", False) try: if use_json: return bool(target_comm.send_commands(['{"CMD":"reset"}\n'])) # legacy path sends preparatory rows and atomic tgtset target_comm.send_commands(["mex.t_rows=80\n"]) return bool(target_comm.send_commands(["tgtset /-s\n"])) except Exception: return False inst.simulation_controller = _StubSimulationController(comm) return inst, comm def test_reset_uses_json_when_configured(): inst, comm = _make_minimal_mainview_with_comm(use_json=True) ok = inst._reset_radar_state() assert ok is True # send_commands should be called once with the JSON reset payload assert comm.send_commands.called sent = comm.send_commands.call_args[0][0] assert isinstance(sent, list) assert sent == ['{"CMD":"reset"}\n'] def test_reset_uses_legacy_when_not_configured(): inst, comm = _make_minimal_mainview_with_comm(use_json=False) ok = inst._reset_radar_state() assert ok is True assert comm.send_commands.called # The implementation may send preparatory commands in a separate # call followed by the atomic tgtset. Collect all sent command lists # across calls and verify the expected sequence appears. assert comm.send_commands.call_count >= 1 all_sent = [] for call in comm.send_commands.call_args_list: args = call[0][0] if call and call[0] else [] if isinstance(args, list): all_sent.extend(args) assert "mex.t_rows=80\n" in all_sent assert "tgtset /-s\n" in all_sent import pytest from target_simulator.gui.main_view import MainView from target_simulator.core.models import Target class DummyCommunicator: def __init__(self): self.sent_commands = [] self.is_open = True def send_commands(self, commands): self.sent_commands.extend(commands) return True def connect(self, cfg): self.is_open = True return True def disconnect(self): self.is_open = False @pytest.fixture def main_view(tmp_path, monkeypatch): # Create a MainView but avoid opening the Tk mainloop mv = MainView() # Replace actual communicators with dummy to avoid I/O dummy = DummyCommunicator() mv.target_communicator = dummy return mv def test_reset_radar_state_sends_atomic_tgtset(main_view): mv = main_view # Ensure that the communicator reports open assert mv.target_communicator.is_open # Call the reset function result = mv._reset_radar_state() # Expect success assert result is True # Verify that two commands were sent: preparatory mex.t_rows=80 then tgtset /-s assert len(mv.target_communicator.sent_commands) == 2 assert mv.target_communicator.sent_commands[0].strip() == "mex.t_rows=80" assert mv.target_communicator.sent_commands[1].strip() == "tgtset /-s"