Переглянути джерело

fix(http_request): simplify JSON handling in requests (#9616)

-LAN- 6 місяців тому
батько
коміт
7263af13ed

+ 6 - 12
api/core/workflow/nodes/http_request/executor.py

@@ -1,5 +1,5 @@
 import json
-from collections.abc import Mapping, Sequence
+from collections.abc import Mapping
 from copy import deepcopy
 from random import randint
 from typing import Any, Literal
@@ -60,7 +60,7 @@ class Executor:
         self.method = node_data.method
         self.auth = node_data.authorization
         self.timeout = timeout
-        self.params = None
+        self.params = {}
         self.headers = {}
         self.content = None
         self.files = None
@@ -108,8 +108,10 @@ class Executor:
                 case "raw-text":
                     self.content = self.variable_pool.convert_template(data[0].value).text
                 case "json":
-                    json_object = json.loads(data[0].value)
-                    self.json = self._parse_object_contains_variables(json_object)
+                    json_string = self.variable_pool.convert_template(data[0].value).text
+                    json_object = json.loads(json_string)
+                    self.json = json_object
+                    # self.json = self._parse_object_contains_variables(json_object)
                 case "binary":
                     file_selector = data[0].file
                     file_variable = self.variable_pool.get_file(file_selector)
@@ -274,14 +276,6 @@ class Executor:
 
         return raw
 
-    def _parse_object_contains_variables(self, obj: str | dict | list, /) -> Mapping[str, Any] | Sequence[Any] | str:
-        if isinstance(obj, dict):
-            return {k: self._parse_object_contains_variables(v) for k, v in obj.items()}
-        elif isinstance(obj, list):
-            return [self._parse_object_contains_variables(v) for v in obj]
-        elif isinstance(obj, str):
-            return self.variable_pool.convert_template(obj).text
-
 
 def _plain_text_to_dict(text: str, /) -> dict[str, str]:
     """

+ 168 - 1
api/tests/unit_tests/core/workflow/nodes/test_http_request_node.py

@@ -1,3 +1,5 @@
+import json
+
 import httpx
 
 from core.app.entities.app_invoke_entities import InvokeFrom
@@ -14,7 +16,8 @@ from core.workflow.nodes.http_request import (
     HttpRequestNodeBody,
     HttpRequestNodeData,
 )
-from core.workflow.nodes.http_request.executor import _plain_text_to_dict
+from core.workflow.nodes.http_request.entities import HttpRequestNodeTimeout
+from core.workflow.nodes.http_request.executor import Executor, _plain_text_to_dict
 from models.enums import UserFrom
 from models.workflow import WorkflowNodeExecutionStatus, WorkflowType
 
@@ -200,3 +203,167 @@ def test_http_request_node_form_with_file(monkeypatch):
     assert result.status == WorkflowNodeExecutionStatus.SUCCEEDED
     assert result.outputs is not None
     assert result.outputs["body"] == ""
+
+
+def test_executor_with_json_body_and_number_variable():
+    # Prepare the variable pool
+    variable_pool = VariablePool(
+        system_variables={},
+        user_inputs={},
+    )
+    variable_pool.add(["pre_node_id", "number"], 42)
+
+    # Prepare the node data
+    node_data = HttpRequestNodeData(
+        title="Test JSON Body with Number Variable",
+        method="post",
+        url="https://api.example.com/data",
+        authorization=HttpRequestNodeAuthorization(type="no-auth"),
+        headers="Content-Type: application/json",
+        params="",
+        body=HttpRequestNodeBody(
+            type="json",
+            data=[
+                BodyData(
+                    key="",
+                    type="text",
+                    value='{"number": {{#pre_node_id.number#}}}',
+                )
+            ],
+        ),
+    )
+
+    # Initialize the Executor
+    executor = Executor(
+        node_data=node_data,
+        timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
+        variable_pool=variable_pool,
+    )
+
+    # Check the executor's data
+    assert executor.method == "post"
+    assert executor.url == "https://api.example.com/data"
+    assert executor.headers == {"Content-Type": "application/json"}
+    assert executor.params == {}
+    assert executor.json == {"number": 42}
+    assert executor.data is None
+    assert executor.files is None
+    assert executor.content is None
+
+    # Check the raw request (to_log method)
+    raw_request = executor.to_log()
+    assert "POST /data HTTP/1.1" in raw_request
+    assert "Host: api.example.com" in raw_request
+    assert "Content-Type: application/json" in raw_request
+    assert '{"number": 42}' in raw_request
+
+
+def test_executor_with_json_body_and_object_variable():
+    # Prepare the variable pool
+    variable_pool = VariablePool(
+        system_variables={},
+        user_inputs={},
+    )
+    variable_pool.add(["pre_node_id", "object"], {"name": "John Doe", "age": 30, "email": "john@example.com"})
+
+    # Prepare the node data
+    node_data = HttpRequestNodeData(
+        title="Test JSON Body with Object Variable",
+        method="post",
+        url="https://api.example.com/data",
+        authorization=HttpRequestNodeAuthorization(type="no-auth"),
+        headers="Content-Type: application/json",
+        params="",
+        body=HttpRequestNodeBody(
+            type="json",
+            data=[
+                BodyData(
+                    key="",
+                    type="text",
+                    value="{{#pre_node_id.object#}}",
+                )
+            ],
+        ),
+    )
+
+    # Initialize the Executor
+    executor = Executor(
+        node_data=node_data,
+        timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
+        variable_pool=variable_pool,
+    )
+
+    # Check the executor's data
+    assert executor.method == "post"
+    assert executor.url == "https://api.example.com/data"
+    assert executor.headers == {"Content-Type": "application/json"}
+    assert executor.params == {}
+    assert executor.json == {"name": "John Doe", "age": 30, "email": "john@example.com"}
+    assert executor.data is None
+    assert executor.files is None
+    assert executor.content is None
+
+    # Check the raw request (to_log method)
+    raw_request = executor.to_log()
+    assert "POST /data HTTP/1.1" in raw_request
+    assert "Host: api.example.com" in raw_request
+    assert "Content-Type: application/json" in raw_request
+    assert '"name": "John Doe"' in raw_request
+    assert '"age": 30' in raw_request
+    assert '"email": "john@example.com"' in raw_request
+
+
+def test_executor_with_json_body_and_nested_object_variable():
+    # Prepare the variable pool
+    variable_pool = VariablePool(
+        system_variables={},
+        user_inputs={},
+    )
+    variable_pool.add(["pre_node_id", "object"], {"name": "John Doe", "age": 30, "email": "john@example.com"})
+
+    # Prepare the node data
+    node_data = HttpRequestNodeData(
+        title="Test JSON Body with Nested Object Variable",
+        method="post",
+        url="https://api.example.com/data",
+        authorization=HttpRequestNodeAuthorization(type="no-auth"),
+        headers="Content-Type: application/json",
+        params="",
+        body=HttpRequestNodeBody(
+            type="json",
+            data=[
+                BodyData(
+                    key="",
+                    type="text",
+                    value='{"object": {{#pre_node_id.object#}}}',
+                )
+            ],
+        ),
+    )
+
+    # Initialize the Executor
+    executor = Executor(
+        node_data=node_data,
+        timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
+        variable_pool=variable_pool,
+    )
+
+    # Check the executor's data
+    assert executor.method == "post"
+    assert executor.url == "https://api.example.com/data"
+    assert executor.headers == {"Content-Type": "application/json"}
+    assert executor.params == {}
+    assert executor.json == {"object": {"name": "John Doe", "age": 30, "email": "john@example.com"}}
+    assert executor.data is None
+    assert executor.files is None
+    assert executor.content is None
+
+    # Check the raw request (to_log method)
+    raw_request = executor.to_log()
+    assert "POST /data HTTP/1.1" in raw_request
+    assert "Host: api.example.com" in raw_request
+    assert "Content-Type: application/json" in raw_request
+    assert '"object": {' in raw_request
+    assert '"name": "John Doe"' in raw_request
+    assert '"age": 30' in raw_request
+    assert '"email": "john@example.com"' in raw_request