Every test double that stood in for `_adapter_for_source` was a delivery stand-in. Part of #88715 (phase 4).
103 lines
5.3 KiB
Python
103 lines
5.3 KiB
Python
"""Real native/relay send paths and profile-scoped effective warning policy.
|
|
|
|
Only external Slack SDK/relay transport I/O is replaced; config, agent emission,
|
|
callback classification, presentation and adapter send/edit are production code.
|
|
"""
|
|
|
|
import asyncio
|
|
from types import SimpleNamespace
|
|
from unittest.mock import AsyncMock
|
|
|
|
import pytest
|
|
|
|
from agent.status_output import StatusOutputMixin
|
|
from gateway.config import Platform, PlatformConfig
|
|
from gateway.platforms.event import MessageEvent, MessageType
|
|
from gateway.relay.adapter import RelayAdapter
|
|
from gateway.relay.descriptor import CONTRACT_VERSION, CapabilityDescriptor
|
|
from gateway.run import GatewayRunner, _load_gateway_config, _profile_runtime_scope
|
|
from gateway.run_turn_runner import TurnRunner
|
|
from gateway.session import SessionSource
|
|
from gateway.turn_context import TurnContext
|
|
from plugins.platforms.slack.adapter import SlackAdapter
|
|
from tests.gateway.relay.stub_connector import StubConnector
|
|
|
|
|
|
@pytest.mark.parametrize("relay", [False, True])
|
|
@pytest.mark.parametrize("thread_id", [None, "1700.1"])
|
|
@pytest.mark.parametrize("enabled", [False, True])
|
|
def test_warning_policy_reaches_slack_transport(tmp_path, monkeypatch, relay, thread_id, enabled):
|
|
from gateway import run
|
|
|
|
(tmp_path / "config.yaml").write_text(f"display: {{suppress_warning_notifications: {str(not enabled).lower()}}}")
|
|
monkeypatch.setattr(run, "_hermes_home", tmp_path)
|
|
source = SessionSource(platform=Platform.SLACK, chat_id="D1", chat_type="dm", user_id="U1", thread_id=thread_id)
|
|
metadata = {"thread_id": thread_id} if thread_id else {}
|
|
pc = PlatformConfig(extra={"reply_in_thread": bool(thread_id)})
|
|
if relay:
|
|
desc = CapabilityDescriptor(contract_version=CONTRACT_VERSION, platform="slack", label="Slack",
|
|
max_message_length=4000, supports_edit=True, supports_threads=True, supports_draft_streaming=False,
|
|
markdown_dialect="mrkdwn", len_unit="chars", emoji="", platform_hint="", pii_safe=False)
|
|
transport = StubConnector(desc)
|
|
adapter = RelayAdapter(pc, desc, transport=transport)
|
|
adapter._capture_scope(MessageEvent(text="hello", source=source, message_type=MessageType.TEXT))
|
|
sent = transport.sent
|
|
else:
|
|
adapter = SlackAdapter(pc)
|
|
client = AsyncMock()
|
|
client.chat_postMessage.return_value = {"ok": True, "ts": "1700.2"}
|
|
client.chat_update.return_value = {"ok": True, "ts": "1700.2"}
|
|
adapter._app = SimpleNamespace(client=client)
|
|
sent = []
|
|
async def post(**kwargs):
|
|
sent.append(kwargs)
|
|
return {"ok": True, "ts": "1700.2"}
|
|
client.chat_postMessage.side_effect = post
|
|
client.chat_update.side_effect = post
|
|
gateway = object.__new__(GatewayRunner)
|
|
gateway.config = None
|
|
gateway._delivery_adapter_for = lambda source: adapter
|
|
gateway._thread_metadata_for_source = lambda source: metadata
|
|
ctx = TurnContext(source=source, user_config=_load_gateway_config(), _run_still_current=lambda: True,
|
|
_status_adapter=adapter, _status_chat_id="D1", _status_thread_metadata=metadata)
|
|
turn = TurnRunner(gateway, ctx)
|
|
monkeypatch.setattr(run, "safe_schedule_threadsafe", lambda coro, *a, **k: asyncio.run(coro))
|
|
agent = StatusOutputMixin()
|
|
agent.suppress_status_output = True
|
|
agent.status_callback = turn._status_callback_sync
|
|
for _ in range(2):
|
|
agent._emit_warning("⚠ Context is over the limit; compression blocked")
|
|
assert len(sent) == (2 if enabled else 0)
|
|
# A warning-looking real answer remains deliverable through the identical adapter.
|
|
result = asyncio.run(adapter.send("D1", "⚠ The requested task failed", metadata=metadata))
|
|
assert result.success
|
|
assert len(sent) == (2 if enabled else 0) + 1
|
|
if relay:
|
|
assert sent[-1]["metadata"].get("thread_id") == thread_id
|
|
else:
|
|
assert sent[-1].get("thread_ts") == thread_id
|
|
|
|
|
|
def test_profile_config_isolated_for_callbacks_and_direct_warnings(tmp_path, monkeypatch):
|
|
from gateway import run
|
|
from gateway.warning_notifications import warning_notifications_enabled
|
|
|
|
homes = [tmp_path / name for name in ("a", "b")]
|
|
for home, value in zip(homes, ("true", "false")):
|
|
home.mkdir()
|
|
(home / "config.yaml").write_text(f"display: {{suppress_warning_notifications: {value}}}")
|
|
# The ambient default must never override the context-bound owner.
|
|
monkeypatch.setattr(run, "_hermes_home", homes[1])
|
|
source = SessionSource(platform=Platform.SLACK, chat_id="D1", chat_type="dm", user_id="U1")
|
|
for home, expected in ((homes[0], False), (homes[1], True), (homes[0], False)):
|
|
with _profile_runtime_scope(home, prepared_secret_scope={}):
|
|
config = _load_gateway_config()
|
|
assert warning_notifications_enabled(source.platform, config) is expected
|
|
from tests.gateway.test_warning_notifications import RecordingAdapter
|
|
adapter = RecordingAdapter()
|
|
adapter.send = AsyncMock()
|
|
gateway = object.__new__(GatewayRunner)
|
|
gateway._delivery_adapter_for = lambda source: adapter
|
|
asyncio.run(gateway._hmwa_hygiene_notify(source, {}, "Compression failed", "failure"))
|
|
assert adapter.send.await_count == int(expected)
|