mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-14 16:36:07 +00:00
Add tests for DingTalk adapter content processing and markdown formatting
- Updated the assertion in `test_dingtalk_completed_input_lines_include_text_and_select_values` to remove unnecessary markdown formatting. - Added new tests to verify that `_dingtalk_clean_form_content` maintains the order of prompts and completed values in various scenarios. - Introduced `test_dingtalk_card_markdown_preserves_internal_line_breaks` to ensure internal line breaks are correctly converted to HTML line breaks.
This commit is contained in:
@@ -602,9 +602,9 @@ def build_editor_data():
|
||||
'conditionGroup': '',
|
||||
'children': [
|
||||
avatar('node_avatar', name='LangBot'),
|
||||
markdown_block('node_text_content', variable='content'),
|
||||
input_block('node_input'),
|
||||
select_block('node_select'),
|
||||
markdown_block('node_text_content', variable='content'),
|
||||
button_group('node_btn_group'),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -213,12 +213,45 @@ def _dingtalk_pending_input_defs(form_data: dict) -> list[dict]:
|
||||
|
||||
|
||||
def _dingtalk_clean_form_content(form_data: dict) -> str:
|
||||
content = form_data.get('raw_form_content') or form_data.get('form_content') or ''
|
||||
field_names = {
|
||||
str(field.get('output_variable_name') or '').strip()
|
||||
for field in _dingtalk_form_input_defs(form_data)
|
||||
if str(field.get('output_variable_name') or '').strip()
|
||||
}
|
||||
is_field_step = bool(form_data.get('_current_input_field')) and not form_data.get('_action_select_only')
|
||||
raw_content = str(form_data.get('raw_form_content') or '')
|
||||
content = raw_content or form_data.get('form_content') or ''
|
||||
input_defs = _dingtalk_form_input_defs(form_data)
|
||||
field_names = {_dingtalk_field_name(field) for field in input_defs if _dingtalk_field_name(field)}
|
||||
|
||||
if is_field_step and raw_content:
|
||||
current_field = str(form_data.get('_current_input_field') or '').strip()
|
||||
current_placeholder = next(
|
||||
(
|
||||
match
|
||||
for match in re.finditer(r'\{\{#\$output\.([^#{}]+)#\}\}', raw_content)
|
||||
if match.group(1).strip() == current_field
|
||||
),
|
||||
None,
|
||||
)
|
||||
content = (
|
||||
raw_content[: current_placeholder.end()] if current_placeholder else form_data.get('form_content') or ''
|
||||
)
|
||||
|
||||
if form_data.get('_action_select_only') or is_field_step:
|
||||
fields = {_dingtalk_field_name(field): field for field in input_defs if _dingtalk_field_name(field)}
|
||||
inputs = form_data.get('inputs') or {}
|
||||
|
||||
def replace_placeholder(match: re.Match[str]) -> str:
|
||||
field_name = match.group(1).strip()
|
||||
field = fields.get(field_name)
|
||||
if not field or inputs.get(field_name) in (None, '', []):
|
||||
return ''
|
||||
lines = _dingtalk_completed_input_lines(
|
||||
{
|
||||
'input_defs': [field],
|
||||
'inputs': {field_name: inputs[field_name]},
|
||||
}
|
||||
)
|
||||
return lines[0] if lines else ''
|
||||
|
||||
content = re.sub(r'\{\{#\$output\.([^#{}]+)#\}\}', replace_placeholder, str(content))
|
||||
|
||||
kept_lines: list[str] = []
|
||||
for line in str(content).splitlines():
|
||||
placeholder = re.fullmatch(r'\s*\{\{#\$output\.([^#{}]+)#\}\}\s*', line)
|
||||
@@ -228,6 +261,11 @@ def _dingtalk_clean_form_content(form_data: dict) -> str:
|
||||
return re.sub(r'\n{3,}', '\n\n', '\n'.join(kept_lines).strip())
|
||||
|
||||
|
||||
def _dingtalk_card_markdown(content: str) -> str:
|
||||
"""Preserve line breaks inside DingTalk card-template markdown slots."""
|
||||
return '<br>'.join(str(content or '').splitlines())
|
||||
|
||||
|
||||
def _dingtalk_form_input_defs(form_data: dict) -> list[dict]:
|
||||
return list(form_data.get('all_input_defs') or form_data.get('input_defs') or [])
|
||||
|
||||
@@ -261,11 +299,11 @@ def _dingtalk_completed_input_lines(form_data: dict) -> list[str]:
|
||||
field_type = _dingtalk_field_type(field)
|
||||
display_value = _dingtalk_display_input_value(field, value)
|
||||
if field_type == 'select':
|
||||
lines.append(f'✅ 已选择 {field_name}:**{display_value}**')
|
||||
lines.append(f'✅ 已选择 {field_name}:{display_value}')
|
||||
elif field_type in {'file', 'file-list'}:
|
||||
lines.append(f'✅ 已上传 {field_name}:**{display_value}**')
|
||||
lines.append(f'✅ 已上传 {field_name}:{display_value}')
|
||||
else:
|
||||
lines.append(f'✅ 已填写 {field_name}:**{display_value}**')
|
||||
lines.append(f'✅ 已填写 {field_name}:{display_value}')
|
||||
return lines
|
||||
|
||||
|
||||
@@ -915,9 +953,9 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
if node_title:
|
||||
parts.append(f'**{node_title}**')
|
||||
if form_content:
|
||||
parts.append(form_content)
|
||||
parts.append(_dingtalk_card_markdown(form_content))
|
||||
completed_lines = _dingtalk_completed_input_lines(form_data)
|
||||
if completed_lines:
|
||||
if completed_lines and not all(line in form_content for line in completed_lines):
|
||||
parts.append('<hr>' + '<br>'.join(completed_lines))
|
||||
input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data)
|
||||
if input_hint_lines:
|
||||
@@ -1017,9 +1055,9 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
if node_title:
|
||||
parts.append(f'**{node_title}**')
|
||||
if form_content:
|
||||
parts.append(form_content)
|
||||
parts.append(_dingtalk_card_markdown(form_content))
|
||||
completed_lines = _dingtalk_completed_input_lines(form_data)
|
||||
if completed_lines:
|
||||
if completed_lines and not all(line in form_content for line in completed_lines):
|
||||
parts.append('<hr>' + '<br>'.join(completed_lines))
|
||||
input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data)
|
||||
if input_hint_lines:
|
||||
@@ -1457,14 +1495,14 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
if node_title:
|
||||
parts.append(f'**{node_title}**')
|
||||
if form_content:
|
||||
parts.append(form_content)
|
||||
parts.append(_dingtalk_card_markdown(form_content))
|
||||
completed_lines = _dingtalk_completed_input_lines(
|
||||
{
|
||||
'input_defs': input_defs or [],
|
||||
'inputs': inputs or {},
|
||||
}
|
||||
)
|
||||
if completed_lines:
|
||||
if completed_lines and not all(line in form_content for line in completed_lines):
|
||||
parts.append('<hr>' + '<br>'.join(completed_lines))
|
||||
parts.append(f'<hr>✅ 已选择:**{action_title}**')
|
||||
content = '<br><br>'.join(parts)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ import pytest
|
||||
|
||||
from langbot.pkg.platform.sources.dingtalk import (
|
||||
DingTalkAdapter,
|
||||
_dingtalk_card_markdown,
|
||||
_dingtalk_clean_form_content,
|
||||
_dingtalk_completed_input_lines,
|
||||
_dingtalk_extract_component_inputs,
|
||||
@@ -92,7 +93,7 @@ def test_dingtalk_completed_input_lines_include_text_and_select_values():
|
||||
}
|
||||
)
|
||||
|
||||
assert lines == ['✅ 已填写 comment:**looks good**', '✅ 已选择 choice:**B**']
|
||||
assert lines == ['✅ 已填写 comment:looks good', '✅ 已选择 choice:B']
|
||||
|
||||
|
||||
def test_dingtalk_clean_form_content_uses_all_input_defs():
|
||||
@@ -110,6 +111,49 @@ def test_dingtalk_clean_form_content_uses_all_input_defs():
|
||||
assert content == 'Hello'
|
||||
|
||||
|
||||
def test_dingtalk_field_stage_keeps_prior_prompts_and_completed_values():
|
||||
content = _dingtalk_clean_form_content(
|
||||
{
|
||||
'_current_input_field': 'choice',
|
||||
'raw_form_content': ('Question\n{{#$output.comment#}}\nChoose an answer\n{{#$output.choice#}}'),
|
||||
'form_content': 'Choose an answer',
|
||||
'input_defs': [
|
||||
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
||||
{'output_variable_name': 'choice', 'type': 'select'},
|
||||
],
|
||||
'inputs': {'comment': 'hello'},
|
||||
}
|
||||
)
|
||||
|
||||
assert '{{#$output.' not in content
|
||||
assert content.index('Question') < content.index('comment')
|
||||
assert content.index('comment') < content.index('Choose an answer')
|
||||
|
||||
|
||||
def test_dingtalk_final_action_stage_interleaves_prompts_and_completed_values():
|
||||
content = _dingtalk_clean_form_content(
|
||||
{
|
||||
'_action_select_only': True,
|
||||
'raw_form_content': ('11\nQuestion\n{{#$output.comment#}}\nChoose an answer\n{{#$output.choice#}}'),
|
||||
'all_input_defs': [
|
||||
{'output_variable_name': 'comment', 'type': 'paragraph'},
|
||||
{'output_variable_name': 'choice', 'type': 'select'},
|
||||
],
|
||||
'inputs': {'comment': 'hello', 'choice': 'B'},
|
||||
}
|
||||
)
|
||||
|
||||
assert '{{#$output.' not in content
|
||||
assert content.startswith('11\nQuestion')
|
||||
assert content.index('Question') < content.index('comment')
|
||||
assert content.index('comment') < content.index('Choose an answer')
|
||||
assert content.index('Choose an answer') < content.index('choice')
|
||||
|
||||
|
||||
def test_dingtalk_card_markdown_preserves_internal_line_breaks():
|
||||
assert _dingtalk_card_markdown('11\nQuestion\nCompleted') == '11<br>Question<br>Completed'
|
||||
|
||||
|
||||
def _build_card_action_adapter() -> DingTalkAdapter:
|
||||
adapter = DingTalkAdapter.model_construct(
|
||||
card_state={
|
||||
|
||||
Reference in New Issue
Block a user