import pytest from duck_core.tools.file_read import FileReadTool from duck_core.tools.file_write import FileWriteTool from duck_core.tools.gateway import ToolGateway from duck_core.tools.os_update_check import OsUpdateCheckTool from duck_core.tools.shell_exec_safe import ShellExecSafeTool @pytest.mark.asyncio async def test_file_tools_stay_inside_workspace(tmp_path): write = FileWriteTool(str(tmp_path)) read = FileReadTool(str(tmp_path)) result = await write.run({"path": "tmp/note.txt", "content": "hello duck"}) loaded = await read.run({"path": "tmp/note.txt"}) escaped = await read.run({"path": "../outside.txt"}) assert result.ok is True assert loaded.output == "hello duck" assert escaped.ok is False @pytest.mark.asyncio async def test_shell_tool_blocks_dangerous_commands(tmp_path): shell = ShellExecSafeTool(str(tmp_path)) allowed = await shell.run({"command": "pwd"}) blocked = await shell.run({"command": "rm -rf ."}) sudo = await shell.run({"command": "sudo apt update"}) assert allowed.ok is True assert blocked.ok is False assert blocked.metadata.get("requires_approval") is not True assert blocked.metadata["blocked"] is True assert sudo.ok is False assert sudo.metadata.get("requires_approval") is not True assert sudo.metadata["blocked"] is True @pytest.mark.asyncio async def test_tool_gateway_runs_allowed_directive(tmp_path): gateway = ToolGateway.default(str(tmp_path)) result = await gateway.run_action( {"tool": "file_write", "args": {"path": "a.txt", "content": "x"}} ) assert result.ok is True assert result.metadata["path"].endswith("a.txt") @pytest.mark.asyncio async def test_tool_gateway_lists_workspace_directory(tmp_path): (tmp_path / "src").mkdir() (tmp_path / "src" / "app.py").write_text("print('duck')") (tmp_path / "README.md").write_text("hello") gateway = ToolGateway.default(str(tmp_path)) result = await gateway.run_action({"tool": "list_dir", "args": {"path": "."}}) escaped = await gateway.run_action({"tool": "list_dir", "args": {"path": ".."}}) assert result.ok is True assert "README.md" in result.output assert "src/" in result.output assert escaped.ok is False @pytest.mark.asyncio async def test_tool_gateway_searches_file_contents(tmp_path): (tmp_path / "src").mkdir() (tmp_path / "src" / "app.py").write_text("duck tool gateway\\n") (tmp_path / "notes.txt").write_text("other content\\n") gateway = ToolGateway.default(str(tmp_path)) result = await gateway.run_action( {"tool": "search_files", "args": {"query": "duck tool", "path": "."}} ) escaped = await gateway.run_action( {"tool": "search_files", "args": {"query": "duck", "path": ".."}} ) assert result.ok is True assert "src/app.py:1:duck tool gateway" in result.output assert result.metadata["matches"] == 1 assert escaped.ok is False @pytest.mark.asyncio async def test_os_update_check_reports_apt_upgradable_packages(monkeypatch, tmp_path): class Completed: returncode = 0 stdout = "Listing...\\nbootlogd/stable 3.14-4 amd64 [upgradable from: 3.06-4]\\n" stderr = "WARNING: apt does not have a stable CLI interface.\\n" monkeypatch.setattr("duck_core.tools.os_update_check.shutil.which", lambda name: "/usr/bin/apt") monkeypatch.setattr( "duck_core.tools.os_update_check.subprocess.run", lambda *args, **kwargs: Completed(), ) tool = OsUpdateCheckTool(str(tmp_path)) result = await tool.run({}) assert result.ok is True assert "bootlogd" in result.output assert result.metadata["package_manager"] == "apt" assert result.metadata["upgradable_count"] == 1 assert result.metadata["refreshed_cache"] is False