From 0fba9397b65a692a125e0626fa518917782e779f Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:18:25 +0530 Subject: [PATCH 1/2] test: cover attachment initialization failures --- tests/test_click_tree_attachment.py | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_click_tree_attachment.py b/tests/test_click_tree_attachment.py index 632db75..c776940 100644 --- a/tests/test_click_tree_attachment.py +++ b/tests/test_click_tree_attachment.py @@ -11,6 +11,7 @@ from unittest import mock import base_cli +from base_cli._runtime import RuntimeDirectoryError from base_cli.testing import invoke @@ -619,6 +620,55 @@ def context_factory(context: base_cli.Context) -> object: with self.assertRaisesRegex(RuntimeError, "context is not active"): base_cli.get_current_context() + def test_factory_configuration_and_runtime_failures_translate_to_click_errors(self) -> None: + import click + + cases = ( + (base_cli.ConfigurationError("invalid application configuration"), click.UsageError), + (RuntimeDirectoryError("runtime directory unavailable"), click.ClickException), + ) + for failure, expected_type in cases: + with self.subTest(failure=type(failure).__name__): + + @click.command(name=f"factory-{type(failure).__name__.lower()}") + def command() -> None: + self.fail("factory failure should prevent callback execution") + + app = _CountingApp(name=command.name or "factory", log_to_file=False) + app.attach(command, context_factory=lambda _context, failure=failure: (_ for _ in ()).throw(failure)) + with tempfile.TemporaryDirectory() as tmpdir: + result = invoke(app, [], home=Path(tmpdir)) + + self.assertEqual(result.exit_code, 2 if expected_type is click.UsageError else 1, result.output) + self.assertIn(str(failure), result.output) + self.assertEqual(app.context_cleanup_count, 1) + + def test_partial_attachment_initialization_finalizes_before_reraising(self) -> None: + import click + + class PartialInitializationFailure(BaseException): + pass + + @click.command(name="partial-attachment") + def command() -> None: + self.fail("partial initialization should prevent callback execution") + + app = _CountingApp(name="partial-attachment", log_to_file=False) + app.attach(command) + failure = PartialInitializationFailure("context activation interrupted") + with tempfile.TemporaryDirectory() as tmpdir: + with mock.patch( + "base_cli._attach.set_current_context", + side_effect=failure, + ): + with self.assertRaises(PartialInitializationFailure) as raised: + invoke(app, [], home=Path(tmpdir), reraise_unexpected=True) + + self.assertIs(raised.exception, failure) + self.assertEqual(app.context_cleanup_count, 1) + with self.assertRaisesRegex(RuntimeError, "context is not active"): + base_cli.get_current_context() + def test_factory_registered_click_resources_close_inside_lifecycle(self) -> None: import click From 1547487296788289620b6ff0c79324566cf8c610 Mon Sep 17 00:00:00 2001 From: Ramesh Padmanabhaiah <22363102+codeforester@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:57:58 +0530 Subject: [PATCH 2/2] test: assert translated attachment errors portably --- tests/test_click_tree_attachment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_click_tree_attachment.py b/tests/test_click_tree_attachment.py index c776940..f1b6be4 100644 --- a/tests/test_click_tree_attachment.py +++ b/tests/test_click_tree_attachment.py @@ -639,8 +639,8 @@ def command() -> None: with tempfile.TemporaryDirectory() as tmpdir: result = invoke(app, [], home=Path(tmpdir)) - self.assertEqual(result.exit_code, 2 if expected_type is click.UsageError else 1, result.output) - self.assertIn(str(failure), result.output) + self.assertEqual(result.exit_code, 2 if expected_type is click.UsageError else 1, _all_output(result)) + self.assertIn(str(failure), _all_output(result)) self.assertEqual(app.context_cleanup_count, 1) def test_partial_attachment_initialization_finalizes_before_reraising(self) -> None: