Files
xiaoli-cli/tests/conftest.py
T
Xiaoli Dev ff53a89754 feat: 重写沙箱 + 完整测试套件 (192 tests)
沙箱 v2 - 多层隔离:
- AST 静态分析: 30+ 危险模式检测
- 子进程隔离: 独立进程执行 + 资源限制
- 网络阻断: socket 模块替换
- 模块白名单: 30 个安全模块
- 文件系统限制: 临时目录隔离
- SandboxResult 数据类 + 执行历史

测试套件 (8 个模块, 192 个用例):
- test_sandbox.py:     AST检查(47) + 沙箱执行(15) + 安全拒绝(20) + 超时(2) + 模式对比(3)
- test_conversation.py: 响应解析(13) + 思考标记(5) + 工具提取(10)
- test_config.py:       配置加载/保存/缓存/损坏处理(8)
- test_history.py:      历史CRUD/持久化/边界(12)
- test_engines.py:      引擎管理/加载/切换(14)
- test_plugins.py:      code_editor(10) + code_search(7) + git_tools(4) + tool_manager(7)
- test_integration.py:  沙箱+对话(2) + 历史+对话(2) + 安全链(3) + 提示词(1)
2026-04-25 11:43:47 +08:00

184 lines
4.8 KiB
Python

"""共享测试 fixtures"""
import os
import sys
import json
import tempfile
import shutil
import pytest
# 确保项目根目录在 sys.path 中
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, PROJECT_ROOT)
@pytest.fixture
def tmp_dir():
"""创建临时目录,测试后清理"""
d = tempfile.mkdtemp(prefix='xiaoli_test_')
yield d
shutil.rmtree(d, ignore_errors=True)
@pytest.fixture
def sample_python_code():
"""安全的示例 Python 代码"""
return '''
import json
import math
result = math.sqrt(144)
data = {"value": result, "squared": result ** 2}
print(json.dumps(data))
'''
@pytest.fixture
def dangerous_codes():
"""各种危险代码样本"""
return {
'os_import': 'import os\nos.system("rm -rf /")',
'subprocess': 'import subprocess\nsubprocess.run(["ls"])',
'exec_call': 'exec("import os")',
'eval_call': 'eval("__import__(\'os\')")',
'dunder': 'x = (1).__class__.__bases__[0].__subclasses__()',
'open_file': 'f = open("/etc/passwd")',
'getattr_bypass': 'getattr(__builtins__, "exec")',
'socket': 'import socket\ns = socket.socket()',
'shutil': 'import shutil\nshutil.rmtree("/")',
'compile': 'compile("import os", "<string>", "exec")',
'globals': 'globals()["__builtins__"]',
'breakpoint': 'breakpoint()',
'del_stmt': 'del some_var',
'global_stmt': 'global x',
}
@pytest.fixture
def safe_codes():
"""各种安全代码样本"""
return {
'math': 'import math\nresult = math.pi',
'json': 'import json\nresult = json.dumps({"a": 1})',
're': 'import re\nresult = re.findall(r"\\d+", "abc123")',
'datetime': 'from datetime import datetime\nresult = datetime.now().year',
'collections': 'from collections import Counter\nresult = Counter("aabbc")',
'list_comp': 'result = [x**2 for x in range(10)]',
'function': 'def add(a, b): return a + b\nresult = add(3, 4)',
'string_ops': 'result = "hello world".upper().split()',
'sorting': 'result = sorted([3,1,2], reverse=True)',
'fizzbuzz': 'result = ["fizz"*(i%3==0)+"buzz"*(i%5==0) or str(i) for i in range(1,16)]',
}
@pytest.fixture
def config_file(tmp_dir):
"""创建临时配置文件"""
config = {
"system": {
"default_engine": "ollama",
"max_history": 100,
"code_execution_timeout": 10,
},
"api": {
"engines": {
"ollama": {"base_url": "http://localhost:11434"},
"mimo": {"api_key": "test-key", "base_url": "http://test.com"},
}
},
"plugins": {},
}
path = os.path.join(tmp_dir, "config.json")
with open(path, 'w') as f:
json.dump(config, f)
return path
@pytest.fixture
def history_dir(tmp_dir):
"""创建临时历史目录"""
d = os.path.join(tmp_dir, "chat_history")
os.makedirs(d)
return d
@pytest.fixture
def plugins_dir(tmp_dir):
"""创建带示例插件的目录"""
d = os.path.join(tmp_dir, "plugins")
os.makedirs(d)
# __init__.py
with open(os.path.join(d, "__init__.py"), 'w') as f:
f.write("")
# 简单插件
with open(os.path.join(d, "test_plugin.py"), 'w') as f:
f.write('''
class Plugin:
def __init__(self):
self.cli = None
def set_cli(self, cli):
self.cli = cli
def get_tool_info(self):
return {
"name": "test_tool",
"description": "A test tool",
"keywords": ["test"],
}
def get_mcp_definition(self):
return {
"name": "test_tool",
"description": "A test tool",
"inputSchema": {
"type": "object",
"properties": {
"operation": {"type": "string"},
},
"required": ["operation"]
}
}
def convert_mcp_args(self, arguments):
return arguments.get("operation", "")
def handle(self, args):
return f"test result: {args}"
''')
return d
@pytest.fixture
def skills_dir(tmp_dir):
"""创建带示例 skill 的目录"""
d = os.path.join(tmp_dir, "skills")
os.makedirs(d)
with open(os.path.join(d, "__init__.py"), 'w') as f:
f.write("")
with open(os.path.join(d, "test_skill.py"), 'w') as f:
f.write('''
class Skill:
def __init__(self):
self.cli = None
def set_cli(self, cli):
self.cli = cli
def get_tool_info(self):
return {
"name": "test_skill",
"description": "A test skill",
"keywords": ["test", "skill"],
}
def handle(self, args):
return f"skill result: {args}"
''')
return d