From e3aa485a4dd4087e704d2612ae25ed9a32764449 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Tue, 3 Feb 2026 01:25:51 +0000 Subject: [PATCH] Add test files --- tests/test_cli.py | 242 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 tests/test_cli.py diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..97df058 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,242 @@ +"""Tests for CLI commands.""" + +import pytest +from unittest.mock import Mock, patch +from click.testing import CliRunner + +from src.cli.commands import cli, index_command, search_command, list_command +from src.cli.interactive import run_interactive + + +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)) + + result = runner.invoke(cli, ["interactive"]) + + mock_run.assert_called_once()