Files
kaizen-agentic/tests/test_cli_error_handling.py
tegwick 3858141ce6 Extend update command error handling and update documentation
- Extend safe_cli_wrapper() to suppress spurious Click errors for both
  install and update commands; add success indicators for update output
- Add test_update_command_error_suppression to verify error suppression
- Expand CLAUDE.md to document all 17 agents with categories
- Add Keep a Contributing-File format header to CONTRIBUTING.md
- Fix TodoFileGuide URL reference in TODO.md
- Add RELEASE_NOTES_v1.0.1.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 09:02:04 +01:00

246 lines
11 KiB
Python

"""
Tests for CLI error handling and Click library workaround.
This module tests the safe_cli_wrapper function and its ability to handle
spurious Click library error messages while preserving legitimate errors.
"""
import subprocess
import sys
import pytest
from io import StringIO
from unittest.mock import patch
from kaizen_agentic.cli import safe_cli_wrapper, cli
class TestClickWorkaround:
"""Test the Click library error message suppression workaround."""
def test_install_command_error_suppression(self):
"""Test that spurious 'unexpected extra argument' errors are suppressed for install commands."""
# Test the install command that previously showed spurious errors
with patch('sys.argv', ['kaizen-agentic', 'install', 'tdd-workflow', '--target', '/tmp/test']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
try:
safe_cli_wrapper()
except SystemExit:
pass # Expected for CLI commands
stdout_content = mock_stdout.getvalue()
stderr_content = mock_stderr.getvalue()
# Should show installation message
assert "Installing agents to:" in stdout_content
# Should NOT show spurious error message
assert "Got unexpected extra argument" not in stdout_content
assert "Got unexpected extra argument" not in stderr_content
def test_update_command_error_suppression(self):
"""Test that spurious 'unexpected extra argument' errors are suppressed for update commands."""
# Test the update command that also shows spurious errors
with patch('sys.argv', ['kaizen-agentic', 'update']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
try:
safe_cli_wrapper()
except SystemExit:
pass # Expected for CLI commands
stdout_content = mock_stdout.getvalue()
stderr_content = mock_stderr.getvalue()
# Should show update message
assert "Updating all installed agents:" in stdout_content
# Should NOT show spurious error message
assert "Got unexpected extra argument" not in stdout_content
assert "Got unexpected extra argument" not in stderr_content
def test_non_install_command_normal_operation(self):
"""Test that non-install commands work normally without interference."""
with patch('sys.argv', ['kaizen-agentic', 'list']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
try:
safe_cli_wrapper()
except SystemExit:
pass # Expected for CLI commands
stdout_content = mock_stdout.getvalue()
# Should show list output
assert "Available Agents" in stdout_content
# Should not interfere with normal operation
assert "Error:" not in stdout_content
def test_legitimate_error_preservation(self):
"""Test that legitimate errors are still displayed for non-install commands."""
with patch('sys.argv', ['kaizen-agentic', 'invalid-command']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
try:
safe_cli_wrapper()
except SystemExit as e:
# Should exit with error code for invalid commands
assert e.code != 0
# Should show legitimate error for invalid commands
stdout_content = mock_stdout.getvalue()
stderr_content = mock_stderr.getvalue()
# The error should be shown (either in stdout or stderr)
all_output = stdout_content + stderr_content
assert "Error:" in all_output or "Usage:" in all_output
def test_help_commands_work_normally(self):
"""Test that help commands work without interference."""
with patch('sys.argv', ['kaizen-agentic', '--help']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
try:
safe_cli_wrapper()
except SystemExit as e:
# Help should exit with code 0
assert e.code == 0
stdout_content = mock_stdout.getvalue()
assert "Kaizen Agentic - AI agent development framework" in stdout_content
assert "Commands:" in stdout_content
class TestInstallCommandSpecifics:
"""Test specific install command scenarios."""
def test_install_with_valid_agent(self):
"""Test install command with a valid agent name."""
with patch('sys.argv', ['kaizen-agentic', 'install', 'tdd-workflow']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
try:
safe_cli_wrapper()
except SystemExit:
pass
stdout_content = mock_stdout.getvalue()
stderr_content = mock_stderr.getvalue()
# Should show clean installation output
assert "Installing agents to:" in stdout_content
# Should not show Click error
assert "Got unexpected extra argument" not in (stdout_content + stderr_content)
def test_install_with_target_option(self):
"""Test install command with target directory option."""
with patch('sys.argv', ['kaizen-agentic', 'install', 'tdd-workflow', '--target', '/tmp/test']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
try:
safe_cli_wrapper()
except SystemExit:
pass
stdout_content = mock_stdout.getvalue()
# Should show target directory in output
assert "/tmp/test" in stdout_content
def test_install_help_works(self):
"""Test that install command help works correctly."""
with patch('sys.argv', ['kaizen-agentic', 'install', '--help']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
try:
safe_cli_wrapper()
except SystemExit as e:
assert e.code == 0
stdout_content = mock_stdout.getvalue()
assert "Install agents into a project" in stdout_content
assert "AGENTS" in stdout_content
class TestWorkaroundRemovalReadiness:
"""Tests to help determine when the workaround can be safely removed."""
def test_direct_cli_function_behavior(self):
"""
Test the direct CLI function to compare with wrapper behavior.
This test helps identify when the underlying Click issue is resolved
by testing the direct CLI function without the wrapper.
When this test starts passing (no spurious errors), the workaround
may be ready for removal.
"""
# Skip this test in normal runs since it's expected to show the spurious error
pytest.skip("This test demonstrates the underlying Click issue. "
"Enable when testing Click library updates.")
with patch('sys.argv', ['kaizen-agentic', 'install', 'tdd-workflow']):
with patch('sys.stdout', new_callable=StringIO) as mock_stdout:
with patch('sys.stderr', new_callable=StringIO) as mock_stderr:
try:
cli(standalone_mode=False)
except SystemExit:
pass
stdout_content = mock_stdout.getvalue()
stderr_content = mock_stderr.getvalue()
all_output = stdout_content + stderr_content
# When Click is fixed, this assertion should pass:
# assert "Got unexpected extra argument" not in all_output
# Currently, this demonstrates the issue:
print(f"Direct CLI stdout: {stdout_content}")
print(f"Direct CLI stderr: {stderr_content}")
@pytest.mark.integration
def test_subprocess_cli_invocation(self):
"""
Integration test using subprocess to test the actual CLI entry point.
This tests the real user experience with the installed CLI.
"""
# Test that the CLI works when invoked as a subprocess
result = subprocess.run(
['python', '-c', 'from kaizen_agentic.cli import safe_cli_wrapper; import sys; sys.argv = ["kaizen-agentic", "list"]; safe_cli_wrapper()'],
capture_output=True,
text=True
)
assert "Available Agents" in result.stdout
# Should not contain spurious errors
assert "Got unexpected extra argument" not in result.stderr
assert "Got unexpected extra argument" not in result.stdout
class TestErrorMessagePatterns:
"""Test specific error message patterns and filtering."""
def test_spurious_error_pattern_detection(self):
"""Test that the wrapper correctly identifies spurious error patterns."""
spurious_patterns = [
"Got unexpected extra argument (tdd-workflow)",
"Got unexpected extra argument (some-agent)",
"Error: Got unexpected extra argument"
]
for pattern in spurious_patterns:
# Test that these patterns would be detected as spurious for install commands
# This is tested implicitly through the integration tests above
assert "Got unexpected extra argument" in pattern
def test_legitimate_error_patterns_preserved(self):
"""Test that legitimate error patterns are not filtered out."""
legitimate_patterns = [
"Error: No such file or directory",
"Error: Permission denied",
"Error: Invalid agent name",
"Error: Configuration file not found"
]
for pattern in legitimate_patterns:
# These should NOT be filtered out
assert "Got unexpected extra argument" not in pattern
if __name__ == "__main__":
pytest.main([__file__])