refactor: move tests folder structure and update pytest commands (#2785)

* refactor: move tests folder to src/backend

* chore(Makefile): update pytest commands to run tests from the correct directory paths for unit and integration tests

* refactor: update file path in test_custom_component.py

The file path in the test_custom_component.py file has been updated to use the correct relative path to the component_multiple_outputs.py file. This change ensures that the test code can access the correct file and improves the reliability of the test.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-07-18 12:19:43 -03:00 • committed by GitHub
commit 0122a50a35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 5 additions and 5 deletions

View file

View file

@ -0,0 +1,200 @@
import pickle
from collections import defaultdict
import pytest
from langflow.graph.graph.runnable_vertices_manager import RunnableVerticesManager
@pytest.fixture
def data():
run_map: defaultdict(list) = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}
run_predecessors: defaultdict(set) = {"A": set(), "B": {"A"}, "C": {"A"}, "D": {"B", "C"}}
vertices_to_run: set = {"A", "B", "C"}
vertices_being_run = {"A"}
return {
"run_map": run_map,
"run_predecessors": run_predecessors,
"vertices_to_run": vertices_to_run,
"vertices_being_run": vertices_being_run,
}
def test_to_dict(data):
result = RunnableVerticesManager.from_dict(data).to_dict()
assert all(key in result.keys() for key in data.keys())
def test_from_dict(data):
result = RunnableVerticesManager.from_dict(data)
assert isinstance(result, RunnableVerticesManager)
def test_from_dict_without_run_map__bad_case(data):
data.pop("run_map")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_from_dict_without_run_predecessors__bad_case(data):
data.pop("run_predecessors")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_from_dict_without_vertices_to_run__bad_case(data):
data.pop("vertices_to_run")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_from_dict_without_vertices_being_run__bad_case(data):
data.pop("vertices_being_run")
with pytest.raises(KeyError):
RunnableVerticesManager.from_dict(data)
def test_pickle(data):
manager = RunnableVerticesManager.from_dict(data)
binary = pickle.dumps(manager)
result = pickle.loads(binary)
assert result.run_map == manager.run_map
assert result.run_predecessors == manager.run_predecessors
assert result.vertices_to_run == manager.vertices_to_run
assert result.vertices_being_run == manager.vertices_being_run
def test_update_run_state(data):
manager = RunnableVerticesManager.from_dict(data)
run_predecessors = {"E": {"D"}}
vertices_to_run = {"D"}
manager.update_run_state(run_predecessors, vertices_to_run)
assert "D" in manager.run_map
assert "D" in manager.vertices_to_run
assert "D" in manager.run_predecessors["E"]
def test_is_vertex_runnable(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
is_active = True
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_is_vertex_runnable__wrong_is_active(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
is_active = False
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_is_vertex_runnable__wrong_vertices_to_run(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "D"
is_active = True
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_is_vertex_runnable__wrong_run_predecessors(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
is_active = True
result = manager.is_vertex_runnable(vertex_id, is_active)
assert result is False
def test_are_all_predecessors_fulfilled(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
result = manager.are_all_predecessors_fulfilled(vertex_id)
assert result is True
def test_are_all_predecessors_fulfilled__wrong(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "D"
result = manager.are_all_predecessors_fulfilled(vertex_id)
assert result is False
def test_remove_from_predecessors(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "A"
manager.remove_from_predecessors(vertex_id)
assert all(vertex_id not in predecessors for predecessors in manager.run_predecessors.values())
def test_build_run_map(data):
manager = RunnableVerticesManager.from_dict(data)
vertices_to_run = {}
predecessor_map = {"Z": set(), "X": {"Z"}, "Y": {"Z"}, "W": {"X", "Y"}}
manager.build_run_map(predecessor_map, vertices_to_run)
assert all(v in manager.run_map.keys() for v in ["Z", "X", "Y"])
assert "W" not in manager.run_map.keys()
def test_update_vertex_run_state(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
is_runnable = True
manager.update_vertex_run_state(vertex_id, is_runnable)
assert vertex_id in manager.vertices_to_run
def test_update_vertex_run_state__bad_case(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
is_runnable = False
manager.update_vertex_run_state(vertex_id, is_runnable)
assert vertex_id not in manager.vertices_being_run
def test_remove_vertex_from_runnables(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
manager.remove_vertex_from_runnables(vertex_id)
assert vertex_id not in manager.vertices_being_run
def test_add_to_vertices_being_run(data):
manager = RunnableVerticesManager.from_dict(data)
vertex_id = "C"
manager.add_to_vertices_being_run(vertex_id)
assert vertex_id in manager.vertices_being_run

View file

@ -0,0 +1,122 @@
import pytest
from langflow.graph.graph import utils
@pytest.fixture
def graph():
return {
"A": {"successors": ["B"], "predecessors": []},
"B": {"successors": ["D"], "predecessors": ["A", "C"]},
"C": {"successors": ["B", "I"], "predecessors": ["N"]},
"D": {"successors": ["E", "F"], "predecessors": ["B"]},
"E": {"successors": ["G"], "predecessors": ["D"]},
"F": {"successors": ["G", "H"], "predecessors": ["D"]},
"G": {"successors": [], "predecessors": ["E", "F"]},
"H": {"successors": [], "predecessors": ["F"]},
"I": {"successors": ["M"], "predecessors": ["C", "J"]},
"J": {"successors": ["I", "K"], "predecessors": ["N"]},
"K": {"successors": ["Q", "P", "O"], "predecessors": ["J", "L"]},
"L": {"successors": ["K"], "predecessors": []},
"M": {"successors": [], "predecessors": ["I"]},
"N": {"successors": ["C", "J"], "predecessors": []},
"O": {"successors": ["R"], "predecessors": ["K"]},
"P": {"successors": ["U"], "predecessors": ["K"]},
"Q": {"successors": ["V"], "predecessors": ["K"]},
"R": {"successors": ["S"], "predecessors": ["O"]},
"S": {"successors": ["T"], "predecessors": ["R"]},
"T": {"successors": [], "predecessors": ["S"]},
"U": {"successors": ["W"], "predecessors": ["P"]},
"V": {"successors": ["Y"], "predecessors": ["Q"]},
"W": {"successors": ["X"], "predecessors": ["U"]},
"X": {"successors": [], "predecessors": ["W"]},
"Y": {"successors": ["Z"], "predecessors": ["V"]},
"Z": {"successors": [], "predecessors": ["Y"]},
}
def test_get_successors_a(graph):
vertex_id = "A"
result = utils.get_successors(graph, vertex_id)
assert set(result) == {"A", "B", "D", "E", "F", "H", "G"}
def test_get_successors_z(graph):
vertex_id = "Z"
result = utils.get_successors(graph, vertex_id)
assert set(result) == {"Z"}
def test_sort_up_to_vertex_n_is_start(graph):
vertex_id = "N"
result = utils.sort_up_to_vertex(graph, vertex_id, is_start=True)
# Result shoud be all the vertices
assert set(result) == set(graph.keys())
def test_sort_up_to_vertex_z(graph):
vertex_id = "Z"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"L", "N", "J", "K", "Q", "V", "Y", "Z"}
def test_sort_up_to_vertex_x(graph):
vertex_id = "X"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"L", "N", "J", "K", "P", "U", "W", "X"}
def test_sort_up_to_vertex_t(graph):
vertex_id = "T"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"L", "N", "J", "K", "O", "R", "S", "T"}
def test_sort_up_to_vertex_m(graph):
vertex_id = "M"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"N", "C", "J", "I", "M"}
def test_sort_up_to_vertex_h(graph):
vertex_id = "H"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"N", "C", "A", "B", "D", "F", "H"}
def test_sort_up_to_vertex_g(graph):
vertex_id = "G"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"N", "C", "A", "B", "D", "F", "E", "G"}
def test_sort_up_to_vertex_a(graph):
vertex_id = "A"
result = utils.sort_up_to_vertex(graph, vertex_id)
assert set(result) == {"A"}
def test_sort_up_to_vertex_invalid_vertex(graph):
vertex_id = "7"
with pytest.raises(ValueError):
utils.sort_up_to_vertex(graph, vertex_id)

View file

@ -0,0 +1,45 @@
import pytest
from langflow.services.database.models.api_key import ApiKeyCreate
@pytest.fixture
def api_key(client, logged_in_headers, active_user):
api_key = ApiKeyCreate(name="test-api-key")
response = client.post("api/v1/api_key", data=api_key.model_dump_json(), headers=logged_in_headers)
assert response.status_code == 200, response.text
return response.json()
def test_get_api_keys(client, logged_in_headers, api_key):
response = client.get("api/v1/api_key", headers=logged_in_headers)
assert response.status_code == 200, response.text
data = response.json()
assert "total_count" in data
assert "user_id" in data
assert "api_keys" in data
assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"])
# assert all api keys in data["api_keys"] are masked
assert all("**" in api_key["api_key"] for api_key in data["api_keys"])
def test_create_api_key(client, logged_in_headers):
api_key_name = "test-api-key"
response = client.post("api/v1/api_key", json={"name": api_key_name}, headers=logged_in_headers)
assert response.status_code == 200
data = response.json()
assert "name" in data and data["name"] == api_key_name
assert "api_key" in data
# When creating the API key is returned which is
# the only time the API key is unmasked
assert "**" not in data["api_key"]
def test_delete_api_key(client, logged_in_headers, active_user, api_key):
# Assuming a function to create a test API key, returning the key ID
api_key_id = api_key["id"]
response = client.delete(f"api/v1/api_key/{api_key_id}", headers=logged_in_headers)
assert response.status_code == 200
data = response.json()
assert data["detail"] == "API Key deleted"
# Optionally, add a follow-up check to ensure that the key is actually removed from the database

View file

@ -0,0 +1,47 @@
import json
import pytest
from langflow.graph import Graph
def get_graph(_type="basic"):
"""Get a graph from a json file"""
if _type == "basic":
path = pytest.BASIC_EXAMPLE_PATH
elif _type == "complex":
path = pytest.COMPLEX_EXAMPLE_PATH
elif _type == "openapi":
path = pytest.OPENAPI_EXAMPLE_PATH
with open(path, "r") as f:
flow_graph = json.load(f)
return flow_graph["data"]
@pytest.fixture
def basic_data_graph():
return get_graph()
@pytest.fixture
def complex_data_graph():
return get_graph("complex")
@pytest.fixture
def openapi_data_graph():
return get_graph("openapi")
def langchain_objects_are_equal(obj1, obj2):
return str(obj1) == str(obj2)
# Test build_graph
@pytest.mark.asyncio
async def test_build_graph(client, basic_data_graph):
graph = Graph.from_payload(basic_data_graph)
assert graph is not None
assert len(graph.vertices) == len(basic_data_graph["nodes"])
assert len(graph.edges) == len(basic_data_graph["edges"])

View file

@ -0,0 +1,37 @@
from pathlib import Path
from tempfile import tempdir
import pytest
from langflow.__main__ import app
from langflow.services import deps
@pytest.fixture(scope="module")
def default_settings():
return [
"--backend-only",
"--no-open-browser",
]
def test_components_path(runner, client, default_settings):
# Create a foldr in the tmp directory
temp_dir = Path(tempdir)
# create a "components" folder
temp_dir = temp_dir / "components"
temp_dir.mkdir(exist_ok=True)
result = runner.invoke(
app,
["run", "--components-path", str(temp_dir), *default_settings],
)
assert result.exit_code == 0, result.stdout
settings_service = deps.get_settings_service()
assert str(temp_dir) in settings_service.settings.components_path
def test_superuser(runner, client, session):
result = runner.invoke(app, ["superuser"], input="admin\nadmin\n")
assert result.exit_code == 0, result.stdout
assert "Superuser created successfully." in result.stdout

View file

@ -0,0 +1,528 @@
import ast
import types
from uuid import uuid4
import pytest
from langchain_core.documents import Document
from langflow.custom import Component, CustomComponent
from langflow.custom.code_parser.code_parser import CodeParser, CodeSyntaxError
from langflow.custom.custom_component.base_component import BaseComponent, ComponentCodeNullError
from langflow.custom.utils import build_custom_component_template
from langflow.services.database.models.flow import Flow, FlowCreate
@pytest.fixture
def code_component_with_multiple_outputs():
with open("src/backend/tests/data/component_multiple_outputs.py", "r") as f:
code = f.read()
return Component(code=code)
code_default = """
from langflow.custom import CustomComponent
from langflow.field_typing import BaseLanguageModel
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_core.documents import Document
import requests
class YourComponent(CustomComponent):
display_name: str = "Your Component"
description: str = "Your description"
field_config = { "url": { "multiline": True, "required": True } }
def build(self, url: str, llm: BaseLanguageModel) -> Document:
return Document(page_content="Hello World")
"""
def test_code_parser_init():
"""
Test the initialization of the CodeParser class.
"""
parser = CodeParser(code_default)
assert parser.code == code_default
def test_code_parser_get_tree():
"""
Test the __get_tree method of the CodeParser class.
"""
parser = CodeParser(code_default)
tree = parser.get_tree()
assert isinstance(tree, ast.AST)
def test_code_parser_syntax_error():
"""
Test the __get_tree method raises the
CodeSyntaxError when given incorrect syntax.
"""
code_syntax_error = "zzz import os"
parser = CodeParser(code_syntax_error)
with pytest.raises(CodeSyntaxError):
parser.get_tree()
def test_component_init():
"""
Test the initialization of the Component class.
"""
component = BaseComponent(code=code_default, function_entrypoint_name="build")
assert component.code == code_default
assert component.function_entrypoint_name == "build"
def test_component_get_code_tree():
"""
Test the get_code_tree method of the Component class.
"""
component = BaseComponent(code=code_default, function_entrypoint_name="build")
tree = component.get_code_tree(component.code)
assert "imports" in tree
def test_component_code_null_error():
"""
Test the get_function method raises the
ComponentCodeNullError when the code is empty.
"""
component = BaseComponent(code="", function_entrypoint_name="")
with pytest.raises(ComponentCodeNullError):
component.get_function()
def test_custom_component_init():
"""
Test the initialization of the CustomComponent class.
"""
function_entrypoint_name = "build"
custom_component = CustomComponent(code=code_default, function_entrypoint_name=function_entrypoint_name)
assert custom_component.code == code_default
assert custom_component.function_entrypoint_name == function_entrypoint_name
def test_custom_component_build_template_config():
"""
Test the build_template_config property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
config = custom_component.build_template_config()
assert isinstance(config, dict)
def test_custom_component_get_function():
"""
Test the get_function property of the CustomComponent class.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = custom_component.get_function()
assert isinstance(my_function, types.FunctionType)
def test_code_parser_parse_imports_import():
"""
Test the parse_imports method of the CodeParser
class with an import statement.
"""
parser = CodeParser(code_default)
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.Import):
parser.parse_imports(node)
assert "requests" in parser.data["imports"]
def test_code_parser_parse_imports_importfrom():
"""
Test the parse_imports method of the CodeParser
class with an import from statement.
"""
parser = CodeParser("from os import path")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
parser.parse_imports(node)
assert ("os", "path") in parser.data["imports"]
def test_code_parser_parse_functions():
"""
Test the parse_functions method of the CodeParser class.
"""
parser = CodeParser("def test(): pass")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
parser.parse_functions(node)
assert len(parser.data["functions"]) == 1
assert parser.data["functions"][0]["name"] == "test"
def test_code_parser_parse_classes():
"""
Test the parse_classes method of the CodeParser class.
"""
parser = CodeParser("class Test: pass")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
parser.parse_classes(node)
assert len(parser.data["classes"]) == 1
assert parser.data["classes"][0]["name"] == "Test"
def test_code_parser_parse_global_vars():
"""
Test the parse_global_vars method of the CodeParser class.
"""
parser = CodeParser("x = 1")
tree = parser.get_tree()
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
parser.parse_global_vars(node)
assert len(parser.data["global_vars"]) == 1
assert parser.data["global_vars"][0]["targets"] == ["x"]
def test_component_get_function_valid():
"""
Test the get_function method of the Component
class with valid code and function_entrypoint_name.
"""
component = BaseComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = component.get_function()
assert callable(my_function)
def test_custom_component_get_function_entrypoint_args():
"""
Test the get_function_entrypoint_args
property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
args = custom_component.get_function_entrypoint_args
assert len(args) == 3
assert args[0]["name"] == "self"
assert args[1]["name"] == "url"
assert args[2]["name"] == "llm"
def test_custom_component_get_function_entrypoint_return_type():
"""
Test the get_function_entrypoint_return_type
property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type
assert return_type == [Document]
def test_custom_component_get_main_class_name():
"""
Test the get_main_class_name property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
class_name = custom_component.get_main_class_name
assert class_name == "YourComponent"
def test_custom_component_get_function_valid():
"""
Test the get_function property of the CustomComponent
class with valid code and function_entrypoint_name.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = custom_component.get_function
assert callable(my_function)
def test_code_parser_parse_arg_no_annotation():
"""
Test the parse_arg method of the CodeParser class without an annotation.
"""
parser = CodeParser("")
arg = ast.arg(arg="x", annotation=None)
result = parser.parse_arg(arg, None)
assert result["name"] == "x"
assert "type" not in result
def test_code_parser_parse_arg_with_annotation():
"""
Test the parse_arg method of the CodeParser class with an annotation.
"""
parser = CodeParser("")
arg = ast.arg(arg="x", annotation=ast.Name(id="int", ctx=ast.Load()))
result = parser.parse_arg(arg, None)
assert result["name"] == "x"
assert result["type"] == "int"
def test_code_parser_parse_callable_details_no_args():
"""
Test the parse_callable_details method of the
CodeParser class with a function with no arguments.
"""
parser = CodeParser("")
node = ast.FunctionDef(
name="test",
args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=[],
decorator_list=[],
returns=None,
)
result = parser.parse_callable_details(node)
assert result["name"] == "test"
assert len(result["args"]) == 0
def test_code_parser_parse_assign():
"""
Test the parse_assign method of the CodeParser class.
"""
parser = CodeParser("")
stmt = ast.Assign(targets=[ast.Name(id="x", ctx=ast.Store())], value=ast.Num(n=1))
result = parser.parse_assign(stmt)
assert result["name"] == "x"
assert result["value"] == "1"
def test_code_parser_parse_ann_assign():
"""
Test the parse_ann_assign method of the CodeParser class.
"""
parser = CodeParser("")
stmt = ast.AnnAssign(
target=ast.Name(id="x", ctx=ast.Store()),
annotation=ast.Name(id="int", ctx=ast.Load()),
value=ast.Num(n=1),
simple=1,
)
result = parser.parse_ann_assign(stmt)
assert result["name"] == "x"
assert result["value"] == "1"
assert result["annotation"] == "int"
def test_code_parser_parse_function_def_not_init():
"""
Test the parse_function_def method of the
CodeParser class with a function that is not __init__.
"""
parser = CodeParser("")
stmt = ast.FunctionDef(
name="test",
args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=[],
decorator_list=[],
returns=None,
)
result, is_init = parser.parse_function_def(stmt)
assert result["name"] == "test"
assert not is_init
def test_code_parser_parse_function_def_init():
"""
Test the parse_function_def method of the
CodeParser class with an __init__ function.
"""
parser = CodeParser("")
stmt = ast.FunctionDef(
name="__init__",
args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
body=[],
decorator_list=[],
returns=None,
)
result, is_init = parser.parse_function_def(stmt)
assert result["name"] == "__init__"
assert is_init
def test_component_get_code_tree_syntax_error():
"""
Test the get_code_tree method of the Component class
raises the CodeSyntaxError when given incorrect syntax.
"""
component = BaseComponent(code="import os as", function_entrypoint_name="build")
with pytest.raises(CodeSyntaxError):
component.get_code_tree(component.code)
def test_custom_component_class_template_validation_no_code():
"""
Test the _class_template_validation method of the CustomComponent class
raises the HTTPException when the code is None.
"""
custom_component = CustomComponent(code=None, function_entrypoint_name="build")
with pytest.raises(TypeError):
custom_component.get_function()
def test_custom_component_get_code_tree_syntax_error():
"""
Test the get_code_tree method of the CustomComponent class
raises the CodeSyntaxError when given incorrect syntax.
"""
custom_component = CustomComponent(code="import os as", function_entrypoint_name="build")
with pytest.raises(CodeSyntaxError):
custom_component.get_code_tree(custom_component.code)
def test_custom_component_get_function_entrypoint_args_no_args():
"""
Test the get_function_entrypoint_args property of
the CustomComponent class with a build method with no arguments.
"""
my_code = """
class MyMainClass(CustomComponent):
def build():
pass"""
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
args = custom_component.get_function_entrypoint_args
assert len(args) == 0
def test_custom_component_get_function_entrypoint_return_type_no_return_type():
"""
Test the get_function_entrypoint_return_type property of the
CustomComponent class with a build method with no return type.
"""
my_code = """
class MyClass(CustomComponent):
def build():
pass"""
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type
assert return_type == []
def test_custom_component_get_main_class_name_no_main_class():
"""
Test the get_main_class_name property of the
CustomComponent class when there is no main class.
"""
my_code = """
def build():
pass"""
custom_component = CustomComponent(code=my_code, function_entrypoint_name="build")
class_name = custom_component.get_main_class_name
assert class_name == ""
def test_custom_component_build_not_implemented():
"""
Test the build method of the CustomComponent
class raises the NotImplementedError.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
with pytest.raises(NotImplementedError):
custom_component.build()
def test_build_config_no_code():
component = CustomComponent(code=None)
assert component.get_function_entrypoint_args == []
assert component.get_function_entrypoint_return_type == []
@pytest.fixture
def component(client, active_user):
return CustomComponent(
user_id=active_user.id,
field_config={
"fields": {
"llm": {"type": "str"},
"url": {"type": "str"},
"year": {"type": "int"},
}
},
)
@pytest.fixture(scope="session")
def test_flow(db):
flow_data = {
"nodes": [{"id": "1"}, {"id": "2"}],
"edges": [{"source": "1", "target": "2"}],
}
# Create flow
flow = FlowCreate(id=uuid4(), name="Test Flow", description="Fixture flow", data=flow_data)
# Add to database
db.add(flow)
db.commit()
yield flow
# Clean up
db.delete(flow)
db.commit()
@pytest.fixture(scope="session")
def db(app):
# Setup database for tests
yield app.db
# Teardown
app.db.drop_all()
def test_list_flows_return_type(component):
flows = component.list_flows()
assert isinstance(flows, list)
def test_list_flows_flow_objects(component):
flows = component.list_flows()
assert all(isinstance(flow, Flow) for flow in flows)
def test_build_config_return_type(component):
config = component.build_config()
assert isinstance(config, dict)
def test_build_config_has_fields(component):
config = component.build_config()
assert "fields" in config
def test_build_config_fields_dict(component):
config = component.build_config()
assert isinstance(config["fields"], dict)
def test_build_config_field_keys(component):
config = component.build_config()
assert all(isinstance(key, str) for key in config["fields"])
def test_build_config_field_values_dict(component):
config = component.build_config()
assert all(isinstance(value, dict) for value in config["fields"].values())
def test_build_config_field_value_keys(component):
config = component.build_config()
field_values = config["fields"].values()
assert all("type" in value for value in field_values)
def test_custom_component_multiple_outputs(code_component_with_multiple_outputs, active_user):
frontnd_node_dict, _ = build_custom_component_template(code_component_with_multiple_outputs, active_user.id)
assert frontnd_node_dict["outputs"][0]["types"] == ["Text"]

View file

@ -0,0 +1,140 @@
import pytest
from langchain_core.documents import Document
from langflow.schema import Data
def test_data_initialization():
record = Data(text_key="msg", data={"msg": "Hello, World!", "extra": "value"})
assert record.msg == "Hello, World!"
assert record.extra == "value"
def test_validate_data_with_extra_keys():
record = Data(dummy_key="dummy", data={"key": "value"})
assert record.data["dummy_key"] == "dummy"
assert "dummy_key" in record.data
assert record.key == "value"
def test_conversion_to_document():
record = Data(data={"text": "Sample text", "meta": "data"})
document = record.to_lc_document()
assert document.page_content == "Sample text"
assert document.metadata == {"meta": "data"}
def test_conversion_from_document():
document = Document(page_content="Doc content", metadata={"meta": "info"})
record = Data.from_document(document)
assert record.text == "Doc content"
assert record.meta == "info"
def test_add_method_for_strings():
record1 = Data(data={"text": "Hello"})
record2 = Data(data={"text": " World"})
combined = record1 + record2
assert combined.text == "Hello World"
def test_add_method_for_integers():
record1 = Data(data={"number": 5})
record2 = Data(data={"number": 10})
combined = record1 + record2
assert combined.number == 15
def test_add_method_with_non_overlapping_keys():
record1 = Data(data={"text": "Hello"})
record2 = Data(data={"number": 10})
combined = record1 + record2
assert combined.text == "Hello"
assert combined.number == 10
def test_custom_attribute_get_set_del():
record = Data()
record.custom_attr = "custom_value"
assert record.custom_attr == "custom_value"
del record.custom_attr
with pytest.raises(AttributeError):
_ = record.custom_attr
def test_deep_copy():
import copy
record1 = Data(data={"text": "Hello", "number": 10})
record2 = copy.deepcopy(record1)
assert record2.text == "Hello"
assert record2.number == 10
record2.text = "World"
assert record1.text == "Hello" # Ensure original is unchanged
def test_custom_attribute_setting_and_getting():
record = Data()
record.dynamic_attribute = "Dynamic Value"
assert record.dynamic_attribute == "Dynamic Value"
def test_str_and_dir_methods():
record = Data(text_key="text", data={"text": "Test Text", "key": "value"})
assert "Test Text" in str(record)
assert "key" in dir(record)
assert "data" in dir(record)
def test_dir_includes_data_keys():
record = Data(data={"text": "Hello", "new_attr": "value"})
dir_output = dir(record)
# Check for standard attributes
assert "data" in dir_output
assert "text_key" in dir_output
assert "__add__" in dir_output # Checking for a method
# Check for dynamic attributes from data
assert "text" in dir_output
assert "new_attr" in dir_output
# Optionally, verify that dynamically added attributes are listed
record.dynamic_attr = "dynamic"
assert "dynamic_attr" in dir_output or "dynamic_attr" in dir(record) # To account for the change
def test_dir_reflects_attribute_deletion():
record = Data(data={"removable": "I can be removed"})
assert "removable" in dir(record)
# Delete the attribute and check again
del record.removable
assert "removable" not in dir(record)
def test_get_text_with_text_key():
data = {"text": "Hello, World!"}
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "Hello, World!"
def test_get_text_without_text_key():
data = {"other_key": "Hello, World!"}
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"
def test_get_text_with_empty_data():
data = {}
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"
def test_get_text_with_none_data():
data = None
schema = Data(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"

View file

@ -0,0 +1,201 @@
import json
import os
from pathlib import Path
from unittest.mock import Mock, patch
import httpx
import pytest
import respx
from dictdiffer import diff
from httpx import Response
from langflow.components import data
@pytest.fixture
def api_request():
# This fixture provides an instance of APIRequest for each test case
return data.APIRequestComponent()
@pytest.mark.asyncio
@respx.mock
async def test_successful_get_request(api_request):
# Mocking a successful GET request
url = "https://example.com/api/test"
method = "GET"
mock_response = {"success": True}
respx.get(url).mock(return_value=Response(200, json=mock_response))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url)
# Assertions
assert result.data["status_code"] == 200
assert result.data["result"] == mock_response
def test_parse_curl(api_request):
# Arrange
field_value = (
"curl -X GET https://example.com/api/test -H 'Content-Type: application/json' -d '{\"key\": \"value\"}'"
)
build_config = {
"method": {"value": ""},
"urls": {"value": []},
"headers": {},
"body": {},
}
# Act
new_build_config = api_request.parse_curl(field_value, build_config.copy())
# Assert
assert new_build_config["method"]["value"] == "GET"
assert new_build_config["urls"]["value"] == ["https://example.com/api/test"]
assert new_build_config["headers"]["value"] == {"Content-Type": "application/json"}
assert new_build_config["body"]["value"] == {"key": "value"}
@pytest.mark.asyncio
@respx.mock
async def test_failed_request(api_request):
# Mocking a failed GET request
url = "https://example.com/api/test"
method = "GET"
respx.get(url).mock(return_value=Response(404))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url)
# Assertions
assert result.data["status_code"] == 404
@pytest.mark.asyncio
@respx.mock
async def test_timeout(api_request):
# Mocking a timeout
url = "https://example.com/api/timeout"
method = "GET"
respx.get(url).mock(side_effect=httpx.TimeoutException(message="Timeout", request=None))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url, timeout=1)
# Assertions
assert result.data["status_code"] == 408
assert result.data["error"] == "Request timed out"
@pytest.mark.asyncio
@respx.mock
async def test_build_with_multiple_urls(api_request):
# This test depends on having a working internet connection and accessible URLs
# It's better to mock these requests using respx or a similar library
# Setup for multiple URLs
method = "GET"
urls = ["https://example.com/api/one", "https://example.com/api/two"]
# You would mock these requests similarly to the single request tests
for url in urls:
respx.get(url).mock(return_value=Response(200, json={"success": True}))
# Do I have to mock the async client?
#
# Execute the build method
api_request.set_attributes(
{
"method": method,
"urls": urls,
}
)
results = await api_request.make_requests()
# Assertions
assert len(results) == len(urls)
@patch("langflow.components.data.Directory.parallel_load_data")
@patch("langflow.components.data.Directory.retrieve_file_paths")
@patch("langflow.components.data.DirectoryComponent.resolve_path")
def test_directory_component_build_with_multithreading(
mock_resolve_path, mock_retrieve_file_paths, mock_parallel_load_data
):
# Arrange
directory_component = data.DirectoryComponent()
path = os.path.dirname(os.path.abspath(__file__))
depth = 1
max_concurrency = 2
load_hidden = False
recursive = True
silent_errors = False
use_multithreading = True
mock_resolve_path.return_value = path
mock_retrieve_file_paths.return_value = [
os.path.join(path, file) for file in os.listdir(path) if file.endswith(".py")
]
mock_parallel_load_data.return_value = [Mock()]
# Act
directory_component.set_attributes(
{
"path": path,
"depth": depth,
"max_concurrency": max_concurrency,
"load_hidden": load_hidden,
"recursive": recursive,
"silent_errors": silent_errors,
"use_multithreading": use_multithreading,
}
)
directory_component.load_directory()
# Assert
mock_resolve_path.assert_called_once_with(path)
mock_retrieve_file_paths.assert_called_once_with(path, load_hidden, recursive, depth)
mock_parallel_load_data.assert_called_once_with(
mock_retrieve_file_paths.return_value, silent_errors, max_concurrency
)
def test_directory_without_mocks():
directory_component = data.DirectoryComponent()
from langflow.initial_setup import setup
from langflow.initial_setup.setup import load_starter_projects
_, projects = zip(*load_starter_projects())
# the setup module has a folder where the projects are stored
# the contents of that folder are in the projects variable
# the directory component can be used to load the projects
# and we can validate if the contents are the same as the projects variable
setup_path = Path(setup.__file__).parent / "starter_projects"
directory_component.set_attributes({"path": str(setup_path), "use_multithreading": False})
results = directory_component.load_directory()
assert len(results) == len(projects)
# each result is a Data that contains the content attribute
# each are dict that are exactly the same as one of the projects
for i, result in enumerate(results):
file_dict = json.loads(result.text)
assert file_dict in projects, list(diff(file_dict, projects[i]))
# in ../docs/docs/components there are many mdx files
# check if the directory component can load them
# just check if the number of results is the same as the number of files
directory_component = data.DirectoryComponent()
docs_path = Path(__file__).parent.parent.parent.parent.parent / "docs" / "docs" / "Components"
directory_component.set_attributes({"path": str(docs_path), "use_multithreading": False})
results = directory_component.load_directory()
docs_files = list(docs_path.glob("*.md")) + list(docs_path.glob("*.json"))
assert len(results) == len(docs_files)
def test_url_component():
url_component = data.URLComponent()
url_component.set_attributes({"urls": ["https://langflow.org"]})
# the url component can be used to load the contents of a website
_data = url_component.fetch_content()
assert all(value.data for value in _data)
assert all(value.text for value in _data)
assert all(value.source for value in _data)

View file

@ -0,0 +1,281 @@
from uuid import UUID, uuid4
import orjson
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session
from langflow.api.v1.schemas import FlowListCreate
from langflow.initial_setup.setup import load_starter_projects, load_flows_from_directory
from langflow.services.database.models.base import orjson_dumps
from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
@pytest.fixture(scope="module")
def json_style():
# class FlowStyleBase(SQLModel):
# color: str = Field(index=True)
# emoji: str = Field(index=False)
# flow_id: UUID = Field(default=None, foreign_key="flow.id")
return orjson_dumps(
{
"color": "red",
"emoji": "👍",
}
)
def test_create_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
# flow is optional so we can create a flow without a flow
flow = FlowCreate(name="Test Flow")
response = client.post("api/v1/flows/", json=flow.model_dump(exclude_unset=True), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
response = client.get("api/v1/flows/", headers=logged_in_headers)
assert response.status_code == 200
assert len(response.json()) > 0
def test_read_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"] # flow_id should be a UUID but is a string
# turn it into a UUID
flow_id = UUID(flow_id)
response = client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"]
updated_flow = FlowUpdate(
name="Updated Flow",
description="updated description",
data=data,
)
response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["name"] == updated_flow.name
assert response.json()["description"] == updated_flow.description
# assert response.json()["data"] == updated_flow.data
def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"]
response = client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["message"] == "Flow deleted successfully"
def test_delete_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
# Create ten flows
number_of_flows = 10
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = []
for flow in flows:
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
flow_ids.append(response.json()["id"])
response = client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids)
assert response.status_code == 200, response.content
assert response.json().get("deleted") == number_of_flows
def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
flows=[
FlowCreate(name="Flow 1", description="description", data=data),
FlowCreate(name="Flow 2", description="description", data=data),
]
)
# Make request to endpoint
response = client.post("api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers)
# Check response status code
assert response.status_code == 201
# Check response data
response_data = response.json()
assert len(response_data) == 2
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
assert response_data[1]["name"] == "Flow 2"
assert response_data[1]["description"] == "description"
assert response_data[1]["data"] == data
def test_upload_file(client: TestClient, session: Session, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
flows=[
FlowCreate(name="Flow 1", description="description", data=data),
FlowCreate(name="Flow 2", description="description", data=data),
]
)
file_contents = orjson_dumps(flow_list.dict())
response = client.post(
"api/v1/flows/upload/",
files={"file": ("examples.json", file_contents, "application/json")},
headers=logged_in_headers,
)
# Check response status code
assert response.status_code == 201
# Check response data
response_data = response.json()
assert len(response_data) == 2
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
assert response_data[1]["name"] == "Flow 2"
assert response_data[1]["description"] == "description"
assert response_data[1]["data"] == data
def test_download_file(
client: TestClient,
session: Session,
json_flow,
active_user,
logged_in_headers,
):
flow = orjson.loads(json_flow)
data = flow["data"]
# Create test data
flow_list = FlowListCreate(
flows=[
FlowCreate(name="Flow 1", description="description", data=data),
FlowCreate(name="Flow 2", description="description", data=data),
]
)
db_manager = get_db_service()
with session_getter(db_manager) as session:
for flow in flow_list.flows:
flow.user_id = active_user.id
db_flow = Flow.model_validate(flow, from_attributes=True)
session.add(db_flow)
session.commit()
# Make request to endpoint
response = client.get("api/v1/flows/download/", headers=logged_in_headers)
# Check response status code
assert response.status_code == 200, response.json()
# Check response data
response_data = response.json()["flows"]
starter_projects = load_starter_projects()
number_of_projects = len(starter_projects) + len(flow_list.flows)
assert len(response_data) == number_of_projects, response_data
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
assert response_data[1]["name"] == "Flow 2"
assert response_data[1]["description"] == "description"
assert response_data[1]["data"] == data
def test_create_flow_with_invalid_data(client: TestClient, active_user, logged_in_headers):
flow = {"name": "a" * 256, "data": "Invalid flow data"}
response = client.post("api/v1/flows/", json=flow, headers=logged_in_headers)
assert response.status_code == 422
def test_get_nonexistent_flow(client: TestClient, active_user, logged_in_headers):
uuid = uuid4()
response = client.get(f"api/v1/flows/{uuid}", headers=logged_in_headers)
assert response.status_code == 404
def test_update_flow_idempotency(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow_data = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers)
flow_id = response.json()["id"]
updated_flow = FlowCreate(name="Updated Flow", description="description", data=data)
response1 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
response2 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response1.json() == response2.json()
def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
uuid = uuid4()
updated_flow = FlowCreate(
name="Updated Flow",
description="description",
data=data,
)
response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 404, response.text
def test_delete_nonexistent_flow(client: TestClient, active_user, logged_in_headers):
uuid = uuid4()
response = client.delete(f"api/v1/flows/{uuid}", headers=logged_in_headers)
assert response.status_code == 404
def test_read_only_starter_projects(client: TestClient, active_user, logged_in_headers):
response = client.get("api/v1/flows/", headers=logged_in_headers)
starter_projects = load_starter_projects()
assert response.status_code == 200
assert len(response.json()) == len(starter_projects)
@pytest.mark.load_flows
def test_load_flows(client: TestClient, load_flows_dir):
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
assert response.status_code == 200
assert response.json()["name"] == "BasicExample"
# re-run to ensure updates work well
load_flows_from_directory()
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
assert response.status_code == 200
assert response.json()["name"] == "BasicExample"

View file

@ -0,0 +1,104 @@
from unittest.mock import MagicMock
import pytest
from langflow.services.deps import get_storage_service
from langflow.services.storage.service import StorageService
@pytest.fixture
def mock_storage_service():
# Create a mock instance of StorageService
service = MagicMock(spec=StorageService)
# Setup mock behaviors for the service methods as needed
service.save_file.return_value = None
service.get_file.return_value = b"file content" # Binary content for files
service.list_files.return_value = ["file1.txt", "file2.jpg"]
service.delete_file.return_value = None
return service
def test_upload_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
# Replace the actual storage service with the mock
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.post(
f"api/v1/files/upload/{flow.id}",
files={"file": ("test.txt", b"test content")},
headers=headers,
)
assert response.status_code == 201
assert response.json() == {
"flowId": str(flow.id),
"file_path": f"{flow.id}/test.txt",
}
def test_download_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.get(f"api/v1/files/download/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200
assert response.content == b"file content"
def test_list_files(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.get(f"api/v1/files/list/{flow.id}", headers=headers)
assert response.status_code == 200
assert response.json() == {"files": ["file1.txt", "file2.jpg"]}
def test_delete_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.delete(f"api/v1/files/delete/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "File test.txt deleted successfully"}
def test_file_operations(client, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
flow_id = flow.id
file_name = "test.txt"
file_content = b"Hello, world!"
# Step 1: Upload the file
response = client.post(
f"api/v1/files/upload/{flow_id}",
files={"file": (file_name, file_content)},
headers=headers,
)
assert response.status_code == 201
assert response.json() == {
"flowId": str(flow_id),
"file_path": f"{flow_id}/{file_name}",
}
# Step 2: List files in the folder
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert response.status_code == 200
assert file_name in response.json()["files"]
# Step 3: Download the file and verify its content
response = client.get(f"api/v1/files/download/{flow_id}/{file_name}", headers=headers)
assert response.status_code == 200
assert response.content == file_content
# the headers are application/octet-stream
assert response.headers["content-type"] == "application/octet-stream"
# mime_type is inside media_type
# Step 4: Delete the file
response = client.delete(f"api/v1/files/delete/{flow_id}/{file_name}", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": f"File {file_name} deleted successfully"}
# Verify that the file is indeed deleted
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert file_name not in response.json()["files"]

View file

@ -0,0 +1,57 @@
import pytest
from langflow.template.field.base import Input
from langflow.template.frontend_node.base import FrontendNode
from langflow.template.template.base import Template
@pytest.fixture
def sample_template_field() -> Input:
return Input(name="test_field", field_type="str")
@pytest.fixture
def sample_template(sample_template_field: Input) -> Template:
return Template(type_name="test_template", fields=[sample_template_field])
@pytest.fixture
def sample_frontend_node(sample_template: Template) -> FrontendNode:
return FrontendNode(
template=sample_template,
description="test description",
base_classes=["base_class1", "base_class2"],
name="test_frontend_node",
)
def test_template_field_defaults(sample_template_field: Input):
assert sample_template_field.field_type == "str"
assert sample_template_field.required is False
assert sample_template_field.placeholder == ""
assert sample_template_field.is_list is False
assert sample_template_field.show is True
assert sample_template_field.multiline is False
assert sample_template_field.value is None
assert sample_template_field.file_types == []
assert sample_template_field.file_path == ""
assert sample_template_field.password is False
assert sample_template_field.name == "test_field"
def test_template_to_dict(sample_template: Template, sample_template_field: Input):
template_dict = sample_template.to_dict()
assert template_dict["_type"] == "test_template"
assert len(template_dict) == 2 # _type and test_field
assert "test_field" in template_dict
assert "type" in template_dict["test_field"]
assert "required" in template_dict["test_field"]
def test_frontend_node_to_dict(sample_frontend_node: FrontendNode):
node_dict = sample_frontend_node.to_dict()
assert len(node_dict) == 1
assert "test_frontend_node" in node_dict
assert "description" in node_dict["test_frontend_node"]
assert "template" in node_dict["test_frontend_node"]
assert "base_classes" in node_dict["test_frontend_node"]

View file

@ -0,0 +1,418 @@
import copy
import json
import pickle
from typing import Type, Union
import pytest
from langflow.graph import Graph
from langflow.graph.edge.base import Edge
from langflow.graph.graph.utils import (
find_last_node,
process_flow,
set_new_target_handle,
ungroup_node,
update_source_handle,
update_target_handle,
update_template,
)
from langflow.graph.vertex.base import Vertex
from langflow.initial_setup.setup import load_starter_projects
from langflow.utils.payload import get_root_vertex
# Test cases for the graph module
# now we have three types of graph:
# BASIC_EXAMPLE_PATH, COMPLEX_EXAMPLE_PATH, OPENAPI_EXAMPLE_PATH
@pytest.fixture
def sample_template():
return {
"field1": {"proxy": {"field": "some_field", "id": "node1"}},
"field2": {"proxy": {"field": "other_field", "id": "node2"}},
}
@pytest.fixture
def sample_nodes():
return [
{
"id": "node1",
"data": {"node": {"template": {"some_field": {"show": True, "advanced": False, "name": "Name1"}}}},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"other_field": {
"show": False,
"advanced": True,
"display_name": "DisplayName2",
}
}
}
},
},
{
"id": "node3",
"data": {"node": {"template": {"unrelated_field": {"show": True, "advanced": True}}}},
},
]
def get_node_by_type(graph, node_type: Type[Vertex]) -> Union[Vertex, None]:
"""Get a node by type"""
return next((node for node in graph.vertices if isinstance(node, node_type)), None)
def test_graph_structure(basic_graph):
assert isinstance(basic_graph, Graph)
assert len(basic_graph.vertices) > 0
assert len(basic_graph.edges) > 0
for node in basic_graph.vertices:
assert isinstance(node, Vertex)
for edge in basic_graph.edges:
assert isinstance(edge, Edge)
source_vertex = basic_graph.get_vertex(edge.source_id)
target_vertex = basic_graph.get_vertex(edge.target_id)
assert source_vertex in basic_graph.vertices
assert target_vertex in basic_graph.vertices
def test_circular_dependencies(basic_graph):
assert isinstance(basic_graph, Graph)
def check_circular(node, visited):
visited.add(node)
neighbors = basic_graph.get_vertices_with_target(node)
for neighbor in neighbors:
if neighbor in visited:
return True
if check_circular(neighbor, visited.copy()):
return True
return False
for node in basic_graph.vertices:
assert not check_circular(node, set())
def test_invalid_node_types():
graph_data = {
"nodes": [
{
"id": "1",
"data": {
"node": {
"base_classes": ["BaseClass"],
"template": {
"_type": "InvalidNodeType",
},
},
},
},
],
"edges": [],
}
with pytest.raises(Exception):
Graph(graph_data["nodes"], graph_data["edges"])
def test_get_vertices_with_target(basic_graph):
"""Test getting connected nodes"""
assert isinstance(basic_graph, Graph)
# Get root node
root = get_root_vertex(basic_graph)
assert root is not None
connected_nodes = basic_graph.get_vertices_with_target(root.id)
assert connected_nodes is not None
def test_get_node_neighbors_basic(basic_graph):
"""Test getting node neighbors"""
assert isinstance(basic_graph, Graph)
# Get root node
root = get_root_vertex(basic_graph)
assert root is not None
neighbors = basic_graph.get_vertex_neighbors(root)
assert neighbors is not None
assert isinstance(neighbors, dict)
# Root Node is an Agent, it requires an LLMChain and tools
# We need to check if there is a Chain in the one of the neighbors'
# data attribute in the type key
assert any("ConversationBufferMemory" in neighbor.data["type"] for neighbor, val in neighbors.items() if val)
assert any("OpenAI" in neighbor.data["type"] for neighbor, val in neighbors.items() if val)
def test_get_node(basic_graph):
"""Test getting a single node"""
node_id = basic_graph.vertices[0].id
node = basic_graph.get_vertex(node_id)
assert isinstance(node, Vertex)
assert node.id == node_id
def test_build_nodes(basic_graph):
"""Test building nodes"""
assert len(basic_graph.vertices) == len(basic_graph._vertices)
for node in basic_graph.vertices:
assert isinstance(node, Vertex)
def test_build_edges(basic_graph):
"""Test building edges"""
assert len(basic_graph.edges) == len(basic_graph._edges)
for edge in basic_graph.edges:
assert isinstance(edge, Edge)
assert isinstance(edge.source_id, str)
assert isinstance(edge.target_id, str)
def test_get_root_vertex(client, basic_graph, complex_graph):
"""Test getting root node"""
assert isinstance(basic_graph, Graph)
root = get_root_vertex(basic_graph)
assert root is not None
assert isinstance(root, Vertex)
assert root.data["type"] == "TimeTravelGuideChain"
# For complex example, the root node is a ZeroShotAgent too
assert isinstance(complex_graph, Graph)
root = get_root_vertex(complex_graph)
assert root is not None
assert isinstance(root, Vertex)
assert root.data["type"] == "ZeroShotAgent"
def test_validate_edges(basic_graph):
"""Test validating edges"""
assert isinstance(basic_graph, Graph)
# all edges should be valid
assert all(edge.valid for edge in basic_graph.edges)
def test_matched_type(basic_graph):
"""Test matched type attribute in Edge"""
assert isinstance(basic_graph, Graph)
# all edges should be valid
assert all(edge.valid for edge in basic_graph.edges)
# all edges should have a matched_type attribute
assert all(hasattr(edge, "matched_type") for edge in basic_graph.edges)
# The matched_type attribute should be in the source_types attr
assert all(edge.matched_type in edge.source_types for edge in basic_graph.edges)
def test_build_params(basic_graph):
"""Test building params"""
assert isinstance(basic_graph, Graph)
# all edges should be valid
assert all(edge.valid for edge in basic_graph.edges)
# all edges should have a matched_type attribute
assert all(hasattr(edge, "matched_type") for edge in basic_graph.edges)
# The matched_type attribute should be in the source_types attr
assert all(edge.matched_type in edge.source_types for edge in basic_graph.edges)
# Get the root node
root = get_root_vertex(basic_graph)
# Root node is a TimeTravelGuideChain
# which requires an llm and memory
assert root is not None
assert isinstance(root.params, dict)
assert "llm" in root.params
assert "memory" in root.params
# def test_wrapper_node_build(openapi_graph):
# wrapper_node = get_node_by_type(openapi_graph, WrapperVertex)
# assert wrapper_node is not None
# built_object = wrapper_node.build()
# assert built_object is not None
def test_find_last_node(grouped_chat_json_flow):
grouped_chat_data = json.loads(grouped_chat_json_flow).get("data")
nodes, edges = grouped_chat_data["nodes"], grouped_chat_data["edges"]
last_node = find_last_node(nodes, edges)
assert last_node is not None # Replace with the actual expected value
assert last_node["id"] == "LLMChain-pimAb" # Replace with the actual expected value
def test_ungroup_node(grouped_chat_json_flow):
grouped_chat_data = json.loads(grouped_chat_json_flow).get("data")
group_node = grouped_chat_data["nodes"][2] # Assuming the first node is a group node
base_flow = copy.deepcopy(grouped_chat_data)
ungroup_node(group_node["data"], base_flow)
# after ungroup_node is called, the base_flow and grouped_chat_data should be different
assert base_flow != grouped_chat_data
# assert node 2 is not a group node anymore
assert base_flow["nodes"][2]["data"]["node"].get("flow") is None
# assert the edges are updated
assert len(base_flow["edges"]) > len(grouped_chat_data["edges"])
assert base_flow["edges"][0]["source"] == "ConversationBufferMemory-kUMif"
assert base_flow["edges"][0]["target"] == "LLMChain-2P369"
assert base_flow["edges"][1]["source"] == "PromptTemplate-Wjk4g"
assert base_flow["edges"][1]["target"] == "LLMChain-2P369"
assert base_flow["edges"][2]["source"] == "ChatOpenAI-rUJ1b"
assert base_flow["edges"][2]["target"] == "LLMChain-2P369"
def test_process_flow(grouped_chat_json_flow):
grouped_chat_data = json.loads(grouped_chat_json_flow).get("data")
processed_flow = process_flow(grouped_chat_data)
assert processed_flow is not None
assert isinstance(processed_flow, dict)
assert "nodes" in processed_flow
assert "edges" in processed_flow
def test_process_flow_one_group(one_grouped_chat_json_flow):
grouped_chat_data = json.loads(one_grouped_chat_json_flow).get("data")
# There should be only one node
assert len(grouped_chat_data["nodes"]) == 1
# Get the node, it should be a group node
group_node = grouped_chat_data["nodes"][0]
node_data = group_node["data"]["node"]
assert node_data.get("flow") is not None
template_data = node_data["template"]
assert any("openai_api_key" in key for key in template_data.keys())
# Get the openai_api_key dict
openai_api_key = next(
(template_data[key] for key in template_data.keys() if "openai_api_key" in key),
None,
)
assert openai_api_key is not None
assert openai_api_key["value"] == "test"
processed_flow = process_flow(grouped_chat_data)
assert processed_flow is not None
assert isinstance(processed_flow, dict)
assert "nodes" in processed_flow
assert "edges" in processed_flow
# Now get the node that has ChatOpenAI in its id
chat_openai_node = next((node for node in processed_flow["nodes"] if "ChatOpenAI" in node["id"]), None)
assert chat_openai_node is not None
assert chat_openai_node["data"]["node"]["template"]["openai_api_key"]["value"] == "test"
def test_process_flow_vector_store_grouped(vector_store_grouped_json_flow):
grouped_chat_data = json.loads(vector_store_grouped_json_flow).get("data")
nodes = grouped_chat_data["nodes"]
assert len(nodes) == 4
# There are two group nodes in this flow
# One of them is inside the other totalling 7 nodes
# 4 nodes grouped, one of these turns into 1 normal node and 1 group node
# This group node has 2 nodes inside it
processed_flow = process_flow(grouped_chat_data)
assert processed_flow is not None
processed_nodes = processed_flow["nodes"]
assert len(processed_nodes) == 7
assert isinstance(processed_flow, dict)
assert "nodes" in processed_flow
assert "edges" in processed_flow
edges = processed_flow["edges"]
# Expected keywords in source and target fields
expected_keywords = [
{"source": "VectorStoreInfo", "target": "VectorStoreAgent"},
{"source": "ChatOpenAI", "target": "VectorStoreAgent"},
{"source": "OpenAIEmbeddings", "target": "Chroma"},
{"source": "Chroma", "target": "VectorStoreInfo"},
{"source": "WebBaseLoader", "target": "RecursiveCharacterTextSplitter"},
{"source": "RecursiveCharacterTextSplitter", "target": "Chroma"},
]
for idx, expected_keyword in enumerate(expected_keywords):
for key, value in expected_keyword.items():
assert (
value in edges[idx][key].split("-")[0]
), f"Edge {idx}, key {key} expected to contain {value} but got {edges[idx][key]}"
def test_update_template(sample_template, sample_nodes):
# Making a deep copy to keep original sample_nodes unchanged
nodes_copy = copy.deepcopy(sample_nodes)
update_template(sample_template, nodes_copy)
# Now, validate the updates.
node1_updated = next((n for n in nodes_copy if n["id"] == "node1"), None)
node2_updated = next((n for n in nodes_copy if n["id"] == "node2"), None)
node3_updated = next((n for n in nodes_copy if n["id"] == "node3"), None)
assert node1_updated["data"]["node"]["template"]["some_field"]["show"] is True
assert node1_updated["data"]["node"]["template"]["some_field"]["advanced"] is False
assert node1_updated["data"]["node"]["template"]["some_field"]["display_name"] == "Name1"
assert node2_updated["data"]["node"]["template"]["other_field"]["show"] is False
assert node2_updated["data"]["node"]["template"]["other_field"]["advanced"] is True
assert node2_updated["data"]["node"]["template"]["other_field"]["display_name"] == "DisplayName2"
# Ensure node3 remains unchanged
assert node3_updated == sample_nodes[2]
# Test `update_target_handle`
def test_update_target_handle_proxy():
new_edge = {
"data": {
"targetHandle": {
"type": "some_type",
"proxy": {"id": "some_id", "field": ""},
}
}
}
g_nodes = [{"id": "some_id", "data": {"node": {"flow": None}}}]
group_node_id = "group_id"
updated_edge = update_target_handle(new_edge, g_nodes, group_node_id)
assert updated_edge["data"]["targetHandle"] == new_edge["data"]["targetHandle"]
# Test `set_new_target_handle`
def test_set_new_target_handle():
proxy_id = "proxy_id"
new_edge = {"target": None, "data": {"targetHandle": {}}}
target_handle = {"type": "type_1", "proxy": {"field": "field_1"}}
node = {
"data": {
"node": {
"flow": True,
"template": {"field_1": {"proxy": {"field": "new_field", "id": "new_id"}}},
}
}
}
set_new_target_handle(proxy_id, new_edge, target_handle, node)
assert new_edge["target"] == "proxy_id"
assert new_edge["data"]["targetHandle"]["fieldName"] == "field_1"
assert new_edge["data"]["targetHandle"]["proxy"] == {
"field": "new_field",
"id": "new_id",
}
# Test `update_source_handle`
def test_update_source_handle():
new_edge = {"source": None, "data": {"sourceHandle": {"id": None}}}
flow_data = {
"nodes": [{"id": "some_node"}, {"id": "last_node"}],
"edges": [{"source": "some_node"}],
}
updated_edge = update_source_handle(new_edge, flow_data["nodes"], flow_data["edges"])
assert updated_edge["source"] == "last_node"
assert updated_edge["data"]["sourceHandle"]["id"] == "last_node"
@pytest.mark.asyncio
async def test_pickle_graph():
starter_projects = load_starter_projects()
data = starter_projects[0][1]["data"]
graph = Graph.from_payload(data)
assert isinstance(graph, Graph)
pickled = pickle.dumps(graph)
assert pickled is not None
unpickled = pickle.loads(pickled)
assert unpickled is not None

View file

@ -0,0 +1,79 @@
from langflow.components import helpers
from langflow.custom.utils import build_custom_component_template
from langflow.schema import Data
# def test_update_data_component():
# # Arrange
# update_data_component = helpers.UpdateDataComponent()
# # Act
# new_data = {"new_key": "new_value"}
# existing_data = Data(data={"existing_key": "existing_value"})
# result = update_data_component.build(existing_data, new_data)
# assert result.data == {"existing_key": "existing_value", "new_key": "new_value"}
# assert result.existing_key == "existing_value"
# assert result.new_key == "new_value"
# def test_document_to_data_component():
# # Arrange
# document_to_data_component = helpers.DocumentsToDataComponent()
# # Act
# # Replace with your actual test data
# document = Document(page_content="key: value", metadata={"url": "https://example.com"})
# result = document_to_data_component.build(document)
# # Assert
# # Replace with your actual expected result
# assert result == [Data(data={"text": "key: value", "url": "https://example.com"})]
def test_uuid_generator_component():
# Arrange
uuid_generator_component = helpers.IDGeneratorComponent()
uuid_generator_component.code = open(helpers.IDGenerator.__file__, "r").read()
frontend_node, _ = build_custom_component_template(uuid_generator_component)
# Act
build_config = frontend_node.get("template")
field_name = "unique_id"
build_config = uuid_generator_component.update_build_config(build_config, None, field_name)
unique_id = build_config["unique_id"]["value"]
result = uuid_generator_component.build(unique_id)
# Assert
# UUID should be a string of length 36
assert isinstance(result, str)
assert len(result) == 36
def test_data_as_text_component():
# Arrange
data_as_text_component = helpers.ParseDataComponent()
# Act
# Replace with your actual test data
data = [Data(data={"key": "value", "bacon": "eggs"})]
template = "Data:{data} -- Bacon:{bacon}"
data_as_text_component.set_attributes({"data": data, "template": template})
result = data_as_text_component.parse_data()
# Assert
# Replace with your actual expected result
assert result.text == "Data:{'key': 'value', 'bacon': 'eggs'} -- Bacon:eggs"
# def test_text_to_data_component():
# # Arrange
# text_to_data_component = helpers.CreateDataComponent()
# # Act
# # Replace with your actual test data
# dict_with_text = {"field_1": {"key": "value"}}
# result = text_to_data_component.build(number_of_fields=1, **dict_with_text)
# # Assert
# # Replace with your actual expected result
# assert result == Data(data={"key": "value"})

View file

@ -0,0 +1,88 @@
from datetime import datetime
from pathlib import Path
import pytest
from sqlmodel import select
from langflow.initial_setup.setup import STARTER_FOLDER_NAME, get_project_data, load_starter_projects
from langflow.services.database.models.folder.model import Folder
from langflow.services.deps import session_scope
def test_load_starter_projects():
projects = load_starter_projects()
assert isinstance(projects, list)
assert all(isinstance(project[1], dict) for project in projects)
assert all(isinstance(project[0], Path) for project in projects)
def test_get_project_data():
projects = load_starter_projects()
for _, project in projects:
(
project_name,
project_description,
project_is_component,
updated_at_datetime,
project_data,
project_icon,
project_icon_bg_color,
) = get_project_data(project)
assert isinstance(project_name, str)
assert isinstance(project_description, str)
assert isinstance(project_is_component, bool)
assert isinstance(updated_at_datetime, datetime)
assert isinstance(project_data, dict)
assert isinstance(project_icon, str) or project_icon is None
assert isinstance(project_icon_bg_color, str) or project_icon_bg_color is None
@pytest.mark.asyncio
async def test_create_or_update_starter_projects():
with session_scope() as session:
# Get the number of projects returned by load_starter_projects
num_projects = len(load_starter_projects())
# Get the number of projects in the database
folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
assert folder is not None
num_db_projects = len(folder.flows)
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
assert num_db_projects == num_projects
# Some starter projects require integration
# @pytest.mark.asyncio
# async def test_starter_projects_can_run_successfully(client):
# with session_scope() as session:
# # Run the function to create or update projects
# create_or_update_starter_projects()
# # Get the number of projects returned by load_starter_projects
# num_projects = len(load_starter_projects())
# # Get the number of projects in the database
# num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
# # Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
# assert num_db_projects == num_projects
# # Get all the starter projects
# projects = session.exec(select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)).all()
# graphs: list[tuple[str, Graph]] = []
# for project in projects:
# # Add tweaks to make file_path work
# tweaks = {"path": __file__}
# graph_data = process_tweaks(project.data, tweaks)
# graph_object = Graph.from_payload(graph_data, flow_id=project.id)
# graphs.append((project.name, graph_object))
# assert len(graphs) == len(projects)
# for name, graph in graphs:
# outputs = await graph.arun(
# inputs={},
# outputs=[],
# session_id="test",
# )
# assert all(isinstance(output, RunOutputs) for output in outputs), f"Project {name} error: {outputs}"
# delete_messages(session_id="test")

View file

@ -0,0 +1,107 @@
import pytest
from unittest.mock import MagicMock
from kubernetes.client import V1ObjectMeta, V1Secret
from base64 import b64encode
from uuid import UUID
from langflow.services.variable.kubernetes_secrets import KubernetesSecretManager, encode_user_id
@pytest.fixture
def mock_kube_config(mocker):
mocker.patch("kubernetes.config.load_kube_config")
mocker.patch("kubernetes.config.load_incluster_config")
@pytest.fixture
def secret_manager(mock_kube_config):
return KubernetesSecretManager(namespace="test-namespace")
def test_create_secret(secret_manager, mocker):
mocker.patch.object(
secret_manager.core_api,
"create_namespaced_secret",
return_value=V1Secret(metadata=V1ObjectMeta(name="test-secret")),
)
secret_manager.create_secret(name="test-secret", data={"key": "value"})
secret_manager.core_api.create_namespaced_secret.assert_called_once_with(
"test-namespace",
V1Secret(
api_version="v1",
kind="Secret",
metadata=V1ObjectMeta(name="test-secret"),
type="Opaque",
data={"key": b64encode("value".encode()).decode()},
),
)
def test_get_secret(secret_manager, mocker):
mock_secret = V1Secret(data={"key": b64encode("value".encode()).decode()})
mocker.patch.object(secret_manager.core_api, "read_namespaced_secret", return_value=mock_secret)
secret_data = secret_manager.get_secret(name="test-secret")
secret_manager.core_api.read_namespaced_secret.assert_called_once_with("test-secret", "test-namespace")
assert secret_data == {"key": "value"}
def test_delete_secret(secret_manager, mocker):
mocker.patch.object(secret_manager.core_api, "delete_namespaced_secret", return_value=MagicMock(status="Success"))
secret_manager.delete_secret(name="test-secret")
secret_manager.core_api.delete_namespaced_secret.assert_called_once_with("test-secret", "test-namespace")
def test_encode_uuid():
uuid = UUID("123e4567-e89b-12d3-a456-426614174000")
result = encode_user_id(uuid)
assert result == "uuid-123e4567-e89b-12d3-a456-426614174000"
assert len(result) < 253
assert result[0].isalnum()
assert result[-1].isalnum()
def test_encode_string():
string_id = "user@example.com"
result = encode_user_id(string_id)
# assert (result.isalnum() or '-' in result or '_' in result)
assert len(result) < 253
assert result[0].isalnum()
assert result[-1].isalnum()
def test_long_string():
long_string = "a" * 300
result = encode_user_id(long_string)
assert len(result) <= 253
def test_starts_with_non_alphanumeric():
non_alnum_start = "+user123"
result = encode_user_id(non_alnum_start)
assert result[0].isalnum()
def test_ends_with_non_alphanumeric():
non_alnum_end = "user123+"
result = encode_user_id(non_alnum_end)
assert result[-1].isalnum()
def test_email_address():
email = "User.Name@Example.com"
result = encode_user_id(email)
assert result.isalnum() or "-" in result or "_" in result
assert len(result) < 253
assert result[0].isalnum()
assert result[-1].isalnum()
def test_uuid_case_insensitivity():
uuid_upper = UUID("123E4567-E89B-12D3-A456-426614174000")
uuid_lower = UUID("123e4567-e89b-12d3-a456-426614174000")
result_upper = encode_user_id(uuid_upper)
result_lower = encode_user_id(uuid_lower)
assert result_upper == result_lower

View file

@ -0,0 +1,32 @@
import pytest
from langflow.graph import Graph
from langflow.initial_setup.setup import load_starter_projects
from langflow.load import load_flow_from_json
@pytest.mark.noclient
def test_load_flow_from_json():
"""Test loading a flow from a json file"""
loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH)
assert loaded is not None
assert isinstance(loaded, Graph)
@pytest.mark.noclient
def test_load_flow_from_json_with_tweaks():
"""Test loading a flow from a json file and applying tweaks"""
tweaks = {"dndnode_82": {"model_name": "gpt-3.5-turbo-16k-0613"}}
loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH, tweaks=tweaks)
assert loaded is not None
assert isinstance(loaded, Graph)
@pytest.mark.noclient
def test_load_flow_from_json_object():
"""Test loading a flow from a json file and applying tweaks"""
_, projects = zip(*load_starter_projects())
project = projects[0]
loaded = load_flow_from_json(project)
assert loaded is not None
assert isinstance(loaded, Graph)

View file

@ -0,0 +1,100 @@
import pytest
import os
import json
from unittest.mock import patch
from langflow.utils.logger import SizedLogBuffer
@pytest.fixture
def sized_log_buffer():
return SizedLogBuffer()
def test_init_default():
buffer = SizedLogBuffer()
assert buffer.max == 0
assert buffer._max_readers == 20
def test_init_with_env_variable():
with patch.dict(os.environ, {"LANGFLOW_LOG_RETRIEVER_BUFFER_SIZE": "100"}):
buffer = SizedLogBuffer()
assert buffer.max == 100
def test_write(sized_log_buffer):
message = json.dumps({"text": "Test log", "record": {"time": {"timestamp": 1625097600.1244334}}})
sized_log_buffer.max = 1 # Set max size to 1 for testing
sized_log_buffer.write(message)
assert len(sized_log_buffer.buffer) == 1
assert 1625097600124 == sized_log_buffer.buffer[0][0]
assert "Test log" == sized_log_buffer.buffer[0][1]
def test_write_overflow(sized_log_buffer):
sized_log_buffer.max = 2
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(3)]
for message in messages:
sized_log_buffer.write(message)
assert len(sized_log_buffer.buffer) == 2
assert 1625097601000 == sized_log_buffer.buffer[0][0]
assert 1625097602000 == sized_log_buffer.buffer[1][0]
def test_len(sized_log_buffer):
sized_log_buffer.max = 3
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(3)]
for message in messages:
sized_log_buffer.write(message)
assert len(sized_log_buffer) == 3
def test_get_after_timestamp(sized_log_buffer):
sized_log_buffer.max = 5
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(5)]
for message in messages:
sized_log_buffer.write(message)
result = sized_log_buffer.get_after_timestamp(1625097602000, lines=2)
assert len(result) == 2
assert 1625097603000 in result
assert 1625097602000 in result
def test_get_before_timestamp(sized_log_buffer):
sized_log_buffer.max = 5
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(5)]
for message in messages:
sized_log_buffer.write(message)
result = sized_log_buffer.get_before_timestamp(1625097603000, lines=2)
assert len(result) == 2
assert 1625097601000 in result
assert 1625097602000 in result
def test_get_last_n(sized_log_buffer):
sized_log_buffer.max = 5
messages = [json.dumps({"text": f"Log {i}", "record": {"time": {"timestamp": 1625097600 + i}}}) for i in range(5)]
for message in messages:
sized_log_buffer.write(message)
result = sized_log_buffer.get_last_n(3)
assert len(result) == 3
assert 1625097602000 in result
assert 1625097603000 in result
assert 1625097604000 in result
def test_enabled(sized_log_buffer):
assert not sized_log_buffer.enabled()
sized_log_buffer.max = 1
assert sized_log_buffer.enabled()
def test_max_size(sized_log_buffer):
assert sized_log_buffer.max_size() == 0
sized_log_buffer.max = 100
assert sized_log_buffer.max_size() == 100

View file

@ -0,0 +1,45 @@
import pytest
from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.user import User
from langflow.services.deps import session_scope
from sqlalchemy.exc import IntegrityError
@pytest.fixture
def test_user():
return User(
username="testuser",
password=get_password_hash("testpassword"), # Assuming password needs to be hashed
is_active=True,
is_superuser=False,
)
def test_login_successful(client, test_user):
# Adding the test user to the database
try:
with session_scope() as session:
session.add(test_user)
session.commit()
except IntegrityError:
pass
response = client.post("api/v1/login", data={"username": "testuser", "password": "testpassword"})
assert response.status_code == 200
assert "access_token" in response.json()
def test_login_unsuccessful_wrong_username(client):
response = client.post("api/v1/login", data={"username": "wrongusername", "password": "testpassword"})
assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password"
def test_login_unsuccessful_wrong_password(client, test_user, session):
# Adding the test user to the database
session.add(test_user)
session.commit()
response = client.post("api/v1/login", data={"username": "testuser", "password": "wrongpassword"})
assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password"

View file

@ -0,0 +1,102 @@
import pytest
from langflow.memory import add_messages, add_messagetables, delete_messages, get_messages, store_message
from langflow.schema.message import Message
# Assuming you have these imports available
from langflow.services.database.models.message import MessageCreate, MessageRead
from langflow.services.database.models.message.model import MessageTable
from langflow.services.deps import session_scope
from langflow.services.tracing.utils import convert_to_langchain_type
@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]
messagetables = add_messagetables(messagetables, session)
messages_read = [
MessageRead.model_validate(messagetable, from_attributes=True) for messagetable in messagetables
]
return messages_read
def test_get_messages():
add_messages(
[
Message(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"),
Message(text="Test message 2", sender="User", sender_name="User", session_id="session_id2"),
]
)
messages = get_messages(sender="User", session_id="session_id2", limit=2)
assert len(messages) == 2
assert messages[0].text == "Test message 1"
assert messages[1].text == "Test message 2"
def test_add_messages():
message = Message(text="New Test message", sender="User", sender_name="User", session_id="new_session_id")
messages = add_messages(message)
assert len(messages) == 1
assert messages[0].text == "New Test message"
def test_add_messagetables(session):
messages = [MessageTable(text="New Test message", sender="User", sender_name="User", session_id="new_session_id")]
added_messages = add_messagetables(messages, session)
assert len(added_messages) == 1
assert added_messages[0].text == "New Test message"
def test_delete_messages(session):
session_id = "session_id2"
delete_messages(session_id)
messages = session.query(MessageTable).filter(MessageTable.session_id == session_id).all()
assert len(messages) == 0
def test_store_message():
message = Message(text="Stored message", sender="User", sender_name="User", session_id="stored_session_id")
stored_messages = store_message(message)
assert len(stored_messages) == 1
assert stored_messages[0].text == "Stored message"
@pytest.mark.parametrize("method_name", ["message", "convert_to_langchain_type"])
def test_convert_to_langchain(method_name):
def convert(value):
if method_name == "message":
return value.to_lc_message()
elif method_name == "convert_to_langchain_type":
return convert_to_langchain_type(value)
else:
raise ValueError(f"Invalid method: {method_name}")
lc_message = convert(Message(text="Test message 1", sender="User", sender_name="User", session_id="session_id2"))
assert lc_message.content == "Test message 1"
assert lc_message.type == "human"
lc_message = convert(Message(text="Test message 2", sender="AI", session_id="session_id2"))
assert lc_message.content == "Test message 2"
assert lc_message.type == "ai"
iterator = iter(["stream", "message"])
lc_message = convert(Message(text=iterator, sender="AI", session_id="session_id2"))
assert lc_message.content == ""
assert lc_message.type == "ai"
assert len(list(iterator)) == 2

View file

@ -0,0 +1,303 @@
import pytest
from langflow.processing.process import process_tweaks
from langflow.services.deps import get_session_service
def test_no_tweaks():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1},
"param2": {"value": 2},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3},
"param2": {"value": 4},
}
}
},
},
]
}
}
tweaks = {}
result = process_tweaks(graph_data, tweaks)
assert result == graph_data
def test_single_tweak():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {"node1": {"param1": 5}}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
def test_multiple_tweaks():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {
"node1": {"param1": 5, "param2": 6},
"node2": {"param1": 7},
}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 6, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 7, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
# Test twekas that just pass the param and value but no node id.
# This is a new feature that was added to the process_tweaks function
def test_tweak_no_node_id():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {"param1": 5}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 5, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
def test_tweak_not_in_template():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1, "type": "int"},
"param2": {"value": 2, "type": "int"},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3, "type": "int"},
"param2": {"value": 4, "type": "int"},
}
}
},
},
]
}
}
tweaks = {"node1": {"param3": 5}}
result = process_tweaks(graph_data, tweaks)
assert result == graph_data
@pytest.mark.asyncio
async def test_load_langchain_object_with_cached_session(client, basic_graph_data):
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = "non-existent-session-id"
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
assert graph1 == graph2
assert artifacts1 == artifacts2
@pytest.mark.asyncio
async def test_load_langchain_object_with_no_cached_session(client, basic_graph_data):
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = "non-existent-session-id"
session_id = session_service.build_key(session_id1, basic_graph_data)
graph1, artifacts1 = await session_service.load_session(session_id, data_graph=basic_graph_data, flow_id="flow_id")
# Clear the cache
await session_service.clear_session(session_id)
# Use the new session_id to get the graph again
graph2, artifacts2 = await session_service.load_session(session_id, data_graph=basic_graph_data, flow_id="flow_id")
# Since the cache was cleared, objects should be different
assert id(graph1) != id(graph2)
@pytest.mark.asyncio
async def test_load_langchain_object_without_session_id(client, basic_graph_data):
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = None
graph1, artifacts1 = await session_service.load_session(session_id1, data_graph=basic_graph_data, flow_id="flow_id")
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(session_id1, data_graph=basic_graph_data, flow_id="flow_id")
assert graph1 == graph2

View file

@ -0,0 +1,132 @@
from unittest.mock import MagicMock, patch
from langflow.services.settings.constants import (
DEFAULT_SUPERUSER,
DEFAULT_SUPERUSER_PASSWORD,
)
from langflow.services.utils import teardown_superuser
# @patch("langflow.services.deps.get_session")
# @patch("langflow.services.utils.create_super_user")
# @patch("langflow.services.deps.get_settings_service")
# # @patch("langflow.services.utils.verify_password")
# def test_setup_superuser(
# mock_get_session, mock_create_super_user, mock_get_settings_service
# ):
# # Test when AUTO_LOGIN is True
# calls = []
# mock_settings_service = Mock()
# mock_settings_service.auth_settings.AUTO_LOGIN = True
# mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = DEFAULT_SUPERUSER_PASSWORD
# mock_get_settings_service.return_value = mock_settings_service
# mock_session = Mock()
# mock_session.query.return_value.filter.return_value.first.return_value = (
# mock_session
# )
# # return value of get_session is a generator
# mock_get_session.return_value = iter([mock_session, mock_session, mock_session])
# setup_superuser(mock_settings_service, mock_session)
# mock_session.query.assert_called_once_with(User)
# # Set return value of filter to be None
# mock_session.query.return_value.filter.return_value.first.return_value = None
# actual_expr = mock_session.query.return_value.filter.call_args[0][0]
# expected_expr = User.username == DEFAULT_SUPERUSER
# assert str(actual_expr) == str(expected_expr)
# create_call = call(
# db=mock_session, username=DEFAULT_SUPERUSER, password=DEFAULT_SUPERUSER_PASSWORD
# )
# calls.append(create_call)
# # mock_create_super_user.assert_has_calls(calls)
# assert 1 == mock_create_super_user.call_count
# def reset_mock_credentials():
# mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = (
# DEFAULT_SUPERUSER_PASSWORD
# )
# ADMIN_USER_NAME = "admin_user"
# # Test when username and password are default
# mock_settings_service.auth_settings = Mock()
# mock_settings_service.auth_settings.AUTO_LOGIN = False
# mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
# mock_settings_service.auth_settings.reset_credentials = Mock(
# side_effect=reset_mock_credentials
# )
# mock_get_settings_service.return_value = mock_settings_service
# setup_superuser(mock_settings_service, mock_session)
# mock_session.query.assert_called_with(User)
# actual_expr = mock_session.query.return_value.filter.call_args[0][0]
# expected_expr = User.username == ADMIN_USER_NAME
# assert str(actual_expr) == str(expected_expr)
# create_call = call(db=mock_session, username=ADMIN_USER_NAME, password="password")
# calls.append(create_call)
# # mock_create_super_user.assert_has_calls(calls)
# assert 2 == mock_create_super_user.call_count
# # Test that superuser credentials are reset
# mock_settings_service.auth_settings.reset_credentials.assert_called_once()
# assert mock_settings_service.auth_settings.SUPERUSER != ADMIN_USER_NAME
# assert mock_settings_service.auth_settings.SUPERUSER_PASSWORD != "password"
# # Test when superuser already exists
# mock_settings_service.auth_settings.AUTO_LOGIN = False
# mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
# mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
# mock_user = Mock()
# mock_user.is_superuser = True
# mock_session.query.return_value.filter.return_value.first.return_value = mock_user
# setup_superuser(mock_settings_service, mock_session)
# mock_session.query.assert_called_with(User)
# actual_expr = mock_session.query.return_value.filter.call_args[0][0]
# expected_expr = User.username == ADMIN_USER_NAME
# assert str(actual_expr) == str(expected_expr)
@patch("langflow.services.deps.get_settings_service")
@patch("langflow.services.deps.get_session")
def test_teardown_superuser_default_superuser(mock_get_session, mock_get_settings_service):
mock_settings_service = MagicMock()
mock_settings_service.auth_settings.AUTO_LOGIN = True
mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
mock_settings_service.auth_settings.SUPERUSER_PASSWORD = DEFAULT_SUPERUSER_PASSWORD
mock_get_settings_service.return_value = mock_settings_service
mock_session = MagicMock()
mock_user = MagicMock()
mock_user.is_superuser = True
mock_session.query.return_value.filter.return_value.first.return_value = mock_user
mock_get_session.return_value = iter([mock_session])
teardown_superuser(mock_settings_service, mock_session)
mock_session.query.assert_not_called()
@patch("langflow.services.deps.get_settings_service")
@patch("langflow.services.deps.get_session")
def test_teardown_superuser_no_default_superuser(mock_get_session, mock_get_settings_service):
ADMIN_USER_NAME = "admin_user"
mock_settings_service = MagicMock()
mock_settings_service.auth_settings.AUTO_LOGIN = False
mock_settings_service.auth_settings.SUPERUSER = ADMIN_USER_NAME
mock_settings_service.auth_settings.SUPERUSER_PASSWORD = "password"
mock_get_settings_service.return_value = mock_settings_service
mock_session = MagicMock()
mock_user = MagicMock()
mock_user.is_superuser = False
mock_session.query.return_value.filter.return_value.first.return_value = mock_user
mock_get_session.return_value = [mock_session]
teardown_superuser(mock_settings_service, mock_session)
mock_session.query.assert_not_called()
mock_session.delete.assert_not_called()
mock_session.commit.assert_not_called()

View file

@ -0,0 +1,112 @@
import pytest
import threading
from langflow.services.telemetry.opentelemetry import OpenTelemetry
from concurrent.futures import ThreadPoolExecutor, as_completed
fixed_labels = {"flow_id": "this_flow_id", "service": "this", "user": "that"}
@pytest.fixture
def opentelemetry_instance():
return OpenTelemetry()
def test_init(opentelemetry_instance):
assert isinstance(opentelemetry_instance, OpenTelemetry)
assert len(opentelemetry_instance._metrics) > 1
assert len(opentelemetry_instance._metrics) == len(opentelemetry_instance._metrics_registry) == 2
assert "file_uploads" in opentelemetry_instance._metrics
def test_gauge(opentelemetry_instance):
opentelemetry_instance.update_gauge("file_uploads", 1024, fixed_labels)
def test_gauge_with_counter_method(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'file_uploads' is not a counter"):
opentelemetry_instance.increment_counter(metric_name="file_uploads", value=1, labels=fixed_labels)
def test_gauge_with_historgram_method(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'file_uploads' is not a histogram"):
opentelemetry_instance.observe_histogram("file_uploads", 1, fixed_labels)
def test_gauge_with_up_down_counter_method(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'file_uploads' is not an up down counter"):
opentelemetry_instance.up_down_counter("file_uploads", 1, labels=fixed_labels)
def test_increment_counter(opentelemetry_instance):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels=fixed_labels)
def test_increment_counter_empty_label(opentelemetry_instance):
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels={})
def test_increment_counter_missing_mandatory_label(opentelemetry_instance):
with pytest.raises(ValueError, match="Missing required labels: {'flow_id'}"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels={"service": "one"})
def test_increment_counter_unregisted_metric(opentelemetry_instance):
with pytest.raises(ValueError, match="Metric 'num_files_uploaded_1' is not registered"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded_1", value=5, labels=fixed_labels)
def test_opentelementry_singleton(opentelemetry_instance):
opentelemetry_instance_2 = OpenTelemetry()
assert opentelemetry_instance is opentelemetry_instance_2
opentelemetry_instance_3 = OpenTelemetry(prometheus_enabled=False)
assert opentelemetry_instance is opentelemetry_instance_3
assert opentelemetry_instance.prometheus_enabled == opentelemetry_instance_3.prometheus_enabled
def test_missing_labels(opentelemetry_instance):
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", labels=None, value=1.0)
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.up_down_counter("num_files_uploaded", 1, None)
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.update_gauge(metric_name="num_files_uploaded", value=1.0, labels=dict())
with pytest.raises(ValueError, match="Labels must be provided for the metric"):
opentelemetry_instance.observe_histogram("num_files_uploaded", 1, dict())
def test_multithreaded_singleton():
def create_instance():
return OpenTelemetry()
# Create instances in multiple threads
with ThreadPoolExecutor(max_workers=10) as executor:
futures = [executor.submit(create_instance) for _ in range(100)]
instances = [future.result() for future in as_completed(futures)]
# Check that all instances are the same
first_instance = instances[0]
for instance in instances[1:]:
assert instance is first_instance
def test_multithreaded_singleton_race_condition():
# This test simulates a potential race condition
start_event = threading.Event()
def create_instance():
start_event.wait() # Wait for all threads to be ready
return OpenTelemetry()
# Create instances in multiple threads, all starting at the same time
with ThreadPoolExecutor(max_workers=100) as executor:
futures = [executor.submit(create_instance) for _ in range(100)]
start_event.set() # Start all threads simultaneously
instances = [future.result() for future in as_completed(futures)]
# Check that all instances are the same
first_instance = instances[0]
for instance in instances[1:]:
assert instance is first_instance

View file

@ -0,0 +1,93 @@
import importlib
from typing import Dict, List, Optional
import pytest
from langflow.utils.util import build_template_from_function, get_base_classes, get_default_factory
from pydantic import BaseModel
# Dummy classes for testing purposes
class Parent(BaseModel):
"""Parent Class"""
parent_field: str
class Child(Parent):
"""Child Class"""
child_field: int
class ExampleClass1(BaseModel):
"""Example class 1."""
def __init__(self, data: Optional[List[int]] = None):
self.data = data or [1, 2, 3]
class ExampleClass2(BaseModel):
"""Example class 2."""
def __init__(self, data: Optional[Dict[str, int]] = None):
self.data = data or {"a": 1, "b": 2, "c": 3}
def example_loader_1() -> ExampleClass1:
"""Example loader function 1."""
return ExampleClass1()
def example_loader_2() -> ExampleClass2:
"""Example loader function 2."""
return ExampleClass2()
def test_build_template_from_function():
type_to_loader_dict = {
"example1": example_loader_1,
"example2": example_loader_2,
}
# Test with valid name
result = build_template_from_function("ExampleClass1", type_to_loader_dict)
assert result is not None
assert "template" in result
assert "description" in result
assert "base_classes" in result
# Test with add_function=True
result_with_function = build_template_from_function("ExampleClass1", type_to_loader_dict, add_function=True)
assert result_with_function is not None
assert "Callable" in result_with_function["base_classes"]
# Test with invalid name
with pytest.raises(ValueError, match=r".* not found"):
build_template_from_function("NonExistent", type_to_loader_dict)
# Test get_base_classes
def test_get_base_classes():
base_classes_parent = get_base_classes(Parent)
base_classes_child = get_base_classes(Child)
assert "Parent" in base_classes_parent
assert "Child" in base_classes_child
assert "Parent" in base_classes_child
# Test get_default_factory
def test_get_default_factory():
module_name = "langflow.utils.util"
function_repr = "<function dummy_function>"
def dummy_function():
return "default_value"
# Add dummy_function to your_module
setattr(importlib.import_module(module_name), "dummy_function", dummy_function)
default_value = get_default_factory(module_name, function_repr)
assert default_value == "default_value"

View file

@ -0,0 +1,104 @@
from pathlib import Path
from unittest import mock
import pytest
from requests.exceptions import MissingSchema
from langflow.utils.validate import create_function, execute_function, extract_function_name, validate_code
def test_create_function():
code = """
from pathlib import Path
def my_function(x: str) -> Path:
return Path(x)
"""
function_name = extract_function_name(code)
function = create_function(code, function_name)
result = function("test")
assert result == Path("test")
def test_validate_code():
# Test case with a valid import and function
code1 = """
import math
def square(x):
return x ** 2
"""
errors1 = validate_code(code1)
assert errors1 == {"imports": {"errors": []}, "function": {"errors": []}}
# Test case with an invalid import and valid function
code2 = """
import non_existent_module
def square(x):
return x ** 2
"""
errors2 = validate_code(code2)
assert errors2 == {
"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
"""
errors3 = validate_code(code3)
assert errors3 == {
"imports": {"errors": []},
"function": {"errors": ["expected ':' (<unknown>, line 4)"]},
}
def test_execute_function_success():
code = """
import math
def my_function(x):
return math.sin(x) + 1
"""
result = execute_function(code, "my_function", 0.5)
assert result == 1.479425538604203
def test_execute_function_missing_module():
code = """
import some_missing_module
def my_function(x):
return some_missing_module.some_function(x)
"""
with pytest.raises(ModuleNotFoundError):
execute_function(code, "my_function", 0.5)
def test_execute_function_missing_function():
code = """
import math
def my_function(x):
return math.some_missing_function(x)
"""
with pytest.raises(AttributeError):
execute_function(code, "my_function", 0.5)
def test_execute_function_missing_schema():
code = """
import requests
def my_function(x):
return requests.get(x).text
"""
with mock.patch("requests.get", side_effect=MissingSchema):
with pytest.raises(MissingSchema):
execute_function(code, "my_function", "invalid_url")

View file

@ -0,0 +1,15 @@
from langflow.components import prototypes
def test_python_function_component():
# Arrange
python_function_component = prototypes.PythonFunctionComponent()
# Act
# function must be a string representation
function = "def function():\n return 'Hello, World!'"
# result is the callable function
result = python_function_component.build(function)
# Assert
assert result() == "Hello, World!"