#!/usr/bin/env python3 """ CLI Consolidation Integration Tests Tests to ensure proper CLI interface consolidation and prevent regression of missing CLI commands. This test suite verifies: 1. All CLI entry points are properly installed 2. No functionality duplication between CLIs 3. Each CLI has clear separation of concerns 4. Help commands work for all CLIs 5. Core functionality is accessible Purpose: Prevent the loss of CLI interfaces that occurred previously due to lack of testing. """ import pytest import subprocess import shutil import sys from pathlib import Path class TestCLIConsolidation: """Test suite for CLI consolidation and interface availability.""" def test_all_cli_commands_installed(self): """Ensure all CLI commands are properly installed and accessible.""" # Test that all three CLI commands exist markitect_path = shutil.which("markitect") tddai_path = shutil.which("tddai") issue_path = shutil.which("issue") assert markitect_path is not None, "markitect CLI command not found - check pyproject.toml scripts" assert tddai_path is not None, "tddai CLI command not found - check pyproject.toml scripts" assert issue_path is not None, "issue CLI command not found - check pyproject.toml scripts" def test_cli_help_commands_work(self): """Verify help commands work for all CLIs without errors.""" cli_commands = ["markitect", "tddai", "issue"] for cmd in cli_commands: try: result = subprocess.run( [cmd, "--help"], capture_output=True, text=True, timeout=30 ) assert result.returncode == 0, f"{cmd} --help failed with exit code {result.returncode}" assert len(result.stdout) > 100, f"{cmd} --help produced minimal output: {result.stdout[:200]}" except subprocess.TimeoutExpired: pytest.fail(f"{cmd} --help timed out") except FileNotFoundError: pytest.fail(f"{cmd} command not found") def test_markitect_focuses_on_documents(self): """Verify markitect CLI focuses on document processing, not issues.""" result = subprocess.run( ["markitect", "--help"], capture_output=True, text=True ) help_text = result.stdout.lower() # Should have document-related commands document_keywords = ["md-ingest", "query", "template", "cache", "perf"] for keyword in document_keywords: assert keyword in help_text, f"markitect should include {keyword} functionality" # Should have issue commands alongside dedicated CLIs for unified access # NOTE: markitect provides issues group for unified interface while dedicated CLIs provide specialized access assert "issues" in help_text, "markitect should include issues functionality for unified access" def test_tddai_focuses_on_workflow(self): """Verify tddai CLI focuses on TDD workflow management.""" result = subprocess.run( ["tddai", "--help"], capture_output=True, text=True ) help_text = result.stdout.lower() # Should have TDD workflow commands tdd_keywords = ["start-issue", "finish-issue", "workspace", "coverage", "test"] for keyword in tdd_keywords: assert keyword in help_text, f"tddai should include {keyword} functionality" def test_issue_focuses_on_issue_management(self): """Verify issue CLI focuses purely on issue operations.""" result = subprocess.run( ["issue", "--help"], capture_output=True, text=True ) help_text = result.stdout.lower() # Should have issue management commands issue_keywords = ["list", "show", "create", "close", "assign", "priority"] for keyword in issue_keywords: assert keyword in help_text, f"issue CLI should include {keyword} functionality" def test_cli_separation_of_concerns(self): """Ensure CLIs maintain appropriate separation of concerns while allowing unified access.""" # Get help text for all CLIs markitect_help = subprocess.run(["markitect", "--help"], capture_output=True, text=True).stdout tddai_help = subprocess.run(["tddai", "--help"], capture_output=True, text=True).stdout issue_help = subprocess.run(["issue", "--help"], capture_output=True, text=True).stdout # markitect should have both document processing AND issues (unified interface) assert "md-ingest" in markitect_help, "markitect should have document processing" assert "issues" in markitect_help, "markitect should have unified issues access" # tddai should focus on workflow assert "workspace" in tddai_help.lower(), "tddai should have workflow features" assert "start-issue" in tddai_help, "tddai should have TDD workflow" # issue CLI should focus on pure issue management assert "list" in issue_help, "issue CLI should have list functionality" assert "create" in issue_help, "issue CLI should have create functionality" def test_cli_integration_imports(self): """Test that CLI modules can be imported without errors.""" try: # Test tddai_cli import import tddai_cli assert hasattr(tddai_cli, 'main'), "tddai_cli should have main() function" # Test issue CLI import from cli import issue_cli assert hasattr(issue_cli, 'main'), "issue_cli should have main() function" # Test markitect CLI import from markitect import cli as markitect_cli assert hasattr(markitect_cli, 'main'), "markitect.cli should have main() function" except ImportError as e: pytest.fail(f"CLI import failed: {e}") def test_cli_framework_integration(self): """Test that the CLI framework is properly integrated.""" try: from cli.core import CLIFramework # Initialize framework (should not raise errors) framework = CLIFramework() # Test that key methods exist required_methods = [ 'list_issues', 'show_issue', 'close_issue', 'create_issue', 'start_issue', 'finish_issue', 'workspace_status' ] for method in required_methods: assert hasattr(framework, method), f"CLIFramework missing method: {method}" except Exception as e: pytest.fail(f"CLI framework integration failed: {e}") def test_make_targets_work(self): """Test that Makefile targets work with the new CLI structure.""" # Test that make targets exist for issue operations makefile_path = Path(__file__).parent.parent / "Makefile" if makefile_path.exists(): makefile_content = makefile_path.read_text() # Check for issue-related targets expected_targets = [ "close-issue", "close-issue-enhanced", "close-issues-batch", "list-issues", "show-issue" ] for target in expected_targets: assert target in makefile_content, f"Makefile missing target: {target}" def test_pyproject_toml_entries(self): """Test that pyproject.toml has correct CLI entry points.""" pyproject_path = Path(__file__).parent.parent / "pyproject.toml" if pyproject_path.exists(): content = pyproject_path.read_text() # Check for all three CLI entry points expected_entries = [ 'markitect = "markitect.cli:main"', 'tddai = "tddai_cli:main"', 'issue = "cli.issue_cli:main"' ] for entry in expected_entries: assert entry in content, f"pyproject.toml missing entry: {entry}" class TestCLIFunctionality: """Comprehensive functional tests for all CLI commands.""" def test_markitect_document_commands(self): """Test markitect document processing commands.""" # Test that markitect has core document commands result = subprocess.run(["markitect", "--help"], capture_output=True, text=True) help_text = result.stdout # Core document processing commands should be present expected_commands = [ "md-ingest", "md-list", "md-get", "stats", "metadata", "schema-generate", "template-render", "perf-benchmark" ] for cmd in expected_commands: assert cmd in help_text, f"markitect missing document command: {cmd}" def test_tddai_workflow_commands(self): """Test tddai TDD workflow commands.""" result = subprocess.run(["tddai", "--help"], capture_output=True, text=True) help_text = result.stdout # TDD workflow commands should be present expected_commands = [ "workspace-status", "start-issue", "finish-issue", "list-issues", "close-issue", "analyze-coverage" ] for cmd in expected_commands: assert cmd in help_text, f"tddai missing workflow command: {cmd}" def test_issue_management_commands(self): """Test issue CLI management commands.""" result = subprocess.run(["issue", "--help"], capture_output=True, text=True) help_text = result.stdout # Issue management commands should be present expected_commands = [ "list", "show", "create", "close", "assign", "priority", "state", "export" ] for cmd in expected_commands: assert cmd in help_text, f"issue CLI missing command: {cmd}" def test_cli_subcommand_help(self): """Test that subcommands have proper help text.""" test_cases = [ ("tddai", "list-issues", "--help"), ("issue", "list", "--help"), ("markitect", "stats", "--help"), ] for cli, subcommand, help_flag in test_cases: try: result = subprocess.run( [cli, subcommand, help_flag], capture_output=True, text=True, timeout=10 ) # Should either succeed or show usage (not crash) assert result.returncode in [0, 2], f"{cli} {subcommand} help failed" assert len(result.stdout) > 10 or len(result.stderr) > 10, f"{cli} {subcommand} no help output" except subprocess.TimeoutExpired: pytest.fail(f"{cli} {subcommand} --help timed out") def test_cli_error_handling(self): """Test that CLIs handle invalid commands gracefully.""" test_cases = [ ("tddai", "invalid-command"), ("issue", "invalid-command"), ("markitect", "invalid-command"), ] for cli, invalid_cmd in test_cases: result = subprocess.run( [cli, invalid_cmd], capture_output=True, text=True ) # Should fail gracefully, not crash assert result.returncode != 0, f"{cli} should reject invalid command {invalid_cmd}" # Should have error output assert len(result.stderr) > 0 or "error" in result.stdout.lower(), f"{cli} should show error for {invalid_cmd}" def test_cli_list_commands_functional(self): """Test that list commands actually work.""" # Test that list commands don't crash test_cases = [ ("tddai", "list-issues"), ("issue", "list"), ("markitect", "md-list"), ] for cli, list_cmd in test_cases: try: result = subprocess.run( [cli, list_cmd], capture_output=True, text=True, timeout=30 ) # Should not crash (may return empty list) assert result.returncode == 0, f"{cli} {list_cmd} failed with exit code {result.returncode}" except subprocess.TimeoutExpired: pytest.fail(f"{cli} {list_cmd} timed out - may be hanging") def test_cli_configuration_access(self): """Test that CLIs can access configuration.""" # Test config-related commands result = subprocess.run( ["tddai", "config-show"], capture_output=True, text=True, timeout=15 ) # Should not crash (may show config or error message) assert result.returncode in [0, 1], "tddai config-show should handle config access" class TestCLIRegression: """Tests to prevent regression of CLI functionality.""" def test_prevent_cli_loss(self): """Prevent loss of CLI commands (primary regression test).""" # This is the main test that should have prevented the original issue required_clis = ["markitect", "tddai", "issue"] for cli in required_clis: # Test that command exists assert shutil.which(cli) is not None, f"REGRESSION: {cli} CLI lost - not installed" # Test that command responds result = subprocess.run([cli, "--help"], capture_output=True, text=True) assert result.returncode == 0, f"REGRESSION: {cli} CLI broken - help fails" def test_core_issue_operations_accessible(self): """Ensure core issue operations remain accessible through some CLI.""" # Test that basic issue operations are available core_operations = [ ("list issues", ["tddai", "list-issues"]), ("show issue", ["tddai", "show-issue", "42"]), # Will fail but should parse ("close issue", ["tddai", "close-issue", "42"]) # Will fail but should parse ] for operation_name, cmd in core_operations: try: # We expect these to fail (no real issue 42), but the CLI should parse the command result = subprocess.run( cmd, capture_output=True, text=True, timeout=10 ) # Command should be recognized (not return "unknown command" error) assert "unknown" not in result.stderr.lower(), f"{operation_name} not accessible via CLI" except subprocess.TimeoutExpired: # Timeout is okay - means command is running pass if __name__ == '__main__': pytest.main([__file__, "-v"])