""" Unit tests for connection_manager parsing functions. Tests the pure functions that parse GRBL responses: - Machine position parsing (MPos and WPos formats) - Serial port listing/filtering """ import pytest from unittest.mock import patch, MagicMock class TestParseMachinePosition: """Tests for parse_machine_position function.""" def test_parse_machine_position_mpos_format(self): """Test parsing MPos format from GRBL status response.""" from modules.connection.connection_manager import parse_machine_position response = "" result = parse_machine_position(response) assert result is not None assert result == (100.5, -50.25) def test_parse_machine_position_wpos_format(self): """Test parsing WPos format from GRBL status response.""" from modules.connection.connection_manager import parse_machine_position response = "" result = parse_machine_position(response) assert result is not None assert result == (0.0, 19.0) def test_parse_machine_position_prefers_mpos(self): """Test that MPos is preferred when both are present (rare but possible).""" from modules.connection.connection_manager import parse_machine_position # This response has both MPos and WPos - MPos should be used first response = "" result = parse_machine_position(response) assert result is not None assert result == (10.0, 20.0) def test_parse_machine_position_invalid(self): """Test parsing returns None for invalid response.""" from modules.connection.connection_manager import parse_machine_position # No position info result = parse_machine_position("ok") assert result is None # Empty string result = parse_machine_position("") assert result is None # Malformed response result = parse_machine_position("") assert result is None def test_parse_machine_position_run_state(self): """Test parsing position during run state.""" from modules.connection.connection_manager import parse_machine_position response = "" result = parse_machine_position(response) assert result is not None assert result[0] == pytest.approx(-994.869) assert result[1] == pytest.approx(-321.861) def test_parse_machine_position_alarm_state(self): """Test parsing position during alarm state.""" from modules.connection.connection_manager import parse_machine_position response = "" result = parse_machine_position(response) assert result is not None assert result == (0.0, 0.0) def test_parse_machine_position_with_extra_info(self): """Test parsing position with extra fields in response.""" from modules.connection.connection_manager import parse_machine_position # Response with WCO (Work Coordinate Offset) response = "" result = parse_machine_position(response) assert result is not None assert result == (5.0, 10.0) def test_parse_machine_position_negative_coords(self): """Test parsing negative coordinates.""" from modules.connection.connection_manager import parse_machine_position response = "" result = parse_machine_position(response) assert result is not None assert result[0] == pytest.approx(-100.123) assert result[1] == pytest.approx(-200.456) def test_parse_machine_position_high_precision(self): """Test parsing high precision coordinates.""" from modules.connection.connection_manager import parse_machine_position response = "" result = parse_machine_position(response) assert result is not None assert result[0] == pytest.approx(123.456789) assert result[1] == pytest.approx(987.654321) class TestListSerialPorts: """Tests for list_serial_ports function.""" def test_list_serial_ports_filters_ignored(self): """Test that ignored ports are filtered out.""" # Create mock port objects mock_port1 = MagicMock() mock_port1.device = "/dev/ttyUSB0" mock_port2 = MagicMock() mock_port2.device = "/dev/cu.debug-console" # Should be filtered mock_port3 = MagicMock() mock_port3.device = "/dev/cu.Bluetooth-Incoming-Port" # Should be filtered mock_port4 = MagicMock() mock_port4.device = "/dev/ttyACM0" with patch("serial.tools.list_ports.comports", return_value=[mock_port1, mock_port2, mock_port3, mock_port4]): from modules.connection.connection_manager import list_serial_ports ports = list_serial_ports() assert "/dev/ttyUSB0" in ports assert "/dev/ttyACM0" in ports assert "/dev/cu.debug-console" not in ports assert "/dev/cu.Bluetooth-Incoming-Port" not in ports assert len(ports) == 2 def test_list_serial_ports_empty(self): """Test list_serial_ports returns empty when no ports available.""" with patch("serial.tools.list_ports.comports", return_value=[]): from modules.connection.connection_manager import list_serial_ports ports = list_serial_ports() assert ports == [] def test_list_serial_ports_all_ignored(self): """Test list_serial_ports when all ports are ignored.""" mock_port1 = MagicMock() mock_port1.device = "/dev/cu.debug-console" mock_port2 = MagicMock() mock_port2.device = "/dev/cu.Bluetooth-Incoming-Port" with patch("serial.tools.list_ports.comports", return_value=[mock_port1, mock_port2]): from modules.connection.connection_manager import list_serial_ports ports = list_serial_ports() assert ports == [] class TestConnectionClasses: """Tests for connection class structure (no hardware required).""" def test_base_connection_interface(self): """Test that BaseConnection defines required interface.""" from modules.connection.connection_manager import BaseConnection # BaseConnection should have these abstract methods base = BaseConnection() with pytest.raises(NotImplementedError): base.send("test") with pytest.raises(NotImplementedError): base.flush() with pytest.raises(NotImplementedError): base.readline() with pytest.raises(NotImplementedError): base.in_waiting() with pytest.raises(NotImplementedError): base.is_connected() with pytest.raises(NotImplementedError): base.close() def test_serial_connection_inherits_base(self): """Test SerialConnection inherits from BaseConnection.""" from modules.connection.connection_manager import SerialConnection, BaseConnection assert issubclass(SerialConnection, BaseConnection) def test_websocket_connection_inherits_base(self): """Test WebSocketConnection inherits from BaseConnection.""" from modules.connection.connection_manager import WebSocketConnection, BaseConnection assert issubclass(WebSocketConnection, BaseConnection) class TestIgnorePorts: """Tests for IGNORE_PORTS and DEPRIORITIZED_PORTS constants.""" def test_ignore_ports_defined(self): """Test that IGNORE_PORTS constant is defined.""" from modules.connection.connection_manager import IGNORE_PORTS assert isinstance(IGNORE_PORTS, list) assert "/dev/cu.debug-console" in IGNORE_PORTS assert "/dev/cu.Bluetooth-Incoming-Port" in IGNORE_PORTS def test_deprioritized_ports_defined(self): """Test that DEPRIORITIZED_PORTS constant is defined.""" from modules.connection.connection_manager import DEPRIORITIZED_PORTS assert isinstance(DEPRIORITIZED_PORTS, list) # ttyS0 is typically the Pi hardware UART - should be deprioritized assert "/dev/ttyS0" in DEPRIORITIZED_PORTS class TestIsMachineIdle: """Tests for is_machine_idle function.""" def test_is_machine_idle_no_connection(self, mock_state): """Test is_machine_idle returns False when no connection.""" mock_state.conn = None with patch("modules.connection.connection_manager.state", mock_state): from modules.connection.connection_manager import is_machine_idle result = is_machine_idle() assert result is False def test_is_machine_idle_disconnected(self, mock_state): """Test is_machine_idle returns False when disconnected.""" mock_state.conn.is_connected.return_value = False with patch("modules.connection.connection_manager.state", mock_state): from modules.connection.connection_manager import is_machine_idle result = is_machine_idle() assert result is False def test_is_machine_idle_when_idle(self, mock_state): """Test is_machine_idle returns True when machine is idle.""" mock_state.conn.is_connected.return_value = True mock_state.conn.send = MagicMock() mock_state.conn.readline.return_value = "" with patch("modules.connection.connection_manager.state", mock_state): from modules.connection.connection_manager import is_machine_idle result = is_machine_idle() assert result is True mock_state.conn.send.assert_called_with('?') def test_is_machine_idle_when_running(self, mock_state): """Test is_machine_idle returns False when machine is running.""" mock_state.conn.is_connected.return_value = True mock_state.conn.send = MagicMock() mock_state.conn.readline.return_value = "" with patch("modules.connection.connection_manager.state", mock_state): from modules.connection.connection_manager import is_machine_idle result = is_machine_idle() assert result is False