chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from opensquilla.engine.types import ToolCall
|
||||
from opensquilla.tools.dispatch import build_tool_handler, preflight_tool_call
|
||||
from opensquilla.tools.registry import ToolRegistry
|
||||
from opensquilla.tools.types import ToolContext, ToolSpec
|
||||
|
||||
|
||||
def _payload(result) -> dict[str, object]:
|
||||
assert result is not None
|
||||
return json.loads(result.content)
|
||||
|
||||
|
||||
def _assert_invalid_execution_status(result, reason_code: str) -> None:
|
||||
assert result is not None
|
||||
assert result.execution_status is not None
|
||||
assert result.execution_status["status"] == "error"
|
||||
assert result.execution_status["reason"] == "invalid_tool_arguments"
|
||||
assert result.execution_status["preflight_rejected"] is True
|
||||
assert result.execution_status["reason_code"] == reason_code
|
||||
|
||||
|
||||
def _registry_with_edit_file() -> ToolRegistry:
|
||||
registry = ToolRegistry()
|
||||
|
||||
async def _edit_file(
|
||||
path: str,
|
||||
old_text: str | None = None,
|
||||
new_text: str | None = None,
|
||||
edits: list[dict[str, object]] | None = None,
|
||||
) -> str:
|
||||
raise AssertionError("preflight should not execute edit_file handler")
|
||||
|
||||
registry.register(
|
||||
ToolSpec(
|
||||
name="edit_file",
|
||||
description="edit file",
|
||||
parameters={
|
||||
"path": {"type": "string"},
|
||||
"old_text": {"type": "string"},
|
||||
"new_text": {"type": "string"},
|
||||
"edits": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"old_text": {"type": "string"},
|
||||
"new_text": {"type": "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
required=["path"],
|
||||
),
|
||||
_edit_file,
|
||||
)
|
||||
return registry
|
||||
|
||||
|
||||
def _registry_with_apply_patch() -> ToolRegistry:
|
||||
registry = ToolRegistry()
|
||||
|
||||
async def _apply_patch(patch: str | None = None, path: str | None = None) -> str:
|
||||
raise AssertionError("preflight should not execute apply_patch handler")
|
||||
|
||||
registry.register(
|
||||
ToolSpec(
|
||||
name="apply_patch",
|
||||
description="apply patch",
|
||||
parameters={
|
||||
"patch": {"type": "string"},
|
||||
"path": {"type": "string"},
|
||||
},
|
||||
required=[],
|
||||
),
|
||||
_apply_patch,
|
||||
)
|
||||
return registry
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_file_path_only_is_rejected_before_handler() -> None:
|
||||
events: list[dict[str, object]] = []
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_edit_file(),
|
||||
ctx=ToolContext(on_runtime_event=events.append, session_key="agent:test"),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u1",
|
||||
tool_name="edit_file",
|
||||
arguments={"path": "src/a.py"},
|
||||
),
|
||||
)
|
||||
|
||||
_assert_invalid_execution_status(result, "missing_executable_shape")
|
||||
payload = _payload(result)
|
||||
assert payload["status"] == "rejected"
|
||||
assert payload["reason_code"] == "missing_executable_shape"
|
||||
assert payload["tool"] == "edit_file"
|
||||
assert payload["received_keys"] == ["path"]
|
||||
assert payload["retry_allowed"] is True
|
||||
assert payload["error_class"] == "InvalidToolArgumentsError"
|
||||
assert "old_text" in payload["user_message"]
|
||||
matching = [
|
||||
event
|
||||
for event in events
|
||||
if event["name"] == "dispatch.invalid_tool_arguments"
|
||||
]
|
||||
assert len(matching) == 1
|
||||
event = matching[0]
|
||||
assert event["reason"] == "missing_executable_shape"
|
||||
assert event["tool"] == "edit_file"
|
||||
assert event["executed"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_file_path_only_handler_path_emits_one_invalid_event() -> None:
|
||||
events: list[dict[str, object]] = []
|
||||
handler = build_tool_handler(
|
||||
_registry_with_edit_file(),
|
||||
ToolContext(on_runtime_event=events.append, session_key="agent:test"),
|
||||
)
|
||||
|
||||
result = await handler(
|
||||
ToolCall(
|
||||
tool_use_id="u1-handler",
|
||||
tool_name="edit_file",
|
||||
arguments={"path": "src/a.py"},
|
||||
)
|
||||
)
|
||||
|
||||
_assert_invalid_execution_status(result, "missing_executable_shape")
|
||||
payload = _payload(result)
|
||||
assert payload["error_class"] == "InvalidToolArgumentsError"
|
||||
matching = [
|
||||
event
|
||||
for event in events
|
||||
if event["name"] == "dispatch.invalid_tool_arguments"
|
||||
]
|
||||
assert len(matching) == 1
|
||||
assert matching[0]["reason"] == "missing_executable_shape"
|
||||
assert matching[0]["tool"] == "edit_file"
|
||||
assert matching[0]["executed"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_file_single_replacement_shape_passes() -> None:
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_edit_file(),
|
||||
ctx=ToolContext(),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u2",
|
||||
tool_name="edit_file",
|
||||
arguments={
|
||||
"path": "src/a.py",
|
||||
"old_text": "before",
|
||||
"new_text": "after",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_file_whitespace_old_text_shape_passes() -> None:
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_edit_file(),
|
||||
ctx=ToolContext(),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u2-whitespace",
|
||||
tool_name="edit_file",
|
||||
arguments={
|
||||
"path": "src/a.py",
|
||||
"old_text": " ",
|
||||
"new_text": "x",
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_file_multi_replacement_shape_passes() -> None:
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_edit_file(),
|
||||
ctx=ToolContext(),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u3",
|
||||
tool_name="edit_file",
|
||||
arguments={
|
||||
"path": "src/a.py",
|
||||
"edits": [{"old_text": "before", "new_text": "after"}],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_edit_file_multi_replacement_alias_shape_passes() -> None:
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_edit_file(),
|
||||
ctx=ToolContext(),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u3-alias",
|
||||
tool_name="edit_file",
|
||||
arguments={
|
||||
"path": "src/a.py",
|
||||
"edits": [{"oldText": "before", "newText": "after"}],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
assert result is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_patch_empty_arguments_are_rejected_before_handler() -> None:
|
||||
events: list[dict[str, object]] = []
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_apply_patch(),
|
||||
ctx=ToolContext(on_runtime_event=events.append, session_key="agent:test"),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u4",
|
||||
tool_name="apply_patch",
|
||||
arguments={},
|
||||
),
|
||||
)
|
||||
|
||||
_assert_invalid_execution_status(result, "missing_executable_shape")
|
||||
payload = _payload(result)
|
||||
assert payload["status"] == "rejected"
|
||||
assert payload["reason_code"] == "missing_executable_shape"
|
||||
assert payload["tool"] == "apply_patch"
|
||||
assert payload["received_keys"] == []
|
||||
assert payload["retry_allowed"] is True
|
||||
assert payload["error_class"] == "InvalidToolArgumentsError"
|
||||
assert "patch" in payload["user_message"]
|
||||
matching = [
|
||||
event
|
||||
for event in events
|
||||
if event["name"] == "dispatch.invalid_tool_arguments"
|
||||
]
|
||||
assert len(matching) == 1
|
||||
event = matching[0]
|
||||
assert event["reason"] == "missing_executable_shape"
|
||||
assert event["tool"] == "apply_patch"
|
||||
assert event["executed"] is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_patch_inline_patch_shape_passes() -> None:
|
||||
result = await preflight_tool_call(
|
||||
registry=_registry_with_apply_patch(),
|
||||
ctx=ToolContext(),
|
||||
tool_call=ToolCall(
|
||||
tool_use_id="u5",
|
||||
tool_name="apply_patch",
|
||||
arguments={"patch": "*** Begin Patch\n*** End Patch"},
|
||||
),
|
||||
)
|
||||
|
||||
assert result is None
|
||||
Reference in New Issue
Block a user