"""内置工具规格测试:覆盖风险分级、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