"""Tests for CLI commands.""" import pytest from unittest.mock import Mock, patch from click.testing import CliRunner from src.cli.commands import cli class TestCLIBasics: """Basic CLI tests.""" @pytest.fixture def runner(self): """Create a CLI runner.""" return CliRunner() def test_cli_help(self, runner): """Test CLI help output.""" result = runner.invoke(cli, ["--help"]) assert result.exit_code == 0 assert "Local API Docs Search" in result.output def test_cli_verbose_flag(self, runner): """Test verbose flag parsing.""" with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_searcher.get_stats.return_value = Mock(total_documents=0) MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["--verbose", "list"]) assert result.exit_code == 0 class TestIndexCommand: """Tests for the index command.""" @pytest.fixture def runner(self): """Create a CLI runner.""" return CliRunner() def test_index_requires_path(self, runner): """Test that index command requires a path.""" result = runner.invoke(cli, ["index"]) assert result.exit_code != 0 assert "Missing argument" in result.output or "path" in result.output.lower() def test_index_invalid_path(self, runner): """Test index with non-existent path.""" result = runner.invoke(cli, ["index", "/nonexistent/path"]) assert result.exit_code != 0 def test_index_type_option(self, runner, tmp_path): """Test index with type option.""" openapi_file = tmp_path / "test.yaml" openapi_file.write_text("openapi: '3.0.0'\ninfo:\n title: Test\n version: '1.0'\npaths: {}") with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_searcher.index.return_value = 1 MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["index", str(tmp_path), "--type", "openapi"]) assert result.exit_code == 0 mock_searcher.index.assert_called_once() def test_index_recursive_option(self, runner, tmp_path): """Test index with recursive option.""" subdir = tmp_path / "subdir" subdir.mkdir() openapi_file = subdir / "test.yaml" openapi_file.write_text("openapi: '3.0.0'\ninfo:\n title: Test\n version: '1.0'\npaths: {}") with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_searcher.index.return_value = 1 MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["index", str(tmp_path), "--recursive"]) assert result.exit_code == 0 class TestSearchCommand: """Tests for the search command.""" @pytest.fixture def runner(self): """Create a CLI runner.""" return CliRunner() def test_search_requires_query(self, runner): """Test that search requires a query.""" result = runner.invoke(cli, ["search"]) assert result.exit_code != 0 assert "Missing argument" in result.output def test_search_limit_option(self, runner): """Test search with limit option.""" with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_searcher.hybrid_search.return_value = [] MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["search", "test", "--limit", "5"]) assert result.exit_code == 0 mock_searcher.hybrid_search.assert_called_once() def test_search_type_filter(self, runner): """Test search with type filter.""" with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_searcher.hybrid_search.return_value = [] MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["search", "test", "--type", "openapi"]) assert result.exit_code == 0 def test_search_json_output(self, runner): """Test search with JSON output.""" from src.models.document import Document, SourceType, SearchResult with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_doc = Document( id="test", content="test content", source_type=SourceType.OPENAPI, title="Test", ) mock_result = SearchResult(document=mock_doc, score=0.9) mock_searcher.hybrid_search.return_value = [mock_result] MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["search", "test", "--json"]) assert result.exit_code == 0 assert "test" in result.output or '"' in result.output class TestListCommand: """Tests for the list command.""" @pytest.fixture def runner(self): """Create a CLI runner.""" return CliRunner() def test_list_command(self, runner): """Test list command.""" from src.models.document import IndexStats with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_stats = IndexStats( total_documents=10, openapi_count=5, readme_count=3, code_count=2, ) mock_searcher.get_stats.return_value = mock_stats MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["list"]) assert result.exit_code == 0 assert "10" in result.output or "total" in result.output.lower() def test_list_with_type_filter(self, runner): """Test list with type filter.""" with patch("src.cli.commands.Searcher") as MockSearcher: mock_searcher = Mock() mock_searcher.get_stats.return_value = Mock(total_documents=5) MockSearcher.return_value = mock_searcher result = runner.invoke(cli, ["list", "--type", "openapi"]) assert result.exit_code == 0 class TestConfigCommand: """Tests for the config command.""" @pytest.fixture def runner(self): """Create a CLI runner.""" return CliRunner() def test_config_show(self, runner): """Test config show command.""" with patch("src.cli.commands.get_config") as mock_get_config: mock_config = Mock() mock_config.to_dict.return_value = { "index_path": "./docs", "model_name": "test-model", } mock_get_config.return_value = mock_config result = runner.invoke(cli, ["config", "--show"]) assert result.exit_code == 0 assert "./docs" in result.output or "test-model" in result.output def test_config_reset(self, runner): """Test config reset command.""" with patch("src.cli.commands.get_config") as mock_get_config: mock_config = Mock() mock_get_config.return_value = mock_config result = runner.invoke(cli, ["config", "--reset"]) assert result.exit_code == 0 mock_config.reset.assert_called_once() class TestInteractiveCommand: """Tests for the interactive command.""" @pytest.fixture def runner(self): """Create a CLI runner.""" return CliRunner() def test_interactive_command(self, runner): """Test interactive command.""" with patch("src.cli.interactive.run_interactive") as mock_run: mock_run.side_effect = (KeyboardInterrupt, SystemExit(0)) runner.invoke(cli, ["interactive"]) mock_run.assert_called_once()