diff --git a/api/services/workflow/trigger_paths.py b/api/services/workflow/trigger_paths.py index ed34345b..f53f6934 100644 --- a/api/services/workflow/trigger_paths.py +++ b/api/services/workflow/trigger_paths.py @@ -127,8 +127,7 @@ def validate_trigger_paths( ) ) - first_node_id = seen_paths.get(trigger_path) - if first_node_id is None: + if trigger_path not in seen_paths: seen_paths[trigger_path] = node_id else: issues.append( diff --git a/api/tests/test_trigger_path_validation.py b/api/tests/test_trigger_path_validation.py index 758eceb4..1b828450 100644 --- a/api/tests/test_trigger_path_validation.py +++ b/api/tests/test_trigger_path_validation.py @@ -54,3 +54,37 @@ def test_validate_trigger_paths_rejects_long_and_duplicate_paths(): in messages ) assert "Trigger path is duplicated in this workflow." in messages + + +def test_validate_trigger_paths_detects_duplicate_when_first_node_has_no_id(): + """A duplicate trigger path must be flagged even when the first node sharing + that path has no ``id`` (``node.get("id")`` is None). + + Regression: the duplicate check previously used ``seen_paths.get(path)`` and + treated a ``None`` result as "not seen yet", so a first node with a missing + id (stored as None) made every later node with the same path slip through + undetected. + """ + workflow_definition = { + "nodes": [ + # No "id" key -> node_id resolves to None. + {"type": "trigger", "data": {"trigger_path": "sales_agent"}}, + { + "id": "trigger-2", + "type": "trigger", + "data": {"trigger_path": "sales_agent"}, + }, + ], + "edges": [], + } + + issues = validate_trigger_paths(workflow_definition) + messages = [issue.message for issue in issues] + + assert "Trigger path is duplicated in this workflow." in messages + duplicate_issue = next( + issue + for issue in issues + if issue.message == "Trigger path is duplicated in this workflow." + ) + assert duplicate_issue.node_id == "trigger-2"