@@ -1023,3 +1023,211 @@ def test_cmd_file_with_crlf(self, tmp_path):
10231023
10241024 check_cmd_file (device )
10251025 assert not cmd_file .exists ()
1026+
1027+
1028+ class TestMonitorTickWithThreshold :
1029+ """Test monitor tick with threshold alarm."""
1030+
1031+ def test_monitor_tick_with_threshold_enabled (self ):
1032+ """Test monitor tick with threshold enabled."""
1033+ from monitor import _create_monitor_tick
1034+ from state import DeviceState
1035+
1036+ device = DeviceState ("test" , "Test" )
1037+ device .monitor_running = True
1038+ device .monitor_mode_0 = "cpu-usage"
1039+ device .monitor_mode_1 = "none"
1040+ device .motor_min = 0
1041+ device .motor_max = 1000
1042+ device .ser = None
1043+ device .threshold_enable = True
1044+ device .threshold_mode = "cpu-usage"
1045+ device .threshold_value = 0 # Set to 0 to always trigger
1046+ device .threshold_freq = 1000
1047+ device .threshold_duration = 100
1048+ device .last_alarm_time = 0
1049+
1050+ tick = _create_monitor_tick (device )
1051+ tick ()
1052+
1053+ # Threshold should have been checked
1054+ assert device .last_percent_0 is not None
1055+
1056+
1057+ class TestMonitorTickCH1Only :
1058+ """Test monitor tick with CH1 only."""
1059+
1060+ def test_monitor_tick_ch1_only (self ):
1061+ """Test monitor tick with only CH1 active."""
1062+ from monitor import _create_monitor_tick
1063+ from state import DeviceState
1064+
1065+ device = DeviceState ("test" , "Test" )
1066+ device .monitor_running = True
1067+ device .monitor_mode_0 = "none"
1068+ device .monitor_mode_1 = "cpu-usage"
1069+ device .motor_min = 0
1070+ device .motor_max = 1000
1071+ device .ser = None
1072+ device .threshold_enable = False
1073+
1074+ tick = _create_monitor_tick (device )
1075+ tick ()
1076+
1077+ # last_percent_1 should be updated
1078+ assert device .last_percent_1 is not None
1079+ # last_percent should use CH1 value since CH0 is none
1080+ assert device .last_percent == device .last_percent_1
1081+
1082+
1083+ class TestMonitorTickWithSerialCH1 :
1084+ """Test monitor tick with serial for CH1."""
1085+
1086+ def test_monitor_tick_ch1_with_serial (self ):
1087+ """Test monitor tick CH1 with serial port."""
1088+ from monitor import _create_monitor_tick
1089+ from state import DeviceState
1090+
1091+ device = DeviceState ("test" , "Test" )
1092+ device .monitor_running = True
1093+ device .monitor_mode_0 = "none"
1094+ device .monitor_mode_1 = "mem-usage"
1095+ device .motor_min = 0
1096+ device .motor_max = 1000
1097+ device .ser = MagicMock ()
1098+ device .ser .isOpen .return_value = True
1099+ device .threshold_enable = False
1100+
1101+ tick = _create_monitor_tick (device )
1102+ tick ()
1103+
1104+ assert device .last_percent_1 is not None
1105+
1106+
1107+ class TestStartMonitorWithAudioMode :
1108+ """Test start_monitor with audio mode."""
1109+
1110+ def test_start_monitor_audio_mode (self ):
1111+ """Test start_monitor with audio-level mode."""
1112+ from monitor import start_monitor , stop_monitor
1113+ from state import DeviceState
1114+
1115+ device = DeviceState ("test" , "Test" )
1116+ device .monitor_mode_0 = "audio-level"
1117+ device .monitor_mode_1 = "none"
1118+ device .period = 1.0
1119+
1120+ result , error = start_monitor (device , "audio-level" )
1121+ assert result is True
1122+
1123+ # Cleanup
1124+ stop_monitor (device )
1125+
1126+ def test_start_monitor_audio_left_mode (self ):
1127+ """Test start_monitor with audio-left mode."""
1128+ from monitor import start_monitor , stop_monitor
1129+ from state import DeviceState
1130+
1131+ device = DeviceState ("test" , "Test" )
1132+ device .monitor_mode_0 = "audio-left"
1133+ device .monitor_mode_1 = "none"
1134+ device .period = 1.0
1135+
1136+ result , error = start_monitor (device , "audio-left" )
1137+ assert result is True
1138+
1139+ # Cleanup
1140+ stop_monitor (device )
1141+
1142+ def test_start_monitor_audio_right_mode (self ):
1143+ """Test start_monitor with audio-right mode."""
1144+ from monitor import start_monitor , stop_monitor
1145+ from state import DeviceState
1146+
1147+ device = DeviceState ("test" , "Test" )
1148+ device .monitor_mode_0 = "none"
1149+ device .monitor_mode_1 = "audio-right"
1150+ device .period = 1.0
1151+
1152+ result , error = start_monitor (device , "audio-right" )
1153+ assert result is True
1154+
1155+ # Cleanup
1156+ stop_monitor (device )
1157+
1158+
1159+ class TestStopMonitorWithTimers :
1160+ """Test stop_monitor with various timer states."""
1161+
1162+ def test_stop_monitor_no_timers (self ):
1163+ """Test stop_monitor when no timers exist."""
1164+ from monitor import stop_monitor
1165+ from state import DeviceState
1166+
1167+ device = DeviceState ("test" , "Test" )
1168+ device .monitor_running = True
1169+ device .monitor_timer = None
1170+ device .cmd_file_timer = None
1171+ device .audio_recorder = None
1172+
1173+ mock_tm = MagicMock ()
1174+ device .worker = MagicMock ()
1175+ device .worker .run_in_worker = MagicMock (side_effect = lambda f , t : f ())
1176+ device .worker .get_timer_manager .return_value = mock_tm
1177+
1178+ result , error = stop_monitor (device )
1179+ assert result is True
1180+ assert device .monitor_running is False
1181+
1182+
1183+ class TestGetAudioLevelRightChannelMono :
1184+ """Test get_audio_level with right channel on mono audio."""
1185+
1186+ def test_get_audio_level_right_channel_mono (self ):
1187+ """Test get_audio_level right channel with mono audio."""
1188+ from monitor import get_audio_level , sc
1189+ from state import DeviceState
1190+
1191+ if sc is None :
1192+ return
1193+
1194+ import numpy as np
1195+
1196+ device = DeviceState ("test" , "Test" )
1197+ mock_recorder = MagicMock ()
1198+ # Simulate mono audio data (single channel)
1199+ mock_recorder .record .return_value = np .array ([[0.5 ], [0.4 ], [0.6 ]])
1200+ device .audio_recorder = mock_recorder
1201+ device .audio_db_min = - 60
1202+ device .audio_db_max = 0
1203+ device .audio_channel = "right"
1204+
1205+ value , error = get_audio_level (device )
1206+ # Should fall back to channel 0 for mono
1207+ assert error is None
1208+
1209+
1210+ class TestGetAudioLevelChannelMono :
1211+ """Test get_audio_level_channel with mono audio."""
1212+
1213+ def test_get_audio_level_channel_right_mono (self ):
1214+ """Test get_audio_level_channel right with mono audio."""
1215+ from monitor import get_audio_level_channel , sc
1216+ from state import DeviceState
1217+
1218+ if sc is None :
1219+ return
1220+
1221+ import numpy as np
1222+
1223+ device = DeviceState ("test" , "Test" )
1224+ mock_recorder = MagicMock ()
1225+ # Simulate mono audio data
1226+ mock_recorder .record .return_value = np .array ([[0.5 ], [0.4 ], [0.6 ]])
1227+ device .audio_recorder = mock_recorder
1228+ device .audio_db_min = - 60
1229+ device .audio_db_max = 0
1230+
1231+ value , error = get_audio_level_channel (device , "right" )
1232+ # Should fall back to channel 0
1233+ assert error is None
0 commit comments