Files
Claude-Code-Workflow/codex-lens/tests/test_cli_output.py
catlog22 4faa5f1c95 Add comprehensive tests for semantic chunking and search functionality
- Implemented tests for the ChunkConfig and Chunker classes, covering default and custom configurations.
- Added tests for symbol-based chunking, including single and multiple symbols, handling of empty symbols, and preservation of line numbers.
- Developed tests for sliding window chunking, ensuring correct chunking behavior with various content sizes and configurations.
- Created integration tests for semantic search, validating embedding generation, vector storage, and search accuracy across a complex codebase.
- Included performance tests for embedding generation and search operations.
- Established tests for chunking strategies, comparing symbol-based and sliding window approaches.
- Enhanced test coverage for edge cases, including handling of unicode characters and out-of-bounds symbol ranges.
2025-12-12 19:55:35 +08:00

281 lines
9.4 KiB
Python

"""Tests for CodexLens CLI output functions."""
import json
from dataclasses import dataclass
from io import StringIO
from pathlib import Path
from unittest.mock import patch
import pytest
from rich.console import Console
from codexlens.cli.output import (
_to_jsonable,
print_json,
render_file_inspect,
render_search_results,
render_status,
render_symbols,
)
from codexlens.entities import SearchResult, Symbol
class TestToJsonable:
"""Tests for _to_jsonable helper function."""
def test_none_value(self):
"""Test converting None."""
assert _to_jsonable(None) is None
def test_primitive_values(self):
"""Test converting primitive values."""
assert _to_jsonable("string") == "string"
assert _to_jsonable(42) == 42
assert _to_jsonable(3.14) == 3.14
assert _to_jsonable(True) is True
def test_path_conversion(self):
"""Test converting Path to string."""
path = Path("/test/file.py")
result = _to_jsonable(path)
assert result == str(path)
def test_dict_conversion(self):
"""Test converting dict with nested values."""
data = {"key": "value", "path": Path("/test.py"), "nested": {"a": 1}}
result = _to_jsonable(data)
assert result["key"] == "value"
# Path conversion uses str(), which may differ by OS
assert result["path"] == str(Path("/test.py"))
assert result["nested"]["a"] == 1
def test_list_conversion(self):
"""Test converting list with various items."""
data = ["string", 42, Path("/test.py")]
result = _to_jsonable(data)
assert result == ["string", 42, str(Path("/test.py"))]
def test_tuple_conversion(self):
"""Test converting tuple."""
data = ("a", "b", Path("/test.py"))
result = _to_jsonable(data)
assert result == ["a", "b", str(Path("/test.py"))]
def test_set_conversion(self):
"""Test converting set."""
data = {1, 2, 3}
result = _to_jsonable(data)
assert set(result) == {1, 2, 3}
def test_pydantic_model_conversion(self):
"""Test converting Pydantic model."""
symbol = Symbol(name="test", kind="function", range=(1, 5))
result = _to_jsonable(symbol)
assert result["name"] == "test"
assert result["kind"] == "function"
assert result["range"] == (1, 5)
def test_dataclass_conversion(self):
"""Test converting dataclass."""
@dataclass
class TestData:
name: str
value: int
data = TestData(name="test", value=42)
result = _to_jsonable(data)
assert result["name"] == "test"
assert result["value"] == 42
class TestPrintJson:
"""Tests for print_json function."""
def test_print_success_json(self, capsys):
"""Test printing success JSON."""
with patch("codexlens.cli.output.console") as mock_console:
captured_output = []
mock_console.print_json = lambda x: captured_output.append(x)
print_json(success=True, result={"key": "value"})
output = json.loads(captured_output[0])
assert output["success"] is True
assert output["result"]["key"] == "value"
def test_print_error_json(self, capsys):
"""Test printing error JSON."""
with patch("codexlens.cli.output.console") as mock_console:
captured_output = []
mock_console.print_json = lambda x: captured_output.append(x)
print_json(success=False, error="Something went wrong")
output = json.loads(captured_output[0])
assert output["success"] is False
assert output["error"] == "Something went wrong"
def test_print_error_default_message(self, capsys):
"""Test printing error with default message."""
with patch("codexlens.cli.output.console") as mock_console:
captured_output = []
mock_console.print_json = lambda x: captured_output.append(x)
print_json(success=False)
output = json.loads(captured_output[0])
assert output["error"] == "Unknown error"
class TestRenderSearchResults:
"""Tests for render_search_results function."""
def test_render_empty_results(self):
"""Test rendering empty results."""
with patch("codexlens.cli.output.console") as mock_console:
render_search_results([])
mock_console.print.assert_called_once()
def test_render_results_with_data(self):
"""Test rendering results with data."""
results = [
SearchResult(path="/test/a.py", score=0.95, excerpt="test excerpt"),
SearchResult(path="/test/b.py", score=0.85, excerpt="another excerpt"),
]
with patch("codexlens.cli.output.console") as mock_console:
render_search_results(results)
mock_console.print.assert_called_once()
def test_render_results_custom_title(self):
"""Test rendering results with custom title."""
results = [SearchResult(path="/test.py", score=0.5)]
with patch("codexlens.cli.output.console") as mock_console:
render_search_results(results, title="Custom Title")
mock_console.print.assert_called_once()
class TestRenderSymbols:
"""Tests for render_symbols function."""
def test_render_empty_symbols(self):
"""Test rendering empty symbols list."""
with patch("codexlens.cli.output.console") as mock_console:
render_symbols([])
mock_console.print.assert_called_once()
def test_render_symbols_with_data(self):
"""Test rendering symbols with data."""
symbols = [
Symbol(name="MyClass", kind="class", range=(1, 10)),
Symbol(name="my_func", kind="function", range=(12, 20)),
]
with patch("codexlens.cli.output.console") as mock_console:
render_symbols(symbols)
mock_console.print.assert_called_once()
def test_render_symbols_custom_title(self):
"""Test rendering symbols with custom title."""
symbols = [Symbol(name="test", kind="function", range=(1, 1))]
with patch("codexlens.cli.output.console") as mock_console:
render_symbols(symbols, title="Functions Found")
mock_console.print.assert_called_once()
class TestRenderStatus:
"""Tests for render_status function."""
def test_render_basic_stats(self):
"""Test rendering basic stats."""
stats = {"files": 100, "symbols": 500}
with patch("codexlens.cli.output.console") as mock_console:
render_status(stats)
mock_console.print.assert_called_once()
def test_render_stats_with_nested_dict(self):
"""Test rendering stats with nested dict."""
stats = {
"files": 100,
"languages": {"python": 50, "javascript": 30, "go": 20},
}
with patch("codexlens.cli.output.console") as mock_console:
render_status(stats)
mock_console.print.assert_called_once()
def test_render_stats_with_list(self):
"""Test rendering stats with list value."""
stats = {
"files": 100,
"recent_files": ["/a.py", "/b.py", "/c.py"],
}
with patch("codexlens.cli.output.console") as mock_console:
render_status(stats)
mock_console.print.assert_called_once()
class TestRenderFileInspect:
"""Tests for render_file_inspect function."""
def test_render_file_with_symbols(self):
"""Test rendering file inspection with symbols."""
symbols = [
Symbol(name="hello", kind="function", range=(1, 5)),
Symbol(name="MyClass", kind="class", range=(7, 20)),
]
with patch("codexlens.cli.output.console") as mock_console:
render_file_inspect("/test/file.py", "python", symbols)
# Should be called twice: once for header, once for symbols table
assert mock_console.print.call_count == 2
def test_render_file_without_symbols(self):
"""Test rendering file inspection without symbols."""
with patch("codexlens.cli.output.console") as mock_console:
render_file_inspect("/test/file.py", "python", [])
assert mock_console.print.call_count == 2
class TestJsonOutputIntegration:
"""Integration tests for JSON output."""
def test_search_result_to_json(self):
"""Test converting SearchResult to JSON."""
result = SearchResult(
path="/test.py",
score=0.95,
excerpt="test code here",
metadata={"line": 10},
)
jsonable = _to_jsonable(result)
# Verify it can be JSON serialized
json_str = json.dumps(jsonable)
parsed = json.loads(json_str)
assert parsed["path"] == "/test.py"
assert parsed["score"] == 0.95
assert parsed["excerpt"] == "test code here"
def test_nested_results_to_json(self):
"""Test converting nested structure to JSON."""
data = {
"query": "test",
"results": [
SearchResult(path="/a.py", score=0.9),
SearchResult(path="/b.py", score=0.8),
],
}
jsonable = _to_jsonable(data)
json_str = json.dumps(jsonable)
parsed = json.loads(json_str)
assert parsed["query"] == "test"
assert len(parsed["results"]) == 2