Files
Claude-Code-Workflow/codex-lens/tests/test_static_graph_integration.py
catlog22 a512564b5a Add integration verification and validation phases, role templates, and static graph tests
- Implement Phase 4: Integration Verification to ensure skill package consistency.
- Implement Phase 5: Validation to verify quality and deliver the final skill package.
- Create role-template.md for generating per-role execution detail files.
- Create skill-router-template.md for generating SKILL.md with role-based routing.
- Add tests for static graph relationship writing during index build in test_static_graph_integration.py.
2026-02-13 12:35:31 +08:00

290 lines
9.5 KiB
Python

"""Tests for static graph relationship writing during index build (T2).
Verifies that IndexTreeBuilder._build_single_dir and _build_dir_worker
correctly write relationships to GlobalSymbolIndex when
config.static_graph_enabled is True.
"""
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from codexlens.config import Config
from codexlens.entities import (
CodeRelationship,
IndexedFile,
RelationshipType,
Symbol,
)
from codexlens.storage.global_index import GlobalSymbolIndex
@pytest.fixture()
def temp_dir():
tmpdir = tempfile.TemporaryDirectory(ignore_cleanup_errors=True)
yield Path(tmpdir.name)
try:
tmpdir.cleanup()
except (PermissionError, OSError):
pass
def _make_indexed_file(file_path: str) -> IndexedFile:
"""Create a test IndexedFile with symbols and relationships."""
return IndexedFile(
path=file_path,
language="python",
symbols=[
Symbol(name="MyClass", kind="class", range=(1, 20)),
Symbol(name="helper", kind="function", range=(22, 30)),
],
relationships=[
CodeRelationship(
source_symbol="MyClass",
target_symbol="BaseClass",
relationship_type=RelationshipType.INHERITS,
source_file=file_path,
target_file="other/base.py",
source_line=1,
),
CodeRelationship(
source_symbol="MyClass",
target_symbol="os",
relationship_type=RelationshipType.IMPORTS,
source_file=file_path,
source_line=2,
),
CodeRelationship(
source_symbol="helper",
target_symbol="external_func",
relationship_type=RelationshipType.CALL,
source_file=file_path,
source_line=25,
),
],
)
def test_build_single_dir_writes_global_relationships_when_enabled(temp_dir: Path) -> None:
"""When static_graph_enabled=True, relationships should be written to global index."""
from codexlens.storage.index_tree import IndexTreeBuilder
config = Config(
data_dir=temp_dir / "data",
static_graph_enabled=True,
static_graph_relationship_types=["imports", "inherits"],
global_symbol_index_enabled=True,
)
# Set up real GlobalSymbolIndex
global_db_path = temp_dir / "global_symbols.db"
global_index = GlobalSymbolIndex(global_db_path, project_id=1)
global_index.initialize()
# Create a source file
src_dir = temp_dir / "src"
src_dir.mkdir()
test_file = src_dir / "module.py"
test_file.write_text("class MyClass(BaseClass):\n pass\n", encoding="utf-8")
indexed_file = _make_indexed_file(str(test_file))
# Mock parser to return our test IndexedFile
mock_parser = MagicMock()
mock_parser.parse.return_value = indexed_file
mock_mapper = MagicMock()
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
mock_registry = MagicMock()
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
builder.parser_factory = MagicMock()
builder.parser_factory.get_parser.return_value = mock_parser
result = builder._build_single_dir(
src_dir,
languages=None,
project_id=1,
global_index_db_path=global_db_path,
)
assert result.error is None
assert result.files_count == 1
# Verify relationships were written to global index
# Only IMPORTS and INHERITS should be written (not CALL)
rels = global_index.query_by_target("BaseClass", prefix_mode=True)
rels += global_index.query_by_target("os", prefix_mode=True)
assert len(rels) >= 1, "Expected at least 1 relationship written to global index"
# CALL relationship for external_func should NOT be present
call_rels = global_index.query_by_target("external_func", prefix_mode=True)
assert len(call_rels) == 0, "CALL relationships should not be written"
global_index.close()
def test_build_single_dir_skips_relationships_when_disabled(temp_dir: Path) -> None:
"""When static_graph_enabled=False, no relationships should be written."""
from codexlens.storage.index_tree import IndexTreeBuilder
config = Config(
data_dir=temp_dir / "data",
static_graph_enabled=False,
global_symbol_index_enabled=True,
)
global_db_path = temp_dir / "global_symbols.db"
global_index = GlobalSymbolIndex(global_db_path, project_id=1)
global_index.initialize()
src_dir = temp_dir / "src"
src_dir.mkdir()
test_file = src_dir / "module.py"
test_file.write_text("import os\n", encoding="utf-8")
indexed_file = _make_indexed_file(str(test_file))
mock_parser = MagicMock()
mock_parser.parse.return_value = indexed_file
mock_mapper = MagicMock()
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
mock_registry = MagicMock()
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
builder.parser_factory = MagicMock()
builder.parser_factory.get_parser.return_value = mock_parser
result = builder._build_single_dir(
src_dir,
languages=None,
project_id=1,
global_index_db_path=global_db_path,
)
assert result.error is None
# No relationships should be in global index
conn = global_index._get_connection()
count = conn.execute("SELECT COUNT(*) FROM global_relationships").fetchone()[0]
assert count == 0, "No relationships should be written when static_graph_enabled=False"
global_index.close()
def test_relationship_write_failure_does_not_block_indexing(temp_dir: Path) -> None:
"""If global_index.update_file_relationships raises, file indexing continues."""
from codexlens.storage.index_tree import IndexTreeBuilder
config = Config(
data_dir=temp_dir / "data",
static_graph_enabled=True,
static_graph_relationship_types=["imports", "inherits"],
global_symbol_index_enabled=True,
)
src_dir = temp_dir / "src"
src_dir.mkdir()
test_file = src_dir / "module.py"
test_file.write_text("import os\n", encoding="utf-8")
indexed_file = _make_indexed_file(str(test_file))
mock_parser = MagicMock()
mock_parser.parse.return_value = indexed_file
mock_mapper = MagicMock()
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
mock_registry = MagicMock()
# Create a mock GlobalSymbolIndex that fails on update_file_relationships
mock_global_db_path = temp_dir / "global_symbols.db"
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
builder.parser_factory = MagicMock()
builder.parser_factory.get_parser.return_value = mock_parser
# Patch GlobalSymbolIndex so update_file_relationships raises
with patch("codexlens.storage.index_tree.GlobalSymbolIndex") as MockGSI:
mock_gsi_instance = MagicMock()
mock_gsi_instance.update_file_relationships.side_effect = RuntimeError("DB locked")
MockGSI.return_value = mock_gsi_instance
result = builder._build_single_dir(
src_dir,
languages=None,
project_id=1,
global_index_db_path=mock_global_db_path,
)
# File should still be indexed despite relationship write failure
assert result.error is None
assert result.files_count == 1
def test_only_configured_relationship_types_written(temp_dir: Path) -> None:
"""Only relationship types in static_graph_relationship_types should be written."""
from codexlens.storage.index_tree import IndexTreeBuilder
# Only allow 'imports' (not 'inherits')
config = Config(
data_dir=temp_dir / "data",
static_graph_enabled=True,
static_graph_relationship_types=["imports"],
global_symbol_index_enabled=True,
)
global_db_path = temp_dir / "global_symbols.db"
global_index = GlobalSymbolIndex(global_db_path, project_id=1)
global_index.initialize()
src_dir = temp_dir / "src"
src_dir.mkdir()
test_file = src_dir / "module.py"
test_file.write_text("import os\nclass Foo(Bar): pass\n", encoding="utf-8")
indexed_file = _make_indexed_file(str(test_file))
mock_parser = MagicMock()
mock_parser.parse.return_value = indexed_file
mock_mapper = MagicMock()
mock_mapper.source_to_index_db.return_value = temp_dir / "index" / "_index.db"
mock_registry = MagicMock()
builder = IndexTreeBuilder(mock_registry, mock_mapper, config=config, incremental=False)
builder.parser_factory = MagicMock()
builder.parser_factory.get_parser.return_value = mock_parser
result = builder._build_single_dir(
src_dir,
languages=None,
project_id=1,
global_index_db_path=global_db_path,
)
assert result.error is None
# Only IMPORTS should be written
conn = global_index._get_connection()
rows = conn.execute(
"SELECT relationship_type FROM global_relationships"
).fetchall()
rel_types = {row[0] for row in rows}
assert "imports" in rel_types or len(rows) == 0 or rel_types == {"imports"}, \
f"Expected only 'imports', got {rel_types}"
# INHERITS should NOT be present
assert "inherits" not in rel_types, "inherits should not be written when not in config"
# CALL should NOT be present
assert "calls" not in rel_types, "calls should not be written"
global_index.close()