|
|
import pytest |
|
|
from langflow.base.tools.constants import TOOL_OUTPUT_DISPLAY_NAME, TOOL_OUTPUT_NAME |
|
|
from langflow.custom.custom_component.component import Component |
|
|
|
|
|
|
|
|
class TestComponentOutputs: |
|
|
def test_run_and_validate_update_outputs_tool_mode(self): |
|
|
"""Test run_and_validate_update_outputs with tool_mode field.""" |
|
|
|
|
|
class TestComponent(Component): |
|
|
def build(self) -> None: |
|
|
pass |
|
|
|
|
|
component = TestComponent() |
|
|
|
|
|
|
|
|
original_outputs = [ |
|
|
{ |
|
|
"name": "regular_output", |
|
|
"type": "str", |
|
|
"display_name": "Regular Output", |
|
|
"method": "get_output", |
|
|
"types": ["Any"], |
|
|
"selected": "Any", |
|
|
"value": "__UNDEFINED__", |
|
|
"cache": True, |
|
|
"required_inputs": None, |
|
|
"hidden": None, |
|
|
} |
|
|
] |
|
|
frontend_node = { |
|
|
"outputs": original_outputs.copy() |
|
|
} |
|
|
|
|
|
|
|
|
updated_node = component.run_and_validate_update_outputs( |
|
|
frontend_node=frontend_node.copy(), |
|
|
field_name="tool_mode", |
|
|
field_value=True, |
|
|
) |
|
|
|
|
|
|
|
|
assert len(updated_node["outputs"]) == 1 |
|
|
assert updated_node["outputs"][0]["name"] == TOOL_OUTPUT_NAME |
|
|
assert updated_node["outputs"][0]["display_name"] == TOOL_OUTPUT_DISPLAY_NAME |
|
|
|
|
|
|
|
|
updated_node = component.run_and_validate_update_outputs( |
|
|
frontend_node={"outputs": original_outputs.copy()}, |
|
|
field_name="tool_mode", |
|
|
field_value=False, |
|
|
) |
|
|
|
|
|
|
|
|
assert len(updated_node["outputs"]) == 1 |
|
|
|
|
|
assert updated_node["outputs"][0]["name"] == original_outputs[0]["name"] |
|
|
assert updated_node["outputs"][0]["display_name"] == original_outputs[0]["display_name"] |
|
|
assert updated_node["outputs"][0]["method"] == original_outputs[0]["method"] |
|
|
assert "types" in updated_node["outputs"][0] |
|
|
assert "selected" in updated_node["outputs"][0] |
|
|
|
|
|
def test_run_and_validate_update_outputs_invalid_output(self): |
|
|
"""Test run_and_validate_update_outputs with invalid output structure.""" |
|
|
|
|
|
class TestComponent(Component): |
|
|
def build(self) -> None: |
|
|
pass |
|
|
|
|
|
component = TestComponent() |
|
|
|
|
|
|
|
|
frontend_node = {"outputs": [{"invalid_field": "value"}]} |
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="Invalid output: 1 validation error for Output"): |
|
|
component.run_and_validate_update_outputs( |
|
|
frontend_node=frontend_node, field_name="some_field", field_value="some_value" |
|
|
) |
|
|
|
|
|
def test_run_and_validate_update_outputs_custom_update(self): |
|
|
"""Test run_and_validate_update_outputs with custom update logic.""" |
|
|
|
|
|
class CustomComponent(Component): |
|
|
def build(self) -> None: |
|
|
pass |
|
|
|
|
|
def get_custom(self) -> str: |
|
|
"""Method that returns a string.""" |
|
|
return "custom output" |
|
|
|
|
|
def update_outputs(self, frontend_node, field_name, field_value): |
|
|
if field_name == "custom_field": |
|
|
frontend_node["outputs"].append( |
|
|
{ |
|
|
"name": "custom_output", |
|
|
"type": "str", |
|
|
"display_name": "Custom Output", |
|
|
"method": "get_custom", |
|
|
"types": ["Any"], |
|
|
"selected": "Any", |
|
|
"value": "__UNDEFINED__", |
|
|
"cache": True, |
|
|
"required_inputs": None, |
|
|
"hidden": None, |
|
|
} |
|
|
) |
|
|
return frontend_node |
|
|
|
|
|
component = CustomComponent() |
|
|
frontend_node = {"outputs": []} |
|
|
|
|
|
|
|
|
updated_node = component.run_and_validate_update_outputs( |
|
|
frontend_node=frontend_node, field_name="custom_field", field_value="custom_value" |
|
|
) |
|
|
|
|
|
assert len(updated_node["outputs"]) == 1 |
|
|
assert updated_node["outputs"][0]["name"] == "custom_output" |
|
|
assert updated_node["outputs"][0]["display_name"] == "Custom Output" |
|
|
assert updated_node["outputs"][0]["method"] == "get_custom" |
|
|
assert "types" in updated_node["outputs"][0] |
|
|
assert "selected" in updated_node["outputs"][0] |
|
|
|
|
|
def test_run_and_validate_update_outputs_with_existing_tool_output(self): |
|
|
"""Test run_and_validate_update_outputs when tool output already exists.""" |
|
|
|
|
|
class TestComponent(Component): |
|
|
def build(self) -> None: |
|
|
pass |
|
|
|
|
|
def to_toolkit(self) -> list: |
|
|
"""Method that returns a list of tools.""" |
|
|
return [] |
|
|
|
|
|
component = TestComponent() |
|
|
|
|
|
|
|
|
frontend_node = { |
|
|
"outputs": [ |
|
|
{ |
|
|
"name": TOOL_OUTPUT_NAME, |
|
|
"type": "Tool", |
|
|
"display_name": TOOL_OUTPUT_DISPLAY_NAME, |
|
|
"method": "to_toolkit", |
|
|
"types": ["Tool"], |
|
|
"selected": "Tool", |
|
|
"value": "__UNDEFINED__", |
|
|
"cache": True, |
|
|
"required_inputs": None, |
|
|
"hidden": None, |
|
|
} |
|
|
] |
|
|
} |
|
|
|
|
|
|
|
|
updated_node = component.run_and_validate_update_outputs( |
|
|
frontend_node=frontend_node, field_name="tool_mode", field_value=True |
|
|
) |
|
|
|
|
|
assert len(updated_node["outputs"]) == 1 |
|
|
assert updated_node["outputs"][0]["name"] == TOOL_OUTPUT_NAME |
|
|
assert updated_node["outputs"][0]["display_name"] == TOOL_OUTPUT_DISPLAY_NAME |
|
|
assert "types" in updated_node["outputs"][0] |
|
|
assert "selected" in updated_node["outputs"][0] |
|
|
|
|
|
def test_run_and_validate_update_outputs_with_multiple_outputs(self): |
|
|
"""Test run_and_validate_update_outputs with multiple outputs.""" |
|
|
|
|
|
class TestComponent(Component): |
|
|
def build(self) -> None: |
|
|
pass |
|
|
|
|
|
def get_output1(self) -> str: |
|
|
"""Method that returns a string.""" |
|
|
return "output1" |
|
|
|
|
|
def get_output2(self) -> str: |
|
|
"""Method that returns a string.""" |
|
|
return "output2" |
|
|
|
|
|
def update_outputs(self, frontend_node, field_name, field_value): |
|
|
if field_name == "add_output": |
|
|
frontend_node["outputs"].extend( |
|
|
[ |
|
|
{ |
|
|
"name": "output1", |
|
|
"type": "str", |
|
|
"display_name": "Output 1", |
|
|
"method": "get_output1", |
|
|
}, |
|
|
{ |
|
|
"name": "output2", |
|
|
"type": "str", |
|
|
"display_name": "Output 2", |
|
|
"method": "get_output2", |
|
|
}, |
|
|
] |
|
|
) |
|
|
return frontend_node |
|
|
|
|
|
component = TestComponent() |
|
|
frontend_node = {"outputs": []} |
|
|
|
|
|
|
|
|
updated_node = component.run_and_validate_update_outputs( |
|
|
frontend_node=frontend_node, field_name="add_output", field_value=True |
|
|
) |
|
|
|
|
|
assert len(updated_node["outputs"]) == 2 |
|
|
assert updated_node["outputs"][0]["name"] == "output1" |
|
|
assert updated_node["outputs"][1]["name"] == "output2" |
|
|
for output in updated_node["outputs"]: |
|
|
assert "types" in output |
|
|
assert "selected" in output |
|
|
|
|
|
assert set(output["types"]) == {"Text"} |
|
|
assert output["selected"] == "Text" |
|
|
|
|
|
def test_run_and_validate_update_outputs_output_validation(self): |
|
|
"""Test output validation in run_and_validate_update_outputs.""" |
|
|
|
|
|
class TestComponent(Component): |
|
|
def build(self) -> None: |
|
|
pass |
|
|
|
|
|
def get_test(self) -> str: |
|
|
"""Test method.""" |
|
|
return "test" |
|
|
|
|
|
component = TestComponent() |
|
|
|
|
|
|
|
|
invalid_node = { |
|
|
"outputs": [{"name": "test", "type": "str", "method": "nonexistent_method", "display_name": "Test"}] |
|
|
} |
|
|
|
|
|
with pytest.raises(AttributeError, match="nonexistent_method not found in TestComponent"): |
|
|
component.run_and_validate_update_outputs(frontend_node=invalid_node, field_name="test", field_value=True) |
|
|
|
|
|
|
|
|
invalid_node = {"outputs": [{"name": "test", "type": "str", "display_name": "Test"}]} |
|
|
|
|
|
with pytest.raises(ValueError, match="Output test does not have a method"): |
|
|
component.run_and_validate_update_outputs(frontend_node=invalid_node, field_name="test", field_value=True) |
|
|
|