ducklm/tests/test_tools_flow.py

148 lines
5.5 KiB
Python

import json
from pathlib import Path
from app.core.contracts import UserTask
from app.runtime.runtime_controller import RuntimeController
def _write_config_tree(base_dir: Path) -> None:
(base_dir / "config").mkdir()
(base_dir / "data" / "events").mkdir(parents=True, exist_ok=True)
(base_dir / "data" / "state").mkdir(parents=True, exist_ok=True)
(base_dir / "data" / "permissions").mkdir(parents=True, exist_ok=True)
(base_dir / "models").mkdir(exist_ok=True)
configs = {
"models.json": {
"orchestrator_path": "models/llama.gguf",
"coder_path": "models/xcoder.gguf",
"critic_path": "models/gemma.gguf",
"embeddings_path": "models/all-MiniLM-L6-v2",
"inference": {},
},
"prompts.json": {
"orchestration_prompt": "",
"planning_prompt": "",
"coder_prompt": "",
"critic_prompt": "",
},
"permissions.json": {
"dangerous_commands": {"rm": "ask_always", "sudo": "ask_always"},
"sensitive_paths": ["/etc", "/usr", "/var"],
"default_approval_behavior": "ask_always",
},
"runtime.json": {
"step_timeout_ms": 5000,
"task_timeout_ms": 30000,
"planner_retry_limit": 1,
"tool_retry_limit": 0,
"replan_limit": 0,
"max_execution_steps": 5,
"retrieval_top_k": 3,
"memory_thresholds": {},
"critic_fallback_policy": "continue_without_critic",
"checkpoint_policy": {"save_on_transition": True},
"event_retention_policy": {"keep_all": True},
"streaming_settings": {"enabled": True},
},
}
for name, payload in configs.items():
(base_dir / "config" / name).write_text(json.dumps(payload), encoding="utf-8")
def test_file_write_and_read_tool_flow(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
target = tmp_path / "notes" / "test.txt"
write_result = controller.handle_task(
UserTask(
input="write a file",
context={
"requested_tool": "file_write",
"tool_args": {"path": str(target), "content": "hello from ducklm"},
},
)
)
assert write_result["status"] == "completed"
assert target.read_text(encoding="utf-8") == "hello from ducklm"
read_result = controller.handle_task(
UserTask(
input="read the file",
context={
"requested_tool": "file_read",
"tool_args": {"path": str(target)},
},
)
)
assert read_result["status"] == "completed"
assert read_result["result"]["output"] == "hello from ducklm"
def test_shell_exec_requires_permission_for_dangerous_command(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
result = controller.handle_task(
UserTask(
input="run dangerous shell command",
context={
"requested_tool": "shell_exec",
"tool_args": {"command": "rm -rf /tmp/nonexistent"},
},
)
)
assert result["status"] == "awaiting_permission"
assert "permission_request" in result["result"]
def test_shell_exec_allows_safe_command(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
result = controller.handle_task(
UserTask(
input="run safe shell command",
context={
"requested_tool": "shell_exec",
"tool_args": {"command": "pwd"},
},
)
)
assert result["status"] == "completed"
assert str(tmp_path) in result["result"]["output"]
def test_permission_resolution_can_resume_task(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
initial = controller.handle_task(
UserTask(
input="запусти sudo apt update",
)
)
assert initial["status"] == "awaiting_permission"
resumed = controller.resolve_permission(task_id=initial["task_id"], decision="deny")
assert resumed["status"] == "failed"
assert resumed["result"]["error"] == "Permission denied by user."
def test_sudo_permission_resolution_requests_secret_input(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
initial = controller.handle_task(UserTask(input="запусти sudo apt update"))
assert initial["status"] == "awaiting_permission"
resumed = controller.resolve_permission(task_id=initial["task_id"], decision="allow_once")
assert resumed["status"] == "awaiting_input"
assert resumed["result"]["secret_request"]["kind"] == "sudo_password"
def test_secret_resolution_continues_after_pending_secret_saved(tmp_path: Path) -> None:
_write_config_tree(tmp_path)
controller = RuntimeController(base_dir=tmp_path)
initial = controller.handle_task(UserTask(input="запусти sudo apt update"))
resumed = controller.resolve_permission(task_id=initial["task_id"], decision="allow_once")
assert resumed["status"] == "awaiting_input"
final = controller.resolve_secret(task_id=initial["task_id"], secret="wrongpass")
assert final["status"] in {"completed", "failed"}
assert "error" in final["result"] or "output" in final["result"]