Skip to content

Commit cbbb821

Browse files
committed
Add tests for exception formatting and TTY guard logging
1 parent 099cb2c commit cbbb821

File tree

3 files changed

+117
-6
lines changed

3 files changed

+117
-6
lines changed

modules/clitt/src/test/test_terminal.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
setup_test_environment()
1818

19+
from clitt.core.exception.exceptions import NotATerminalError
1920
from clitt.core.term.screen import Screen
2021
from clitt.core.term.terminal import Terminal
2122
from hspylib.modules.application.exit_status import ExitStatus
@@ -24,11 +25,16 @@
2425
class TestTerminalAttributes(unittest.TestCase):
2526
@patch.object(Terminal, "is_a_tty", return_value=False)
2627
def test_set_enable_echo_should_guard_when_not_tty(self, mock_is_tty):
27-
with patch("clitt.core.term.terminal.os.popen") as mock_popen:
28+
with patch("clitt.core.term.terminal.os.popen") as mock_popen, \
29+
patch("clitt.core.term.terminal.log.warning") as mock_warning:
2830
Terminal.set_enable_echo(True)
2931

3032
mock_is_tty.assert_called_once()
3133
mock_popen.assert_not_called()
34+
mock_warning.assert_called_once()
35+
warning_arg = mock_warning.call_args.args[0]
36+
self.assertIsInstance(warning_arg, NotATerminalError)
37+
self.assertIn("set_enable_echo:: Requires a terminal (TTY)", str(warning_arg))
3238

3339
@patch.object(Terminal, "is_a_tty", return_value=True)
3440
def test_set_enable_echo_should_invoke_stty_when_tty(self, mock_is_tty):
@@ -42,11 +48,16 @@ def test_set_enable_echo_should_invoke_stty_when_tty(self, mock_is_tty):
4248

4349
@patch.object(Terminal, "is_a_tty", return_value=False)
4450
def test_set_auto_wrap_should_guard_when_not_tty(self, mock_is_tty):
45-
with patch("clitt.core.term.terminal.sysout") as mock_sysout:
51+
with patch("clitt.core.term.terminal.sysout") as mock_sysout, \
52+
patch("clitt.core.term.terminal.log.warning") as mock_warning:
4653
Terminal.set_auto_wrap(True)
4754

4855
mock_is_tty.assert_called_once()
4956
mock_sysout.assert_not_called()
57+
mock_warning.assert_called_once()
58+
warning_arg = mock_warning.call_args.args[0]
59+
self.assertIsInstance(warning_arg, NotATerminalError)
60+
self.assertIn("set_auto_wrap:: Requires a terminal (TTY)", str(warning_arg))
5061

5162
@patch.object(Terminal, "is_a_tty", return_value=True)
5263
def test_set_auto_wrap_should_emit_escape_sequence_when_tty(self, mock_is_tty):
@@ -60,11 +71,16 @@ def test_set_auto_wrap_should_emit_escape_sequence_when_tty(self, mock_is_tty):
6071

6172
@patch.object(Terminal, "is_a_tty", return_value=False)
6273
def test_set_show_cursor_should_guard_when_not_tty(self, mock_is_tty):
63-
with patch("clitt.core.term.terminal.sysout") as mock_sysout:
74+
with patch("clitt.core.term.terminal.sysout") as mock_sysout, \
75+
patch("clitt.core.term.terminal.log.warning") as mock_warning:
6476
Terminal.set_show_cursor(True)
6577

6678
mock_is_tty.assert_called_once()
6779
mock_sysout.assert_not_called()
80+
mock_warning.assert_called_once()
81+
warning_arg = mock_warning.call_args.args[0]
82+
self.assertIsInstance(warning_arg, NotATerminalError)
83+
self.assertIn("set_show_cursor:: Requires a terminal (TTY)", str(warning_arg))
6884

6985
@patch.object(Terminal, "is_a_tty", return_value=True)
7086
def test_set_show_cursor_should_emit_escape_sequence_when_tty(self, mock_is_tty):

modules/hspylib/src/test/__init__.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,21 @@
55
# Package: test
66
"""Package initialization."""
77

8+
from pathlib import Path
9+
import sys
10+
11+
_TEST_PACKAGE = Path(__file__).resolve().parent
12+
_SRC_ROOT = _TEST_PACKAGE.parent
13+
_MAIN_SRC = _SRC_ROOT / "main"
14+
15+
for path in (_SRC_ROOT, _MAIN_SRC):
16+
if str(path) not in sys.path:
17+
sys.path.insert(0, str(path))
18+
819
__all__ = [
9-
'core',
10-
'mock',
11-
'modules',
20+
'core',
21+
'mock',
22+
'modules',
1223
'shared'
1324
]
1425
__version__ = '1.12.54'
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
@project: HsPyLib
6+
test.core.exception
7+
@file: test_exceptions.py
8+
@created: Fri, 10 May 2024
9+
@license: MIT - Please refer to <https://opensource.org/licenses/MIT>
10+
"""
11+
12+
import sys
13+
import unittest
14+
from pathlib import Path
15+
from unittest.mock import patch
16+
17+
TEST_ROOT = Path(__file__).resolve().parents[3]
18+
if str(TEST_ROOT) not in sys.path:
19+
sys.path.insert(0, str(TEST_ROOT))
20+
SRC_MAIN = TEST_ROOT / "main"
21+
if str(SRC_MAIN) not in sys.path:
22+
sys.path.insert(0, str(SRC_MAIN))
23+
24+
from hspylib.core.exception.exceptions import (
25+
ApplicationError,
26+
HSBaseException,
27+
InvalidArgumentError,
28+
InvalidInputError,
29+
InvalidJsonMapping,
30+
InvalidMapping,
31+
InvalidOptionError,
32+
InvalidStateError,
33+
KeyboardInputError,
34+
PropertyError,
35+
WidgetExecutionError,
36+
WidgetNotFoundError,
37+
)
38+
39+
40+
class TestHsBaseException(unittest.TestCase):
41+
def test_should_format_message_with_cause_and_context(self) -> None:
42+
with patch("hspylib.core.exception.exceptions.log.error") as mock_log:
43+
try:
44+
raise ValueError("bad value")
45+
except ValueError as err:
46+
exc = HSBaseException("Something happened", err)
47+
48+
message = str(exc)
49+
self.assertTrue(message.startswith("### Something happened :bad value:"))
50+
self.assertRegex(message, r"\(File .*test_exceptions.py, Line ")
51+
mock_log.assert_called_once_with(message)
52+
53+
def test_should_preserve_message_when_no_active_exception(self) -> None:
54+
with patch("hspylib.core.exception.exceptions.log.error") as mock_log:
55+
exc = HSBaseException("Just a message")
56+
57+
self.assertEqual("Just a message", str(exc))
58+
mock_log.assert_called_once_with("Just a message")
59+
60+
def test_custom_exceptions_should_preserve_hierarchy(self) -> None:
61+
expected_subclasses = [
62+
ApplicationError,
63+
PropertyError,
64+
KeyboardInputError,
65+
InvalidOptionError,
66+
WidgetExecutionError,
67+
WidgetNotFoundError,
68+
InvalidMapping,
69+
InvalidJsonMapping,
70+
]
71+
for subclass in expected_subclasses:
72+
with self.subTest(subclass=subclass.__name__):
73+
self.assertTrue(issubclass(subclass, HSBaseException))
74+
75+
direct_exceptions = [InvalidInputError, InvalidArgumentError, InvalidStateError]
76+
for exc_type in direct_exceptions:
77+
with self.subTest(exc_type=exc_type.__name__):
78+
self.assertTrue(issubclass(exc_type, Exception))
79+
self.assertFalse(issubclass(exc_type, HSBaseException))
80+
81+
82+
if __name__ == "__main__":
83+
suite = unittest.TestLoader().loadTestsFromTestCase(TestHsBaseException)
84+
unittest.TextTestRunner(verbosity=2, failfast=True).run(suite)

0 commit comments

Comments
 (0)