mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
- 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.
290 lines
9.5 KiB
Python
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()
|