mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-15 00:46:07 +00:00
98a633c712
- Implement tests for DingTalk adapter helper functions including form content cleaning, input extraction, and completed input lines. - Create unit tests for Lark adapter helper functions focusing on input extraction and completed input lines. - Add tests for WeComBot template card functionalities, including event extraction and payload building for human input. - Enhance Dify service API runner tests to cover human input forms, including input collection, action handling, and form snapshot extraction.
672 lines
25 KiB
Python
672 lines
25 KiB
Python
"""Tests for DifyServiceAPIRunner pure utility methods.
|
|
|
|
Tests the helper methods that don't require real Dify API calls.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
|
|
|
|
|
class TestDifyExtractTextOutput:
|
|
"""Tests for _extract_dify_text_output method."""
|
|
|
|
def _create_runner(self):
|
|
"""Create runner instance."""
|
|
from unittest.mock import MagicMock
|
|
|
|
from langbot.pkg.provider.runners.difysvapi import DifyServiceAPIRunner
|
|
|
|
mock_app = MagicMock()
|
|
pipeline_config = {
|
|
'ai': {
|
|
'dify-service-api': {
|
|
'app-type': 'chat',
|
|
'api-key': 'test-key',
|
|
'base-url': 'https://api.dify.ai',
|
|
}
|
|
},
|
|
'output': {'misc': {}},
|
|
}
|
|
|
|
runner = DifyServiceAPIRunner(mock_app, pipeline_config)
|
|
runner.dify_client = MagicMock()
|
|
|
|
return runner
|
|
|
|
def test_extract_none_value(self):
|
|
"""None returns empty string."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output(None)
|
|
|
|
assert result == ''
|
|
|
|
def test_extract_string_value(self):
|
|
"""Plain string is returned."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output('plain text')
|
|
|
|
assert result == 'plain text'
|
|
|
|
def test_extract_dict_with_content(self):
|
|
"""Dict with 'content' key extracts content."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output({'content': 'extracted content'})
|
|
|
|
assert result == 'extracted content'
|
|
|
|
def test_extract_dict_without_content(self):
|
|
"""Dict without 'content' key is JSON dumped."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output({'key': 'value'})
|
|
|
|
assert 'key' in result
|
|
assert 'value' in result
|
|
|
|
def test_extract_json_string_with_content(self):
|
|
"""JSON string with 'content' key extracts content."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output('{"content": "json content"}')
|
|
|
|
assert result == 'json content'
|
|
|
|
def test_extract_json_string_without_content(self):
|
|
"""JSON string without 'content' key returns original."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output('{"other": "value"}')
|
|
|
|
assert '{"other": "value"}' in result
|
|
|
|
def test_extract_whitespace_string(self):
|
|
"""Whitespace string returns empty."""
|
|
runner = self._create_runner()
|
|
|
|
result = runner._extract_dify_text_output(' ')
|
|
|
|
assert result == ''
|
|
|
|
|
|
class TestDifyRunnerConfigValidation:
|
|
"""Tests for runner config validation."""
|
|
|
|
def test_invalid_app_type_raises(self):
|
|
"""Invalid app-type raises DifyAPIError."""
|
|
from unittest.mock import MagicMock
|
|
|
|
from langbot.pkg.provider.runners.difysvapi import DifyServiceAPIRunner
|
|
from langbot.libs.dify_service_api.v1.errors import DifyAPIError
|
|
|
|
mock_app = MagicMock()
|
|
pipeline_config = {
|
|
'ai': {
|
|
'dify-service-api': {
|
|
'app-type': 'invalid-type',
|
|
'api-key': 'test',
|
|
'base-url': 'https://api.dify.ai',
|
|
}
|
|
},
|
|
'output': {'misc': {}},
|
|
}
|
|
|
|
with pytest.raises(DifyAPIError, match='不支持'):
|
|
DifyServiceAPIRunner(mock_app, pipeline_config)
|
|
|
|
def test_valid_app_types(self):
|
|
"""Valid app-types don't raise."""
|
|
from unittest.mock import MagicMock
|
|
|
|
from langbot.pkg.provider.runners.difysvapi import DifyServiceAPIRunner
|
|
|
|
mock_app = MagicMock()
|
|
|
|
for app_type in ['chat', 'agent', 'workflow']:
|
|
pipeline_config = {
|
|
'ai': {
|
|
'dify-service-api': {
|
|
'app-type': app_type,
|
|
'api-key': 'test',
|
|
'base-url': 'https://api.dify.ai',
|
|
}
|
|
},
|
|
'output': {'misc': {}},
|
|
}
|
|
|
|
runner = DifyServiceAPIRunner(mock_app, pipeline_config)
|
|
# Should not raise
|
|
assert runner is not None
|
|
|
|
|
|
class TestDifyRunnerInit:
|
|
"""Tests for runner initialization."""
|
|
|
|
def test_runner_stores_config(self):
|
|
"""Runner stores pipeline_config."""
|
|
from unittest.mock import MagicMock
|
|
|
|
from langbot.pkg.provider.runners.difysvapi import DifyServiceAPIRunner
|
|
|
|
mock_app = MagicMock()
|
|
pipeline_config = {
|
|
'ai': {
|
|
'dify-service-api': {
|
|
'app-type': 'chat',
|
|
'api-key': 'test-key',
|
|
'base-url': 'https://api.dify.ai',
|
|
}
|
|
},
|
|
'output': {'misc': {}},
|
|
}
|
|
|
|
runner = DifyServiceAPIRunner(mock_app, pipeline_config)
|
|
|
|
assert runner.pipeline_config == pipeline_config
|
|
assert runner.ap == mock_app
|
|
|
|
|
|
class TestDifyHumanInputForms:
|
|
"""Tests for Dify human-input form helpers."""
|
|
|
|
def _create_runner(self):
|
|
from langbot.pkg.provider.runners.difysvapi import DifyServiceAPIRunner
|
|
|
|
mock_app = MagicMock()
|
|
mock_app.logger = MagicMock()
|
|
pipeline_config = {
|
|
'ai': {
|
|
'dify-service-api': {
|
|
'app-type': 'workflow',
|
|
'api-key': 'test-key',
|
|
'base-url': 'https://api.dify.ai',
|
|
'base-prompt': '',
|
|
}
|
|
},
|
|
'output': {'misc': {}},
|
|
}
|
|
runner = DifyServiceAPIRunner(mock_app, pipeline_config)
|
|
runner.dify_client = MagicMock()
|
|
runner.dify_client.upload_file = AsyncMock(return_value={'id': 'upload-1'})
|
|
return runner
|
|
|
|
def test_format_human_input_text_includes_field_help(self):
|
|
from langbot.pkg.provider.runners.difysvapi import _format_human_input_text
|
|
|
|
text = _format_human_input_text(
|
|
'Manual Review',
|
|
'Please fill fields.',
|
|
[{'id': 'yes', 'title': 'Yes'}],
|
|
[
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'choice',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['A', 'B']},
|
|
},
|
|
{'output_variable_name': 'attachment', 'type': 'file-list', 'number_limits': 2},
|
|
],
|
|
)
|
|
|
|
assert 'comment: <value>' in text
|
|
assert 'choice (select): 1. A, 2. B' in text
|
|
assert 'attachment (file-list' in text
|
|
assert 'action: <number or title>' in text
|
|
|
|
def test_form_snapshot_for_platform_omits_action_text_and_placeholders(self):
|
|
from langbot.pkg.provider.runners.difysvapi import _extract_form_snapshot
|
|
|
|
snapshot, _, _, display_form_content = _extract_form_snapshot(
|
|
'run-1',
|
|
{
|
|
'form_token': 'token-1',
|
|
'node_title': 'Manual Review',
|
|
'form_content': 'Hello\n\n{{#$output.comment#}}\n\n{{#$output.choice#}}\n',
|
|
'inputs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'choice',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['A', 'B']},
|
|
},
|
|
],
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}],
|
|
},
|
|
'person_user-1',
|
|
)
|
|
|
|
assert '{{#$output.comment#}}' not in display_form_content
|
|
assert 'Actions:' not in display_form_content
|
|
assert 'comment (paragraph)' in display_form_content
|
|
assert snapshot['form_content'] == display_form_content
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_match_pending_form_collects_select_and_text_inputs(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'node_title': 'Manual Review',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}],
|
|
'input_defs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'choice',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['A', 'B']},
|
|
},
|
|
],
|
|
'inputs': {},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='')])
|
|
|
|
action = await runner._match_pending_form_action(
|
|
query,
|
|
session_key,
|
|
'action: yes\ncomment: looks good\nchoice: 2',
|
|
)
|
|
|
|
assert action['action_id'] == 'yes'
|
|
assert action['inputs'] == {'comment': 'looks good', 'choice': 'B'}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_collect_form_inputs_uploads_files(self):
|
|
runner = self._create_runner()
|
|
image = platform_message.Image(base64='data:image/png;base64,aGVsbG8=')
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([image])
|
|
|
|
inputs = await runner._collect_form_inputs_from_query(
|
|
query,
|
|
{
|
|
'input_defs': [{'output_variable_name': 'photo', 'type': 'file'}],
|
|
'inputs': {},
|
|
'user': 'person_user-1',
|
|
},
|
|
'',
|
|
)
|
|
|
|
assert inputs['photo'] == {
|
|
'type': 'image',
|
|
'transfer_method': 'local_file',
|
|
'upload_file_id': 'upload-1',
|
|
}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_partial_input_with_multiple_actions_waits_for_missing_fields(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'node_title': 'Manual Review',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}, {'id': 'no', 'title': 'No'}],
|
|
'input_defs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'choice',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['A', 'B']},
|
|
},
|
|
],
|
|
'inputs': {},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='')])
|
|
|
|
action = await runner._match_pending_form_action(query, session_key, 'comment: ready')
|
|
|
|
assert action['_partial'] is True
|
|
assert action['inputs'] == {'comment': 'ready'}
|
|
assert action['_form_data']['_current_input_field'] == 'choice'
|
|
assert 'choice (select)' in action['notice']
|
|
assert difysvapi._get_latest_pending_form(session_key)['inputs'] == {'comment': 'ready'}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_complete_partial_input_with_multiple_actions_renders_action_form(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'node_title': 'Manual Review',
|
|
'raw_form_content': 'Please review\n\n{{#$output.comment#}}\n',
|
|
'form_content': 'Please review\n\nFields:\n - comment (paragraph): reply "comment: <value>"',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}, {'id': 'no', 'title': 'No'}],
|
|
'input_defs': [{'output_variable_name': 'comment', 'type': 'paragraph'}],
|
|
'inputs': {},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='')])
|
|
|
|
action = await runner._match_pending_form_action(query, session_key, 'comment: ready')
|
|
|
|
assert action['_partial'] is True
|
|
assert action['inputs'] == {'comment': 'ready'}
|
|
assert 'action: <number or title>' in action['notice']
|
|
assert action['_form_data']['_action_select_only'] is True
|
|
assert action['_form_data']['input_defs'] == []
|
|
assert action['_form_data']['actions'] == [{'id': 'yes', 'title': 'Yes'}, {'id': 'no', 'title': 'No'}]
|
|
assert '{{#$output.comment#}}' not in action['_form_data']['form_content']
|
|
assert difysvapi._get_latest_pending_form(session_key)['inputs'] == {'comment': 'ready'}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_sequential_field_collection_advances_one_field_at_a_time(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'node_title': 'Manual Review',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}, {'id': 'no', 'title': 'No'}],
|
|
'input_defs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'choice',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['A', 'B']},
|
|
},
|
|
],
|
|
'inputs': {},
|
|
'current_input_field': 'comment',
|
|
'user': session_key,
|
|
},
|
|
)
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='')])
|
|
|
|
first = await runner._match_pending_form_action(query, session_key, 'looks good')
|
|
|
|
assert first['_partial'] is True
|
|
assert first['inputs'] == {'comment': 'looks good'}
|
|
assert first['_form_data']['_current_input_field'] == 'choice'
|
|
assert first['_form_data']['input_defs'][0]['output_variable_name'] == 'comment'
|
|
assert 'choice (select)' in first['_form_data']['form_content']
|
|
|
|
second = await runner._match_pending_form_action(query, session_key, '2')
|
|
|
|
assert second['_partial'] is True
|
|
assert second['inputs'] == {'comment': 'looks good', 'choice': 'B'}
|
|
assert second['_form_data']['_action_select_only'] is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_digit_reply_fills_missing_select_before_matching_action_number(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'node_title': 'Manual Review',
|
|
'actions': [
|
|
{'id': 'yes', 'title': 'yes'},
|
|
{'id': 'no', 'title': 'no'},
|
|
{'id': 'or', 'title': 'or'},
|
|
{'id': 'but', 'title': 'but'},
|
|
],
|
|
'input_defs': [
|
|
{'output_variable_name': 'us_input', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'xiala',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['1', '2']},
|
|
},
|
|
],
|
|
'inputs': {'us_input': 'hello'},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='')])
|
|
|
|
action = await runner._match_pending_form_action(query, session_key, '2')
|
|
|
|
assert action['_partial'] is True
|
|
assert action['inputs'] == {'us_input': 'hello', 'xiala': '2'}
|
|
assert action['_form_data']['_action_select_only'] is True
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_workflow_pause_without_text_yields_form_chunk(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
async def workflow_run(**kwargs):
|
|
del kwargs
|
|
yield {
|
|
'event': 'workflow_started',
|
|
'data': {'workflow_run_id': 'run-1'},
|
|
}
|
|
yield {
|
|
'event': 'workflow_paused',
|
|
'data': {
|
|
'workflow_run_id': 'run-1',
|
|
'reasons': [
|
|
{
|
|
'TYPE': 'human_input_required',
|
|
'form_token': 'token-1',
|
|
'node_title': 'Manual Review',
|
|
'form_content': 'Please review\n\n{{#$output.comment#}}\n',
|
|
'inputs': [{'output_variable_name': 'comment', 'type': 'paragraph'}],
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}],
|
|
}
|
|
],
|
|
},
|
|
}
|
|
|
|
runner = self._create_runner()
|
|
runner.dify_client.workflow_run = workflow_run
|
|
query = MagicMock()
|
|
query.session.launcher_type.value = 'person'
|
|
query.session.launcher_id = 'user-1'
|
|
query.session.using_conversation.uuid = 'conversation-1'
|
|
query.variables = {
|
|
'session_id': 'session-1',
|
|
'conversation_id': 'conversation-1',
|
|
'msg_create_time': '0',
|
|
}
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='hello')])
|
|
|
|
difysvapi._PENDING_FORMS.clear()
|
|
chunks = [chunk async for chunk in runner._workflow_messages_chunk(query)]
|
|
|
|
assert chunks[-1].is_final is True
|
|
assert chunks[-1].content == difysvapi._STREAM_FORM_PLACEHOLDER
|
|
assert chunks[-1]._form_data['form_token'] == 'token-1'
|
|
assert chunks[-1]._form_data['_current_input_field'] == 'comment'
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_action_after_partial_input_reuses_saved_inputs(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'node_title': 'Manual Review',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}, {'id': 'no', 'title': 'No'}],
|
|
'input_defs': [{'output_variable_name': 'comment', 'type': 'paragraph'}],
|
|
'inputs': {},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
query = MagicMock()
|
|
query.message_chain = platform_message.MessageChain([platform_message.Plain(text='')])
|
|
|
|
await runner._match_pending_form_action(query, session_key, 'comment: ready')
|
|
action = await runner._match_pending_form_action(query, session_key, 'action: yes')
|
|
|
|
assert action.get('_partial') is not True
|
|
assert action['action_id'] == 'yes'
|
|
assert action['inputs'] == {'comment': 'ready'}
|
|
|
|
def test_form_action_merges_card_inputs_with_saved_inputs(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}],
|
|
'input_defs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{'output_variable_name': 'photo', 'type': 'file'},
|
|
],
|
|
'inputs': {'photo': {'type': 'image', 'transfer_method': 'local_file', 'upload_file_id': 'upload-1'}},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
|
|
action = runner._merge_pending_form_action(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'action_id': 'yes',
|
|
'inputs': {'comment': 'ready'},
|
|
},
|
|
)
|
|
|
|
assert action['inputs'] == {
|
|
'comment': 'ready',
|
|
'photo': {'type': 'image', 'transfer_method': 'local_file', 'upload_file_id': 'upload-1'},
|
|
}
|
|
|
|
def test_form_action_with_missing_required_file_fields_stays_partial(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}],
|
|
'input_defs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{'output_variable_name': 'file', 'type': 'file'},
|
|
{'output_variable_name': 'files', 'type': 'file-list', 'number_limits': 5},
|
|
],
|
|
'inputs': {},
|
|
'user': session_key,
|
|
},
|
|
)
|
|
|
|
action = runner._merge_pending_form_action(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'action_id': 'yes',
|
|
'inputs': {'comment': 'ready'},
|
|
},
|
|
)
|
|
|
|
assert action['_partial'] is True
|
|
assert action['inputs'] == {'comment': 'ready'}
|
|
assert 'file, files' in action['notice']
|
|
assert action['_form_data']['_current_input_field'] == 'file'
|
|
assert action['_form_data']['input_defs'][1]['output_variable_name'] == 'file'
|
|
|
|
def test_card_component_input_progress_maps_to_current_field(self):
|
|
from langbot.pkg.provider.runners import difysvapi
|
|
|
|
runner = self._create_runner()
|
|
session_key = 'person_user-1'
|
|
difysvapi._PENDING_FORMS.clear()
|
|
difysvapi._set_pending_form(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'actions': [{'id': 'yes', 'title': 'Yes'}, {'id': 'no', 'title': 'No'}],
|
|
'input_defs': [
|
|
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
|
{
|
|
'output_variable_name': 'choice',
|
|
'type': 'select',
|
|
'option_source': {'type': 'constant', 'value': ['A', 'B']},
|
|
},
|
|
],
|
|
'inputs': {},
|
|
'current_input_field': 'comment',
|
|
'user': session_key,
|
|
},
|
|
)
|
|
|
|
first = runner._merge_pending_form_action(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'inputs': {'input': 'looks good'},
|
|
'_current_input_field': 'comment',
|
|
'_input_progress': True,
|
|
},
|
|
)
|
|
|
|
assert first['_partial'] is True
|
|
assert first['inputs'] == {'comment': 'looks good'}
|
|
assert first['_form_data']['_current_input_field'] == 'choice'
|
|
|
|
second = runner._merge_pending_form_action(
|
|
session_key,
|
|
{
|
|
'form_token': 'token-1',
|
|
'workflow_run_id': 'run-1',
|
|
'inputs': {'select': '{"index": 1, "value": "B"}'},
|
|
'_current_input_field': 'choice',
|
|
'_input_progress': True,
|
|
},
|
|
)
|
|
|
|
assert second['_partial'] is True
|
|
assert second['inputs'] == {'comment': 'looks good', 'choice': 'B'}
|
|
assert second['_form_data']['_action_select_only'] is True
|