mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-05 01:50:27 +08:00
重构 ccw cli 模板系统: - 新增 template-discovery.ts 模块,支持扁平化模板自动发现 - 添加 --rule <template> 选项,自动加载 protocol 和 template - 模板目录从嵌套结构 (prompts/category/file.txt) 迁移到扁平结构 (prompts/category-function.txt) - 更新所有 agent/command 文件,使用 $PROTO $TMPL 环境变量替代 $(cat ...) 模式 - 支持模糊匹配:--rule 02-review-architecture 可匹配 analysis-review-architecture.txt 其他更新: - Dashboard: 添加 Claude Manager 和 Issue Manager 页面 - Codex-lens: 增强 chain_search 和 clustering 模块 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
211 lines
6.5 KiB
Python
211 lines
6.5 KiB
Python
"""Tests for codex-lens LSP server."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from codexlens.entities import Symbol
|
|
|
|
|
|
class TestCodexLensLanguageServer:
|
|
"""Tests for CodexLensLanguageServer."""
|
|
|
|
def test_server_import(self):
|
|
"""Test that server module can be imported."""
|
|
pytest.importorskip("pygls")
|
|
pytest.importorskip("lsprotocol")
|
|
|
|
from codexlens.lsp.server import CodexLensLanguageServer, server
|
|
|
|
assert CodexLensLanguageServer is not None
|
|
assert server is not None
|
|
assert server.name == "codexlens-lsp"
|
|
|
|
def test_server_initialization(self):
|
|
"""Test server instance creation."""
|
|
pytest.importorskip("pygls")
|
|
|
|
from codexlens.lsp.server import CodexLensLanguageServer
|
|
|
|
ls = CodexLensLanguageServer()
|
|
assert ls.registry is None
|
|
assert ls.mapper is None
|
|
assert ls.global_index is None
|
|
assert ls.search_engine is None
|
|
assert ls.workspace_root is None
|
|
|
|
|
|
class TestDefinitionHandler:
|
|
"""Tests for definition handler."""
|
|
|
|
def test_definition_lookup(self):
|
|
"""Test definition lookup returns location for known symbol."""
|
|
pytest.importorskip("pygls")
|
|
pytest.importorskip("lsprotocol")
|
|
|
|
from lsprotocol import types as lsp
|
|
from codexlens.lsp.handlers import symbol_to_location
|
|
|
|
symbol = Symbol(
|
|
name="test_function",
|
|
kind="function",
|
|
range=(10, 15),
|
|
file="/path/to/file.py",
|
|
)
|
|
|
|
location = symbol_to_location(symbol)
|
|
|
|
assert location is not None
|
|
assert isinstance(location, lsp.Location)
|
|
# LSP uses 0-based lines
|
|
assert location.range.start.line == 9
|
|
assert location.range.end.line == 14
|
|
|
|
def test_definition_no_file(self):
|
|
"""Test definition lookup returns None for symbol without file."""
|
|
pytest.importorskip("pygls")
|
|
|
|
from codexlens.lsp.handlers import symbol_to_location
|
|
|
|
symbol = Symbol(
|
|
name="test_function",
|
|
kind="function",
|
|
range=(10, 15),
|
|
file=None,
|
|
)
|
|
|
|
location = symbol_to_location(symbol)
|
|
assert location is None
|
|
|
|
|
|
class TestCompletionHandler:
|
|
"""Tests for completion handler."""
|
|
|
|
def test_get_prefix_at_position(self):
|
|
"""Test extracting prefix at cursor position."""
|
|
pytest.importorskip("pygls")
|
|
|
|
from codexlens.lsp.handlers import _get_prefix_at_position
|
|
|
|
document_text = "def hello_world():\n print(hel"
|
|
|
|
# Cursor at end of "hel"
|
|
prefix = _get_prefix_at_position(document_text, 1, 14)
|
|
assert prefix == "hel"
|
|
|
|
# Cursor at beginning of line (after whitespace)
|
|
prefix = _get_prefix_at_position(document_text, 1, 4)
|
|
assert prefix == ""
|
|
|
|
# Cursor after "he" in "hello_world" - returns text before cursor
|
|
prefix = _get_prefix_at_position(document_text, 0, 6)
|
|
assert prefix == "he"
|
|
|
|
# Cursor at end of "hello_world"
|
|
prefix = _get_prefix_at_position(document_text, 0, 15)
|
|
assert prefix == "hello_world"
|
|
|
|
def test_get_word_at_position(self):
|
|
"""Test extracting word at cursor position."""
|
|
pytest.importorskip("pygls")
|
|
|
|
from codexlens.lsp.handlers import _get_word_at_position
|
|
|
|
document_text = "def hello_world():\n print(msg)"
|
|
|
|
# Cursor on "hello_world"
|
|
word = _get_word_at_position(document_text, 0, 6)
|
|
assert word == "hello_world"
|
|
|
|
# Cursor on "print"
|
|
word = _get_word_at_position(document_text, 1, 6)
|
|
assert word == "print"
|
|
|
|
# Cursor on "msg"
|
|
word = _get_word_at_position(document_text, 1, 11)
|
|
assert word == "msg"
|
|
|
|
def test_symbol_kind_mapping(self):
|
|
"""Test symbol kind to completion kind mapping."""
|
|
pytest.importorskip("pygls")
|
|
pytest.importorskip("lsprotocol")
|
|
|
|
from lsprotocol import types as lsp
|
|
from codexlens.lsp.handlers import _symbol_kind_to_completion_kind
|
|
|
|
assert _symbol_kind_to_completion_kind("function") == lsp.CompletionItemKind.Function
|
|
assert _symbol_kind_to_completion_kind("class") == lsp.CompletionItemKind.Class
|
|
assert _symbol_kind_to_completion_kind("method") == lsp.CompletionItemKind.Method
|
|
assert _symbol_kind_to_completion_kind("variable") == lsp.CompletionItemKind.Variable
|
|
|
|
# Unknown kind should default to Text
|
|
assert _symbol_kind_to_completion_kind("unknown") == lsp.CompletionItemKind.Text
|
|
|
|
|
|
class TestWorkspaceSymbolHandler:
|
|
"""Tests for workspace symbol handler."""
|
|
|
|
def test_symbol_kind_to_lsp(self):
|
|
"""Test symbol kind to LSP SymbolKind mapping."""
|
|
pytest.importorskip("pygls")
|
|
pytest.importorskip("lsprotocol")
|
|
|
|
from lsprotocol import types as lsp
|
|
from codexlens.lsp.handlers import _symbol_kind_to_lsp
|
|
|
|
assert _symbol_kind_to_lsp("function") == lsp.SymbolKind.Function
|
|
assert _symbol_kind_to_lsp("class") == lsp.SymbolKind.Class
|
|
assert _symbol_kind_to_lsp("method") == lsp.SymbolKind.Method
|
|
assert _symbol_kind_to_lsp("interface") == lsp.SymbolKind.Interface
|
|
|
|
# Unknown kind should default to Variable
|
|
assert _symbol_kind_to_lsp("unknown") == lsp.SymbolKind.Variable
|
|
|
|
|
|
class TestUriConversion:
|
|
"""Tests for URI path conversion."""
|
|
|
|
def test_path_to_uri(self):
|
|
"""Test path to URI conversion."""
|
|
pytest.importorskip("pygls")
|
|
|
|
from codexlens.lsp.handlers import _path_to_uri
|
|
|
|
# Unix path
|
|
uri = _path_to_uri("/home/user/file.py")
|
|
assert uri.startswith("file://")
|
|
assert "file.py" in uri
|
|
|
|
def test_uri_to_path(self):
|
|
"""Test URI to path conversion."""
|
|
pytest.importorskip("pygls")
|
|
|
|
from codexlens.lsp.handlers import _uri_to_path
|
|
|
|
# Basic URI
|
|
path = _uri_to_path("file:///home/user/file.py")
|
|
assert path.name == "file.py"
|
|
|
|
|
|
class TestMainEntryPoint:
|
|
"""Tests for main entry point."""
|
|
|
|
def test_main_help(self):
|
|
"""Test that main shows help without errors."""
|
|
pytest.importorskip("pygls")
|
|
|
|
import sys
|
|
from unittest.mock import patch
|
|
|
|
# Patch sys.argv to show help
|
|
with patch.object(sys, 'argv', ['codexlens-lsp', '--help']):
|
|
from codexlens.lsp.server import main
|
|
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
main()
|
|
|
|
# Help exits with 0
|
|
assert exc_info.value.code == 0
|