Files
hunjing/backend/tests/application/tool_runtime/test_builtins.py
T
2026-08-21 16:00:13 +08:00

87 lines
2.9 KiB
Python

"""内置工具规格测试:覆盖风险分级、env 传参与必填校验。"""
from __future__ import annotations
import pytest
from app.application.tool_runtime.builtins import builtin_specs
from app.modules.safety.governor import PermissionPolicy
def test_builtin_specs_cover_risk_levels() -> None:
specs = builtin_specs()
assert {"L0", "L1", "L2", "L3"} <= {s.risk_level for s in specs.values()}
assert {
"datetime",
"read_file",
"list_dir",
"write_file",
"shell",
"fetch_url",
} == set(specs)
def test_write_file_is_ask_and_side_effect() -> None:
spec = builtin_specs()["write_file"]
assert spec.permission_policy is PermissionPolicy.ask
assert spec.side_effect is True
def test_read_tools_allow_and_no_side_effect() -> None:
specs = builtin_specs()
for name in ["datetime", "read_file", "list_dir"]:
assert specs[name].permission_policy is PermissionPolicy.allow
assert specs[name].side_effect is False
def test_file_tools_use_workspace() -> None:
req = builtin_specs()["read_file"].build_request({"path": "a.txt"})
assert req.workdir == "workspace"
assert req.env.get("PATH_ARG") == "a.txt"
def test_read_file_pagination_builds_sed() -> None:
"""read_file 传 offset/limit 时用 sed 行分页,缺省时 cat 全文。"""
specs = builtin_specs()
full = specs["read_file"].build_request({"path": "a.txt"})
assert full.argv[2] == 'cat -- "$PATH_ARG"'
paged = specs["read_file"].build_request({"path": "a.txt", "offset": 10, "limit": 5})
assert paged.argv[2] == "sed -n '10,14p' \"$PATH_ARG\""
assert paged.env.get("PATH_ARG") == "a.txt"
assert paged.workdir == "workspace"
tail = specs["read_file"].build_request({"path": "a.txt", "offset": 100})
assert tail.argv[2] == "sed -n '100,$p' \"$PATH_ARG\""
def test_read_file_pagination_ignores_bad_input() -> None:
"""offset/limit 非正整数时回退全文 cat,不误构造非法 sed 地址。"""
spec = builtin_specs()["read_file"]
assert (
spec.build_request({"path": "a", "offset": "x", "limit": -3}).argv[2]
== 'cat -- "$PATH_ARG"'
)
assert spec.build_request({"path": "a", "offset": 0}).argv[2] == 'cat -- "$PATH_ARG"'
def test_required_inputs_validated() -> None:
specs = builtin_specs()
with pytest.raises(ValueError):
specs["read_file"].build_request({})
with pytest.raises(ValueError):
specs["write_file"].build_request({"content": "x"})
with pytest.raises(ValueError):
specs["shell"].build_request({})
def test_fetch_url_uses_executor_not_sandbox() -> None:
"""fetch_url 使用 executor 直接执行,不走沙箱。"""
specs = builtin_specs()
spec = specs["fetch_url"]
assert spec.executor is not None
assert spec.permission_policy is PermissionPolicy.ask
assert spec.side_effect is True
assert spec.audit_required is True