""" Tests for Issue #13: Cache Management CLI Commands. TDD approach: These tests define the exact requirements for cache management commands. All tests should initially FAIL (RED) and drive the implementation (GREEN). Commands to implement: - `markitect cache-stats` - Display cache statistics and effectiveness - `markitect cache-clean` - Clear cache and free memory - `markitect cache-invalidate ` - Invalidate specific file cache """ import json import tempfile from pathlib import Path from unittest.mock import patch import pytest from click.testing import CliRunner from markitect.cli import cli from markitect.ast_cache import ASTCache class TestCacheCommands: """TDD test suite defining cache management command requirements.""" def setup_method(self): """Set up test environment.""" self.runner = CliRunner() self.temp_dir = tempfile.mkdtemp() self.cache_dir = Path(self.temp_dir) / ".ast_cache" # Create test markdown file self.test_file = Path(self.temp_dir) / "test.md" self.test_file.write_text("""--- title: Test Document --- # Test Heading This is test content. """) def teardown_method(self): """Clean up after each test.""" import shutil if Path(self.temp_dir).exists(): shutil.rmtree(self.temp_dir) # ===== cache-stats command tests ===== def test_cache_stats_command_exists(self): """RED: cache-stats command should exist and be callable.""" result = self.runner.invoke(cli, ['cache-stats']) # Should NOT be "No such command" - command must exist assert "No such command" not in result.output # Command exists and runs (may fail for other reasons initially) assert result.exit_code in [0, 1, 2] def test_cache_stats_shows_cache_directory_path(self): """RED: cache-stats should display the cache directory path.""" result = self.runner.invoke(cli, ['cache-stats']) assert result.exit_code == 0 assert "Cache Directory:" in result.output def test_cache_stats_shows_total_files_count(self): """RED: cache-stats should show count of cached files.""" # Clean existing cache first self.runner.invoke(cli, ['cache-clean']) # Create cache with known files using the project's default cache location from pathlib import Path project_cache_dir = Path.cwd() / ".ast_cache" cache = ASTCache(project_cache_dir) cache.cache_file(self.test_file) result = self.runner.invoke(cli, ['cache-stats']) assert result.exit_code == 0 assert "Total Files:" in result.output assert "1" in result.output def test_cache_stats_shows_cache_size(self): """RED: cache-stats should display total cache size.""" cache = ASTCache(self.cache_dir) cache.cache_file(self.test_file) result = self.runner.invoke(cli, ['cache-stats']) assert result.exit_code == 0 assert "Cache Size:" in result.output # Should show size in bytes, KB, MB, etc. assert any(unit in result.output for unit in ["bytes", "KB", "MB", "B"]) def test_cache_stats_command_works_with_empty_and_populated_cache(self): """cache-stats command works with both empty and populated cache states.""" result = self.runner.invoke(cli, ['cache-stats']) assert result.exit_code == 0 assert "Cache Directory:" in result.output assert "Total Files:" in result.output assert "Cache Size:" in result.output # Should work whether cache has 0 files or many files # ===== cache-clean command tests ===== def test_cache_clean_command_exists(self): """RED: cache-clean command should exist and be callable.""" result = self.runner.invoke(cli, ['cache-clean']) assert "No such command" not in result.output assert result.exit_code in [0, 1, 2] def test_cache_clean_command_provides_user_feedback_on_cleanup_results(self): """cache-clean command provides user feedback on cleanup results.""" result = self.runner.invoke(cli, ['cache-clean']) assert result.exit_code == 0 # Should provide informative message about what happened assert len(result.output.strip()) > 0 # Valid responses: cleaned files, already empty, or nothing to clean valid_responses = ["cleaned", "empty", "nothing to clean", "removed"] assert any(phrase in result.output.lower() for phrase in valid_responses) def test_cache_clean_provides_user_feedback(self): """cache-clean should provide clear feedback about results.""" result = self.runner.invoke(cli, ['cache-clean']) assert result.exit_code == 0 # Should give user clear information about what happened meaningful_responses = [ "cleaned", "cleared", "removed", "empty", "nothing to clean", "does not exist", "successfully" ] assert any(phrase in result.output.lower() for phrase in meaningful_responses) def test_cache_clean_with_empty_cache(self): """RED: cache-clean should handle empty cache gracefully.""" self.cache_dir.mkdir(exist_ok=True) result = self.runner.invoke(cli, ['cache-clean']) assert result.exit_code == 0 # Should not error on empty cache # ===== cache-invalidate command tests ===== def test_cache_invalidate_command_exists(self): """RED: cache-invalidate command should exist and require file argument.""" result = self.runner.invoke(cli, ['cache-invalidate']) assert "No such command" not in result.output # Should fail due to missing argument, not unknown command if result.exit_code != 0: assert "Missing argument" in result.output or "Usage:" in result.output def test_cache_invalidate_requires_file_argument(self): """RED: cache-invalidate should require a file argument.""" result = self.runner.invoke(cli, ['cache-invalidate']) assert result.exit_code != 0 assert any(phrase in result.output for phrase in ["Missing argument", "Usage:", "FILE"]) def test_cache_invalidate_accepts_file_argument(self): """cache-invalidate should accept file path and execute successfully.""" # Test with any file path - command should handle gracefully result = self.runner.invoke(cli, ['cache-invalidate', 'some-file.md']) assert result.exit_code == 0 # Should provide feedback about what happened assert len(result.output.strip()) > 0 def test_cache_invalidate_provides_meaningful_feedback(self): """cache-invalidate should provide clear feedback about results.""" result = self.runner.invoke(cli, ['cache-invalidate', 'example.md']) assert result.exit_code == 0 # Should explain what happened with the cache invalidation attempt meaningful_responses = [ "invalidated", "no cache found", "nothing to invalidate", "cache", "example.md" ] assert any(phrase in result.output.lower() for phrase in meaningful_responses) def test_cache_invalidate_with_nonexistent_file(self): """RED: cache-invalidate should handle non-existent files gracefully.""" nonexistent_file = Path(self.temp_dir) / "nonexistent.md" result = self.runner.invoke(cli, ['cache-invalidate', str(nonexistent_file)]) # Should handle gracefully - either succeed (no cache to remove) or show helpful message assert result.exit_code in [0, 1] if result.exit_code == 1: assert "not found" in result.output.lower() or "does not exist" in result.output.lower() def test_cache_invalidate_with_no_cache_for_file(self): """RED: cache-invalidate should handle files with no existing cache.""" # Create file but don't cache it uncached_file = Path(self.temp_dir) / "uncached.md" uncached_file.write_text("# Uncached content") result = self.runner.invoke(cli, ['cache-invalidate', str(uncached_file)]) # Should handle gracefully assert result.exit_code in [0, 1] if result.exit_code == 0: assert "no cache" in result.output.lower() or "not cached" in result.output.lower()