@@ -954,3 +954,221 @@ def test_send_command_exception_in_send(self) -> None:
954954 # Then
955955 assert success is False
956956 assert "failed to send command" in error .lower ()
957+
958+
959+ class TestFlightControllerCommandsMissingConnectionBranches :
960+ """Tests for commands that need a connection but master is None."""
961+
962+ def test_send_command_and_wait_ack_fails_without_connection (self ) -> None :
963+ """
964+ send_command_and_wait_ack fails gracefully when master is None.
965+
966+ GIVEN: No flight controller connection (master is None)
967+ WHEN: send_command_and_wait_ack is called
968+ THEN: False should be returned with appropriate error message
969+ AND: No exceptions should be raised
970+ """
971+ mock_conn_mgr = Mock ()
972+ mock_conn_mgr .master = None
973+ mock_params_mgr = Mock ()
974+
975+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
976+
977+ success , error = commands_mgr .send_command_and_wait_ack (command = 999 , timeout = 0.5 )
978+
979+ assert success is False
980+ assert "connection" in error .lower ()
981+
982+ def test_stop_all_motors_fails_without_connection (self ) -> None :
983+ """
984+ stop_all_motors fails gracefully when master is None.
985+
986+ GIVEN: No flight controller connection
987+ WHEN: stop_all_motors is called
988+ THEN: False should be returned with error message
989+ """
990+ mock_conn_mgr = Mock ()
991+ mock_conn_mgr .master = None
992+ mock_params_mgr = Mock ()
993+
994+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
995+
996+ success , error = commands_mgr .stop_all_motors ()
997+
998+ assert success is False
999+ assert "connection" in error .lower ()
1000+
1001+ def test_request_periodic_battery_status_fails_without_connection (self ) -> None :
1002+ """
1003+ request_periodic_battery_status fails gracefully when master is None.
1004+
1005+ GIVEN: No flight controller connection
1006+ WHEN: request_periodic_battery_status is called
1007+ THEN: False should be returned with error message
1008+ """
1009+ mock_conn_mgr = Mock ()
1010+ mock_conn_mgr .master = None
1011+ mock_params_mgr = Mock ()
1012+
1013+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
1014+
1015+ success , error = commands_mgr .request_periodic_battery_status ()
1016+
1017+ assert success is False
1018+ assert "connection" in error .lower ()
1019+
1020+
1021+ class TestFlightControllerCommandsFailureBranches :
1022+ """Tests for failure branches in command methods."""
1023+
1024+ def _make_commands_mgr_with_ack (self , result_code : int ) -> "FlightControllerCommands" :
1025+ """Create a commands manager that returns a given ACK result code."""
1026+ mock_master = MagicMock ()
1027+ mock_master .target_system = 1
1028+ mock_master .target_component = 1
1029+
1030+ mock_ack = MagicMock ()
1031+ mock_ack .command = mavutil .mavlink .MAV_CMD_DO_MOTOR_TEST
1032+ mock_ack .result = result_code
1033+
1034+ mock_master .recv_match .return_value = mock_ack
1035+ mock_conn_mgr = Mock ()
1036+ mock_conn_mgr .master = mock_master
1037+ mock_params_mgr = Mock ()
1038+ mock_params_mgr .fc_parameters = {}
1039+
1040+ return FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
1041+
1042+ def test_reset_all_parameters_handles_command_failure (self ) -> None :
1043+ """
1044+ reset_all_parameters_to_default handles command failure correctly.
1045+
1046+ GIVEN: Connected flight controller that rejects the parameter reset command
1047+ WHEN: User calls reset_all_parameters_to_default
1048+ THEN: False should be returned with error description
1049+ AND: fc_parameters should NOT be cleared on failure
1050+ """
1051+ mock_master = MagicMock ()
1052+ mock_master .target_system = 1
1053+ mock_master .target_component = 1
1054+
1055+ mock_ack = MagicMock ()
1056+ mock_ack .command = mavutil .mavlink .MAV_CMD_PREFLIGHT_STORAGE
1057+ mock_ack .result = mavutil .mavlink .MAV_RESULT_DENIED
1058+
1059+ mock_master .recv_match .return_value = mock_ack
1060+
1061+ mock_conn_mgr = Mock ()
1062+ mock_conn_mgr .master = mock_master
1063+ mock_params_mgr = Mock ()
1064+ mock_params_mgr .fc_parameters = {"PARAM1" : 1.0 }
1065+
1066+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
1067+
1068+ success , error = commands_mgr .reset_all_parameters_to_default ()
1069+
1070+ assert success is False
1071+ assert "failed" in error .lower () or "denied" in error .lower ()
1072+ # fc_parameters should NOT be cleared on failure
1073+ assert len (mock_params_mgr .fc_parameters ) > 0
1074+
1075+ def test_test_motors_in_sequence_handles_command_failure (self ) -> None :
1076+ """
1077+ test_motors_in_sequence handles command failure correctly.
1078+
1079+ GIVEN: Connected flight controller that rejects the sequential motor test command
1080+ WHEN: User calls test_motors_in_sequence
1081+ THEN: False should be returned with error description
1082+ """
1083+ mock_master = MagicMock ()
1084+ mock_master .target_system = 1
1085+ mock_master .target_component = 1
1086+
1087+ mock_ack = MagicMock ()
1088+ mock_ack .command = mavutil .mavlink .MAV_CMD_DO_MOTOR_TEST
1089+ mock_ack .result = mavutil .mavlink .MAV_RESULT_FAILED
1090+
1091+ mock_master .recv_match .return_value = mock_ack
1092+
1093+ mock_conn_mgr = Mock ()
1094+ mock_conn_mgr .master = mock_master
1095+ mock_params_mgr = Mock ()
1096+
1097+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
1098+
1099+ success , error = commands_mgr .test_motors_in_sequence (
1100+ start_motor = 1 , motor_count = 4 , throttle_percent = 10 , timeout_seconds = 2
1101+ )
1102+
1103+ assert success is False
1104+ assert "failed" in error .lower ()
1105+
1106+ def test_stop_all_motors_handles_command_failure (self ) -> None :
1107+ """
1108+ stop_all_motors handles command failure correctly.
1109+
1110+ GIVEN: Connected flight controller that rejects the stop command
1111+ WHEN: User calls stop_all_motors
1112+ THEN: False should be returned with error description
1113+ """
1114+ mock_master = MagicMock ()
1115+ mock_master .target_system = 1
1116+ mock_master .target_component = 1
1117+
1118+ mock_ack = MagicMock ()
1119+ mock_ack .command = mavutil .mavlink .MAV_CMD_DO_MOTOR_TEST
1120+ mock_ack .result = mavutil .mavlink .MAV_RESULT_UNSUPPORTED
1121+
1122+ mock_master .recv_match .return_value = mock_ack
1123+
1124+ mock_conn_mgr = Mock ()
1125+ mock_conn_mgr .master = mock_master
1126+ mock_params_mgr = Mock ()
1127+
1128+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
1129+
1130+ success , error = commands_mgr .stop_all_motors ()
1131+
1132+ assert success is False
1133+ assert error # Non-empty error message
1134+
1135+ def test_send_command_handles_in_progress_with_zero_progress (self ) -> None :
1136+ """
1137+ send_command_and_wait_ack handles MAV_RESULT_IN_PROGRESS with zero progress.
1138+
1139+ GIVEN: Flight controller sends IN_PROGRESS with progress=0
1140+ WHEN: send_command_and_wait_ack receives an IN_PROGRESS ACK
1141+ THEN: Processing continues waiting without logging (progress <= 0)
1142+ AND: Eventually times out and returns False
1143+ """
1144+ mock_master = MagicMock ()
1145+ mock_master .target_system = 1
1146+ mock_master .target_component = 1
1147+
1148+ # Return IN_PROGRESS ACK with progress=0 (should NOT log) then timeout
1149+ mock_ack = MagicMock ()
1150+ mock_ack .command = 999
1151+ mock_ack .result = mavutil .mavlink .MAV_RESULT_IN_PROGRESS
1152+ mock_ack .progress = 0 # <= 0, so debug logging is skipped
1153+
1154+ call_count = [0 ]
1155+
1156+ def side_effect_recv_match (* _args , ** _kwargs ) -> object :
1157+ call_count [0 ] += 1
1158+ if call_count [0 ] <= 2 :
1159+ return mock_ack
1160+ return None # Stop returning ACK after a couple of calls
1161+
1162+ mock_master .recv_match .side_effect = side_effect_recv_match
1163+
1164+ mock_conn_mgr = Mock ()
1165+ mock_conn_mgr .master = mock_master
1166+ mock_params_mgr = Mock ()
1167+
1168+ commands_mgr = FlightControllerCommands (params_manager = mock_params_mgr , connection_manager = mock_conn_mgr )
1169+
1170+ # Short timeout so test doesn't hang
1171+ success , _error = commands_mgr .send_command_and_wait_ack (command = 999 , timeout = 0.3 )
1172+
1173+ # Should timeout after IN_PROGRESS messages
1174+ assert success is False
0 commit comments