feat: Enhance API request component (#8070)
* update the api request component * [autofix.ci] apply automated fixes * update the component * Update test_api_request_component.py * [autofix.ci] apply automated fixes * remove MODE_CONFIG unused variable * [autofix.ci] apply automated fixes * use normalize function * Update template * Update test_api_request_component.py * UI test fix * selector fix --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Yuqi Tang <yuqi.tang@datastax.com> Co-authored-by: Mike Fortman <michael.fortman@datastax.com>
This commit is contained in:
parent
c02c237567
commit
ba92fc1e78
5 changed files with 256 additions and 351 deletions
|
|
@ -1,5 +1,4 @@
|
|||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import aiofiles
|
||||
import aiofiles.os
|
||||
|
|
@ -8,7 +7,8 @@ import pytest
|
|||
import respx
|
||||
from httpx import Response
|
||||
from langflow.components.data import APIRequestComponent
|
||||
from langflow.schema import Data, DataFrame
|
||||
from langflow.schema import Data
|
||||
from langflow.schema.dotdict import dotdict
|
||||
|
||||
from tests.base import ComponentTestBaseWithoutClient
|
||||
|
||||
|
|
@ -23,16 +23,17 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
def default_kwargs(self):
|
||||
"""Return the default kwargs for the component."""
|
||||
return {
|
||||
"urls": ["https://example.com/api/test"],
|
||||
"url_input": "https://example.com/api/test",
|
||||
"method": "GET",
|
||||
"headers": [],
|
||||
"headers": [{"key": "User-Agent", "value": "test-agent"}],
|
||||
"body": [],
|
||||
"timeout": 5,
|
||||
"timeout": 30,
|
||||
"follow_redirects": True,
|
||||
"save_to_file": False,
|
||||
"include_httpx_metadata": False,
|
||||
"use_curl": False,
|
||||
"curl": "",
|
||||
"mode": "URL",
|
||||
"curl_input": "",
|
||||
"query_params": {},
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
|
|
@ -50,16 +51,18 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
curl_cmd = (
|
||||
"curl -X GET https://example.com/api/test -H 'Content-Type: application/json' -d '{\"key\": \"value\"}'"
|
||||
)
|
||||
build_config = {
|
||||
"method": {"value": ""},
|
||||
"urls": {"value": []},
|
||||
"headers": {},
|
||||
"body": {},
|
||||
}
|
||||
build_config = dotdict(
|
||||
{
|
||||
"method": {"value": ""},
|
||||
"url_input": {"value": ""},
|
||||
"headers": {"value": []},
|
||||
"body": {"value": []},
|
||||
}
|
||||
)
|
||||
new_build_config = component.parse_curl(curl_cmd, build_config.copy())
|
||||
|
||||
assert new_build_config["method"]["value"] == "GET"
|
||||
assert new_build_config["urls"]["value"] == ["https://example.com/api/test"]
|
||||
assert new_build_config["url_input"]["value"] == "https://example.com/api/test"
|
||||
assert new_build_config["headers"]["value"] == [{"key": "Content-Type", "value": "application/json"}]
|
||||
assert new_build_config["body"]["value"] == [{"key": "key", "value": "value"}]
|
||||
|
||||
|
|
@ -78,7 +81,6 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
# The JSON response is nested in the 'result' key
|
||||
assert "result" in result.data
|
||||
assert result.data["result"]["key"] == "value"
|
||||
|
||||
|
|
@ -145,6 +147,7 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
assert result.data["result"] == binary_content
|
||||
|
||||
@respx.mock
|
||||
async def test_make_request_timeout(self, component):
|
||||
|
|
@ -160,8 +163,8 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
)
|
||||
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["status_code"] == 408
|
||||
assert result.data["error"] == "Request timed out"
|
||||
assert result.data["status_code"] == 500
|
||||
assert "Request timed out" in result.data["error"]
|
||||
|
||||
@respx.mock
|
||||
async def test_make_request_with_redirects(self, component):
|
||||
|
|
@ -235,55 +238,55 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
assert "param1=value1" in result
|
||||
assert "param2=value2" in result
|
||||
|
||||
async def test_output_formats(self, component):
|
||||
# Test different output formats
|
||||
with patch.object(component, "make_requests") as mock_make_requests:
|
||||
mock_make_requests.return_value = [Data(data={"key": "value"})]
|
||||
async def test_make_api_requests(self, component):
|
||||
# Test making API requests
|
||||
url = "https://example.com/api/test"
|
||||
response_data = {"key": "value"}
|
||||
|
||||
# Test DataFrame output
|
||||
df_result = await component.as_dataframe()
|
||||
assert isinstance(df_result, DataFrame)
|
||||
with respx.mock:
|
||||
respx.get(url).mock(return_value=Response(200, json=response_data))
|
||||
|
||||
# Test Data output - to_data returns a list of Data objects
|
||||
test_data = {"test": "value"}
|
||||
data_result = component.to_data(test_data)
|
||||
assert isinstance(data_result, list)
|
||||
assert all(isinstance(item, Data) for item in data_result)
|
||||
result = await component.make_api_requests()
|
||||
|
||||
assert isinstance(result, Data)
|
||||
assert result.data["source"] == url
|
||||
assert result.data["result"]["key"] == "value"
|
||||
|
||||
async def test_invalid_urls(self, component):
|
||||
# Test invalid URL handling
|
||||
component.urls = ["not_a_valid_url"]
|
||||
with pytest.raises(ValueError, match="Invalid URLs provided"):
|
||||
await component.make_requests()
|
||||
component.url_input = "not_a_valid_url"
|
||||
with pytest.raises(ValueError, match="Invalid URL provided"):
|
||||
await component.make_api_requests()
|
||||
|
||||
async def test_update_build_config(self, component):
|
||||
# Test build config updates
|
||||
build_config = {
|
||||
"method": {"value": "GET", "advanced": False},
|
||||
"urls": {"value": [], "advanced": False},
|
||||
"headers": {"value": [], "advanced": True},
|
||||
"body": {"value": [], "advanced": True},
|
||||
"use_curl": {"value": False, "advanced": False},
|
||||
"curl": {"value": "", "advanced": True},
|
||||
"timeout": {"value": 5, "advanced": True},
|
||||
"follow_redirects": {"value": True, "advanced": True},
|
||||
"save_to_file": {"value": False, "advanced": True},
|
||||
"include_httpx_metadata": {"value": False, "advanced": True},
|
||||
"query_params": {"value": {}, "advanced": True},
|
||||
}
|
||||
|
||||
# Test curl mode update
|
||||
updated = component.update_build_config(
|
||||
build_config=build_config.copy(), field_value=True, field_name="use_curl"
|
||||
build_config = dotdict(
|
||||
{
|
||||
"method": {"value": "GET", "advanced": False},
|
||||
"url_input": {"value": "", "advanced": False},
|
||||
"headers": {"value": [], "advanced": True},
|
||||
"body": {"value": [], "advanced": True},
|
||||
"mode": {"value": "URL", "advanced": False},
|
||||
"curl_input": {"value": "curl -X GET https://example.com/api/test", "advanced": True},
|
||||
"timeout": {"value": 30, "advanced": True},
|
||||
"follow_redirects": {"value": True, "advanced": True},
|
||||
"save_to_file": {"value": False, "advanced": True},
|
||||
"include_httpx_metadata": {"value": False, "advanced": True},
|
||||
"query_params": {"value": {}, "advanced": True},
|
||||
}
|
||||
)
|
||||
assert updated["curl"]["advanced"] is False
|
||||
assert updated["urls"]["advanced"] is True
|
||||
|
||||
# Test method update
|
||||
updated = component.update_build_config(
|
||||
build_config=build_config.copy(), field_value="POST", field_name="method"
|
||||
)
|
||||
assert updated["body"]["advanced"] is False
|
||||
# Test URL mode
|
||||
updated = component.update_build_config(build_config=build_config.copy(), field_value="URL", field_name="mode")
|
||||
assert updated["curl_input"]["advanced"] is True
|
||||
assert updated["url_input"]["advanced"] is False
|
||||
|
||||
# Set the component's curl_input attribute to match the build_config before switching to cURL mode
|
||||
component.curl_input = build_config["curl_input"]["value"]
|
||||
# Test cURL mode
|
||||
updated = component.update_build_config(build_config=build_config.copy(), field_value="cURL", field_name="mode")
|
||||
assert updated["curl_input"]["advanced"] is False
|
||||
assert updated["url_input"]["advanced"] is True
|
||||
|
||||
@respx.mock
|
||||
async def test_error_handling(self, component):
|
||||
|
|
@ -307,3 +310,24 @@ class TestAPIRequestComponent(ComponentTestBaseWithoutClient):
|
|||
method="INVALID",
|
||||
url=url,
|
||||
)
|
||||
|
||||
async def test_response_info(self, component):
|
||||
# Test response info handling
|
||||
url = "https://example.com/api/test"
|
||||
request = httpx.Request("GET", url)
|
||||
response = Response(200, text="test content", request=request)
|
||||
is_binary, file_path = await component._response_info(response, with_file_path=True)
|
||||
|
||||
assert not is_binary
|
||||
assert file_path is not None
|
||||
assert file_path.suffix == ".txt"
|
||||
|
||||
# Test binary response
|
||||
binary_response = Response(
|
||||
200, content=b"binary content", headers={"Content-Type": "application/octet-stream"}, request=request
|
||||
)
|
||||
is_binary, file_path = await component._response_info(binary_response, with_file_path=True)
|
||||
|
||||
assert is_binary
|
||||
assert file_path is not None
|
||||
assert file_path.suffix == ".bin"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue