You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_http.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. import time
  2. import uuid
  3. from urllib.parse import urlencode
  4. import pytest
  5. from core.app.entities.app_invoke_entities import InvokeFrom
  6. from core.workflow.entities.variable_pool import VariablePool
  7. from core.workflow.graph_engine.entities.graph import Graph
  8. from core.workflow.graph_engine.entities.graph_init_params import GraphInitParams
  9. from core.workflow.graph_engine.entities.graph_runtime_state import GraphRuntimeState
  10. from core.workflow.nodes.http_request.node import HttpRequestNode
  11. from core.workflow.system_variable import SystemVariable
  12. from models.enums import UserFrom
  13. from models.workflow import WorkflowType
  14. from tests.integration_tests.workflow.nodes.__mock.http import setup_http_mock
  15. def init_http_node(config: dict):
  16. graph_config = {
  17. "edges": [
  18. {
  19. "id": "start-source-next-target",
  20. "source": "start",
  21. "target": "1",
  22. },
  23. ],
  24. "nodes": [{"data": {"type": "start"}, "id": "start"}, config],
  25. }
  26. graph = Graph.init(graph_config=graph_config)
  27. init_params = GraphInitParams(
  28. tenant_id="1",
  29. app_id="1",
  30. workflow_type=WorkflowType.WORKFLOW,
  31. workflow_id="1",
  32. graph_config=graph_config,
  33. user_id="1",
  34. user_from=UserFrom.ACCOUNT,
  35. invoke_from=InvokeFrom.DEBUGGER,
  36. call_depth=0,
  37. )
  38. # construct variable pool
  39. variable_pool = VariablePool(
  40. system_variables=SystemVariable(user_id="aaa", files=[]),
  41. user_inputs={},
  42. environment_variables=[],
  43. conversation_variables=[],
  44. )
  45. variable_pool.add(["a", "args1"], 1)
  46. variable_pool.add(["a", "args2"], 2)
  47. node = HttpRequestNode(
  48. id=str(uuid.uuid4()),
  49. graph_init_params=init_params,
  50. graph=graph,
  51. graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()),
  52. config=config,
  53. )
  54. # Initialize node data
  55. if "data" in config:
  56. node.init_node_data(config["data"])
  57. return node
  58. @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True)
  59. def test_get(setup_http_mock):
  60. node = init_http_node(
  61. config={
  62. "id": "1",
  63. "data": {
  64. "title": "http",
  65. "desc": "",
  66. "method": "get",
  67. "url": "http://example.com",
  68. "authorization": {
  69. "type": "api-key",
  70. "config": {
  71. "type": "basic",
  72. "api_key": "ak-xxx",
  73. "header": "api-key",
  74. },
  75. },
  76. "headers": "X-Header:123",
  77. "params": "A:b",
  78. "body": None,
  79. },
  80. }
  81. )
  82. result = node._run()
  83. assert result.process_data is not None
  84. data = result.process_data.get("request", "")
  85. assert "?A=b" in data
  86. assert "X-Header: 123" in data
  87. @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True)
  88. def test_no_auth(setup_http_mock):
  89. node = init_http_node(
  90. config={
  91. "id": "1",
  92. "data": {
  93. "title": "http",
  94. "desc": "",
  95. "method": "get",
  96. "url": "http://example.com",
  97. "authorization": {
  98. "type": "no-auth",
  99. "config": None,
  100. },
  101. "headers": "X-Header:123",
  102. "params": "A:b",
  103. "body": None,
  104. },
  105. }
  106. )
  107. result = node._run()
  108. assert result.process_data is not None
  109. data = result.process_data.get("request", "")
  110. assert "?A=b" in data
  111. assert "X-Header: 123" in data
  112. @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True)
  113. def test_custom_authorization_header(setup_http_mock):
  114. node = init_http_node(
  115. config={
  116. "id": "1",
  117. "data": {
  118. "title": "http",
  119. "desc": "",
  120. "method": "get",
  121. "url": "http://example.com",
  122. "authorization": {
  123. "type": "api-key",
  124. "config": {
  125. "type": "custom",
  126. "api_key": "Auth",
  127. "header": "X-Auth",
  128. },
  129. },
  130. "headers": "X-Header:123",
  131. "params": "A:b",
  132. "body": None,
  133. },
  134. }
  135. )
  136. result = node._run()
  137. assert result.process_data is not None
  138. data = result.process_data.get("request", "")
  139. assert "?A=b" in data
  140. assert "X-Header: 123" in data
  141. @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True)
  142. def test_template(setup_http_mock):
  143. node = init_http_node(
  144. config={
  145. "id": "1",
  146. "data": {
  147. "title": "http",
  148. "desc": "",
  149. "method": "get",
  150. "url": "http://example.com/{{#a.args2#}}",
  151. "authorization": {
  152. "type": "api-key",
  153. "config": {
  154. "type": "basic",
  155. "api_key": "ak-xxx",
  156. "header": "api-key",
  157. },
  158. },
  159. "headers": "X-Header:123\nX-Header2:{{#a.args2#}}",
  160. "params": "A:b\nTemplate:{{#a.args2#}}",
  161. "body": None,
  162. },
  163. }
  164. )
  165. result = node._run()
  166. assert result.process_data is not None
  167. data = result.process_data.get("request", "")
  168. assert "?A=b" in data
  169. assert "Template=2" in data
  170. assert "X-Header: 123" in data
  171. assert "X-Header2: 2" in data
  172. @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True)
  173. def test_json(setup_http_mock):
  174. node = init_http_node(
  175. config={
  176. "id": "1",
  177. "data": {
  178. "title": "http",
  179. "desc": "",
  180. "method": "post",
  181. "url": "http://example.com",
  182. "authorization": {
  183. "type": "api-key",
  184. "config": {
  185. "type": "basic",
  186. "api_key": "ak-xxx",
  187. "header": "api-key",
  188. },
  189. },
  190. "headers": "X-Header:123",
  191. "params": "A:b",
  192. "body": {
  193. "type": "json",
  194. "data": [
  195. {
  196. "key": "",
  197. "type": "text",
  198. "value": '{"a": "{{#a.args1#}}"}',
  199. },
  200. ],
  201. },
  202. },
  203. }
  204. )
  205. result = node._run()
  206. assert result.process_data is not None
  207. data = result.process_data.get("request", "")
  208. assert '{"a": "1"}' in data
  209. assert "X-Header: 123" in data
  210. def test_x_www_form_urlencoded(setup_http_mock):
  211. node = init_http_node(
  212. config={
  213. "id": "1",
  214. "data": {
  215. "title": "http",
  216. "desc": "",
  217. "method": "post",
  218. "url": "http://example.com",
  219. "authorization": {
  220. "type": "api-key",
  221. "config": {
  222. "type": "basic",
  223. "api_key": "ak-xxx",
  224. "header": "api-key",
  225. },
  226. },
  227. "headers": "X-Header:123",
  228. "params": "A:b",
  229. "body": {
  230. "type": "x-www-form-urlencoded",
  231. "data": [
  232. {
  233. "key": "a",
  234. "type": "text",
  235. "value": "{{#a.args1#}}",
  236. },
  237. {
  238. "key": "b",
  239. "type": "text",
  240. "value": "{{#a.args2#}}",
  241. },
  242. ],
  243. },
  244. },
  245. }
  246. )
  247. result = node._run()
  248. assert result.process_data is not None
  249. data = result.process_data.get("request", "")
  250. assert "a=1&b=2" in data
  251. assert "X-Header: 123" in data
  252. def test_form_data(setup_http_mock):
  253. node = init_http_node(
  254. config={
  255. "id": "1",
  256. "data": {
  257. "title": "http",
  258. "desc": "",
  259. "method": "post",
  260. "url": "http://example.com",
  261. "authorization": {
  262. "type": "api-key",
  263. "config": {
  264. "type": "basic",
  265. "api_key": "ak-xxx",
  266. "header": "api-key",
  267. },
  268. },
  269. "headers": "X-Header:123",
  270. "params": "A:b",
  271. "body": {
  272. "type": "form-data",
  273. "data": [
  274. {
  275. "key": "a",
  276. "type": "text",
  277. "value": "{{#a.args1#}}",
  278. },
  279. {
  280. "key": "b",
  281. "type": "text",
  282. "value": "{{#a.args2#}}",
  283. },
  284. ],
  285. },
  286. },
  287. }
  288. )
  289. result = node._run()
  290. assert result.process_data is not None
  291. data = result.process_data.get("request", "")
  292. assert 'form-data; name="a"' in data
  293. assert "1" in data
  294. assert 'form-data; name="b"' in data
  295. assert "2" in data
  296. assert "X-Header: 123" in data
  297. def test_none_data(setup_http_mock):
  298. node = init_http_node(
  299. config={
  300. "id": "1",
  301. "data": {
  302. "title": "http",
  303. "desc": "",
  304. "method": "post",
  305. "url": "http://example.com",
  306. "authorization": {
  307. "type": "api-key",
  308. "config": {
  309. "type": "basic",
  310. "api_key": "ak-xxx",
  311. "header": "api-key",
  312. },
  313. },
  314. "headers": "X-Header:123",
  315. "params": "A:b",
  316. "body": {"type": "none", "data": []},
  317. },
  318. }
  319. )
  320. result = node._run()
  321. assert result.process_data is not None
  322. data = result.process_data.get("request", "")
  323. assert "X-Header: 123" in data
  324. assert "123123123" not in data
  325. def test_mock_404(setup_http_mock):
  326. node = init_http_node(
  327. config={
  328. "id": "1",
  329. "data": {
  330. "title": "http",
  331. "desc": "",
  332. "method": "get",
  333. "url": "http://404.com",
  334. "authorization": {
  335. "type": "no-auth",
  336. "config": None,
  337. },
  338. "body": None,
  339. "params": "",
  340. "headers": "X-Header:123",
  341. },
  342. }
  343. )
  344. result = node._run()
  345. assert result.outputs is not None
  346. resp = result.outputs
  347. assert resp.get("status_code") == 404
  348. assert "Not Found" in resp.get("body", "")
  349. def test_multi_colons_parse(setup_http_mock):
  350. node = init_http_node(
  351. config={
  352. "id": "1",
  353. "data": {
  354. "title": "http",
  355. "desc": "",
  356. "method": "get",
  357. "url": "http://example.com",
  358. "authorization": {
  359. "type": "no-auth",
  360. "config": None,
  361. },
  362. "params": "Referer:http://example1.com\nRedirect:http://example2.com",
  363. "headers": "Referer:http://example3.com\nRedirect:http://example4.com",
  364. "body": {
  365. "type": "form-data",
  366. "data": [
  367. {
  368. "key": "Referer",
  369. "type": "text",
  370. "value": "http://example5.com",
  371. },
  372. {
  373. "key": "Redirect",
  374. "type": "text",
  375. "value": "http://example6.com",
  376. },
  377. ],
  378. },
  379. },
  380. }
  381. )
  382. result = node._run()
  383. assert result.process_data is not None
  384. assert result.outputs is not None
  385. assert urlencode({"Redirect": "http://example2.com"}) in result.process_data.get("request", "")
  386. assert 'form-data; name="Redirect"\r\n\r\nhttp://example6.com' in result.process_data.get("request", "")
  387. # resp = result.outputs
  388. # assert "http://example3.com" == resp.get("headers", {}).get("referer")
  389. @pytest.mark.parametrize("setup_http_mock", [["none"]], indirect=True)
  390. def test_nested_object_variable_selector(setup_http_mock):
  391. """Test variable selector functionality with nested object properties."""
  392. # Create independent test setup without affecting other tests
  393. graph_config = {
  394. "edges": [
  395. {
  396. "id": "start-source-next-target",
  397. "source": "start",
  398. "target": "1",
  399. },
  400. ],
  401. "nodes": [
  402. {"data": {"type": "start"}, "id": "start"},
  403. {
  404. "id": "1",
  405. "data": {
  406. "title": "http",
  407. "desc": "",
  408. "method": "get",
  409. "url": "http://example.com/{{#a.args2#}}/{{#a.args3.nested#}}",
  410. "authorization": {
  411. "type": "api-key",
  412. "config": {
  413. "type": "basic",
  414. "api_key": "ak-xxx",
  415. "header": "api-key",
  416. },
  417. },
  418. "headers": "X-Header:{{#a.args3.nested#}}",
  419. "params": "nested_param:{{#a.args3.nested#}}",
  420. "body": None,
  421. },
  422. },
  423. ],
  424. }
  425. graph = Graph.init(graph_config=graph_config)
  426. init_params = GraphInitParams(
  427. tenant_id="1",
  428. app_id="1",
  429. workflow_type=WorkflowType.WORKFLOW,
  430. workflow_id="1",
  431. graph_config=graph_config,
  432. user_id="1",
  433. user_from=UserFrom.ACCOUNT,
  434. invoke_from=InvokeFrom.DEBUGGER,
  435. call_depth=0,
  436. )
  437. # Create independent variable pool for this test only
  438. variable_pool = VariablePool(
  439. system_variables=SystemVariable(user_id="aaa", files=[]),
  440. user_inputs={},
  441. environment_variables=[],
  442. conversation_variables=[],
  443. )
  444. variable_pool.add(["a", "args1"], 1)
  445. variable_pool.add(["a", "args2"], 2)
  446. variable_pool.add(["a", "args3"], {"nested": "nested_value"}) # Only for this test
  447. node = HttpRequestNode(
  448. id=str(uuid.uuid4()),
  449. graph_init_params=init_params,
  450. graph=graph,
  451. graph_runtime_state=GraphRuntimeState(variable_pool=variable_pool, start_at=time.perf_counter()),
  452. config=graph_config["nodes"][1],
  453. )
  454. # Initialize node data
  455. if "data" in graph_config["nodes"][1]:
  456. node.init_node_data(graph_config["nodes"][1]["data"])
  457. result = node._run()
  458. assert result.process_data is not None
  459. data = result.process_data.get("request", "")
  460. # Verify nested object property is correctly resolved
  461. assert "/2/nested_value" in data # URL path should contain resolved nested value
  462. assert "X-Header: nested_value" in data # Header should contain nested value
  463. assert "nested_param=nested_value" in data # Param should contain nested value