#!/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 = ["ingest", "query", "template", "cache", "perf"] for keyword in document_keywords: assert keyword in help_text, f"markitect should include {keyword} functionality" # Should NOT have issue commands (they're moved to dedicated CLIs) issue_keywords = ["issue", "close-issue", "create-issue"] for keyword in issue_keywords: assert keyword not in help_text, f"markitect should not include {keyword} - use 'issue' or 'tddai' CLI" 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_no_functionality_duplication(self): """Ensure functionality is not duplicated across CLIs.""" # 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 # Check that markitect doesn't duplicate issue functionality markitect_commands = set() for line in markitect_help.split('\n'): if line.strip().startswith('markitect '): cmd = line.strip().split()[1] if len(line.strip().split()) > 1 else "" if cmd: markitect_commands.add(cmd) # Issue commands should not be in markitect issue_specific = {"list-issues", "show-issue", "create-issue", "close-issue"} overlap = markitect_commands.intersection(issue_specific) assert len(overlap) == 0, f"markitect duplicates issue commands: {overlap}" 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 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"])