mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-15 08:56:07 +00:00
feat: Enhance Telegram and QQ Official adapters with select field handling and form action processing
- Added support for select fields in Telegram adapter, including option extraction and callback handling. - Implemented form action processing for Telegram callbacks, improving user interaction feedback. - Introduced new helper functions for building keyboards and resolving select button actions in QQ Official adapter. - Enhanced DifyServiceAPIRunner to handle cumulative streaming responses and improve error handling during workflow resumes. - Added unit tests for new functionalities in Telegram and QQ Official adapters, ensuring robust behavior for select fields and form actions.
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
"""Tests for QQ Official keyboard payload helpers."""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from langbot.libs.qq_official_api.api import (
|
||||
QQ_SELECT_ACTION_PREFIX,
|
||||
build_keyboard_from_select_field,
|
||||
get_select_field_options,
|
||||
resolve_select_button_action,
|
||||
)
|
||||
|
||||
|
||||
def _select_form_data() -> dict:
|
||||
return {
|
||||
'_current_input_field': 'choice',
|
||||
'input_defs': [
|
||||
{
|
||||
'output_variable_name': 'choice',
|
||||
'type': 'select',
|
||||
'option_source': {'type': 'constant', 'value': ['A', 'B', 'C']},
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_qq_select_field_builds_callback_buttons():
|
||||
keyboard = build_keyboard_from_select_field(_select_form_data(), buttons_per_row=2)
|
||||
|
||||
rows = keyboard['content']['rows']
|
||||
assert [[button['render_data']['label'] for button in row['buttons']] for row in rows] == [
|
||||
['A', 'B'],
|
||||
['C'],
|
||||
]
|
||||
assert rows[0]['buttons'][0]['action']['data'] == f'{QQ_SELECT_ACTION_PREFIX}0'
|
||||
assert rows[0]['buttons'][1]['action']['data'] == f'{QQ_SELECT_ACTION_PREFIX}1'
|
||||
|
||||
|
||||
def test_qq_select_button_resolves_field_and_value():
|
||||
form_data = _select_form_data()
|
||||
|
||||
assert get_select_field_options(form_data) == ('choice', ['A', 'B', 'C'])
|
||||
assert resolve_select_button_action(form_data, f'{QQ_SELECT_ACTION_PREFIX}1') == ('choice', 'B')
|
||||
assert resolve_select_button_action(form_data, f'{QQ_SELECT_ACTION_PREFIX}99') is None
|
||||
|
||||
|
||||
def test_qq_select_keyboard_fits_twenty_five_options():
|
||||
form_data = _select_form_data()
|
||||
form_data['input_defs'][0]['option_source']['value'] = [f'Option {idx}' for idx in range(25)]
|
||||
|
||||
rows = build_keyboard_from_select_field(form_data)['content']['rows']
|
||||
|
||||
assert len(rows) == 5
|
||||
assert all(len(row['buttons']) == 5 for row in rows)
|
||||
|
||||
|
||||
def test_qq_non_select_field_does_not_build_keyboard():
|
||||
form_data = {
|
||||
'_current_input_field': 'comment',
|
||||
'input_defs': [{'output_variable_name': 'comment', 'type': 'paragraph'}],
|
||||
}
|
||||
|
||||
assert build_keyboard_from_select_field(form_data)['content']['rows'] == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_qq_select_click_enqueues_input_progress_query():
|
||||
import langbot.pkg.core.app # noqa: F401
|
||||
from langbot.pkg.platform.sources.qqofficial import QQOfficialAdapter
|
||||
|
||||
adapter = QQOfficialAdapter.model_construct()
|
||||
adapter.logger = AsyncMock()
|
||||
adapter.bot = MagicMock()
|
||||
adapter.bot.ack_interaction = AsyncMock()
|
||||
adapter.ap = MagicMock()
|
||||
adapter.ap.platform_mgr.bots = []
|
||||
adapter.ap.query_pool.add_query = AsyncMock()
|
||||
adapter._pending_forms = {
|
||||
'group_group-1': {
|
||||
'form_data': {
|
||||
**_select_form_data(),
|
||||
'form_token': 'token-1',
|
||||
'workflow_run_id': 'run-1',
|
||||
'node_title': 'Review',
|
||||
'actions': [{'id': 'approve', 'title': 'Approve'}],
|
||||
},
|
||||
'sender_id': 'initiator-1',
|
||||
'posted_at': time.time(),
|
||||
}
|
||||
}
|
||||
adapter._session_event_ids = {}
|
||||
adapter._anchor_msg_seq = {}
|
||||
|
||||
await adapter._handle_interaction_create(
|
||||
{
|
||||
'id': 'interaction-1',
|
||||
'chat_type': 1,
|
||||
'group_openid': 'group-1',
|
||||
'member_openid': 'reviewer-2',
|
||||
'data': {'resolved': {'button_data': f'{QQ_SELECT_ACTION_PREFIX}1'}},
|
||||
},
|
||||
ws_event_id='event-1',
|
||||
)
|
||||
await asyncio.sleep(0)
|
||||
|
||||
call = adapter.ap.query_pool.add_query.await_args
|
||||
form_action = call.kwargs['variables']['_dify_form_action']
|
||||
assert call.kwargs['launcher_id'] == 'group-1'
|
||||
assert call.kwargs['sender_id'] == 'reviewer-2'
|
||||
assert form_action['action_id'] == ''
|
||||
assert form_action['inputs'] == {'select': 'B'}
|
||||
assert form_action['_current_input_field'] == 'choice'
|
||||
assert form_action['_input_progress'] is True
|
||||
adapter.bot.ack_interaction.assert_awaited_once_with('interaction-1', code=0)
|
||||
Reference in New Issue
Block a user