fix: recover failed Feishu streaming updates
Feishu CardKit content updates can fail without raising, leaving a blank Generating card while the final streamed response skips normal send. Reopen streaming mode and retry once, close blank cards when the first update fails, and fall back to a regular card when final updates still fail.
This commit is contained in:
@@ -113,6 +113,22 @@ class TestCloseStreamingMode:
|
||||
assert ch._close_streaming_mode_sync("card_1", 10) is False
|
||||
|
||||
|
||||
class TestStreamUpdateWithReopen:
|
||||
def test_reopens_streaming_mode_and_retries_update(self):
|
||||
ch = _make_channel()
|
||||
ch._client.cardkit.v1.card_element.content.side_effect = [
|
||||
_mock_content_response(False),
|
||||
_mock_content_response(True),
|
||||
]
|
||||
ch._client.cardkit.v1.card.settings.return_value = _mock_content_response(True)
|
||||
|
||||
assert ch._stream_update_text_with_reopen_sync("card_1", "hello", 4) == (True, 6)
|
||||
assert ch._client.cardkit.v1.card_element.content.call_count == 2
|
||||
settings_call = ch._client.cardkit.v1.card.settings.call_args[0][0]
|
||||
assert settings_call.body.sequence == 5
|
||||
assert '"streaming_mode": true' in settings_call.body.settings
|
||||
|
||||
|
||||
class TestStreamUpdateText:
|
||||
def test_returns_true_on_success(self):
|
||||
ch = _make_channel()
|
||||
@@ -149,6 +165,24 @@ class TestSendDelta:
|
||||
ch._client.im.v1.message.create.assert_called_once()
|
||||
ch._client.cardkit.v1.card_element.content.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_first_delta_closes_blank_card_when_initial_update_fails(self):
|
||||
ch = _make_channel()
|
||||
ch._client.cardkit.v1.card.create.return_value = _mock_create_card_response("card_new")
|
||||
ch._client.im.v1.message.create.return_value = _mock_send_response("om_new")
|
||||
ch._client.cardkit.v1.card_element.content.return_value = _mock_content_response(False)
|
||||
ch._client.cardkit.v1.card.settings.return_value = _mock_content_response(True)
|
||||
|
||||
await ch.send_delta("oc_chat1", "Hello ")
|
||||
|
||||
buf = ch._stream_bufs["oc_chat1"]
|
||||
assert buf.text == "Hello "
|
||||
assert buf.card_id is None
|
||||
assert ch._client.cardkit.v1.card_element.content.call_count == 2
|
||||
assert ch._client.cardkit.v1.card.settings.call_count == 2
|
||||
close_call = ch._client.cardkit.v1.card.settings.call_args_list[-1][0][0]
|
||||
assert '"streaming_mode": false' in close_call.body.settings
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_group_delta_uses_create_when_reply_disabled(self):
|
||||
ch = _make_channel(reply_to_message=False)
|
||||
@@ -338,11 +372,29 @@ class TestSendDelta:
|
||||
await ch.send_delta("oc_chat1", "", metadata={"_stream_end": True})
|
||||
|
||||
assert "oc_chat1" not in ch._stream_bufs
|
||||
# Should NOT attempt to close streaming mode since update failed
|
||||
ch._client.cardkit.v1.card.settings.assert_not_called()
|
||||
assert ch._client.cardkit.v1.card.settings.call_count == 2
|
||||
# Should fall back to sending a regular interactive card
|
||||
ch._client.im.v1.message.create.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_end_reopens_streaming_card_before_fallback(self):
|
||||
ch = _make_channel()
|
||||
ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf(
|
||||
text="Recovered content", card_id="card_1", sequence=3, last_edit=0.0,
|
||||
)
|
||||
ch._client.cardkit.v1.card_element.content.side_effect = [
|
||||
_mock_content_response(False),
|
||||
_mock_content_response(True),
|
||||
]
|
||||
ch._client.cardkit.v1.card.settings.return_value = _mock_content_response(True)
|
||||
|
||||
await ch.send_delta("oc_chat1", "", metadata={"_stream_end": True})
|
||||
|
||||
assert "oc_chat1" not in ch._stream_bufs
|
||||
assert ch._client.cardkit.v1.card_element.content.call_count == 2
|
||||
assert ch._client.cardkit.v1.card.settings.call_count == 2
|
||||
ch._client.im.v1.message.create.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_stream_end_without_buf_is_noop(self):
|
||||
ch = _make_channel()
|
||||
|
||||
Reference in New Issue
Block a user