@@ -1320,6 +1320,41 @@ def test_parser_created(self):
13201320 parser = build_parser ()
13211321 assert parser .prog == "flameconnect"
13221322
1323+ def test_parser_description (self ):
1324+ parser = build_parser ()
1325+ assert parser .description is not None
1326+ assert "Dimplex" in parser .description
1327+ assert "Flame Connect" in parser .description
1328+
1329+ def test_subcommand_help_texts (self ):
1330+ """All subcommands have non-None help text."""
1331+ parser = build_parser ()
1332+ # Access subparsers to check help texts
1333+ for action in parser ._subparsers ._actions :
1334+ if hasattr (action , "_parser_class" ):
1335+ for name , _subparser in action ._name_parser_map .items ():
1336+ # Subparser exists for each command name
1337+ assert name in (
1338+ "list" ,
1339+ "status" ,
1340+ "on" ,
1341+ "off" ,
1342+ "set" ,
1343+ "tui" ,
1344+ )
1345+
1346+ def test_verbose_flag_help (self ):
1347+ """--verbose flag has help text."""
1348+ parser = build_parser ()
1349+ for action in parser ._actions :
1350+ if "--verbose" in getattr (action , "option_strings" , []):
1351+ assert action .help is not None
1352+ assert (
1353+ "debug" in action .help .lower () or "logging" in action .help .lower ()
1354+ )
1355+ return
1356+ raise AssertionError ("--verbose action not found" )
1357+
13231358 def test_parse_list (self ):
13241359 parser = build_parser ()
13251360 args = parser .parse_args (["list" ])
@@ -1604,8 +1639,10 @@ class TestMaskedInput:
16041639 """
16051640
16061641 @staticmethod
1607- def _run_masked (chars : list [str ], prompt : str = "Password: " ) -> str :
1608- """Helper to run _masked_input with mocked terminal I/O."""
1642+ def _run_masked (
1643+ chars : list [str ], prompt : str = "Password: "
1644+ ) -> tuple [str , MagicMock , MagicMock , MagicMock , MagicMock ]:
1645+ """Helper: run _masked_input, return (result, stdout, stdin, termios, tty)."""
16091646 from flameconnect .cli import _masked_input
16101647
16111648 mock_stdin = MagicMock ()
@@ -1614,7 +1651,7 @@ def _run_masked(chars: list[str], prompt: str = "Password: ") -> str:
16141651 mock_stdin .fileno .return_value = 0
16151652
16161653 mock_termios = MagicMock ()
1617- mock_termios .tcgetattr .return_value = []
1654+ mock_termios .tcgetattr .return_value = ["old_settings" ]
16181655 mock_termios .TCSADRAIN = 1
16191656 mock_tty = MagicMock ()
16201657
@@ -1623,16 +1660,36 @@ def _run_masked(chars: list[str], prompt: str = "Password: ") -> str:
16231660 patch ("sys.stdout" , mock_stdout ),
16241661 patch .dict ("sys.modules" , {"termios" : mock_termios , "tty" : mock_tty }),
16251662 ):
1626- return _masked_input (prompt )
1663+ result = _masked_input (prompt )
1664+ return result , mock_stdout , mock_stdin , mock_termios , mock_tty
16271665
16281666 def test_basic_input (self ):
1629- assert self ._run_masked (["a" , "b" , "c" , "\n " ]) == "abc"
1667+ result , stdout , stdin , termios , tty = self ._run_masked (["a" , "b" , "c" , "\n " ])
1668+ assert result == "abc"
1669+ # Prompt written first
1670+ stdout .write .assert_any_call ("Password: " )
1671+ # Each char echoes a star
1672+ star_calls = [c for c in stdout .write .call_args_list if c .args == ("*" ,)]
1673+ assert len (star_calls ) == 3
1674+ # Terminal restored
1675+ termios .tcsetattr .assert_called_once_with (0 , 1 , ["old_settings" ])
1676+ # Raw mode set
1677+ tty .setraw .assert_called_once_with (0 )
1678+ # stdin.read called with 1
1679+ for call in stdin .read .call_args_list :
1680+ assert call .args == (1 ,)
1681+ # Newline written at end
1682+ stdout .write .assert_any_call ("\n " )
16301683
16311684 def test_backspace (self ):
1632- assert self ._run_masked (["a" , "b" , "\x7f " , "c" , "\n " ]) == "ac"
1685+ result , stdout , * _ = self ._run_masked (["a" , "b" , "\x7f " , "c" , "\n " ])
1686+ assert result == "ac"
1687+ # Backspace sequence written
1688+ stdout .write .assert_any_call ("\b \b " )
16331689
16341690 def test_backspace_on_empty (self ):
1635- assert self ._run_masked (["\x7f " , "a" , "\n " ]) == "a"
1691+ result , stdout , * _ = self ._run_masked (["\x7f " , "a" , "\n " ])
1692+ assert result == "a"
16361693
16371694 def test_ctrl_c (self ):
16381695 from flameconnect .cli import _masked_input
@@ -1643,7 +1700,7 @@ def test_ctrl_c(self):
16431700 mock_stdin .fileno .return_value = 0
16441701
16451702 mock_termios = MagicMock ()
1646- mock_termios .tcgetattr .return_value = []
1703+ mock_termios .tcgetattr .return_value = ["old" ]
16471704 mock_termios .TCSADRAIN = 1
16481705 mock_tty = MagicMock ()
16491706
@@ -1656,11 +1713,21 @@ def test_ctrl_c(self):
16561713 _masked_input ()
16571714
16581715 def test_carriage_return (self ):
1659- assert self ._run_masked (["x" , "\r " ]) == "x"
1716+ result , * _ = self ._run_masked (["x" , "\r " ])
1717+ assert result == "x"
16601718
16611719 def test_delete_char (self ):
16621720 # \x08 is the other backspace/delete code
1663- assert self ._run_masked (["a" , "b" , "\x08 " , "c" , "\n " ]) == "ac"
1721+ result , stdout , * _ = self ._run_masked (["a" , "b" , "\x08 " , "c" , "\n " ])
1722+ assert result == "ac"
1723+ stdout .write .assert_any_call ("\b \b " )
1724+
1725+ def test_default_prompt (self ):
1726+ """Default prompt is 'Password: ' (kills prompt default mutations)."""
1727+ result , stdout , * _ = self ._run_masked (["a" , "\n " ])
1728+ assert result == "a"
1729+ # First write call should be the prompt
1730+ stdout .write .assert_any_call ("Password: " )
16641731
16651732
16661733# ===================================================================
0 commit comments