Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
"""Tests: SlackAdapter native streaming (chat.startStream/appendStream/stopStream).
|
||||
|
||||
Behaviour contract:
|
||||
* supports_draft_streaming: True when connected with default unfurl behavior;
|
||||
False after a cached feature-gate failure, when disconnected, or when an
|
||||
explicit unfurl control requires the chat.postMessage fallback.
|
||||
* send_draft first frame: chat_startStream with thread_ts + initial text;
|
||||
returns the stream ts as message_id.
|
||||
* send_draft subsequent frames: chat_appendStream with only the delta;
|
||||
trailing cursor glyph stripped before delta computation.
|
||||
* identical frame: no API call, success.
|
||||
* prefix mismatch: stream sealed, frame fails (consumer falls back to edits).
|
||||
* send() finalization: active stream sealed via chat_stopStream with the
|
||||
remaining delta instead of chat_postMessage (no duplicate message).
|
||||
* send() with unrelated content: stream left open, normal post proceeds.
|
||||
* startStream feature-gate error: caches _native_stream_unsupported so
|
||||
future supports_draft_streaming() returns False.
|
||||
* disconnect(): dangling streams sealed.
|
||||
"""
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from gateway.config import PlatformConfig
|
||||
from plugins.platforms.slack.adapter import SlackAdapter
|
||||
|
||||
|
||||
def _make_adapter(extra=None):
|
||||
config = PlatformConfig(enabled=True, token="xoxb-fake", extra=extra or {})
|
||||
a = SlackAdapter(config)
|
||||
a._app = MagicMock()
|
||||
client = AsyncMock()
|
||||
client.chat_postMessage = AsyncMock(return_value={"ts": "999.111"})
|
||||
client.chat_update = AsyncMock(return_value={"ts": "999.111"})
|
||||
client.chat_startStream = AsyncMock(return_value={"ok": True, "ts": "123.456"})
|
||||
client.chat_appendStream = AsyncMock(return_value={"ok": True})
|
||||
client.chat_stopStream = AsyncMock(return_value={"ok": True})
|
||||
a._get_client = MagicMock(return_value=client)
|
||||
a.stop_typing = AsyncMock()
|
||||
a._running = True
|
||||
return a, client
|
||||
|
||||
|
||||
META = {"thread_id": "111.000", "user_id": "U123"}
|
||||
|
||||
|
||||
class TestSupportsDraftStreaming:
|
||||
def test_supported_when_connected(self):
|
||||
adapter, _ = _make_adapter()
|
||||
assert adapter.supports_draft_streaming(chat_type="dm") is True
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("unfurl_key", "configured_value"),
|
||||
[
|
||||
("unfurl_links", False),
|
||||
("unfurl_links", True),
|
||||
("unfurl_media", False),
|
||||
("unfurl_media", True),
|
||||
],
|
||||
)
|
||||
def test_explicit_unfurl_control_disables_native_streaming(
|
||||
self, unfurl_key, configured_value
|
||||
):
|
||||
adapter, _ = _make_adapter({unfurl_key: configured_value})
|
||||
|
||||
assert adapter.supports_draft_streaming(chat_type="dm") is False
|
||||
|
||||
def test_unsupported_when_disconnected(self):
|
||||
adapter, _ = _make_adapter()
|
||||
adapter._app = None
|
||||
assert adapter.supports_draft_streaming() is False
|
||||
|
||||
def test_unsupported_after_feature_gate_failure(self):
|
||||
adapter, _ = _make_adapter()
|
||||
adapter._native_stream_unsupported = True
|
||||
assert adapter.supports_draft_streaming() is False
|
||||
|
||||
|
||||
class TestSendDraft:
|
||||
@pytest.mark.asyncio
|
||||
async def test_first_frame_starts_stream(self):
|
||||
adapter, client = _make_adapter()
|
||||
result = await adapter.send_draft("D1", 7, "Hello wo", metadata=META)
|
||||
assert result.success
|
||||
assert result.message_id == "123.456"
|
||||
kwargs = client.chat_startStream.await_args.kwargs
|
||||
assert kwargs["channel"] == "D1"
|
||||
assert kwargs["thread_ts"] == "111.000"
|
||||
assert kwargs["markdown_text"] == "Hello wo"
|
||||
assert kwargs["recipient_user_id"] == "U123"
|
||||
client.chat_appendStream.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_subsequent_frame_appends_delta_only(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello wo", metadata=META)
|
||||
result = await adapter.send_draft("D1", 7, "Hello world!", metadata=META)
|
||||
assert result.success
|
||||
kwargs = client.chat_appendStream.await_args.kwargs
|
||||
assert kwargs["markdown_text"] == "rld!"
|
||||
assert kwargs["ts"] == "123.456"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cursor_glyph_stripped(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello \u2589", metadata=META)
|
||||
assert client.chat_startStream.await_args.kwargs["markdown_text"] == "Hello"
|
||||
await adapter.send_draft("D1", 7, "Hello world \u2589", metadata=META)
|
||||
assert client.chat_appendStream.await_args.kwargs["markdown_text"] == " world"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_identical_frame_is_noop(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello", metadata=META)
|
||||
result = await adapter.send_draft("D1", 7, "Hello \u2589", metadata=META)
|
||||
assert result.success
|
||||
client.chat_appendStream.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_prefix_mismatch_seals_and_fails(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello", metadata=META)
|
||||
result = await adapter.send_draft("D1", 7, "Rewritten text", metadata=META)
|
||||
assert not result.success
|
||||
client.chat_stopStream.assert_awaited()
|
||||
assert "D1" not in adapter._active_streams
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_thread_ts_fails_cleanly(self):
|
||||
adapter, client = _make_adapter()
|
||||
result = await adapter.send_draft("D1", 7, "Hello", metadata={})
|
||||
assert not result.success
|
||||
client.chat_startStream.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_draft_id_seals_prior_stream(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Segment one", metadata=META)
|
||||
client.chat_startStream.return_value = {"ok": True, "ts": "124.000"}
|
||||
result = await adapter.send_draft("D1", 8, "Segment two", metadata=META)
|
||||
assert result.success
|
||||
client.chat_stopStream.assert_awaited() # sealed segment one
|
||||
assert adapter._active_streams["D1"]["ts"] == "124.000"
|
||||
|
||||
|
||||
class TestFeatureGateFallback:
|
||||
@pytest.mark.asyncio
|
||||
async def test_not_allowed_caches_unsupported(self):
|
||||
adapter, client = _make_adapter()
|
||||
client.chat_startStream = AsyncMock(
|
||||
side_effect=Exception("The request to the Slack API failed. (not_allowed)")
|
||||
)
|
||||
result = await adapter.send_draft("D1", 7, "Hello", metadata=META)
|
||||
assert not result.success
|
||||
assert adapter._native_stream_unsupported is True
|
||||
assert adapter.supports_draft_streaming() is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_transient_error_does_not_cache(self):
|
||||
adapter, client = _make_adapter()
|
||||
client.chat_startStream = AsyncMock(side_effect=Exception("timeout"))
|
||||
result = await adapter.send_draft("D1", 7, "Hello", metadata=META)
|
||||
assert not result.success
|
||||
assert adapter._native_stream_unsupported is False
|
||||
|
||||
|
||||
class TestSendFinalization:
|
||||
@pytest.mark.asyncio
|
||||
async def test_final_send_seals_stream_no_duplicate_post(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello wo", metadata=META)
|
||||
result = await adapter.send("D1", "Hello world, done.", metadata=META)
|
||||
assert result.success
|
||||
assert result.message_id == "123.456"
|
||||
kwargs = client.chat_stopStream.await_args.kwargs
|
||||
assert kwargs["markdown_text"] == "rld, done."
|
||||
client.chat_postMessage.assert_not_awaited()
|
||||
assert "D1" not in adapter._active_streams
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_final_send_equal_content_seals_without_delta(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello world", metadata=META)
|
||||
result = await adapter.send("D1", "Hello world", metadata=META)
|
||||
assert result.success
|
||||
kwargs = client.chat_stopStream.await_args.kwargs
|
||||
assert "markdown_text" not in kwargs
|
||||
client.chat_postMessage.assert_not_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unrelated_send_passes_through(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Streaming text here", metadata=META)
|
||||
result = await adapter.send("D1", "Unrelated notice", metadata=META)
|
||||
assert result.success
|
||||
client.chat_postMessage.assert_awaited()
|
||||
# Stream stays open for its own finalization.
|
||||
assert "D1" in adapter._active_streams
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stop_stream_failure_falls_back_to_post(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Hello", metadata=META)
|
||||
client.chat_stopStream = AsyncMock(side_effect=Exception("boom"))
|
||||
result = await adapter.send("D1", "Hello world", metadata=META)
|
||||
assert result.success
|
||||
client.chat_postMessage.assert_awaited()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rich_blocks_applied_after_seal(self):
|
||||
adapter, client = _make_adapter({"rich_blocks": True})
|
||||
rich = "# Title\n\nbody text"
|
||||
await adapter.send_draft("D1", 7, rich[:5], metadata=META)
|
||||
result = await adapter.send("D1", rich, metadata=META)
|
||||
assert result.success
|
||||
client.chat_update.assert_awaited()
|
||||
assert client.chat_update.await_args.kwargs["blocks"]
|
||||
|
||||
|
||||
class TestDisconnectCleanup:
|
||||
@pytest.mark.asyncio
|
||||
async def test_disconnect_seals_dangling_streams(self):
|
||||
adapter, client = _make_adapter()
|
||||
await adapter.send_draft("D1", 7, "Dangling", metadata=META)
|
||||
adapter._stop_socket_mode_handler = AsyncMock()
|
||||
adapter._release_platform_lock = MagicMock()
|
||||
await adapter.disconnect()
|
||||
client.chat_stopStream.assert_awaited()
|
||||
assert not adapter._active_streams
|
||||
Reference in New Issue
Block a user