Skip to content

[WIP] Add better tests #38

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.10
3.11
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "uipath-langchain"
version = "0.0.110"
version = "0.0.111"
description = "UiPath Langchain"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.10"
Expand All @@ -16,6 +16,7 @@ dependencies = [
"python-dotenv>=1.0.1",
"httpx>=0.27.0",
"openai>=1.65.5",
"pytest-asyncio>=1.0.0",
]
classifiers = [
"Development Status :: 3 - Alpha",
Expand Down
9 changes: 9 additions & 0 deletions tests/cli_run/samples/1-simple-graph/langgraph.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"dependencies": [
"."
],
"graphs": {
"agent": "./main.py:graph"
},
"env": ".env"
}
52 changes: 52 additions & 0 deletions tests/cli_run/samples/1-simple-graph/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import random
from typing import Literal

from langgraph.checkpoint.memory import MemorySaver
from langgraph.graph import END, START, StateGraph
from langgraph.types import interrupt
from typing_extensions import TypedDict
from uipath.models import CreateAction


# State
class State(TypedDict):
graph_state: str


# Nodes
def node_1(state):
print("---Node 1---")
simple_interrupt = interrupt("question: Who are you?")

return {"graph_state": "Hello, I am " + simple_interrupt["answer"] + "!"}


def node_2(state):
print("---Node 2---")
action_interrupt = interrupt(
CreateAction(
app_name="Test-app", title="Test-title", description="Test-description"
)
)
return {"graph_state": state["graph_state"] + action_interrupt["ActionData"]}


def node_3(state):
print("---Node 3---")
return {"graph_state": state["graph_state"] + " end"}


builder = StateGraph(State)
builder.add_node("node_1", node_1)
builder.add_node("node_2", node_2)
builder.add_node("node_3", node_3)

builder.add_edge(START, "node_1")
builder.add_edge("node_1", "node_2")
builder.add_edge("node_2", "node_3")
builder.add_edge("node_3", END)


memory = MemorySaver()

graph = builder.compile(checkpointer=memory)
15 changes: 15 additions & 0 deletions tests/cli_run/samples/1-simple-graph/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "c-host-in-uipath"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [{ name = "Eduard Stanculet", email = "[email protected]" }]
requires-python = ">=3.13"
dependencies = [
"langchain-anthropic>=0.3.10",
"langchain-community>=0.3.21",
"langgraph>=0.3.29",
"tavily-python>=0.5.4",
"uipath>=2.0.8",
"uipath-langchain>=0.0.88",
]
18 changes: 18 additions & 0 deletions tests/cli_run/samples/1-simple-graph/uipath.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"entryPoints": [
{
"filePath": "agent",
"uniqueId": "dcc7a309-fbcc-4999-af4f-2a75a844b49a",
"type": "agent",
"input": {
"type": "string",
"title": "graph_state"
},
"output": {}
}
],
"bindings": {
"version": "2.0",
"resources": []
}
}
103 changes: 103 additions & 0 deletions tests/cli_run/test_run_sample.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import os
import sys
import uuid
from unittest.mock import MagicMock, patch

import pytest
from dotenv import load_dotenv
from uipath._cli._runtime._contracts import UiPathTraceContext

from uipath_langchain._cli._runtime._context import LangGraphRuntimeContext
from uipath_langchain._cli._runtime._runtime import LangGraphRuntime
from uipath_langchain._cli._utils._graph import LangGraphConfig

load_dotenv()


def _create_test_runtime_context(config: LangGraphConfig) -> LangGraphRuntimeContext:
"""Helper function to create and configure LangGraphRuntimeContext for tests."""
context = LangGraphRuntimeContext.from_config(
os.environ.get("UIPATH_CONFIG_PATH", "uipath.json")
)

context.entrypoint = (
None # Or a specific graph name if needed, None will pick the single one
)
context.input = '{ "graph_state": "GET Assets API does not enforce proper permissions Assets.View" }'
context.resume = False
context.langgraph_config = config
context.logs_min_level = os.environ.get("LOG_LEVEL", "INFO")
context.job_id = str(uuid.uuid4())
context.trace_id = str(uuid.uuid4())
# Convert string "True" or "False" to boolean for tracing_enabled
tracing_enabled_str = os.environ.get("UIPATH_TRACING_ENABLED", "True")
context.tracing_enabled = tracing_enabled_str.lower() == "true"
context.trace_context = UiPathTraceContext(
enabled=context.tracing_enabled,
trace_id=str(
uuid.uuid4()
), # Consider passing trace_id if it needs to match context.trace_id
parent_span_id=os.environ.get("UIPATH_PARENT_SPAN_ID"),
root_span_id=os.environ.get("UIPATH_ROOT_SPAN_ID"),
job_id=os.environ.get(
"UIPATH_JOB_KEY"
), # Consider passing job_id if it needs to match context.job_id
org_id=os.environ.get("UIPATH_ORGANIZATION_ID"),
tenant_id=os.environ.get("UIPATH_TENANT_ID"),
process_key=os.environ.get("UIPATH_PROCESS_UUID"),
folder_key=os.environ.get("UIPATH_FOLDER_KEY"),
)
# Convert string "True" or "False" to boolean
langsmith_tracing_enabled_str = os.environ.get("LANGSMITH_TRACING", "False")
context.langsmith_tracing_enabled = langsmith_tracing_enabled_str.lower() == "true"
return context


@pytest.mark.asyncio
async def test_langgraph_runtime():
test_folder_path = os.path.dirname(os.path.abspath(__file__))
sample_path = os.path.join(test_folder_path, "samples", "1-simple-graph")

sys.path.append(sample_path)
os.chdir(sample_path)

config = LangGraphConfig()
if not config.exists:
raise AssertionError("langgraph.json not found in sample path")

context = _create_test_runtime_context(config)

# Mocking UiPath SDK for action creation
with patch("uipath_langchain._cli._runtime._output.UiPath") as MockUiPathClass:
mock_uipath_sdk_instance = MagicMock()
MockUiPathClass.return_value = mock_uipath_sdk_instance
mock_actions_client = MagicMock()
mock_uipath_sdk_instance.actions = mock_actions_client

mock_created_action = MagicMock()
mock_created_action.key = "mock_action_key_from_test"
mock_actions_client.create.return_value = mock_created_action

result = None
async with LangGraphRuntime.from_context(context) as runtime:
result = await runtime.execute()
print("Result:", result)

context.resume = True
context.input = '{ "answer": "John Doe"}' # Simulate some resume data
async with LangGraphRuntime.from_context(context) as runtime:
result = await runtime.execute()
print("Result:", result)

context.resume = True
context.input = (
'{ "ActionData": "Test-ActionData" }' # Simulate some resume data
)
async with LangGraphRuntime.from_context(context) as runtime:
result = await runtime.execute()
print("Result:", result)

assert result is not None, "Result should not be None after execution"
assert (
result.output["graph_state"] == "Hello, I am John Doe!Test-ActionData end"
)
16 changes: 16 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading