243 lines
7.9 KiB
Python
243 lines
7.9 KiB
Python
"""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()
|