feat: OpenMesh 基础平台与 MD/PDF 转换技能
- 后端: coworker 智能体框架, WS API, 文件上传, 附件处理 - 前端: Open WebUI, 文件全量走 upload API (含 MD/TXT/JSON 等文本类) - 技能: md-to-office (pandoc + wkhtmltopdf) - 修复: 上传文件路径丢失, Agent 搜索浪费, 输出文件跑到 uploads/ - 打包: PyInstaller one-dir, 预打包 pandoc/wkhtmltopdf/chromium
This commit is contained in:
151
tests/test_tools_permissions.py
Normal file
151
tests/test_tools_permissions.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""P1 gate tests — tool registry + permission engine."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import aisuite as ai
|
||||
from coworker.permissions import Decision, Mode, PermissionEngine
|
||||
from coworker.tools import ToolRegistry
|
||||
|
||||
|
||||
def _registry(root: Path) -> ToolRegistry:
|
||||
reg = ToolRegistry()
|
||||
reg.register_all(ai.toolkits.files(root=str(root), allow_write=True))
|
||||
reg.register_all(ai.toolkits.git(root=str(root)))
|
||||
return reg
|
||||
|
||||
|
||||
# -- ToolRegistry ---------------------------------------------------------------
|
||||
|
||||
|
||||
def test_registry_exposes_schemas(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
names = set(reg.names())
|
||||
assert {"read_file", "write_file", "list_files", "git_status"} <= names
|
||||
|
||||
schema = reg.get("read_file").schema
|
||||
assert schema["type"] == "function"
|
||||
assert schema["function"]["name"] == "read_file"
|
||||
assert "parameters" in schema["function"]
|
||||
|
||||
|
||||
def test_registry_execute_read_file(tmp_path):
|
||||
(tmp_path / "hello.txt").write_text("hi there", encoding="utf-8")
|
||||
reg = _registry(tmp_path)
|
||||
assert reg.execute("read_file", {"path": "hello.txt"}) == "hi there"
|
||||
|
||||
|
||||
def test_registry_path_traversal_blocked(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
with pytest.raises((PermissionError, ValueError)):
|
||||
reg.execute("read_file", {"path": "../../etc/passwd"})
|
||||
|
||||
|
||||
def test_registry_execute_unknown_tool(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
with pytest.raises(KeyError):
|
||||
reg.execute("nope", {})
|
||||
|
||||
|
||||
# -- PermissionEngine -----------------------------------------------------------
|
||||
|
||||
|
||||
def _meta(reg: ToolRegistry, name: str):
|
||||
return reg.get(name).metadata
|
||||
|
||||
|
||||
def test_read_auto_allowed(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(workspace_root=tmp_path)
|
||||
d = eng.evaluate("read_file", {"path": "x"}, _meta(reg, "read_file"))
|
||||
assert d.allowed and not d.needs_user
|
||||
|
||||
|
||||
def test_write_requires_approval(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(workspace_root=tmp_path)
|
||||
d = eng.evaluate(
|
||||
"write_file", {"path": "x.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
assert not d.allowed and d.needs_user
|
||||
|
||||
|
||||
def test_write_path_escape_denied(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(workspace_root=tmp_path)
|
||||
d = eng.evaluate(
|
||||
"write_file", {"path": "../escape.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
assert not d.allowed and not d.needs_user
|
||||
assert "escape" in d.reason
|
||||
|
||||
|
||||
def test_plan_mode_blocks_writes(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(workspace_root=tmp_path, mode=Mode.PLAN)
|
||||
d = eng.evaluate(
|
||||
"write_file", {"path": "x.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
assert not d.allowed and not d.needs_user
|
||||
assert "read-only" in d.reason
|
||||
|
||||
|
||||
def test_shell_allowlist(tmp_path):
|
||||
eng = PermissionEngine(workspace_root=tmp_path, allowed_commands=["pytest", "ls"])
|
||||
allowed = eng.evaluate("run_shell", {"command": "pytest -q"}, None)
|
||||
asked = eng.evaluate("run_shell", {"command": "rm -rf /"}, None)
|
||||
assert allowed.allowed
|
||||
assert not asked.allowed and asked.needs_user
|
||||
|
||||
|
||||
def test_session_allow_tool_sticks(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(workspace_root=tmp_path)
|
||||
args = {"path": "x.py", "content": "x"}
|
||||
assert eng.evaluate("write_file", args, _meta(reg, "write_file")).needs_user
|
||||
eng.allow_tool_for_session("write_file")
|
||||
d = eng.evaluate("write_file", args, _meta(reg, "write_file"))
|
||||
assert d.allowed and not d.needs_user
|
||||
|
||||
|
||||
def test_session_allow_command_sticks(tmp_path):
|
||||
eng = PermissionEngine(workspace_root=tmp_path)
|
||||
assert eng.evaluate("run_shell", {"command": "make build"}, None).needs_user
|
||||
eng.allow_command_for_session("make build")
|
||||
assert eng.evaluate("run_shell", {"command": "make build"}, None).allowed
|
||||
|
||||
|
||||
def test_custom_mode_auto_allows_configured_tools(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(
|
||||
workspace_root=tmp_path, mode=Mode.CUSTOM, auto_allow_tools={"write_file"}
|
||||
)
|
||||
# configured tool auto-allowed...
|
||||
write = eng.evaluate(
|
||||
"write_file", {"path": "x.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
assert write.allowed and not write.needs_user
|
||||
# ...but a non-configured high-risk tool still asks
|
||||
shell = eng.evaluate("run_shell", {"command": "rm -rf x"}, None)
|
||||
assert not shell.allowed and shell.needs_user
|
||||
# path scoping still enforced in custom mode
|
||||
escape = eng.evaluate(
|
||||
"write_file", {"path": "../x.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
assert not escape.allowed
|
||||
|
||||
|
||||
def test_auto_mode_allows_but_path_scopes(tmp_path):
|
||||
reg = _registry(tmp_path)
|
||||
eng = PermissionEngine(workspace_root=tmp_path, mode=Mode.BYPASS_APPROVALS)
|
||||
ok = eng.evaluate(
|
||||
"write_file", {"path": "x.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
escape = eng.evaluate(
|
||||
"write_file", {"path": "../x.py", "content": "x"}, _meta(reg, "write_file")
|
||||
)
|
||||
assert ok.allowed
|
||||
assert not escape.allowed
|
||||
Reference in New Issue
Block a user