@@ -211,7 +211,7 @@ def test_standby_no_temp_unit(self, capsys):
211211 out = capsys .readouterr ().out
212212 lines = out .strip ().split ("\n " )
213213 assert lines [0 ].strip () == "[321] Mode"
214- assert " ─" * 40 in lines [ 1 ]
214+ assert lines [ 1 ] == " " + " ─" * 40
215215 assert "Mode: Standby" in out
216216 assert "Target Temp: 20.0\u00b0 " in out
217217 # No unit suffix when temp_unit is None (empty string)
@@ -253,7 +253,7 @@ def test_all_fields_displayed(self, capsys):
253253 out = capsys .readouterr ().out
254254 lines = out .strip ().split ("\n " )
255255 assert lines [0 ].strip () == "[322] Flame Effect"
256- assert " ─" * 40 in lines [ 1 ]
256+ assert lines [ 1 ] == " " + " ─" * 40
257257 assert " Flame: On" in out
258258 assert " Flame Speed: 4 / 5" in out
259259 assert " Brightness: Low" in out
@@ -285,10 +285,11 @@ def test_heat_on_no_temp_unit(self, capsys):
285285 out = capsys .readouterr ().out
286286 lines = out .strip ().split ("\n " )
287287 assert lines [0 ].strip () == "[323] Heat Settings"
288- assert " ─" * 40 in lines [ 1 ]
288+ assert lines [ 1 ] == " " + " ─" * 40
289289 assert " Heat: On" in out
290290 assert " Heat Mode: Boost" in out
291- assert " Setpoint Temp: 25.0\u00b0 " in out
291+ # No unit suffix when temp_unit is None (kills "" → "XXXX" mutant)
292+ assert " Setpoint Temp: 25.0\u00b0 \n " in out
292293 assert " Boost Duration: 15" in out
293294
294295 def test_heat_with_celsius (self , capsys ):
@@ -321,7 +322,7 @@ def test_enabled(self, capsys):
321322 out = capsys .readouterr ().out
322323 lines = out .strip ().split ("\n " )
323324 assert lines [0 ].strip () == "[325] Heat Mode"
324- assert " ─" * 40 in lines [ 1 ]
325+ assert lines [ 1 ] == " " + " ─" * 40
325326 assert " Heat Control: Enabled" in out
326327
327328 def test_software_disabled (self , capsys ):
@@ -346,18 +347,35 @@ def test_timer_disabled(self, capsys):
346347 out = capsys .readouterr ().out
347348 lines = out .strip ().split ("\n " )
348349 assert lines [0 ].strip () == "[326] Timer Mode"
349- assert " ─" * 40 in lines [ 1 ]
350+ assert lines [ 1 ] == " " + " ─" * 40
350351 assert " Timer: Disabled" in out
351352 assert " Duration: 0 min (0h 0m)" in out
352353 assert "Off at:" not in out
353354
354355 def test_timer_enabled_with_duration (self , capsys ):
356+ from datetime import datetime , timedelta
357+
355358 param = TimerParam (timer_status = TimerStatus .ENABLED , duration = 90 )
359+ before = datetime .now ()
356360 _display_timer (param )
361+ after = datetime .now ()
357362 out = capsys .readouterr ().out
358363 assert " Timer: Enabled" in out
359364 assert " Duration: 90 min (1h 30m)" in out
360365 assert " Off at:" in out
366+ # Verify the off-at time is in the future (kills + → − mutant)
367+ off_line = [line for line in out .split ("\n " ) if "Off at:" in line ][0 ]
368+ off_time_str = off_line .strip ().split ("Off at:" )[1 ].strip ()
369+ # Must be HH:MM format (kills strftime case mutation)
370+ assert len (off_time_str ) == 5
371+ assert off_time_str [2 ] == ":"
372+ hour , minute = int (off_time_str [:2 ]), int (off_time_str [3 :])
373+ assert 0 <= hour <= 23
374+ assert 0 <= minute <= 59
375+ # Verify time is approximately now + 90 min (kills + → − and // 60 → // 61)
376+ expected_min = (before + timedelta (minutes = 90 )).strftime ("%H:%M" )
377+ expected_max = (after + timedelta (minutes = 90 )).strftime ("%H:%M" )
378+ assert off_time_str in (expected_min , expected_max )
361379
362380 def test_timer_enabled_zero_duration (self , capsys ):
363381 param = TimerParam (timer_status = TimerStatus .ENABLED , duration = 0 )
@@ -394,7 +412,7 @@ def test_version_display(self, capsys):
394412 out = capsys .readouterr ().out
395413 lines = out .strip ().split ("\n " )
396414 assert lines [0 ].strip () == "[327] Software Version"
397- assert " ─" * 40 in lines [ 1 ]
415+ assert lines [ 1 ] == " " + " ─" * 40
398416 assert " UI Version: 1.2.3" in out
399417 assert " Control Version: 4.5.6" in out
400418 assert " Relay Version: 7.8.9" in out
@@ -409,7 +427,7 @@ def test_no_errors(self, capsys):
409427 out = capsys .readouterr ().out
410428 lines = out .strip ().split ("\n " )
411429 assert lines [0 ].strip () == "[329] Error"
412- assert " ─" * 40 in lines [ 1 ]
430+ assert lines [ 1 ] == " " + " ─" * 40
413431 assert " Error Byte 1: 0x00 (00000000)" in out
414432 assert " Active Faults: None" in out
415433
@@ -432,6 +450,34 @@ def test_error_bytes_formatted(self, capsys):
432450 assert " Error Byte 4: 0x08 (00001000)" in out
433451 assert " Active Faults: Yes" in out
434452
453+ def test_error_only_byte3 (self , capsys ):
454+ """Error in byte3 only: kills byte2|byte3 → byte2&byte3 mutant."""
455+ param = ErrorParam (error_byte1 = 0 , error_byte2 = 0 , error_byte3 = 1 , error_byte4 = 0 )
456+ _display_error (param )
457+ out = capsys .readouterr ().out
458+ assert " Active Faults: Yes" in out
459+
460+ def test_error_only_byte4 (self , capsys ):
461+ """Error in byte4 only: kills byte3|byte4 → byte3&byte4 mutant."""
462+ param = ErrorParam (error_byte1 = 0 , error_byte2 = 0 , error_byte3 = 0 , error_byte4 = 1 )
463+ _display_error (param )
464+ out = capsys .readouterr ().out
465+ assert " Active Faults: Yes" in out
466+
467+ def test_active_faults_exact_yes (self , capsys ):
468+ """Exact match for Active Faults line (kills XX prefix/suffix)."""
469+ param = ErrorParam (error_byte1 = 1 , error_byte2 = 0 , error_byte3 = 0 , error_byte4 = 0 )
470+ _display_error (param )
471+ out = capsys .readouterr ().out
472+ assert " Active Faults: Yes\n " in out
473+
474+ def test_active_faults_exact_none (self , capsys ):
475+ """Exact match for Active Faults line (kills XX prefix/suffix)."""
476+ param = ErrorParam (error_byte1 = 0 , error_byte2 = 0 , error_byte3 = 0 , error_byte4 = 0 )
477+ _display_error (param )
478+ out = capsys .readouterr ().out
479+ assert " Active Faults: None\n " in out
480+
435481
436482class TestDisplayTempUnit :
437483 """Tests for _display_temp_unit()."""
@@ -442,7 +488,7 @@ def test_celsius(self, capsys):
442488 out = capsys .readouterr ().out
443489 lines = out .strip ().split ("\n " )
444490 assert lines [0 ].strip () == "[236] Temperature Unit"
445- assert " ─" * 40 in lines [ 1 ]
491+ assert lines [ 1 ] == " " + " ─" * 40
446492 assert " Unit: Celsius" in out
447493
448494 def test_fahrenheit (self , capsys ):
@@ -461,7 +507,7 @@ def test_display(self, capsys):
461507 out = capsys .readouterr ().out
462508 lines = out .strip ().split ("\n " )
463509 assert lines [0 ].strip () == "[369] Sound"
464- assert " ─" * 40 in lines [ 1 ]
510+ assert lines [ 1 ] == " " + " ─" * 40
465511 assert " Volume: 128 / 255" in out
466512 assert " Sound File: 3" in out
467513
@@ -479,7 +525,7 @@ def test_on(self, capsys):
479525 out = capsys .readouterr ().out
480526 lines = out .strip ().split ("\n " )
481527 assert lines [0 ].strip () == "[370] Log Effect"
482- assert " ─" * 40 in lines [ 1 ]
528+ assert lines [ 1 ] == " " + " ─" * 40
483529 assert " Log Effect: On" in out
484530 assert " Colors: RGBW(1, 0, 255, 128)" in out
485531 assert " Pattern: 5" in out
@@ -693,7 +739,10 @@ class TestDisplayFeatures:
693739 def test_all_false (self , capsys ):
694740 _display_features (FireFeatures ())
695741 out = capsys .readouterr ().out
696- assert "Supported Features" in out
742+ # Exact header (kills XX prefix/suffix on "\n Supported Features")
743+ assert "\n Supported Features\n " in out
744+ # Exact separator (kills '─' → 'XX─XX' and * 40 → * 41)
745+ assert " " + "─" * 40 + "\n " in out
697746 # All should show No
698747 assert "Yes" not in out
699748 assert out .count ("No" ) == 24
@@ -705,6 +754,17 @@ def test_some_true(self, capsys):
705754 assert out .count ("Yes" ) == 3
706755 assert out .count ("No" ) == 21
707756
757+ def test_exact_yes_no_values (self , capsys ):
758+ """Exact Yes/No values (kills 'Yes' → 'XXYesXX', 'No' → 'XXNoXX')."""
759+ features = FireFeatures (sound = True )
760+ _display_features (features )
761+ out = capsys .readouterr ().out
762+ lines = out .strip ().split ("\n " )
763+ # Each feature line: " Label: Value"
764+ for line in lines [2 :]: # skip header and separator
765+ value = line .rsplit (None , 1 )[- 1 ]
766+ assert value in ("Yes" , "No" ), f"Unexpected value: { value !r} "
767+
708768 def test_all_labels_present (self , capsys ):
709769 _display_features (FireFeatures ())
710770 out = capsys .readouterr ().out
@@ -1442,10 +1502,11 @@ class TestMain:
14421502 """Tests for the synchronous main() entry point."""
14431503
14441504 def test_main_calls_async_main (self ):
1505+ mock_async_main = MagicMock ()
14451506 with (
14461507 patch ("flameconnect.cli.build_parser" ) as mock_parser_fn ,
14471508 patch ("flameconnect.cli.asyncio" ) as mock_asyncio ,
1448- patch ("flameconnect.cli.async_main" , new = MagicMock () ),
1509+ patch ("flameconnect.cli.async_main" , new = mock_async_main ),
14491510 ):
14501511 mock_parser = MagicMock ()
14511512 mock_args = argparse .Namespace (command = "list" , verbose = False )
@@ -1454,7 +1515,10 @@ def test_main_calls_async_main(self):
14541515
14551516 main ()
14561517
1457- mock_asyncio .run .assert_called_once ()
1518+ # Verify async_main is called with args (kills → None mutant)
1519+ mock_async_main .assert_called_once_with (mock_args )
1520+ # Verify asyncio.run receives the coroutine (kills → None mutant)
1521+ mock_asyncio .run .assert_called_once_with (mock_async_main .return_value )
14581522
14591523 def test_main_verbose_logging (self ):
14601524 with (
0 commit comments