refactor: restructure to core/ + ui/ with multi-threaded backend

This commit is contained in:
root
2026-04-12 18:15:55 +08:00
parent c13d3f671c
commit 83906a6985
64 changed files with 5269 additions and 571 deletions

View File

@ -1,15 +1,12 @@
"""测试配置"""
import pytest
from datetime import datetime
from graph_memory_tui.models.message import Message, ToolCall, ToolResult
from graph_memory_tui.models.config import AppConfig
from graph_memory_tui.models.log_entry import LogEntry
from ui.models.message import Message, ToolCall, ToolResult
from ui.models.config import AppConfig
from ui.models.log_entry import LogEntry
@pytest.fixture
def sample_config():
"""示例配置"""
return AppConfig(
api_key="test-api-key",
model="test-model",
@ -19,7 +16,6 @@ def sample_config():
@pytest.fixture
def sample_message():
"""示例消息"""
return Message(
role="user",
content="测试消息",
@ -29,7 +25,6 @@ def sample_message():
@pytest.fixture
def sample_tool_call():
"""示例工具调用"""
return ToolCall(
id="test-call-id",
name="memory_recall",
@ -39,7 +34,6 @@ def sample_tool_call():
@pytest.fixture
def sample_tool_result():
"""示例工具结果"""
return ToolResult(
tool_call_id="test-call-id",
name="memory_recall",
@ -51,11 +45,10 @@ def sample_tool_result():
@pytest.fixture
def sample_log_entry():
"""示例日志条目"""
return LogEntry(
timestamp=datetime.now(),
tool_name="memory_recall",
arguments={"query_intent": "测试查询"},
result="测试结果",
duration=0.5
)
)

View File

@ -3,7 +3,7 @@
import pytest
import tempfile
import os
from graph_memory_tui.core.embedded_db import EmbeddedGraphDB
from core import EmbeddedGraphDB
@pytest.fixture

View File

@ -1,50 +1,32 @@
"""核心逻辑导入测试"""
import pytest
def test_import_neo4j_graph():
"""测试 Neo4jGraph 类导入"""
from graph_memory_tui.core.imports import Neo4jGraph
assert Neo4jGraph is not None
assert hasattr(Neo4jGraph, 'recall')
assert hasattr(Neo4jGraph, 'commit')
assert hasattr(Neo4jGraph, 'purge')
def test_import_backend_server():
from core import BackendServer
assert BackendServer is not None
def test_import_graph_memory_client():
"""测试 GraphMemoryClient 类导入"""
from graph_memory_tui.core.imports import GraphMemoryClient
def test_import_backend_client():
from core import BackendClient
assert BackendClient is not None
def test_import_embedded_db():
from core import EmbeddedGraphDB
assert EmbeddedGraphDB is not None
def test_import_graph_client():
from core.graph_client import GraphMemoryClient
assert GraphMemoryClient is not None
assert hasattr(GraphMemoryClient, 'send_message')
def test_import_tools():
"""测试 TOOLS 定义导入"""
from graph_memory_tui.core.imports import TOOLS
assert TOOLS is not None
assert isinstance(TOOLS, list)
assert len(TOOLS) > 0
assert any(t['function']['name'] == 'memory_recall' for t in TOOLS)
def test_import_tool_limiter():
from core.tool_limiter import ToolLimiter
assert ToolLimiter is not None
def test_import_execute_tool():
"""测试 execute_tool 函数导入"""
from graph_memory_tui.core.imports import execute_tool
def test_import_tool_executor():
from core.tool_executor import execute_tool
assert execute_tool is not None
assert callable(execute_tool)
def test_import_config_vars():
"""测试配置变量导入"""
from graph_memory_tui.core.imports import (
DEEPSEEK_API_KEY,
DEEPSEEK_BASE_URL,
MODEL_NAME,
NEO4J_URI,
NEO4J_USER,
NEO4J_PASSWORD,
)
assert DEEPSEEK_BASE_URL is not None
assert MODEL_NAME is not None
assert NEO4J_URI is not None
assert callable(execute_tool)

View File

@ -1,138 +0,0 @@
"""记忆工具测试"""
import pytest
from graph_memory_tui.core.tools.memory_tools import (
MEMORY_TOOLS,
PERSONA_TOOLS,
WORKING_MEMORY_TOOLS,
TOOLS
)
def test_memory_tools_exist():
"""测试记忆工具存在"""
assert len(MEMORY_TOOLS) >= 6
def test_persona_tools_exist():
"""测试人设工具存在"""
assert len(PERSONA_TOOLS) >= 2
def test_working_memory_tools_exist():
"""测试工作记忆工具存在"""
assert len(WORKING_MEMORY_TOOLS) >= 4
def test_all_tools_combined():
"""测试工具合并"""
assert len(TOOLS) == len(MEMORY_TOOLS) + len(PERSONA_TOOLS) + len(WORKING_MEMORY_TOOLS)
def test_memory_recall_tool():
"""测试 memory_recall 工具定义"""
recall = next((t for t in TOOLS if t['function']['name'] == 'memory_recall'), None)
assert recall is not None
params = recall['function']['parameters']['properties']
assert 'query_intent' in params
assert 'seed_entities' in params
assert 'depth' in params
def test_memory_commit_tool():
"""测试 memory_commit 工具定义"""
commit = next((t for t in TOOLS if t['function']['name'] == 'memory_commit'), None)
assert commit is not None
params = commit['function']['parameters']['properties']
assert 'triplets' in params
def test_memory_purge_tool():
"""测试 memory_purge 工具定义"""
purge = next((t for t in TOOLS if t['function']['name'] == 'memory_purge'), None)
assert purge is not None
params = purge['function']['parameters']['properties']
assert 'criteria' in params
assert 'mode' in params
def test_memory_introspect_tool():
"""测试 memory_introspect 工具定义"""
introspect = next((t for t in TOOLS if t['function']['name'] == 'memory_introspect'), None)
assert introspect is not None
def test_persona_update_tool():
"""测试 persona_update 工具定义"""
update = next((t for t in TOOLS if t['function']['name'] == 'persona_update'), None)
assert update is not None
params = update['function']['parameters']['properties']
assert 'attributes' in params
def test_persona_clear_tool():
"""测试 persona_clear 工具定义"""
clear = next((t for t in TOOLS if t['function']['name'] == 'persona_clear'), None)
assert clear is not None
def test_task_create_tool():
"""测试 task_create 工具定义"""
create = next((t for t in TOOLS if t['function']['name'] == 'task_create'), None)
assert create is not None
params = create['function']['parameters']['properties']
assert 'task_id' in params
assert 'description' in params
def test_task_set_state_tool():
"""测试 task_set_state 工具定义"""
set_state = next((t for t in TOOLS if t['function']['name'] == 'task_set_state'), None)
assert set_state is not None
params = set_state['function']['parameters']['properties']
assert 'task_id' in params
assert 'state' in params
def test_task_delete_tool():
"""测试 task_delete 工具定义"""
delete = next((t for t in TOOLS if t['function']['name'] == 'task_delete'), None)
assert delete is not None
def test_task_link_info_tool():
"""测试 task_link_info 工具定义"""
link = next((t for t in TOOLS if t['function']['name'] == 'task_link_info'), None)
assert link is not None
params = link['function']['parameters']['properties']
assert 'task_id' in params
assert 'info_node_names' in params
def test_tool_has_required_fields():
"""测试工具都有必需字段"""
for tool in TOOLS:
assert 'type' in tool
assert tool['type'] == 'function'
assert 'function' in tool
assert 'name' in tool['function']
assert 'description' in tool['function']
assert 'parameters' in tool['function']
def test_tool_state_enum():
"""测试 task_set_state 的状态枚举"""
set_state = next((t for t in TOOLS if t['function']['name'] == 'task_set_state'), None)
state_enum = set_state['function']['parameters']['properties']['state']['enum']
assert '进行中' in state_enum
assert '已完成' in state_enum
assert '已暂停' in state_enum
assert '已取消' in state_enum

View File

@ -0,0 +1,57 @@
import pytest
import os
import tempfile
os.environ["DEEPSEEK_API_KEY"] = "fake-test-key"
class TestCoreImport:
def test_import_backend_server(self):
from core import BackendServer
assert BackendServer is not None
def test_import_backend_client(self):
from core import BackendClient
assert BackendClient is not None
def test_import_embedded_db(self):
from core import EmbeddedGraphDB
assert EmbeddedGraphDB is not None
class TestBackendServer:
def test_create_server(self):
from core import BackendServer
server = BackendServer(db_path=":memory:", use_embedded_db=True)
assert server is not None
assert server._running is False
def test_start_server(self):
from core import BackendServer
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
db_path = f.name
try:
server = BackendServer(db_path=db_path, use_embedded_db=True)
server.start(api_key="", base_url="https://api.test.com")
assert server._running is True
assert server._graph is not None
server.shutdown()
finally:
if os.path.exists(db_path):
os.unlink(db_path)
def test_shutdown(self):
from core import BackendServer
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
db_path = f.name
server = BackendServer(db_path=db_path, use_embedded_db=True)
server.start(api_key="", base_url="https://api.test.com")
assert server._running is True
server.shutdown()
assert server._running is False

View File

@ -1,148 +0,0 @@
"""工具限制器测试"""
import pytest
from graph_memory_tui.core.tools.tool_limiter import (
ToolLimiter,
ToolLimits,
ToolCallCount
)
@pytest.fixture
def limiter():
"""创建限制器实例"""
return ToolLimiter()
def test_classify_persona_tools(limiter):
"""测试人设工具分类"""
category, operation = limiter._classify_tool('persona_update', {})
assert category == 'persona'
assert operation == 'update'
category, operation = limiter._classify_tool('persona_clear', {})
assert category == 'persona'
assert operation == 'update'
def test_classify_task_tools(limiter):
"""测试任务工具分类"""
category, operation = limiter._classify_tool('task_create', {})
assert category == 'task'
assert operation == 'update'
category, operation = limiter._classify_tool('task_set_state', {})
assert category == 'task'
assert operation == 'update'
category, operation = limiter._classify_tool('task_delete', {})
assert category == 'task'
assert operation == 'update'
category, operation = limiter._classify_tool('task_link_info', {})
assert category == 'task'
assert operation == 'update'
def test_classify_memory_recall(limiter):
"""测试 memory_recall 分类"""
category, operation = limiter._classify_tool('memory_recall', {'query_intent': 'Python'})
assert category == 'memory'
assert operation == 'query'
def test_classify_memory_recall_persona_query(limiter):
"""测试 memory_recall 查询人设图"""
category, operation = limiter._classify_tool(
'memory_recall',
{'query_intent': 'AI,人设,角色'}
)
assert category == 'persona'
assert operation == 'query'
def test_classify_memory_recall_task_query(limiter):
"""测试 memory_recall 查询工作记忆链"""
category, operation = limiter._classify_tool(
'memory_recall',
{'query_intent': 'TaskNode,工作记忆'}
)
assert category == 'task'
assert operation == 'query'
def test_can_call_allowed(limiter):
"""测试允许调用"""
allowed, reason = limiter.can_call('memory_recall', {'query_intent': 'test'})
assert allowed is True
def test_can_call_limit_reached(limiter):
"""测试达到限制"""
for _ in range(20):
limiter.record_call('memory_recall', {'query_intent': 'test'})
allowed, reason = limiter.can_call('memory_recall', {'query_intent': 'test'})
assert allowed is False
assert '上限' in reason
def test_record_call(limiter):
"""测试记录调用"""
initial_count = limiter.counts.memory_query
limiter.record_call('memory_recall', {'query_intent': 'test'})
assert limiter.counts.memory_query == initial_count + 1
def test_reset(limiter):
"""测试重置计数"""
limiter.record_call('memory_recall', {'query_intent': 'test'})
limiter.record_call('memory_recall', {'query_intent': 'test'})
limiter.reset()
assert limiter.counts.memory_query == 0
assert limiter.counts.memory_update == 0
def test_get_summary(limiter):
"""测试获取统计摘要"""
limiter.record_call('memory_recall', {'query_intent': 'test'})
summary = limiter.get_summary()
assert isinstance(summary, str)
assert '一般记忆' in summary
assert '查询1' in summary
def test_custom_limits():
"""测试自定义限制"""
limits = ToolLimits(
memory_query_max=5,
memory_update_max=3
)
limiter = ToolLimiter(limits)
assert limiter.limits.memory_query_max == 5
assert limiter.limits.memory_update_max == 3
def test_persona_query_limit(limiter):
"""测试人设图查询限制"""
for _ in range(1):
limiter.record_call('memory_recall', {'query_intent': '人设'})
allowed, _ = limiter.can_call('memory_recall', {'query_intent': '人设'})
assert allowed is False
def test_task_query_limit(limiter):
"""测试工作记忆链查询限制"""
for _ in range(4):
limiter.record_call('memory_recall', {'query_intent': 'TaskNode'})
allowed, _ = limiter.can_call('memory_recall', {'query_intent': 'TaskNode'})
assert allowed is False

View File

@ -1 +0,0 @@
"""Tests for Event Handlers"""

View File

@ -1,33 +0,0 @@
"""焦点处理器测试"""
import pytest
from graph_memory_tui.handlers.focus_handler import FocusHandler
def test_focus_handler_creation():
"""测试焦点处理器创建"""
handler = FocusHandler()
assert handler is not None
assert handler._current_index == 0
def test_focus_ring():
"""测试焦点循环"""
handler = FocusHandler()
assert len(handler.FOCUS_RING) == 5
assert "input-textarea" in handler.FOCUS_RING
assert "cypher-textarea" in handler.FOCUS_RING
def test_get_current_focus_name():
"""测试获取当前焦点名称"""
handler = FocusHandler()
name = handler.get_current_focus_name()
assert name == "Input"
def test_focus_names_mapping():
"""测试焦点名称映射"""
handler = FocusHandler()
assert handler.FOCUS_NAMES["input-textarea"] == "Input"
assert handler.FOCUS_NAMES["cypher-textarea"] == "Query"

View File

@ -2,9 +2,9 @@
import pytest
from datetime import datetime
from graph_memory_tui.models.message import Message, ToolCall, ToolResult
from graph_memory_tui.models.config import AppConfig
from graph_memory_tui.models.log_entry import LogEntry
from ui.models.message import Message, ToolCall, ToolResult
from ui.models.config import AppConfig
from ui.models.log_entry import LogEntry
def test_message_creation(sample_message):

View File

@ -1 +0,0 @@
"""Tests for Business Services"""

70
tests/test_ui.py Normal file
View File

@ -0,0 +1,70 @@
import pytest
import os
import tempfile
os.environ["DEEPSEEK_API_KEY"] = "fake-test-key"
class TestUIImport:
def test_import_app(self):
from ui import GraphMemoryApp
assert GraphMemoryApp is not None
def test_import_config(self):
from ui import AppConfig
assert AppConfig is not None
class TestUIApp:
def test_create_app(self):
from ui import GraphMemoryApp
app = GraphMemoryApp()
assert app is not None
assert app._backend_server is None
assert app._backend_client is None
def test_create_app_with_config(self):
from ui import GraphMemoryApp, AppConfig
config = AppConfig(api_key="test-key", base_url="https://api.test.com")
app = GraphMemoryApp(config=config)
assert app._config is config
assert app._config.api_key == "test-key"
def test_create_app_with_backend(self):
from ui import GraphMemoryApp
from core import BackendServer
with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
db_path = f.name
try:
server = BackendServer(db_path=db_path, use_embedded_db=True)
server.start(api_key="", base_url="https://api.test.com")
app = GraphMemoryApp(backend_server=server)
assert app._backend_server is server
assert app._backend_client is not None
server.shutdown()
finally:
if os.path.exists(db_path):
os.unlink(db_path)
class TestAppConfig:
def test_config_from_env(self):
from ui import AppConfig
config = AppConfig.from_env()
assert config is not None
def test_config_default_values(self):
from ui import AppConfig
config = AppConfig()
assert config.api_key == ""
assert config.model == "deepseek-chat"
assert config.base_url == "https://api.deepseek.com"

View File

@ -1 +0,0 @@
"""Tests for UI Widgets"""