tests: update tests to use httpx.AsyncClient (#3984)

* Add async support and dependencies to pyproject.toml files

- Added `asgi-lifespan>=2.1.0` to dependencies.
- Configured `asyncio_mode` and `asyncio_default_fixture_loop_scope` for pytest.
- Updated `tool.uv` section with `asgi-lifespan` in dev-dependencies.

* Convert test fixtures to async and use AsyncClient for HTTP requests

* Handle 'ImportFrom' nodes in AST validation to support module attribute imports

* Convert test cases to use async HTTP client

- Updated test cases in `test_database.py`, `test_endpoints.py`, `test_user.py`, `test_variable.py`, `test_files.py`, `test_chat_endpoint.py`, `test_misc.py`, `test_messages_endpoints.py`, `test_api_key.py`, `test_webhook.py`, and `test_login.py` to use `httpx.AsyncClient` instead of `fastapi.TestClient`.
- Modified test functions to be asynchronous and use `await` for HTTP requests.
- Adjusted fixtures and helper functions to support asynchronous operations.
- Ensured consistency in endpoint paths and request methods across all test cases.

* Refactor string concatenation to f-string in test_chat_endpoint.py

* [autofix.ci] apply automated fixes

* Refactor import validation to use pattern matching for AST nodes

* Set `startup_timeout` and `shutdown_timeout` to `None` in `LifespanManager` for test files.

* Convert test functions to async in `test_messages_endpoints.py`

* Add `api_key_required` marker to assistant component tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-10-02 14:21:52 -03:00 • committed by GitHub
commit 6febae599b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 444 additions and 322 deletions

View file

@ -3,14 +3,14 @@ from uuid import UUID, uuid4
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from httpx import AsyncClient
from langflow.custom.directory_reader.directory_reader import DirectoryReader
from langflow.services.deps import get_settings_service
def run_post(client, flow_id, headers, post_data):
response = client.post(
async def run_post(client, flow_id, headers, post_data):
response = await client.post(
f"api/v1/process/{flow_id}",
headers=headers,
json=post_data,
@ -20,9 +20,9 @@ def run_post(client, flow_id, headers, post_data):
# Helper function to poll task status
def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1):
async def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1):
for _ in range(max_attempts):
task_status_response = client.get(
task_status_response = await client.get(
href,
headers=headers,
)
@ -135,7 +135,7 @@ PROMPT_REQUEST = {
# "session_id": None,
# }
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# assert response.status_code == 403
# assert response.json() == {"detail": "Invalid or missing API key"}
@ -160,7 +160,7 @@ PROMPT_REQUEST = {
# }
# invalid_id = uuid.uuid4()
# response = client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data)
# response = await client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data)
# assert response.status_code == 404
# assert f"Flow {invalid_id} not found" in response.json()["detail"]
@ -216,7 +216,7 @@ PROMPT_REQUEST = {
# # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response
# assert response.status_code == 200, response.json()
@ -253,15 +253,15 @@ PROMPT_REQUEST = {
# # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response
# assert response.status_code == 403, response.json()
# assert response.json() == {"detail": "Invalid or missing API key"}
def test_get_all(client: TestClient, logged_in_headers):
response = client.get("api/v1/all", headers=logged_in_headers)
async def test_get_all(client: AsyncClient, logged_in_headers):
response = await client.get("api/v1/all", headers=logged_in_headers)
assert response.status_code == 200
settings = get_settings_service().settings
dir_reader = DirectoryReader(settings.components_path[0])
@ -278,7 +278,7 @@ def test_get_all(client: TestClient, logged_in_headers):
assert "ChatOutput" in json_response["outputs"]
def test_post_validate_code(client: TestClient):
async def test_post_validate_code(client: AsyncClient):
# Test case with a valid import and function
code1 = """
import math
@ -286,7 +286,7 @@ import math
def square(x):
return x ** 2
"""
response1 = client.post("api/v1/validate/code", json={"code": code1})
response1 = await client.post("api/v1/validate/code", json={"code": code1})
assert response1.status_code == 200
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
@ -297,7 +297,7 @@ import non_existent_module
def square(x):
return x ** 2
"""
response2 = client.post("api/v1/validate/code", json={"code": code2})
response2 = await client.post("api/v1/validate/code", json={"code": code2})
assert response2.status_code == 200
assert response2.json() == {
"imports": {"errors": ["No module named 'non_existent_module'"]},
@ -311,7 +311,7 @@ import math
def square(x)
return x ** 2
"""
response3 = client.post("api/v1/validate/code", json={"code": code3})
response3 = await client.post("api/v1/validate/code", json={"code": code3})
assert response3.status_code == 200
assert response3.json() == {
"imports": {"errors": []},
@ -319,11 +319,11 @@ def square(x)
}
# Test case with invalid JSON payload
response4 = client.post("api/v1/validate/code", json={"invalid_key": code1})
response4 = await client.post("api/v1/validate/code", json={"invalid_key": code1})
assert response4.status_code == 422
# Test case with an empty code string
response5 = client.post("api/v1/validate/code", json={"code": ""})
response5 = await client.post("api/v1/validate/code", json={"code": ""})
assert response5.status_code == 200
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
@ -334,7 +334,7 @@ import math
def square(x)
return x ** 2
"""
response6 = client.post("api/v1/validate/code", json={"code": code6})
response6 = await client.post("api/v1/validate/code", json={"code": code6})
assert response6.status_code == 200
assert response6.json() == {
"imports": {"errors": []},
@ -359,16 +359,16 @@ What is a good name for a company that makes {product}?
INVALID_PROMPT = "This is an invalid prompt without any input variable."
def test_valid_prompt(client: TestClient):
async def test_valid_prompt(client: AsyncClient):
PROMPT_REQUEST["template"] = VALID_PROMPT
response = client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
response = await client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
assert response.status_code == 200
assert response.json()["input_variables"] == ["product"]
def test_invalid_prompt(client: TestClient):
async def test_invalid_prompt(client: AsyncClient):
PROMPT_REQUEST["template"] = INVALID_PROMPT
response = client.post(
response = await client.post(
"api/v1/validate/prompt",
json=PROMPT_REQUEST,
)
@ -385,22 +385,22 @@ def test_invalid_prompt(client: TestClient):
("{a}, {b}, and {c} are variables.", ["a", "b", "c"]),
],
)
def test_various_prompts(client, prompt, expected_input_variables):
async def test_various_prompts(client, prompt, expected_input_variables):
PROMPT_REQUEST["template"] = prompt
response = client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
response = await client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
assert response.status_code == 200
assert response.json()["input_variables"] == expected_input_variables
def test_get_vertices_flow_not_found(client, logged_in_headers):
async def test_get_vertices_flow_not_found(client, logged_in_headers):
uuid = uuid4()
response = client.post(f"/api/v1/build/{uuid}/vertices", headers=logged_in_headers)
response = await client.post(f"/api/v1/build/{uuid}/vertices", headers=logged_in_headers)
assert response.status_code == 500
def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_headers):
async def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"]
response = client.post(f"/api/v1/build/{flow_id}/vertices", headers=logged_in_headers)
response = await client.post(f"/api/v1/build/{flow_id}/vertices", headers=logged_in_headers)
assert response.status_code == 200
assert "ids" in response.json()
# The response should contain the list in this order
@ -415,22 +415,22 @@ def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_head
}
def test_build_vertex_invalid_flow_id(client, logged_in_headers):
async def test_build_vertex_invalid_flow_id(client, logged_in_headers):
uuid = uuid4()
response = client.post(f"/api/v1/build/{uuid}/vertices/vertex_id", headers=logged_in_headers)
response = await client.post(f"/api/v1/build/{uuid}/vertices/vertex_id", headers=logged_in_headers)
assert response.status_code == 500
def test_build_vertex_invalid_vertex_id(client, added_flow_with_prompt_and_history, logged_in_headers):
async def test_build_vertex_invalid_vertex_id(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"]
response = client.post(f"/api/v1/build/{flow_id}/vertices/invalid_vertex_id", headers=logged_in_headers)
response = await client.post(f"/api/v1/build/{flow_id}/vertices/invalid_vertex_id", headers=logged_in_headers)
assert response.status_code == 500
def test_successful_run_no_payload(client, simple_api_test, created_api_key):
async def test_successful_run_no_payload(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -455,13 +455,13 @@ def test_successful_run_no_payload(client, simple_api_test, created_api_key):
assert all([result is not None for result in inner_results]), (outputs_dict, output_results_has_results)
def test_successful_run_with_output_type_text(client, simple_api_test, created_api_key):
async def test_successful_run_with_output_type_text(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"output_type": "text",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -485,14 +485,14 @@ def test_successful_run_with_output_type_text(client, simple_api_test, created_a
assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict
def test_successful_run_with_output_type_any(client, simple_api_test, created_api_key):
async def test_successful_run_with_output_type_any(client, simple_api_test, created_api_key):
# This one should have both the ChatOutput and TextOutput components
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
"output_type": "any",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -516,7 +516,7 @@ def test_successful_run_with_output_type_any(client, simple_api_test, created_ap
assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict
def test_successful_run_with_output_type_debug(client, simple_api_test, created_api_key):
async def test_successful_run_with_output_type_debug(client, simple_api_test, created_api_key):
# This one should return outputs for all components
# Let's just check the amount of outputs(there should be 7)
headers = {"x-api-key": created_api_key.api_key}
@ -524,7 +524,7 @@ def test_successful_run_with_output_type_debug(client, simple_api_test, created_
payload = {
"output_type": "debug",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -541,7 +541,7 @@ def test_successful_run_with_output_type_debug(client, simple_api_test, created_
assert len(outputs_dict.get("outputs")) == 3
def test_successful_run_with_input_type_text(client, simple_api_test, created_api_key):
async def test_successful_run_with_input_type_text(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
@ -549,7 +549,7 @@ def test_successful_run_with_input_type_text(client, simple_api_test, created_ap
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -574,7 +574,8 @@ def test_successful_run_with_input_type_text(client, simple_api_test, created_ap
), text_input_outputs
def test_successful_run_with_input_type_chat(client, simple_api_test, created_api_key):
@pytest.mark.api_key_required
async def test_successful_run_with_input_type_chat(client: AsyncClient, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
@ -582,7 +583,7 @@ def test_successful_run_with_input_type_chat(client, simple_api_test, created_ap
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -606,7 +607,7 @@ def test_successful_run_with_input_type_chat(client, simple_api_test, created_ap
), chat_input_outputs
def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_key):
async def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
@ -615,12 +616,12 @@ def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_k
"input_value": "value1",
"tweaks": {"Chat Input": {"input_value": "value2"}},
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert "If you pass an input_value to the chat input, you cannot pass a tweak with the same name." in response.text
def test_successful_run_with_input_type_any(client, simple_api_test, created_api_key):
async def test_successful_run_with_input_type_any(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"]
payload = {
@ -628,7 +629,7 @@ def test_successful_run_with_input_type_any(client, simple_api_test, created_api
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
@ -660,19 +661,19 @@ def test_successful_run_with_input_type_any(client, simple_api_test, created_api
), any_input_outputs
def test_invalid_flow_id(client, created_api_key):
async def test_invalid_flow_id(client, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = "invalid-flow-id"
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND, response.text
headers = {"x-api-key": created_api_key.api_key}
flow_id = UUID(int=0)
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
response = await client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND, response.text
# Check if the error detail is as expected
def test_starter_projects(client, created_api_key):
async def test_starter_projects(client, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
response = client.get("/api/v1/starter-projects/", headers=headers)
response = await client.get("api/v1/starter-projects/", headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text