mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-25 22:06:06 +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': '',
|
'conditionGroup': '',
|
||||||
'children': [
|
'children': [
|
||||||
avatar('node_avatar', name='LangBot'),
|
avatar('node_avatar', name='LangBot'),
|
||||||
|
markdown_block('node_text_content', variable='content'),
|
||||||
input_block('node_input'),
|
input_block('node_input'),
|
||||||
select_block('node_select'),
|
select_block('node_select'),
|
||||||
markdown_block('node_text_content', variable='content'),
|
|
||||||
button_group('node_btn_group'),
|
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:
|
def _dingtalk_clean_form_content(form_data: dict) -> str:
|
||||||
content = form_data.get('raw_form_content') or form_data.get('form_content') or ''
|
is_field_step = bool(form_data.get('_current_input_field')) and not form_data.get('_action_select_only')
|
||||||
field_names = {
|
raw_content = str(form_data.get('raw_form_content') or '')
|
||||||
str(field.get('output_variable_name') or '').strip()
|
content = raw_content or form_data.get('form_content') or ''
|
||||||
for field in _dingtalk_form_input_defs(form_data)
|
input_defs = _dingtalk_form_input_defs(form_data)
|
||||||
if str(field.get('output_variable_name') or '').strip()
|
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] = []
|
kept_lines: list[str] = []
|
||||||
for line in str(content).splitlines():
|
for line in str(content).splitlines():
|
||||||
placeholder = re.fullmatch(r'\s*\{\{#\$output\.([^#{}]+)#\}\}\s*', line)
|
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())
|
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]:
|
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 [])
|
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)
|
field_type = _dingtalk_field_type(field)
|
||||||
display_value = _dingtalk_display_input_value(field, value)
|
display_value = _dingtalk_display_input_value(field, value)
|
||||||
if field_type == 'select':
|
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'}:
|
elif field_type in {'file', 'file-list'}:
|
||||||
lines.append(f'✅ 已上传 {field_name}:**{display_value}**')
|
lines.append(f'✅ 已上传 {field_name}:{display_value}')
|
||||||
else:
|
else:
|
||||||
lines.append(f'✅ 已填写 {field_name}:**{display_value}**')
|
lines.append(f'✅ 已填写 {field_name}:{display_value}')
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
@@ -915,9 +953,9 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
if node_title:
|
if node_title:
|
||||||
parts.append(f'**{node_title}**')
|
parts.append(f'**{node_title}**')
|
||||||
if form_content:
|
if form_content:
|
||||||
parts.append(form_content)
|
parts.append(_dingtalk_card_markdown(form_content))
|
||||||
completed_lines = _dingtalk_completed_input_lines(form_data)
|
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))
|
parts.append('<hr>' + '<br>'.join(completed_lines))
|
||||||
input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data)
|
input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data)
|
||||||
if input_hint_lines:
|
if input_hint_lines:
|
||||||
@@ -1017,9 +1055,9 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
if node_title:
|
if node_title:
|
||||||
parts.append(f'**{node_title}**')
|
parts.append(f'**{node_title}**')
|
||||||
if form_content:
|
if form_content:
|
||||||
parts.append(form_content)
|
parts.append(_dingtalk_card_markdown(form_content))
|
||||||
completed_lines = _dingtalk_completed_input_lines(form_data)
|
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))
|
parts.append('<hr>' + '<br>'.join(completed_lines))
|
||||||
input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data)
|
input_hint_lines = [] if native_field else _dingtalk_input_hint_lines(form_data)
|
||||||
if input_hint_lines:
|
if input_hint_lines:
|
||||||
@@ -1457,14 +1495,14 @@ class DingTalkAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
|||||||
if node_title:
|
if node_title:
|
||||||
parts.append(f'**{node_title}**')
|
parts.append(f'**{node_title}**')
|
||||||
if form_content:
|
if form_content:
|
||||||
parts.append(form_content)
|
parts.append(_dingtalk_card_markdown(form_content))
|
||||||
completed_lines = _dingtalk_completed_input_lines(
|
completed_lines = _dingtalk_completed_input_lines(
|
||||||
{
|
{
|
||||||
'input_defs': input_defs or [],
|
'input_defs': input_defs or [],
|
||||||
'inputs': inputs 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('<hr>' + '<br>'.join(completed_lines))
|
||||||
parts.append(f'<hr>✅ 已选择:**{action_title}**')
|
parts.append(f'<hr>✅ 已选择:**{action_title}**')
|
||||||
content = '<br><br>'.join(parts)
|
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 (
|
from langbot.pkg.platform.sources.dingtalk import (
|
||||||
DingTalkAdapter,
|
DingTalkAdapter,
|
||||||
|
_dingtalk_card_markdown,
|
||||||
_dingtalk_clean_form_content,
|
_dingtalk_clean_form_content,
|
||||||
_dingtalk_completed_input_lines,
|
_dingtalk_completed_input_lines,
|
||||||
_dingtalk_extract_component_inputs,
|
_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():
|
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'
|
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:
|
def _build_card_action_adapter() -> DingTalkAdapter:
|
||||||
adapter = DingTalkAdapter.model_construct(
|
adapter = DingTalkAdapter.model_construct(
|
||||||
card_state={
|
card_state={
|
||||||
|
|||||||
Reference in New Issue
Block a user