deer-flow/backend/tests/test_mcp_task_toolset_config.py
Aari 47b258ebd7
feat(mcp): add ordinary durable task driver (#4690)
* feat(mcp): add durable task runtime foundation

* fix(chart): sync embedded config version

* fix(mcp): isolate task polls during shutdown

* feat(mcp): track consecutive poll errors on mcp_tasks

poll_attempt_count grows on every claim (successful polls included), so it
cannot drive a failure backoff without misjudging normal long tasks. Add
consecutive_poll_error_count: incremented when a claim is released after a
poll error, reset to zero by any applied snapshot. The backoff/terminal
policy that consumes it lands with the first concrete driver.

* fix(mcp): harden durable task lifecycle

* feat(mcp): add ordinary durable task driver

* test(mcp): address durable task review feedback

* fix(mcp): preserve submit tool descriptions

* fix(mcp): bound remote task calls

* fix(mcp): bound persisted task payloads

* fix(mcp): preserve task tool error details

* fix(mcp): enforce durable task boundaries

* test(mcp): cover task config snapshot lifecycle

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-08-15 14:26:38 +08:00

134 lines
4.5 KiB
Python

import pytest
from pydantic import ValidationError
from app.gateway.routers.mcp import McpServerConfigResponse
from deerflow.config.extensions_config import ExtensionsConfig
def test_task_toolsets_preserve_raw_tool_names_and_support_multiple_groups() -> None:
config = ExtensionsConfig.model_validate(
{
"mcpServers": {
"reports": {
"type": "http",
"url": "https://example.test/mcp",
"task_toolsets": [
{
"name": "report-generation",
"submit_tool": "submit_report",
"status_tool": "get_report_status",
"cancel_tool": "cancel_report",
},
{
"name": "data-export",
"submit_tool": "start_export",
"status_tool": "get_export_status",
"cancel_tool": "cancel_export",
},
],
}
}
}
)
toolsets = config.mcp_servers["reports"].task_toolsets
assert [toolset.name for toolset in toolsets] == ["report-generation", "data-export"]
assert toolsets[0].model_dump() == {
"name": "report-generation",
"submit_tool": "submit_report",
"status_tool": "get_report_status",
"cancel_tool": "cancel_report",
}
response = McpServerConfigResponse.model_validate(config.mcp_servers["reports"].model_dump())
assert response.task_toolsets[0].submit_tool == "submit_report"
@pytest.mark.parametrize(
"duplicate_field,duplicate_value",
[
("status_tool", "submit_report"),
("cancel_tool", "submit_report"),
],
)
def test_task_toolsets_reject_reusing_one_raw_tool_in_multiple_roles(
duplicate_field: str,
duplicate_value: str,
) -> None:
toolset = {
"name": "report-generation",
"submit_tool": "submit_report",
"status_tool": "get_report_status",
"cancel_tool": "cancel_report",
}
toolset[duplicate_field] = duplicate_value
with pytest.raises(ValidationError, match="must be unique across task_toolsets"):
ExtensionsConfig.model_validate(
{
"mcpServers": {
"reports": {
"task_toolsets": [toolset],
}
}
}
)
def test_task_toolsets_reject_reusing_one_raw_tool_across_groups() -> None:
with pytest.raises(ValidationError, match="submit_report.*must be unique"):
ExtensionsConfig.model_validate(
{
"mcpServers": {
"reports": {
"task_toolsets": [
{
"name": "first",
"submit_tool": "submit_report",
"status_tool": "status_report",
"cancel_tool": "cancel_report",
},
{
"name": "second",
"submit_tool": "start_export",
"status_tool": "submit_report",
"cancel_tool": "cancel_export",
},
]
}
}
}
)
@pytest.mark.parametrize(
("server_name", "task_name", "match"),
[
(" ", "reports", "server name.*128"),
("s" * 129, "reports", "server name.*128"),
("reports", " ", "task toolset name must not be empty"),
("reports", "t" * 256, "at most 255"),
],
)
def test_task_toolsets_reject_names_that_do_not_fit_durable_storage(
server_name: str,
task_name: str,
match: str,
) -> None:
with pytest.raises(ValidationError, match=match):
ExtensionsConfig.model_validate(
{
"mcpServers": {
server_name: {
"task_toolsets": [
{
"name": task_name,
"submit_tool": "submit_report",
"status_tool": "status_report",
"cancel_tool": "cancel_report",
}
]
}
}
}
)