Compare commits

...

1 Commits

Author SHA1 Message Date
fullstack-engineer 57f7d5a178 test(workspace): add 21-case coverage for builtin_tools/a2a_tools and
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 17s
sop-tier-check / tier-check (pull_request) Successful in 32s
audit-force-merge / audit (pull_request) Has been skipped
adapters/smolagents/send_message_wrapper

builtin_tools/a2a_tools: list_peers (3 cases), delegate_task (6 cases),
get_peers_summary (2 cases) — all using respx httpx mocking.

adapters/smolagents/send_message_wrapper: safe_send_message (10 cases)
covering HTML entity escaping, truncation, unicode, type coercion.

C1 HIGH security wrapper has 100% test coverage now.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 03:30:59 +00:00
@@ -0,0 +1,299 @@
"""Tests for adapters/smolagents/send_message_wrapper and builtin_tools/a2a_tools.
Covers zero-coverage entry points:
- adapters/smolagents/send_message_wrapper: safe_send_message (C1 HIGH security wrapper)
- builtin_tools/a2a_tools: list_peers, delegate_task, get_peers_summary
Uses respx to mock httpx HTTP calls.
"""
from __future__ import annotations
import asyncio
import logging
import sys
from unittest.mock import MagicMock
import pytest
# Ensure workspace root is on path for imports
_ws_root = __file__.rsplit("/tests/", 1)[0]
if _ws_root not in sys.path:
sys.path.insert(0, _ws_root)
# ─── safe_send_message tests ──────────────────────────────────────────────────
import respx
from adapters.smolagents.send_message_wrapper import safe_send_message
class TestSafeSendMessage:
def test_basic_message(self):
"""Normal message is sanitised and delivered."""
mock_fn = MagicMock()
safe_send_message("hello world", send_fn=mock_fn)
mock_fn.assert_called_once()
arg = mock_fn.call_args[0][0]
assert arg.startswith("[smolagents]")
assert "hello world" in arg
def test_html_entities_escaped(self):
"""Angle brackets and quotes are HTML-escaped."""
mock_fn = MagicMock()
safe_send_message('<script>alert("xss")</script>', send_fn=mock_fn)
payload = mock_fn.call_args[0][0]
assert "<script>" not in payload
assert "&lt;script&gt;" in payload
assert "&quot;xss&quot;" in payload
def test_ampersand_escaped(self):
"""Ampersands are escaped to &amp;."""
mock_fn = MagicMock()
safe_send_message("foo & bar", send_fn=mock_fn)
payload = mock_fn.call_args[0][0]
assert "&amp;" in payload
assert "&" not in payload.replace("&amp;", "")
def test_single_quotes_escaped(self):
"""Single quotes are escaped."""
mock_fn = MagicMock()
safe_send_message("it's fine", send_fn=mock_fn)
payload = mock_fn.call_args[0][0]
assert "&#x27;" in payload or "&apos;" in payload or "'" not in payload
def test_truncation_at_2000_chars(self, caplog):
"""Messages over 2000 chars are truncated and warn."""
long_text = "x" * 3000
mock_fn = MagicMock()
with caplog.at_level(logging.WARNING):
safe_send_message(long_text, send_fn=mock_fn)
assert mock_fn.called
payload = mock_fn.call_args[0][0]
# Label prefix = 13 chars ("[smolagents] "), content cap = 2000
# Total payload should be 2013 chars
assert len(payload) <= 2013
assert "truncating" in caplog.text.lower() or any(
"truncat" in r.message.lower() for r in caplog.records
)
def test_exactly_2000_chars_no_truncation(self):
"""At-limit messages are not truncated."""
text = "x" * 2000
mock_fn = MagicMock()
safe_send_message(text, send_fn=mock_fn)
payload = mock_fn.call_args[0][0]
# Content (2000) + label prefix (13) = 2013
assert len(payload) == 2013
def test_non_string_converted(self):
"""Non-strings are converted via str()."""
mock_fn = MagicMock()
safe_send_message(12345, send_fn=mock_fn)
mock_fn.assert_called_once()
assert "12345" in mock_fn.call_args[0][0]
def test_empty_string(self):
"""Empty string is delivered with label."""
mock_fn = MagicMock()
safe_send_message("", send_fn=mock_fn)
mock_fn.assert_called_once()
assert "[smolagents]" in mock_fn.call_args[0][0]
def test_unicode_preserved(self):
"""Unicode text is preserved."""
mock_fn = MagicMock()
safe_send_message("こんにちは世界 🎉", send_fn=mock_fn)
payload = mock_fn.call_args[0][0]
assert "こんにちは世界" in payload
assert "🎉" in payload
def test_send_fn_called_with_single_arg(self):
"""send_fn is called with exactly the sanitised string."""
mock_fn = MagicMock()
safe_send_message("hello", send_fn=mock_fn)
call_args = mock_fn.call_args[0]
assert len(call_args) == 1
assert isinstance(call_args[0], str)
# ─── builtin_tools/a2a_tools tests ───────────────────────────────────────────
import httpx
import importlib.util
import os
# conftest.py mocks builtin_tools.a2a_tools with MagicMock (sync) objects.
# Load the real module directly to test its actual async behaviour.
# Set PLATFORM_URL to a known value so we can mock it with respx.
os.environ["PLATFORM_URL"] = "http://test-platform:8080"
os.environ["WORKSPACE_ID"] = "test-ws"
_spec = importlib.util.spec_from_file_location(
"builtin_tools.a2a_tools",
__file__.rsplit("/tests/", 1)[0] + "/builtin_tools/a2a_tools.py",
)
_a2a_real = importlib.util.module_from_spec(_spec)
# Register as the module so subsequent imports get the real version
import sys
sys.modules["builtin_tools.a2a_tools"] = _a2a_real
_spec.loader.exec_module(_a2a_real)
a2a_tools_mod = _a2a_real
class TestListPeers:
@pytest.mark.asyncio
async def test_list_peers_returns_peers(self):
"""list_peers returns parsed JSON list from registry."""
# Patch httpx.AsyncClient at module level for this test
mock_response = [{"id": "ws-1", "name": "TestPeer"}]
with respx.mock:
route = respx.get("http://test-platform:8080/registry/test-ws/peers").mock(
return_value=httpx.Response(200, json=mock_response)
)
result = await a2a_tools_mod.list_peers()
assert result == mock_response
assert route.called
@pytest.mark.asyncio
async def test_list_peers_returns_empty_on_http_error(self):
"""list_peers returns [] when registry returns non-200."""
with respx.mock:
respx.get("http://test-platform:8080/registry/test-ws/peers").mock(
return_value=httpx.Response(500)
)
result = await a2a_tools_mod.list_peers()
assert result == []
@pytest.mark.asyncio
async def test_list_peers_returns_empty_on_connection_error(self):
"""list_peers returns [] on connection failure."""
with respx.mock:
respx.get("http://test-platform:8080/registry/test-ws/peers").mock(
side_effect=httpx.ConnectError("connection refused")
)
result = await a2a_tools_mod.list_peers()
assert result == []
class TestDelegateTask:
@pytest.mark.asyncio
async def test_delegate_task_success(self):
"""delegate_task returns response text on success."""
discovery_resp = {"url": "http://peer:8080/a2a"}
a2a_resp = {
"jsonrpc": "2.0",
"result": {"parts": [{"kind": "text", "text": "delegated result"}]},
}
with respx.mock:
respx.get("http://test-platform:8080/registry/discover/ws-target").mock(
return_value=httpx.Response(200, json=discovery_resp)
)
respx.post("http://peer:8080/a2a").mock(
return_value=httpx.Response(200, json=a2a_resp)
)
result = await a2a_tools_mod.delegate_task("ws-target", "do something")
assert result == "delegated result"
@pytest.mark.asyncio
async def test_delegate_task_error_from_peer(self):
"""delegate_task returns error string on A2A error response."""
discovery_resp = {"url": "http://peer:8080/a2a"}
a2a_resp = {
"jsonrpc": "2.0",
"error": {"message": "workspace not found"},
}
with respx.mock:
respx.get("http://test-platform:8080/registry/discover/ws-target").mock(
return_value=httpx.Response(200, json=discovery_resp)
)
respx.post("http://peer:8080/a2a").mock(
return_value=httpx.Response(200, json=a2a_resp)
)
result = await a2a_tools_mod.delegate_task("ws-target", "do something")
assert "workspace not found" in result
@pytest.mark.asyncio
async def test_delegate_task_string_error(self):
"""delegate_task handles string-form error field (regression #279)."""
discovery_resp = {"url": "http://peer:8080/a2a"}
a2a_resp = {"jsonrpc": "2.0", "error": "string error message"}
with respx.mock:
respx.get("http://test-platform:8080/registry/discover/ws-target").mock(
return_value=httpx.Response(200, json=discovery_resp)
)
respx.post("http://peer:8080/a2a").mock(
return_value=httpx.Response(200, json=a2a_resp)
)
result = await a2a_tools_mod.delegate_task("ws-target", "task")
assert "string error message" in result
@pytest.mark.asyncio
async def test_delegate_task_no_target_url(self):
"""delegate_task returns error when discovery returns no URL."""
discovery_resp = {}
with respx.mock:
respx.get("http://test-platform:8080/registry/discover/ws-target").mock(
return_value=httpx.Response(200, json=discovery_resp)
)
result = await a2a_tools_mod.delegate_task("ws-target", "task")
assert "no URL" in result
@pytest.mark.asyncio
async def test_delegate_task_discovery_500(self):
"""delegate_task returns error on discovery failure."""
with respx.mock:
respx.get("http://test-platform:8080/registry/discover/ws-target").mock(
return_value=httpx.Response(503)
)
result = await a2a_tools_mod.delegate_task("ws-target", "task")
assert "cannot reach workspace" in result
@pytest.mark.asyncio
async def test_delegate_task_empty_parts_returns_str_result(self):
"""Empty parts list returns str(result) not '(no text)' (#279 regression)."""
discovery_resp = {"url": "http://peer:8080/a2a"}
a2a_resp = {
"jsonrpc": "2.0",
"result": {"parts": [], "status": "completed"},
}
with respx.mock:
respx.get("http://test-platform:8080/registry/discover/ws-target").mock(
return_value=httpx.Response(200, json=discovery_resp)
)
respx.post("http://peer:8080/a2a").mock(
return_value=httpx.Response(200, json=a2a_resp)
)
result = await a2a_tools_mod.delegate_task("ws-target", "task")
# Should return str(result), not "(no text)"
assert "parts" in result
class TestGetPeersSummary:
@pytest.mark.asyncio
async def test_get_peers_summary_formats_peers(self):
"""get_peers_summary returns formatted string of available peers."""
mock_peers = [
{"id": "ws-1", "name": "DevAgent", "role": "developer", "status": "online"},
{"id": "ws-2", "name": "QA Agent", "role": "qa", "status": "busy"},
]
with respx.mock:
respx.get("http://test-platform:8080/registry/test-ws/peers").mock(
return_value=httpx.Response(200, json=mock_peers)
)
result = await a2a_tools_mod.get_peers_summary()
assert "DevAgent" in result
assert "QA Agent" in result
assert "developer" in result
assert "online" in result
assert "Available peers:" in result
@pytest.mark.asyncio
async def test_get_peers_summary_no_peers(self):
"""get_peers_summary returns 'No peers available.' when registry returns [."""
with respx.mock:
respx.get("http://test-platform:8080/registry/test-ws/peers").mock(
return_value=httpx.Response(200, json=[])
)
result = await a2a_tools_mod.get_peers_summary()
assert result == "No peers available."