mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
feat(cli): 添加 --rule 选项支持模板自动发现
重构 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>
This commit is contained in:
210
codex-lens/tests/lsp/test_server.py
Normal file
210
codex-lens/tests/lsp/test_server.py
Normal file
@@ -0,0 +1,210 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user