"""Tests for tool confirmation enforcement in ToolExecutor.""" from __future__ import annotations from typing import Any from openjarvis.core.types import ToolCall, ToolResult from openjarvis.tools._stubs import BaseTool, ToolExecutor, ToolSpec # --------------------------------------------------------------------------- # Test tool helpers # --------------------------------------------------------------------------- class _SafeTool(BaseTool): """Tool that does NOT require confirmation.""" tool_id = "safe" @property def spec(self) -> ToolSpec: return ToolSpec( name="safe", description="A safe tool.", requires_confirmation=False, ) def execute(self, **params: Any) -> ToolResult: return ToolResult(tool_name="safe", content="safe result", success=True) class _DangerousTool(BaseTool): """Tool that REQUIRES confirmation.""" tool_id = "dangerous" @property def spec(self) -> ToolSpec: return ToolSpec( name="dangerous", description="A dangerous tool.", requires_confirmation=True, ) def execute(self, **params: Any) -> ToolResult: return ToolResult(tool_name="dangerous", content="executed!", success=True) # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- class TestToolConfirmation: def test_requires_confirmation_no_callback(self) -> None: """Tool requiring confirmation but no callback → blocked.""" executor = ToolExecutor([_DangerousTool()]) call = ToolCall(id="1", name="dangerous", arguments="{}") result = executor.execute(call) assert result.success is False assert "requires confirmation" in result.content def test_requires_confirmation_not_interactive(self) -> None: """Tool requiring confirmation but interactive=False → blocked.""" executor = ToolExecutor( [_DangerousTool()], interactive=False, confirm_callback=lambda _: True, ) call = ToolCall(id="1", name="dangerous", arguments="{}") result = executor.execute(call) assert result.success is False assert "requires confirmation" in result.content def test_requires_confirmation_denied(self) -> None: """Tool requiring confirmation, callback returns False → denied.""" executor = ToolExecutor( [_DangerousTool()], interactive=True, confirm_callback=lambda _: False, ) call = ToolCall(id="1", name="dangerous", arguments="{}") result = executor.execute(call) assert result.success is False assert "denied by user" in result.content def test_requires_confirmation_approved(self) -> None: """Tool requiring confirmation, callback returns True → executes.""" executor = ToolExecutor( [_DangerousTool()], interactive=True, confirm_callback=lambda _: True, ) call = ToolCall(id="1", name="dangerous", arguments="{}") result = executor.execute(call) assert result.success is True assert result.content == "executed!" def test_no_confirmation_needed(self) -> None: """Tool without requires_confirmation works normally.""" executor = ToolExecutor([_SafeTool()]) call = ToolCall(id="1", name="safe", arguments="{}") result = executor.execute(call) assert result.success is True assert result.content == "safe result" def test_no_confirmation_needed_with_callback(self) -> None: """Tool without requires_confirmation ignores callback.""" calls = [] executor = ToolExecutor( [_SafeTool()], interactive=True, confirm_callback=lambda msg: calls.append(msg) or True, ) call = ToolCall(id="1", name="safe", arguments="{}") result = executor.execute(call) assert result.success is True # Callback should NOT have been called assert len(calls) == 0 def test_confirmation_callback_receives_message(self) -> None: """Confirm callback receives a descriptive message.""" received = [] def capture(msg: str) -> bool: received.append(msg) return True executor = ToolExecutor( [_DangerousTool()], interactive=True, confirm_callback=capture, ) call = ToolCall(id="1", name="dangerous", arguments='{"action": "delete"}') executor.execute(call) assert len(received) == 1 assert "dangerous" in received[0] assert "action" in received[0]