mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
* Add tuner integration * bump pipecat version * chore: update pipecat submodule to match upstream and use tuner-pipecat-sdk 0.2.0 Update pipecat submodule from 0.0.109.dev23 to 13e98d0d9 (the exact commit upstream dograh-hq/dograh uses after v1.30.1). This installs pipecat-ai as 1.1.0.post277 via setuptools_scm, satisfying tuner-pipecat-sdk 0.2.0's pipecat-ai>=1.0.0 requirement. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * wire tuner * feat: refactor integrations into self contained packages * chore: simplify ensure_public_access_token * fix: remove NodeSpec and make DTOs the source of truth * feat: send relevant signal to mcp using to_mcp_dict * fix: fix tests * cleanup: remove nango integrations * feat: add agents.md for integrations --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Abhishek Kumar <abhishek@a6k.me>
129 lines
4.1 KiB
Python
129 lines
4.1 KiB
Python
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from api.services.workflow.dto import ReactFlowDTO, sanitize_workflow_definition
|
|
|
|
_FIXTURES_DIR = Path(__file__).parent / "dto_fixtures"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_dto():
|
|
# Path resolved relative to this test file so the test works regardless
|
|
# of the cwd pytest is invoked from.
|
|
with open(_FIXTURES_DIR / "sample_branching_workflow.json", "r") as f:
|
|
dto = ReactFlowDTO.model_validate_json(f.read())
|
|
assert dto is not None
|
|
|
|
|
|
def test_dto_ignores_legacy_unknown_node_data_fields():
|
|
dto = ReactFlowDTO.model_validate(
|
|
{
|
|
"nodes": [
|
|
{
|
|
"id": "n1",
|
|
"type": "startCall",
|
|
"position": {"x": 0, "y": 0},
|
|
"data": {
|
|
"name": "Start",
|
|
"prompt": "Hello",
|
|
"is_static": True,
|
|
"detect_voicemail": True,
|
|
"wait_for_user_response": False,
|
|
"wait_for_user_response_timeout": 2.5,
|
|
"legacy_field": "ignored",
|
|
},
|
|
}
|
|
],
|
|
"edges": [],
|
|
}
|
|
)
|
|
|
|
data = dto.nodes[0].data.model_dump()
|
|
assert "is_static" not in data
|
|
assert "detect_voicemail" not in data
|
|
assert "wait_for_user_response" not in data
|
|
assert "wait_for_user_response_timeout" not in data
|
|
assert "legacy_field" not in data
|
|
|
|
|
|
def test_sanitize_strips_ui_runtime_fields():
|
|
definition = {
|
|
"viewport": {"x": 0, "y": 0, "zoom": 1},
|
|
"nodes": [
|
|
{
|
|
"id": "n1",
|
|
"type": "startCall",
|
|
"position": {"x": 0, "y": 0},
|
|
"width": 200, # ReactFlow-computed, preserved
|
|
"selected": True, # ReactFlow runtime, preserved
|
|
"data": {
|
|
"name": "Start",
|
|
"prompt": "hi",
|
|
"greeting": "hello",
|
|
"invalid": True, # UI-only, should be stripped
|
|
"validationMessage": "oops", # UI-only, should be stripped
|
|
"mystery_field": 42, # unknown, should be stripped
|
|
},
|
|
},
|
|
{
|
|
"id": "n2",
|
|
"type": "agentNode",
|
|
"position": {"x": 1, "y": 1},
|
|
"data": {"name": "A", "prompt": "p", "invalid": False},
|
|
},
|
|
],
|
|
"edges": [
|
|
{
|
|
"id": "e1",
|
|
"source": "n1",
|
|
"target": "n2",
|
|
"data": {
|
|
"label": "next",
|
|
"condition": "true",
|
|
"invalid": True, # UI-only, should be stripped
|
|
},
|
|
}
|
|
],
|
|
}
|
|
|
|
out = sanitize_workflow_definition(definition)
|
|
|
|
# Top-level keys preserved
|
|
assert out["viewport"] == {"x": 0, "y": 0, "zoom": 1}
|
|
# ReactFlow runtime fields on the node itself preserved
|
|
assert out["nodes"][0]["width"] == 200
|
|
assert out["nodes"][0]["selected"] is True
|
|
|
|
# node.data stripped of unknowns, known fields kept
|
|
n1_data = out["nodes"][0]["data"]
|
|
assert n1_data == {"name": "Start", "prompt": "hi", "greeting": "hello"}
|
|
assert "invalid" not in n1_data
|
|
assert "validationMessage" not in n1_data
|
|
assert "mystery_field" not in n1_data
|
|
|
|
n2_data = out["nodes"][1]["data"]
|
|
assert n2_data == {"name": "A", "prompt": "p"}
|
|
|
|
# edge.data stripped
|
|
assert out["edges"][0]["data"] == {"label": "next", "condition": "true"}
|
|
|
|
|
|
def test_sanitize_noop_on_empty_and_unknown_types():
|
|
assert sanitize_workflow_definition(None) is None
|
|
assert sanitize_workflow_definition({}) == {}
|
|
|
|
# Unknown node type: pass through unchanged rather than wipe data
|
|
definition = {
|
|
"nodes": [
|
|
{
|
|
"id": "n1",
|
|
"type": "unknownType",
|
|
"position": {"x": 0, "y": 0},
|
|
"data": {"anything": "goes"},
|
|
}
|
|
],
|
|
"edges": [],
|
|
}
|
|
out = sanitize_workflow_definition(definition)
|
|
assert out["nodes"][0]["data"] == {"anything": "goes"}
|