chore: refactor and add components integration tests (#3607)
* improve inegration tests * add fixes * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
4ee25359a5
commit
96872f3aa5
32 changed files with 523 additions and 131 deletions
|
|
@ -21,8 +21,8 @@ async def test_graph_not_prepared():
|
|||
chat_input = ChatInput()
|
||||
chat_output = ChatOutput()
|
||||
graph = Graph()
|
||||
graph.add_component("chat_input", chat_input)
|
||||
graph.add_component("chat_output", chat_output)
|
||||
graph.add_component(chat_input)
|
||||
graph.add_component(chat_output)
|
||||
with pytest.raises(ValueError):
|
||||
await graph.astep()
|
||||
|
||||
|
|
@ -32,8 +32,8 @@ async def test_graph():
|
|||
chat_input = ChatInput()
|
||||
chat_output = ChatOutput()
|
||||
graph = Graph()
|
||||
graph.add_component("chat_input", chat_input)
|
||||
graph.add_component("chat_output", chat_output)
|
||||
graph.add_component(chat_input)
|
||||
graph.add_component(chat_output)
|
||||
with pytest.warns(UserWarning, match="Graph has vertices but no edges"):
|
||||
graph.prepare()
|
||||
|
||||
|
|
@ -43,18 +43,20 @@ async def test_graph_with_edge():
|
|||
chat_input = ChatInput()
|
||||
chat_output = ChatOutput()
|
||||
graph = Graph()
|
||||
graph.add_component("chat_input", chat_input)
|
||||
graph.add_component("chat_output", chat_output)
|
||||
graph.add_component_edge("chat_input", (chat_input.outputs[0].name, chat_input.inputs[0].name), "chat_output")
|
||||
input_id = graph.add_component(chat_input)
|
||||
output_id = graph.add_component(chat_output)
|
||||
graph.add_component_edge(input_id, (chat_input.outputs[0].name, chat_input.inputs[0].name), output_id)
|
||||
graph.prepare()
|
||||
assert graph._run_queue == deque(["chat_input"])
|
||||
# ensure prepare is idempotent
|
||||
graph.prepare()
|
||||
assert graph._run_queue == deque([input_id])
|
||||
await graph.astep()
|
||||
assert graph._run_queue == deque(["chat_output"])
|
||||
assert graph._run_queue == deque([output_id])
|
||||
|
||||
assert graph.vertices[0].id == "chat_input"
|
||||
assert graph.vertices[1].id == "chat_output"
|
||||
assert graph.edges[0].source_id == "chat_input"
|
||||
assert graph.edges[0].target_id == "chat_output"
|
||||
assert graph.vertices[0].id == input_id
|
||||
assert graph.vertices[1].id == output_id
|
||||
assert graph.edges[0].source_id == input_id
|
||||
assert graph.edges[0].target_id == output_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ def test_vector_store_rag_dump_components_and_edges(ingestion_graph, rag_graph):
|
|||
|
||||
ingestion_data = ingestion_graph_dump["data"]
|
||||
ingestion_nodes = ingestion_data["nodes"]
|
||||
assert len(ingestion_nodes) == 4
|
||||
ingestion_edges = ingestion_data["edges"]
|
||||
|
||||
# Sort nodes by id to check components
|
||||
|
|
|
|||
678
src/backend/tests/unit/test_endpoints.py
Normal file
678
src/backend/tests/unit/test_endpoints.py
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
import time
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import status
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
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(
|
||||
f"api/v1/process/{flow_id}",
|
||||
headers=headers,
|
||||
json=post_data,
|
||||
)
|
||||
assert response.status_code == 200, response.json()
|
||||
return response.json()
|
||||
|
||||
|
||||
# Helper function to poll task status
|
||||
def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1):
|
||||
for _ in range(max_attempts):
|
||||
task_status_response = client.get(
|
||||
href,
|
||||
headers=headers,
|
||||
)
|
||||
if task_status_response.status_code == 200 and task_status_response.json()["status"] == "SUCCESS":
|
||||
return task_status_response.json()
|
||||
time.sleep(sleep_time)
|
||||
return None # Return None if task did not complete in time
|
||||
|
||||
|
||||
PROMPT_REQUEST = {
|
||||
"name": "string",
|
||||
"template": "string",
|
||||
"frontend_node": {
|
||||
"template": {},
|
||||
"description": "string",
|
||||
"base_classes": ["string"],
|
||||
"name": "",
|
||||
"display_name": "",
|
||||
"documentation": "",
|
||||
"custom_fields": {},
|
||||
"output_types": [],
|
||||
"field_formatters": {
|
||||
"formatters": {"openai_api_key": {}},
|
||||
"base_formatters": {
|
||||
"kwargs": {},
|
||||
"optional": {},
|
||||
"list": {},
|
||||
"dict": {},
|
||||
"union": {},
|
||||
"multiline": {},
|
||||
"show": {},
|
||||
"password": {},
|
||||
"default": {},
|
||||
"headers": {},
|
||||
"dict_code_file": {},
|
||||
"model_fields": {
|
||||
"MODEL_DICT": {
|
||||
"OpenAI": [
|
||||
"text-davinci-003",
|
||||
"text-davinci-002",
|
||||
"text-curie-001",
|
||||
"text-babbage-001",
|
||||
"text-ada-001",
|
||||
],
|
||||
"ChatOpenAI": [
|
||||
"gpt-4-turbo-preview",
|
||||
"gpt-4-0125-preview",
|
||||
"gpt-4-1106-preview",
|
||||
"gpt-4-vision-preview",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-3.5-turbo-1106",
|
||||
],
|
||||
"Anthropic": [
|
||||
"claude-v1",
|
||||
"claude-v1-100k",
|
||||
"claude-instant-v1",
|
||||
"claude-instant-v1-100k",
|
||||
"claude-v1.3",
|
||||
"claude-v1.3-100k",
|
||||
"claude-v1.2",
|
||||
"claude-v1.0",
|
||||
"claude-instant-v1.1",
|
||||
"claude-instant-v1.1-100k",
|
||||
"claude-instant-v1.0",
|
||||
],
|
||||
"ChatAnthropic": [
|
||||
"claude-v1",
|
||||
"claude-v1-100k",
|
||||
"claude-instant-v1",
|
||||
"claude-instant-v1-100k",
|
||||
"claude-v1.3",
|
||||
"claude-v1.3-100k",
|
||||
"claude-v1.2",
|
||||
"claude-v1.0",
|
||||
"claude-instant-v1.1",
|
||||
"claude-instant-v1.1-100k",
|
||||
"claude-instant-v1.0",
|
||||
],
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# def test_process_flow_invalid_api_key(client, flow, monkeypatch):
|
||||
# # Mock de process_graph_cached
|
||||
# from langflow.api.v1 import endpoints
|
||||
# from langflow.services.database.models.api_key import crud
|
||||
|
||||
# settings_service = get_settings_service()
|
||||
# settings_service.auth_settings.AUTO_LOGIN = False
|
||||
|
||||
# async def mock_process_graph_cached(*args, **kwargs):
|
||||
# return Result(result={}, session_id="session_id_mock")
|
||||
|
||||
# def mock_update_total_uses(*args, **kwargs):
|
||||
# return created_api_key
|
||||
|
||||
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
|
||||
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
|
||||
|
||||
# headers = {"x-api-key": "invalid_api_key"}
|
||||
|
||||
# post_data = {
|
||||
# "inputs": {"key": "value"},
|
||||
# "tweaks": None,
|
||||
# "clear_cache": False,
|
||||
# "session_id": None,
|
||||
# }
|
||||
|
||||
# response = 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"}
|
||||
|
||||
|
||||
# def test_process_flow_invalid_id(client, monkeypatch, created_api_key):
|
||||
# async def mock_process_graph_cached(*args, **kwargs):
|
||||
# return Result(result={}, session_id="session_id_mock")
|
||||
|
||||
# from langflow.api.v1 import endpoints
|
||||
|
||||
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
|
||||
|
||||
# api_key = created_api_key.api_key
|
||||
# headers = {"x-api-key": api_key}
|
||||
|
||||
# post_data = {
|
||||
# "inputs": {"key": "value"},
|
||||
# "tweaks": None,
|
||||
# "clear_cache": False,
|
||||
# "session_id": None,
|
||||
# }
|
||||
|
||||
# invalid_id = uuid.uuid4()
|
||||
# response = 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"]
|
||||
|
||||
|
||||
# def test_process_flow_without_autologin(client, flow, monkeypatch, created_api_key):
|
||||
# # Mock de process_graph_cached
|
||||
# from langflow.api.v1 import endpoints
|
||||
# from langflow.services.database.models.api_key import crud
|
||||
|
||||
# settings_service = get_settings_service()
|
||||
# settings_service.auth_settings.AUTO_LOGIN = False
|
||||
|
||||
# async def mock_process_graph_cached(*args, **kwargs):
|
||||
# return Result(result={}, session_id="session_id_mock")
|
||||
|
||||
# def mock_process_graph_cached_task(*args, **kwargs):
|
||||
# return Result(result={}, session_id="session_id_mock")
|
||||
|
||||
# # The task function is ran like this:
|
||||
# # if not self.use_celery:
|
||||
# # return None, await task_func(*args, **kwargs)
|
||||
# # if not hasattr(task_func, "apply"):
|
||||
# # raise ValueError(f"Task function {task_func} does not have an apply method")
|
||||
# # task = task_func.apply(args=args, kwargs=kwargs)
|
||||
# # result = task.get()
|
||||
# # return task.id, result
|
||||
# # So we need to mock the task function to return a task object
|
||||
# # and then mock the task object to return a result
|
||||
# # maybe a named tuple would be better here
|
||||
# task = namedtuple("task", ["id", "get"])
|
||||
# mock_process_graph_cached_task.apply = lambda *args, **kwargs: task(
|
||||
# id="task_id_mock", get=lambda: Result(result={}, session_id="session_id_mock")
|
||||
# )
|
||||
|
||||
# def mock_update_total_uses(*args, **kwargs):
|
||||
# return created_api_key
|
||||
|
||||
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
|
||||
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
|
||||
# monkeypatch.setattr(endpoints, "process_graph_cached_task", mock_process_graph_cached_task)
|
||||
|
||||
# api_key = created_api_key.api_key
|
||||
# headers = {"x-api-key": api_key}
|
||||
|
||||
# # Dummy POST data
|
||||
# post_data = {
|
||||
# "inputs": {"input": "value"},
|
||||
# "tweaks": None,
|
||||
# "clear_cache": False,
|
||||
# "session_id": None,
|
||||
# }
|
||||
|
||||
# # Make the request to the FastAPI TestClient
|
||||
|
||||
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
|
||||
|
||||
# # Check the response
|
||||
# assert response.status_code == 200, response.json()
|
||||
# assert response.json()["result"] == {}, response.json()
|
||||
# assert response.json()["session_id"] == "session_id_mock", response.json()
|
||||
|
||||
|
||||
# def test_process_flow_fails_autologin_off(client, flow, monkeypatch):
|
||||
# # Mock de process_graph_cached
|
||||
# from langflow.api.v1 import endpoints
|
||||
# from langflow.services.database.models.api_key import crud
|
||||
|
||||
# settings_service = get_settings_service()
|
||||
# settings_service.auth_settings.AUTO_LOGIN = False
|
||||
|
||||
# async def mock_process_graph_cached(*args, **kwargs):
|
||||
# return Result(result={}, session_id="session_id_mock")
|
||||
|
||||
# async def mock_update_total_uses(*args, **kwargs):
|
||||
# return created_api_key
|
||||
|
||||
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
|
||||
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
|
||||
|
||||
# headers = {"x-api-key": "api_key"}
|
||||
|
||||
# # Dummy POST data
|
||||
# post_data = {
|
||||
# "inputs": {"key": "value"},
|
||||
# "tweaks": None,
|
||||
# "clear_cache": False,
|
||||
# "session_id": None,
|
||||
# }
|
||||
|
||||
# # Make the request to the FastAPI TestClient
|
||||
|
||||
# response = 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)
|
||||
assert response.status_code == 200
|
||||
settings = get_settings_service().settings
|
||||
dir_reader = DirectoryReader(settings.components_path[0])
|
||||
files = dir_reader.get_files()
|
||||
# json_response is a dict of dicts
|
||||
all_names = [component_name for _, components in response.json().items() for component_name in components]
|
||||
json_response = response.json()
|
||||
# We need to test the custom nodes
|
||||
assert len(all_names) <= len(
|
||||
files
|
||||
) # Less or equal because we might have some files that don't have the dependencies installed
|
||||
assert "ChatInput" in json_response["inputs"]
|
||||
assert "Prompt" in json_response["prompts"]
|
||||
assert "ChatOutput" in json_response["outputs"]
|
||||
|
||||
|
||||
def test_post_validate_code(client: TestClient):
|
||||
# Test case with a valid import and function
|
||||
code1 = """
|
||||
import math
|
||||
|
||||
def square(x):
|
||||
return x ** 2
|
||||
"""
|
||||
response1 = client.post("api/v1/validate/code", json={"code": code1})
|
||||
assert response1.status_code == 200
|
||||
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
|
||||
|
||||
# Test case with an invalid import and valid function
|
||||
code2 = """
|
||||
import non_existent_module
|
||||
|
||||
def square(x):
|
||||
return x ** 2
|
||||
"""
|
||||
response2 = 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'"]},
|
||||
"function": {"errors": []},
|
||||
}
|
||||
|
||||
# Test case with a valid import and invalid function syntax
|
||||
code3 = """
|
||||
import math
|
||||
|
||||
def square(x)
|
||||
return x ** 2
|
||||
"""
|
||||
response3 = client.post("api/v1/validate/code", json={"code": code3})
|
||||
assert response3.status_code == 200
|
||||
assert response3.json() == {
|
||||
"imports": {"errors": []},
|
||||
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
|
||||
}
|
||||
|
||||
# Test case with invalid JSON payload
|
||||
response4 = 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": ""})
|
||||
assert response5.status_code == 200
|
||||
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
|
||||
|
||||
# Test case with a syntax error in the code
|
||||
code6 = """
|
||||
import math
|
||||
|
||||
def square(x)
|
||||
return x ** 2
|
||||
"""
|
||||
response6 = client.post("api/v1/validate/code", json={"code": code6})
|
||||
assert response6.status_code == 200
|
||||
assert response6.json() == {
|
||||
"imports": {"errors": []},
|
||||
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
|
||||
}
|
||||
|
||||
|
||||
VALID_PROMPT = """
|
||||
I want you to act as a naming consultant for new companies.
|
||||
|
||||
Here are some examples of good company names:
|
||||
|
||||
- search engine, Google
|
||||
- social media, Facebook
|
||||
- video sharing, YouTube
|
||||
|
||||
The name should be short, catchy and easy to remember.
|
||||
|
||||
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):
|
||||
PROMPT_REQUEST["template"] = VALID_PROMPT
|
||||
response = 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):
|
||||
PROMPT_REQUEST["template"] = INVALID_PROMPT
|
||||
response = client.post(
|
||||
"api/v1/validate/prompt",
|
||||
json=PROMPT_REQUEST,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["input_variables"] == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"prompt,expected_input_variables",
|
||||
[
|
||||
("{color} is my favorite color.", ["color"]),
|
||||
("The weather is {weather} today.", ["weather"]),
|
||||
("This prompt has no variables.", []),
|
||||
("{a}, {b}, and {c} are variables.", ["a", "b", "c"]),
|
||||
],
|
||||
)
|
||||
def test_various_prompts(client, prompt, expected_input_variables):
|
||||
PROMPT_REQUEST["template"] = prompt
|
||||
response = 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):
|
||||
uuid = uuid4()
|
||||
response = 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):
|
||||
flow_id = added_flow_with_prompt_and_history["id"]
|
||||
response = 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
|
||||
# ['ConversationBufferMemory-Lu2Nb', 'PromptTemplate-5Q0W8', 'ChatOpenAI-vy7fV', 'LLMChain-UjBh1']
|
||||
# The important part is before the - (ConversationBufferMemory, PromptTemplate, ChatOpenAI, LLMChain)
|
||||
ids = [_id.split("-")[0] for _id in response.json()["ids"]]
|
||||
|
||||
assert set(ids) == {
|
||||
"ChatOpenAI",
|
||||
"PromptTemplate",
|
||||
"ConversationBufferMemory",
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
assert response.status_code == 500
|
||||
|
||||
|
||||
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)
|
||||
assert response.status_code == 500
|
||||
|
||||
|
||||
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)
|
||||
assert response.status_code == status.HTTP_200_OK, response.text
|
||||
# Add more assertions here to validate the response content
|
||||
json_response = response.json()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": ""}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 1
|
||||
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
|
||||
assert all(["ChatOutput" in _id for _id in ids])
|
||||
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
|
||||
assert all([name in display_names for name in ["Chat Output"]])
|
||||
output_results_has_results = all("results" in output.get("results") for output in outputs_dict.get("outputs"))
|
||||
inner_results = [output.get("results") for output in outputs_dict.get("outputs")]
|
||||
|
||||
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):
|
||||
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)
|
||||
assert response.status_code == status.HTTP_200_OK, response.text
|
||||
# Add more assertions here to validate the response content
|
||||
json_response = response.json()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": ""}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 1
|
||||
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
|
||||
assert all(["ChatOutput" in _id for _id in ids]), ids
|
||||
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
|
||||
assert all([name in display_names for name in ["Chat Output"]]), display_names
|
||||
inner_results = [output.get("results") for output in outputs_dict.get("outputs")]
|
||||
expected_keys = ["message"]
|
||||
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):
|
||||
# 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)
|
||||
assert response.status_code == status.HTTP_200_OK, response.text
|
||||
# Add more assertions here to validate the response content
|
||||
json_response = response.json()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": ""}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 1
|
||||
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
|
||||
assert all(["ChatOutput" in _id or "TextOutput" in _id for _id in ids]), ids
|
||||
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
|
||||
assert all([name in display_names for name in ["Chat Output"]]), display_names
|
||||
inner_results = [output.get("results") for output in outputs_dict.get("outputs")]
|
||||
expected_keys = ["message"]
|
||||
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):
|
||||
# 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}
|
||||
flow_id = simple_api_test["id"]
|
||||
payload = {
|
||||
"output_type": "debug",
|
||||
}
|
||||
response = 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()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": ""}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 3
|
||||
|
||||
|
||||
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 = {
|
||||
"input_type": "text",
|
||||
"output_type": "debug",
|
||||
"input_value": "value1",
|
||||
}
|
||||
response = 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()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": "value1"}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 3
|
||||
# Now we get all components that contain TextInput in the component_id
|
||||
text_input_outputs = [output for output in outputs_dict.get("outputs") if "TextInput" in output.get("component_id")]
|
||||
assert len(text_input_outputs) == 1
|
||||
# Now we check if the input_value is correct
|
||||
# We get text key twice because the output is now a Message
|
||||
assert all(
|
||||
[output.get("results").get("text").get("text") == "value1" for output in text_input_outputs]
|
||||
), text_input_outputs
|
||||
|
||||
|
||||
def test_successful_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 = {
|
||||
"input_type": "chat",
|
||||
"output_type": "debug",
|
||||
"input_value": "value1",
|
||||
}
|
||||
response = 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()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": "value1"}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 3
|
||||
# Now we get all components that contain TextInput in the component_id
|
||||
chat_input_outputs = [output for output in outputs_dict.get("outputs") if "ChatInput" in output.get("component_id")]
|
||||
assert len(chat_input_outputs) == 1
|
||||
# Now we check if the input_value is correct
|
||||
assert all(
|
||||
[output.get("results").get("message").get("text") == "value1" for output in chat_input_outputs]
|
||||
), chat_input_outputs
|
||||
|
||||
|
||||
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 = {
|
||||
"input_type": "chat",
|
||||
"output_type": "debug",
|
||||
"input_value": "value1",
|
||||
"tweaks": {"Chat Input": {"input_value": "value2"}},
|
||||
}
|
||||
response = 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):
|
||||
headers = {"x-api-key": created_api_key.api_key}
|
||||
flow_id = simple_api_test["id"]
|
||||
payload = {
|
||||
"input_type": "any",
|
||||
"output_type": "debug",
|
||||
"input_value": "value1",
|
||||
}
|
||||
response = 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()
|
||||
assert "session_id" in json_response
|
||||
assert "outputs" in json_response
|
||||
outer_outputs = json_response["outputs"]
|
||||
assert len(outer_outputs) == 1
|
||||
outputs_dict = outer_outputs[0]
|
||||
assert len(outputs_dict) == 2
|
||||
assert "inputs" in outputs_dict
|
||||
assert "outputs" in outputs_dict
|
||||
assert outputs_dict.get("inputs") == {"input_value": "value1"}
|
||||
assert isinstance(outputs_dict.get("outputs"), list)
|
||||
assert len(outputs_dict.get("outputs")) == 3
|
||||
# Now we get all components that contain TextInput or ChatInput in the component_id
|
||||
any_input_outputs = [
|
||||
output
|
||||
for output in outputs_dict.get("outputs")
|
||||
if "TextInput" in output.get("component_id") or "ChatInput" in output.get("component_id")
|
||||
]
|
||||
assert len(any_input_outputs) == 2
|
||||
# Now we check if the input_value is correct
|
||||
all_result_dicts = [output.get("results") for output in any_input_outputs]
|
||||
all_message_or_text_dicts = [
|
||||
result_dict.get("message", result_dict.get("text")) for result_dict in all_result_dicts
|
||||
]
|
||||
assert all(
|
||||
[message_or_text_dict.get("text") == "value1" for message_or_text_dict in all_message_or_text_dicts]
|
||||
), any_input_outputs
|
||||
|
||||
|
||||
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)
|
||||
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)
|
||||
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):
|
||||
headers = {"x-api-key": created_api_key.api_key}
|
||||
response = client.get("/api/v1/starter-projects/", headers=headers)
|
||||
assert response.status_code == status.HTTP_200_OK, response.text
|
||||
76
src/backend/tests/unit/test_messages_endpoints.py
Normal file
76
src/backend/tests/unit/test_messages_endpoints.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from langflow.memory import add_messagetables
|
||||
|
||||
# Assuming you have these imports available
|
||||
from langflow.services.database.models.message import MessageCreate, MessageRead, MessageUpdate
|
||||
from langflow.services.database.models.message.model import MessageTable
|
||||
from langflow.services.deps import session_scope
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def created_message():
|
||||
with session_scope() as session:
|
||||
message = MessageCreate(text="Test message", sender="User", sender_name="User", session_id="session_id")
|
||||
messagetable = MessageTable.model_validate(message, from_attributes=True)
|
||||
messagetables = add_messagetables([messagetable], session)
|
||||
message_read = MessageRead.model_validate(messagetables[0], from_attributes=True)
|
||||
return message_read
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def created_messages(session):
|
||||
with session_scope() as session:
|
||||
messages = [
|
||||
MessageCreate(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"),
|
||||
MessageCreate(text="Test message 2", sender="User", sender_name="User", session_id="session_id2"),
|
||||
MessageCreate(text="Test message 3", sender="User", sender_name="User", session_id="session_id2"),
|
||||
]
|
||||
messagetables = [MessageTable.model_validate(message, from_attributes=True) for message in messages]
|
||||
message_list = add_messagetables(messagetables, session)
|
||||
|
||||
return message_list
|
||||
|
||||
|
||||
def test_delete_messages(client: TestClient, created_messages, logged_in_headers):
|
||||
response = client.request(
|
||||
"DELETE", "api/v1/monitor/messages", json=[str(msg.id) for msg in created_messages], headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == 204, response.text
|
||||
assert response.reason_phrase == "No Content"
|
||||
|
||||
|
||||
def test_update_message(client: TestClient, logged_in_headers, created_message):
|
||||
message_id = created_message.id
|
||||
message_update = MessageUpdate(text="Updated content")
|
||||
response = client.put(
|
||||
f"api/v1/monitor/messages/{message_id}", json=message_update.model_dump(), headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
updated_message = MessageRead(**response.json())
|
||||
assert updated_message.text == "Updated content"
|
||||
|
||||
|
||||
def test_update_message_not_found(client: TestClient, logged_in_headers):
|
||||
non_existent_id = UUID("00000000-0000-0000-0000-000000000000")
|
||||
message_update = MessageUpdate(text="Updated content")
|
||||
response = client.put(
|
||||
f"api/v1/monitor/messages/{non_existent_id}", json=message_update.model_dump(), headers=logged_in_headers
|
||||
)
|
||||
assert response.status_code == 404, response.text
|
||||
assert response.json()["detail"] == "Message not found"
|
||||
|
||||
|
||||
def test_delete_messages_session(client: TestClient, created_messages, logged_in_headers):
|
||||
session_id = "session_id2"
|
||||
response = client.delete(f"api/v1/monitor/messages/session/{session_id}", headers=logged_in_headers)
|
||||
assert response.status_code == 204
|
||||
assert response.reason_phrase == "No Content"
|
||||
|
||||
assert len(created_messages) == 3
|
||||
response = client.get("api/v1/monitor/messages", headers=logged_in_headers)
|
||||
assert response.status_code == 200
|
||||
assert len(response.json()) == 0
|
||||
132
src/backend/tests/unit/test_schema.py
Normal file
132
src/backend/tests/unit/test_schema.py
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
from typing import Union
|
||||
from collections.abc import Sequence
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from langflow.template import Input, Output
|
||||
from langflow.template.field.base import UNDEFINED
|
||||
from langflow.type_extraction.type_extraction import post_process_type
|
||||
|
||||
|
||||
@pytest.fixture(name="client", autouse=True)
|
||||
def client_fixture():
|
||||
pass
|
||||
|
||||
|
||||
class TestInput:
|
||||
def test_field_type_str(self):
|
||||
input_obj = Input(field_type="str")
|
||||
assert input_obj.field_type == "str"
|
||||
|
||||
def test_field_type_type(self):
|
||||
input_obj = Input(field_type=int)
|
||||
assert input_obj.field_type == "int"
|
||||
|
||||
def test_invalid_field_type(self):
|
||||
with pytest.raises(ValidationError):
|
||||
Input(field_type=123)
|
||||
|
||||
def test_serialize_field_type(self):
|
||||
input_obj = Input(field_type="str")
|
||||
assert input_obj.serialize_field_type("str", None) == "str"
|
||||
|
||||
def test_validate_type_string(self):
|
||||
input_obj = Input(field_type="str")
|
||||
assert input_obj.field_type == "str"
|
||||
|
||||
def test_validate_type_class(self):
|
||||
input_obj = Input(field_type=int)
|
||||
assert input_obj.field_type == "int"
|
||||
|
||||
def test_post_process_type_function(self):
|
||||
assert post_process_type(int) == [int]
|
||||
assert post_process_type(list[int]) == [int]
|
||||
assert post_process_type(Union[int, str]) == [int, str]
|
||||
assert post_process_type(Union[int, Sequence[str]]) == [int, str]
|
||||
assert post_process_type(Union[int, Sequence[int]]) == [int]
|
||||
|
||||
def test_input_to_dict(self):
|
||||
input_obj = Input(field_type="str")
|
||||
assert input_obj.to_dict() == {
|
||||
"type": "str",
|
||||
"required": False,
|
||||
"placeholder": "",
|
||||
"list": False,
|
||||
"show": True,
|
||||
"multiline": False,
|
||||
"fileTypes": [],
|
||||
"file_path": "",
|
||||
"password": False,
|
||||
"advanced": False,
|
||||
"title_case": False,
|
||||
"dynamic": False,
|
||||
"info": "",
|
||||
"input_types": ["Text"],
|
||||
"load_from_db": False,
|
||||
}
|
||||
|
||||
|
||||
class TestOutput:
|
||||
def test_output_default(self):
|
||||
output_obj = Output(name="test_output")
|
||||
assert output_obj.name == "test_output"
|
||||
assert output_obj.value == UNDEFINED
|
||||
assert output_obj.cache is True
|
||||
|
||||
def test_output_add_types(self):
|
||||
output_obj = Output(name="test_output")
|
||||
output_obj.add_types(["str", "int"])
|
||||
assert output_obj.types == ["str", "int"]
|
||||
|
||||
def test_output_set_selected(self):
|
||||
output_obj = Output(name="test_output", types=["str", "int"])
|
||||
output_obj.set_selected()
|
||||
assert output_obj.selected == "str"
|
||||
|
||||
def test_output_to_dict(self):
|
||||
output_obj = Output(name="test_output")
|
||||
assert output_obj.to_dict() == {
|
||||
"types": [],
|
||||
"name": "test_output",
|
||||
"display_name": "test_output",
|
||||
"cache": True,
|
||||
"value": "__UNDEFINED__",
|
||||
}
|
||||
|
||||
def test_output_validate_display_name(self):
|
||||
output_obj = Output(name="test_output")
|
||||
assert output_obj.display_name == "test_output"
|
||||
|
||||
def test_output_validate_model(self):
|
||||
output_obj = Output(name="test_output", value="__UNDEFINED__")
|
||||
assert output_obj.validate_model() == output_obj
|
||||
|
||||
|
||||
class TestPostProcessType:
|
||||
def test_int_type(self):
|
||||
assert post_process_type(int) == [int]
|
||||
|
||||
def test_list_int_type(self):
|
||||
assert post_process_type(list[int]) == [int]
|
||||
|
||||
def test_union_type(self):
|
||||
assert post_process_type(Union[int, str]) == [int, str]
|
||||
|
||||
def test_custom_type(self):
|
||||
class CustomType:
|
||||
pass
|
||||
|
||||
assert post_process_type(CustomType) == [CustomType]
|
||||
|
||||
def test_list_custom_type(self):
|
||||
class CustomType:
|
||||
pass
|
||||
|
||||
assert post_process_type(list[CustomType]) == [CustomType]
|
||||
|
||||
def test_union_custom_type(self):
|
||||
class CustomType:
|
||||
pass
|
||||
|
||||
assert set(post_process_type(Union[CustomType, int])) == {CustomType, int}
|
||||
232
src/backend/tests/unit/test_user.py
Normal file
232
src/backend/tests/unit/test_user.py
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from langflow.services.auth.utils import create_super_user, get_password_hash
|
||||
from langflow.services.database.models.user import UserUpdate
|
||||
from langflow.services.database.models.user.model import User
|
||||
from langflow.services.database.utils import session_getter
|
||||
from langflow.services.deps import get_db_service, get_settings_service
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def super_user(client):
|
||||
settings_manager = get_settings_service()
|
||||
auth_settings = settings_manager.auth_settings
|
||||
with session_getter(get_db_service()) as session:
|
||||
return create_super_user(
|
||||
db=session,
|
||||
username=auth_settings.SUPERUSER,
|
||||
password=auth_settings.SUPERUSER_PASSWORD,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def super_user_headers(client, super_user):
|
||||
settings_service = get_settings_service()
|
||||
auth_settings = settings_service.auth_settings
|
||||
login_data = {
|
||||
"username": auth_settings.SUPERUSER,
|
||||
"password": auth_settings.SUPERUSER_PASSWORD,
|
||||
}
|
||||
response = client.post("/api/v1/login", data=login_data)
|
||||
assert response.status_code == 200
|
||||
tokens = response.json()
|
||||
a_token = tokens["access_token"]
|
||||
return {"Authorization": f"Bearer {a_token}"}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def deactivated_user():
|
||||
with session_getter(get_db_service()) as session:
|
||||
user = User(
|
||||
username="deactivateduser",
|
||||
password=get_password_hash("testpassword"),
|
||||
is_active=False,
|
||||
is_superuser=False,
|
||||
last_login_at=datetime.now(),
|
||||
)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
session.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
def test_user_waiting_for_approval(
|
||||
client,
|
||||
):
|
||||
# Create a user that is not active and has never logged in
|
||||
with session_getter(get_db_service()) as session:
|
||||
user = User(
|
||||
username="waitingforapproval",
|
||||
password=get_password_hash("testpassword"),
|
||||
is_active=False,
|
||||
last_login_at=None,
|
||||
)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
|
||||
login_data = {"username": "waitingforapproval", "password": "testpassword"}
|
||||
response = client.post("/api/v1/login", data=login_data)
|
||||
assert response.status_code == 400
|
||||
assert response.json()["detail"] == "Waiting for approval"
|
||||
|
||||
|
||||
def test_deactivated_user_cannot_login(client, deactivated_user):
|
||||
login_data = {"username": deactivated_user.username, "password": "testpassword"}
|
||||
response = client.post("/api/v1/login", data=login_data)
|
||||
assert response.status_code == 401, response.json()
|
||||
assert response.json()["detail"] == "Inactive user", response.text
|
||||
|
||||
|
||||
def test_deactivated_user_cannot_access(client, deactivated_user, logged_in_headers):
|
||||
# Assuming the headers for deactivated_user
|
||||
response = client.get("/api/v1/users", headers=logged_in_headers)
|
||||
assert response.status_code == 403, response.json()
|
||||
assert response.json()["detail"] == "The user doesn't have enough privileges", response.text
|
||||
|
||||
|
||||
def test_data_consistency_after_update(client, active_user, logged_in_headers, super_user_headers):
|
||||
user_id = active_user.id
|
||||
update_data = UserUpdate(is_active=False)
|
||||
|
||||
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=super_user_headers)
|
||||
assert response.status_code == 200, response.json()
|
||||
|
||||
# Fetch the updated user from the database
|
||||
response = client.get("/api/v1/users/whoami", headers=logged_in_headers)
|
||||
assert response.status_code == 401, response.json()
|
||||
assert response.json()["detail"] == "User not found or is inactive."
|
||||
|
||||
|
||||
def test_data_consistency_after_delete(client, test_user, super_user_headers):
|
||||
user_id = test_user.get("id")
|
||||
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
|
||||
assert response.status_code == 200, response.json()
|
||||
|
||||
# Attempt to fetch the deleted user from the database
|
||||
response = client.get("/api/v1/users", headers=super_user_headers)
|
||||
assert response.status_code == 200
|
||||
assert all(user["id"] != user_id for user in response.json()["users"])
|
||||
|
||||
|
||||
def test_inactive_user(client):
|
||||
# Create a user that is not active and has a last_login_at value
|
||||
with session_getter(get_db_service()) as session:
|
||||
user = User(
|
||||
username="inactiveuser",
|
||||
password=get_password_hash("testpassword"),
|
||||
is_active=False,
|
||||
last_login_at=datetime(2023, 1, 1, 0, 0, 0),
|
||||
)
|
||||
session.add(user)
|
||||
session.commit()
|
||||
|
||||
login_data = {"username": "inactiveuser", "password": "testpassword"}
|
||||
response = client.post("/api/v1/login", data=login_data)
|
||||
assert response.status_code == 401
|
||||
assert response.json()["detail"] == "Inactive user"
|
||||
|
||||
|
||||
def test_add_user(client, test_user):
|
||||
assert test_user["username"] == "testuser"
|
||||
|
||||
|
||||
# This is not used in the Frontend at the moment
|
||||
# def test_read_current_user(client: TestClient, active_user):
|
||||
# # First we need to login to get the access token
|
||||
# login_data = {"username": "testuser", "password": "testpassword"}
|
||||
# response = client.post("/api/v1/login", data=login_data)
|
||||
# assert response.status_code == 200
|
||||
|
||||
# headers = {"Authorization": f"Bearer {response.json()['access_token']}"}
|
||||
|
||||
# response = client.get("/api/v1/user", headers=headers)
|
||||
# assert response.status_code == 200, response.json()
|
||||
# assert response.json()["username"] == "testuser"
|
||||
|
||||
|
||||
def test_read_all_users(client, super_user_headers):
|
||||
response = client.get("/api/v1/users", headers=super_user_headers)
|
||||
assert response.status_code == 200, response.json()
|
||||
assert isinstance(response.json()["users"], list)
|
||||
|
||||
|
||||
def test_normal_user_cant_read_all_users(client, logged_in_headers):
|
||||
response = client.get("/api/v1/users", headers=logged_in_headers)
|
||||
assert response.status_code == 403, response.json()
|
||||
assert response.json() == {"detail": "The user doesn't have enough privileges"}
|
||||
|
||||
|
||||
def test_patch_user(client, active_user, logged_in_headers):
|
||||
user_id = active_user.id
|
||||
update_data = UserUpdate(
|
||||
username="newname",
|
||||
)
|
||||
|
||||
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
|
||||
assert response.status_code == 200, response.json()
|
||||
update_data = UserUpdate(
|
||||
profile_image="new_image",
|
||||
)
|
||||
|
||||
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
|
||||
assert response.status_code == 200, response.json()
|
||||
|
||||
|
||||
def test_patch_reset_password(client, active_user, logged_in_headers):
|
||||
user_id = active_user.id
|
||||
update_data = UserUpdate(
|
||||
password="newpassword",
|
||||
)
|
||||
|
||||
response = client.patch(
|
||||
f"/api/v1/users/{user_id}/reset-password",
|
||||
json=update_data.model_dump(),
|
||||
headers=logged_in_headers,
|
||||
)
|
||||
assert response.status_code == 200, response.json()
|
||||
# Now we need to test if the new password works
|
||||
login_data = {"username": active_user.username, "password": "newpassword"}
|
||||
response = client.post("/api/v1/login", data=login_data)
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_patch_user_wrong_id(client, active_user, logged_in_headers):
|
||||
user_id = "wrong_id"
|
||||
update_data = UserUpdate(
|
||||
username="newname",
|
||||
)
|
||||
|
||||
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
|
||||
assert response.status_code == 422, response.json()
|
||||
json_response = response.json()
|
||||
detail = json_response["detail"]
|
||||
error = detail[0]
|
||||
assert error["loc"] == ["path", "user_id"]
|
||||
assert error["type"] == "uuid_parsing"
|
||||
|
||||
|
||||
def test_delete_user(client, test_user, super_user_headers):
|
||||
user_id = test_user["id"]
|
||||
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"detail": "User deleted"}
|
||||
|
||||
|
||||
def test_delete_user_wrong_id(client, test_user, super_user_headers):
|
||||
user_id = "wrong_id"
|
||||
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
|
||||
assert response.status_code == 422
|
||||
json_response = response.json()
|
||||
detail = json_response["detail"]
|
||||
error = detail[0]
|
||||
assert error["loc"] == ["path", "user_id"]
|
||||
assert error["type"] == "uuid_parsing"
|
||||
|
||||
|
||||
def test_normal_user_cant_delete_user(client, test_user, logged_in_headers):
|
||||
user_id = test_user["id"]
|
||||
response = client.delete(f"/api/v1/users/{user_id}", headers=logged_in_headers)
|
||||
assert response.status_code == 403
|
||||
assert response.json() == {"detail": "The user doesn't have enough privileges"}
|
||||
59
src/backend/tests/unit/test_webhook.py
Normal file
59
src/backend/tests/unit/test_webhook.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def check_openai_api_key_in_environment_variables():
|
||||
pass
|
||||
|
||||
|
||||
def test_webhook_endpoint(client, added_webhook_test):
|
||||
# The test is as follows:
|
||||
# 1. The flow when run will get a "path" from the payload and save a file with the path as the name.
|
||||
# We will create a temporary file path and send it to the webhook endpoint, then check if the file exists.
|
||||
# 2. we will delete the file, then send an invalid payload to the webhook endpoint and check if the file exists.
|
||||
endpoint_name = added_webhook_test["endpoint_name"]
|
||||
endpoint = f"api/v1/webhook/{endpoint_name}"
|
||||
# Create a temporary file
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
file_path = Path(tmp) / "test_file.txt"
|
||||
|
||||
payload = {"path": str(file_path)}
|
||||
|
||||
response = client.post(endpoint, json=payload)
|
||||
assert response.status_code == 202
|
||||
assert file_path.exists()
|
||||
|
||||
assert not file_path.exists()
|
||||
|
||||
# Send an invalid payload
|
||||
payload = {"invalid_key": "invalid_value"}
|
||||
response = client.post(endpoint, json=payload)
|
||||
assert response.status_code == 202
|
||||
assert not file_path.exists()
|
||||
|
||||
|
||||
def test_webhook_flow_on_run_endpoint(client, added_webhook_test, created_api_key):
|
||||
endpoint_name = added_webhook_test["endpoint_name"]
|
||||
endpoint = f"api/v1/run/{endpoint_name}?stream=false"
|
||||
# Just test that "Random Payload" returns 202
|
||||
# returns 202
|
||||
payload = {
|
||||
"output_type": "any",
|
||||
}
|
||||
response = client.post(endpoint, headers={"x-api-key": created_api_key.api_key}, json=payload)
|
||||
assert response.status_code == 200, response.json()
|
||||
|
||||
|
||||
def test_webhook_with_random_payload(client, added_webhook_test):
|
||||
endpoint_name = added_webhook_test["endpoint_name"]
|
||||
endpoint = f"api/v1/webhook/{endpoint_name}"
|
||||
# Just test that "Random Payload" returns 202
|
||||
# returns 202
|
||||
response = client.post(
|
||||
endpoint,
|
||||
json="Random Payload",
|
||||
)
|
||||
assert response.status_code == 202
|
||||
Loading…
Add table
Add a link
Reference in a new issue