crackerjack 0.20.2__tar.gz → 0.20.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {crackerjack-0.20.2 → crackerjack-0.20.3}/PKG-INFO +1 -1
  2. crackerjack-0.20.3/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
  3. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/crackerjack.py +26 -15
  4. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/pyproject.toml +1 -1
  5. {crackerjack-0.20.2 → crackerjack-0.20.3}/pyproject.toml +1 -1
  6. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_crackerjack.py +152 -49
  7. crackerjack-0.20.2/crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
  8. {crackerjack-0.20.2 → crackerjack-0.20.3}/LICENSE +0 -0
  9. {crackerjack-0.20.2 → crackerjack-0.20.3}/README.md +0 -0
  10. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.gitignore +0 -0
  11. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.libcst.codemod.yaml +0 -0
  12. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pdm.toml +0 -0
  13. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pre-commit-config.yaml +0 -0
  14. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pytest_cache/.gitignore +0 -0
  15. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
  16. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pytest_cache/README.md +0 -0
  17. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
  18. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
  19. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/.gitignore +0 -0
  20. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  21. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  22. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  23. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  24. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  25. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  26. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.11/18187162184424859798 +0 -0
  27. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/16869036553936192448 +0 -0
  28. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/1867267426380906393 +0 -0
  29. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/4240757255861806333 +0 -0
  30. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.12/4441409093023629623 +0 -0
  31. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  32. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.3/9818742842212983150 +0 -0
  33. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.4/9818742842212983150 +0 -0
  34. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.6/3557596832929915217 +0 -0
  35. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
  36. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
  37. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.11.8/530407680854991027 +0 -0
  38. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  39. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  40. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  41. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  42. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  43. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  44. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  45. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  46. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  47. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  48. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  49. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  50. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  51. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  52. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  53. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  54. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  55. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  56. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  57. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  58. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  59. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  60. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  61. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  62. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  63. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  64. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
  65. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/__init__.py +0 -0
  66. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/__main__.py +0 -0
  67. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/errors.py +0 -0
  68. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/interactive.py +0 -0
  69. {crackerjack-0.20.2 → crackerjack-0.20.3}/crackerjack/py313.py +0 -0
  70. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/TESTING.md +0 -0
  71. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/__init__.py +0 -0
  72. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/conftest.py +0 -0
  73. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/data/comments_sample.txt +0 -0
  74. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/data/docstrings_sample.txt +0 -0
  75. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/data/expected_comments_sample.txt +0 -0
  76. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/data/init.py +0 -0
  77. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_crackerjack_runner.py +0 -0
  78. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_errors.py +0 -0
  79. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_interactive.py +0 -0
  80. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_interactive_run.py +0 -0
  81. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_main.py +0 -0
  82. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_py313_advanced.py +0 -0
  83. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_py313_features.py +0 -0
  84. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_pytest_features.py +0 -0
  85. {crackerjack-0.20.2 → crackerjack-0.20.3}/tests/test_structured_errors.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.20.2
3
+ Version: 0.20.3
4
4
  Summary: Crackerjack: code quality toolkit
5
5
  Keywords: bandit,black,creosote,mypy,pyright,pytest,refurb,ruff
6
6
  Author-Email: lesleslie <les@wedgwoodwebworks.com>
@@ -1022,23 +1022,34 @@ class Crackerjack:
1022
1022
 
1023
1023
  if options.publish:
1024
1024
  if platform.system() == "Darwin":
1025
- authorize = self.execute_command(
1026
- ["pdm", "self", "add", "keyring"], capture_output=True, text=True
1025
+ # First check if keyring is already installed in PDM
1026
+ check_keyring = self.execute_command(
1027
+ ["pdm", "self", "list"], capture_output=True, text=True
1027
1028
  )
1028
- if authorize.returncode > 0:
1029
- error = PublishError(
1030
- message="Authentication setup failed",
1031
- error_code=ErrorCode.AUTHENTICATION_ERROR,
1032
- details=f"Failed to add keyring support to PDM.\nCommand output:\n{authorize.stderr}",
1033
- recovery="Please manually add your keyring credentials to PDM. Run `pdm self add keyring` and try again.",
1034
- exit_code=1,
1035
- )
1036
- handle_error(
1037
- error=error,
1038
- console=self.console,
1039
- verbose=options.verbose,
1040
- ai_agent=options.ai_agent,
1029
+ keyring_installed = "keyring" in check_keyring.stdout
1030
+
1031
+ if not keyring_installed:
1032
+ # Only attempt to install keyring if it's not already installed
1033
+ self.console.print("Installing keyring for PDM...")
1034
+ authorize = self.execute_command(
1035
+ ["pdm", "self", "add", "keyring"],
1036
+ capture_output=True,
1037
+ text=True,
1041
1038
  )
1039
+ if authorize.returncode > 0:
1040
+ error = PublishError(
1041
+ message="Authentication setup failed",
1042
+ error_code=ErrorCode.AUTHENTICATION_ERROR,
1043
+ details=f"Failed to add keyring support to PDM.\nCommand output:\n{authorize.stderr}",
1044
+ recovery="Please manually add your keyring credentials to PDM. Run `pdm self add keyring` and try again.",
1045
+ exit_code=1,
1046
+ )
1047
+ handle_error(
1048
+ error=error,
1049
+ console=self.console,
1050
+ verbose=options.verbose,
1051
+ ai_agent=options.ai_agent,
1052
+ )
1042
1053
 
1043
1054
  build = self.execute_command(
1044
1055
  ["pdm", "build"], capture_output=True, text=True
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.20.1"
7
+ version = "0.20.2"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -6,7 +6,7 @@ requires = [
6
6
 
7
7
  [project]
8
8
  name = "crackerjack"
9
- version = "0.20.2"
9
+ version = "0.20.3"
10
10
  description = "Crackerjack: code quality toolkit"
11
11
  readme = "README.md"
12
12
  keywords = [
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import subprocess
2
3
  import typing as t
3
4
  from contextlib import suppress
4
5
  from dataclasses import dataclass
@@ -12,6 +13,7 @@ from crackerjack.crackerjack import (
12
13
  CodeCleaner,
13
14
  ConfigManager,
14
15
  Crackerjack,
16
+ OptionsProtocol,
15
17
  ProjectManager,
16
18
  )
17
19
 
@@ -180,7 +182,7 @@ class TestCrackerjackProcess:
180
182
  options = options_factory(commit=True, no_config_updates=True)
181
183
  with patch.object(Crackerjack, "_update_project") as mock_update_project:
182
184
 
183
- def side_effect(opts: t.Any) -> None:
185
+ def side_effect(opts: OptionsProtocol) -> None:
184
186
  if opts.no_config_updates:
185
187
  mock_console_print("Skipping config updates.")
186
188
 
@@ -580,7 +582,9 @@ class TestCrackerjackProcess:
580
582
  with patch("platform.system", return_value="Linux"):
581
583
  with patch.object(Crackerjack, "execute_command") as mock_cj_execute:
582
584
 
583
- def mock_execute_side_effect(*args: t.Any, **kwargs: t.Any):
585
+ def mock_execute_side_effect(
586
+ *args: t.Any, **kwargs: t.Any
587
+ ) -> subprocess.CompletedProcess[str]:
584
588
  cmd = args[0][0]
585
589
  if cmd == "pdm" and "build" in args[0]:
586
590
  return MagicMock(
@@ -596,55 +600,154 @@ class TestCrackerjackProcess:
596
600
 
597
601
  mock_handle_error.assert_called()
598
602
 
599
- def test_process_with_darwin_platform(
600
- self,
601
- mock_execute: MagicMock,
602
- mock_console_print: MagicMock,
603
- tmp_path: Path,
604
- tmp_path_package: Path,
605
- create_package_dir: None,
606
- options_factory: t.Callable[..., OptionsForTesting],
607
- ) -> None:
608
- options = options_factory(publish="micro", no_config_updates=True)
609
- with patch("platform.system", return_value="Darwin"):
610
- with patch.object(Crackerjack, "execute_command") as mock_cj_execute:
611
- mock_cj_execute.side_effect = [
612
- MagicMock(returncode=0, stdout="Success"),
613
- MagicMock(returncode=0, stdout="Success"),
614
- MagicMock(returncode=0, stdout="Success"),
615
- MagicMock(returncode=0, stdout="Success"),
616
- ]
617
- with patch.object(Crackerjack, "_update_project"):
618
- cj = Crackerjack(dry_run=True)
619
- cj.process(options)
620
- mock_cj_execute.assert_any_call(
621
- ["pdm", "self", "add", "keyring"],
622
- capture_output=True,
623
- text=True,
624
- )
603
+ def test_keyring_check_install_darwin(self) -> None:
604
+ """Test checking for keyring on Darwin (macOS) and installing it."""
605
+ # Create a minimal options object
606
+ options = OptionsForTesting(publish=BumpOption.micro)
625
607
 
626
- def test_process_with_darwin_platform_keyring_failure(
627
- self,
628
- mock_execute: MagicMock,
629
- mock_console_print: MagicMock,
630
- tmp_path: Path,
631
- tmp_path_package: Path,
632
- create_package_dir: None,
633
- options_factory: t.Callable[..., OptionsForTesting],
634
- ) -> None:
635
- with patch("crackerjack.errors.handle_error") as mock_handle_error:
636
- options = options_factory(publish="micro", no_config_updates=True)
637
- with patch("platform.system", return_value="Darwin"):
638
- with patch.object(Crackerjack, "execute_command") as mock_cj_execute:
639
- mock_cj_execute.return_value = MagicMock(
640
- returncode=1, stdout="", stderr="Authorization failed"
641
- )
642
- with patch.object(Crackerjack, "_update_project"):
643
- with suppress(SystemExit):
644
- cj = Crackerjack(dry_run=True)
645
- cj.process(options)
608
+ # Use a list to track actual calls
609
+ actual_calls = []
646
610
 
647
- mock_handle_error.assert_called()
611
+ # Create a Crackerjack instance for testing
612
+ crackerjack = Crackerjack(dry_run=False)
613
+
614
+ # Mock platform.system to return 'Darwin'
615
+ with patch("platform.system", return_value="Darwin"):
616
+ # Create a custom execute_command function that tracks calls
617
+ def mock_execute_side_effect(
618
+ *args: t.Any, **kwargs: t.Any
619
+ ) -> subprocess.CompletedProcess[str]:
620
+ cmd = args[0]
621
+ actual_calls.append(cmd) # Track each command
622
+
623
+ if cmd == ["pdm", "self", "list"]:
624
+ # Important: This MUST NOT contain the string "keyring" to trigger installation
625
+ return MagicMock(
626
+ returncode=0, stdout="packages installed: pytest, black"
627
+ )
628
+ elif cmd == ["pdm", "self", "add", "keyring"]:
629
+ return MagicMock(returncode=0, stdout="installed keyring")
630
+ elif cmd == ["pdm", "build"]:
631
+ return MagicMock(returncode=0, stdout="build output")
632
+ elif cmd == ["pdm", "publish", "--no-build"]:
633
+ return MagicMock(returncode=0, stdout="publish output")
634
+ return MagicMock(returncode=0, stdout="")
635
+
636
+ # Mock execute_command with our custom side_effect
637
+ with patch.object(
638
+ crackerjack, "execute_command", side_effect=mock_execute_side_effect
639
+ ):
640
+ # Mock console.print
641
+ with patch.object(crackerjack.console, "print"):
642
+ # Call the method we're testing
643
+ crackerjack._publish_project(options)
644
+
645
+ # Check that all the expected commands were called
646
+ assert ["pdm", "self", "list"] in actual_calls
647
+ assert ["pdm", "self", "add", "keyring"] in actual_calls
648
+ assert ["pdm", "build"] in actual_calls
649
+ assert ["pdm", "publish", "--no-build"] in actual_calls
650
+
651
+ def test_keyring_already_installed_darwin(self) -> None:
652
+ """Test behavior when keyring is already installed on Darwin (macOS)."""
653
+ # Create a minimal options object
654
+ options = OptionsForTesting(publish=BumpOption.micro)
655
+
656
+ # Use a list to track actual calls
657
+ actual_calls = []
658
+
659
+ # Create a Crackerjack instance for testing
660
+ crackerjack = Crackerjack(dry_run=False)
661
+
662
+ # Mock platform.system to return 'Darwin'
663
+ with patch("platform.system", return_value="Darwin"):
664
+ # Create a custom execute_command function that tracks calls
665
+ def mock_execute_side_effect(
666
+ *args: t.Any, **kwargs: t.Any
667
+ ) -> subprocess.CompletedProcess[str]:
668
+ cmd = args[0]
669
+ actual_calls.append(cmd) # Track each command
670
+
671
+ if cmd == ["pdm", "self", "list"]:
672
+ return MagicMock(returncode=0, stdout="keyring 25.6.0")
673
+ elif cmd == ["pdm", "build"]:
674
+ return MagicMock(returncode=0, stdout="build output")
675
+ elif cmd == ["pdm", "publish", "--no-build"]:
676
+ return MagicMock(returncode=0, stdout="publish output")
677
+ return MagicMock(returncode=0, stdout="")
678
+
679
+ # Mock execute_command with our custom side_effect
680
+ with patch.object(
681
+ crackerjack, "execute_command", side_effect=mock_execute_side_effect
682
+ ):
683
+ # Mock console.print
684
+ with patch.object(crackerjack.console, "print"):
685
+ # Call the method we're testing
686
+ crackerjack._publish_project(options)
687
+
688
+ # Check that keyring installation was skipped
689
+ assert ["pdm", "self", "list"] in actual_calls
690
+ assert ["pdm", "self", "add", "keyring"] not in actual_calls
691
+ assert ["pdm", "build"] in actual_calls
692
+ assert ["pdm", "publish", "--no-build"] in actual_calls
693
+
694
+ def test_keyring_install_failure_darwin(self) -> None:
695
+ """Test handling of keyring installation failure on Darwin (macOS)."""
696
+ # Create a minimal options object with ai_agent False to avoid extra mocking
697
+ options = OptionsForTesting(publish=BumpOption.micro, ai_agent=False)
698
+
699
+ # Create a Crackerjack instance for testing
700
+ crackerjack = Crackerjack(dry_run=False)
701
+
702
+ # Mock the PublishError class to capture its creation
703
+ with patch("crackerjack.errors.PublishError") as mock_publish_error:
704
+ # Set up the mock to return a MagicMock that we can track
705
+ error_instance = MagicMock()
706
+ mock_publish_error.return_value = error_instance
707
+
708
+ # Mock handle_error to avoid actual exit
709
+ with patch("crackerjack.errors.handle_error") as mock_handle_error:
710
+ # Mock platform.system to return 'Darwin'
711
+ with patch("platform.system", return_value="Darwin"):
712
+ # Create a custom execute_command function with failure scenario
713
+ def mock_execute_side_effect(
714
+ *args: t.Any, **kwargs: t.Any
715
+ ) -> subprocess.CompletedProcess[str]:
716
+ cmd = args[0]
717
+
718
+ if cmd == ["pdm", "self", "list"]:
719
+ # This must NOT contain the string "keyring" to trigger installation
720
+ return MagicMock(
721
+ returncode=0, stdout="packages installed: pytest, black"
722
+ )
723
+ elif cmd == ["pdm", "self", "add", "keyring"]:
724
+ # Return a failure for keyring installation
725
+ mock_result = MagicMock()
726
+ mock_result.returncode = 1
727
+ mock_result.stdout = ""
728
+ mock_result.stderr = "Installation failed"
729
+ return mock_result
730
+ return MagicMock(returncode=0, stdout="")
731
+
732
+ # Mock execute_command with our custom side_effect
733
+ with patch.object(
734
+ crackerjack,
735
+ "execute_command",
736
+ side_effect=mock_execute_side_effect,
737
+ ):
738
+ # Mock console.print to avoid terminal output
739
+ with patch.object(crackerjack.console, "print"):
740
+ # Call the method we're testing with error suppression
741
+ with suppress(SystemExit):
742
+ crackerjack._publish_project(options)
743
+
744
+ # Check that the error handler was called with the right error
745
+ mock_handle_error.assert_called_once_with(
746
+ error=error_instance,
747
+ console=crackerjack.console,
748
+ verbose=options.verbose,
749
+ ai_agent=options.ai_agent,
750
+ )
648
751
 
649
752
  def test_process_with_commit_input(
650
753
  self,
File without changes
File without changes