Skip to content

Commit fa7f1bb

Browse files
committed
Add tests for human_in_the_loop samples and fix wait_condition graph
- Fix approval_wait_condition graph to use temporal_node_metadata() - Import workflow inside function for sandboxing - Add tests for approval_graph_interrupt sample - Add tests for approval_wait_condition sample - Remove old approval_workflow_test.py
1 parent cc9480c commit fa7f1bb

8 files changed

Lines changed: 160 additions & 60 deletions

File tree

langgraph_samples/activity_from_node/graph.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from langgraph.graph import END, START, StateGraph
1111
from typing_extensions import TypedDict
1212

13-
1413
# =============================================================================
1514
# State Definition
1615
# =============================================================================

langgraph_samples/human_in_the_loop/approval_graph_interrupt/run_respond.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from temporalio.client import Client
1010
from temporalio.envconfig import ClientConfig
1111

12-
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.workflow import ApprovalWorkflow
12+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.workflow import (
13+
ApprovalWorkflow,
14+
)
1315

1416

1517
async def main() -> None:

langgraph_samples/human_in_the_loop/approval_graph_interrupt/run_worker.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@
1111
from temporalio.envconfig import ClientConfig
1212
from temporalio.worker import Worker
1313

14-
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.activities import notify_approver
15-
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.graph import build_approval_graph
16-
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.workflow import ApprovalWorkflow
14+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.activities import (
15+
notify_approver,
16+
)
17+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.graph import (
18+
build_approval_graph,
19+
)
20+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.workflow import (
21+
ApprovalWorkflow,
22+
)
1723

1824
TASK_QUEUE = "langgraph-approval-interrupt"
1925

langgraph_samples/human_in_the_loop/approval_wait_condition/graph.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from typing import Any
1414

1515
from langgraph.graph import END, START, StateGraph
16-
from temporalio import activity, workflow
16+
from temporalio import activity
1717
from typing_extensions import TypedDict
1818

1919

@@ -112,6 +112,10 @@ async def request_approval(state: ApprovalState) -> ApprovalState:
112112
113113
It waits for an approval signal using workflow.wait_condition().
114114
"""
115+
from datetime import timedelta
116+
117+
from temporalio import workflow
118+
115119
# Get access to the workflow instance
116120
wf = workflow.instance()
117121

@@ -128,7 +132,9 @@ async def request_approval(state: ApprovalState) -> ApprovalState:
128132
# Store approval request for queries
129133
wf._pending_approval = approval_request
130134

131-
workflow.logger.info("Workflow paused for approval: %s", approval_request["message"])
135+
workflow.logger.info(
136+
"Workflow paused for approval: %s", approval_request["message"]
137+
)
132138

133139
# Notify the approver via activity
134140
await workflow.execute_activity(
@@ -180,12 +186,18 @@ def build_approval_graph() -> Any:
180186
The request_approval node uses run_in_workflow=True to access
181187
Temporal operations directly (signals, activities, etc.).
182188
"""
189+
from temporalio.contrib.langgraph import temporal_node_metadata
190+
183191
graph = StateGraph(ApprovalState)
184192

185193
# Add nodes
186194
graph.add_node("process_request", process_request)
187195
# Mark request_approval as run_in_workflow - it can access Temporal operations
188-
graph.add_node("request_approval", request_approval, metadata={"run_in_workflow": True})
196+
graph.add_node(
197+
"request_approval",
198+
request_approval,
199+
metadata=temporal_node_metadata(run_in_workflow=True),
200+
)
189201
graph.add_node("execute_action", execute_action)
190202

191203
# Define edges

langgraph_samples/human_in_the_loop/approval_wait_condition/run_respond.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
from temporalio.client import Client
1010
from temporalio.envconfig import ClientConfig
1111

12-
from langgraph_samples.human_in_the_loop.approval_wait_condition.workflow import ApprovalWorkflow
12+
from langgraph_samples.human_in_the_loop.approval_wait_condition.workflow import (
13+
ApprovalWorkflow,
14+
)
1315

1416

1517
async def main() -> None:

langgraph_samples/human_in_the_loop/approval_wait_condition/run_worker.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
build_approval_graph,
1616
notify_approver,
1717
)
18-
from langgraph_samples.human_in_the_loop.approval_wait_condition.workflow import ApprovalWorkflow
18+
from langgraph_samples.human_in_the_loop.approval_wait_condition.workflow import (
19+
ApprovalWorkflow,
20+
)
1921

2022
TASK_QUEUE = "langgraph-approval-condition"
2123

tests/langgraph_samples/approval_workflow_test.py renamed to tests/langgraph_samples/approval_graph_interrupt_test.py

Lines changed: 10 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
"""Tests for the approval_workflow LangGraph sample."""
1+
"""Tests for the approval_graph_interrupt LangGraph sample."""
22

33
import uuid
44

55
from temporalio.client import Client, WorkflowHandle
66
from temporalio.contrib.langgraph import LangGraphPlugin
77
from temporalio.worker import Worker
88

9-
from langgraph_samples.approval_workflow.activities import notify_approver
10-
from langgraph_samples.approval_workflow.graph import build_approval_graph
11-
from langgraph_samples.approval_workflow.workflow import (
9+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.activities import (
10+
notify_approver,
11+
)
12+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.graph import (
13+
build_approval_graph,
14+
)
15+
from langgraph_samples.human_in_the_loop.approval_graph_interrupt.workflow import (
1216
ApprovalRequest,
1317
ApprovalWorkflow,
1418
)
1519

1620

17-
async def test_approval_workflow_approved(client: Client) -> None:
21+
async def test_approval_graph_interrupt_approved(client: Client) -> None:
1822
"""Test approval workflow when request is approved."""
1923
task_queue = f"approval-test-{uuid.uuid4()}"
2024

@@ -67,7 +71,7 @@ async def test_approval_workflow_approved(client: Client) -> None:
6771
assert "manager" in result["result"]
6872

6973

70-
async def test_approval_workflow_rejected(client: Client) -> None:
74+
async def test_approval_graph_interrupt_rejected(client: Client) -> None:
7175
"""Test approval workflow when request is rejected."""
7276
task_queue = f"approval-test-{uuid.uuid4()}"
7377

@@ -113,47 +117,3 @@ async def test_approval_workflow_rejected(client: Client) -> None:
113117
assert result["executed"] is False
114118
assert "rejected" in result["result"]
115119
assert "cfo" in result["result"]
116-
117-
118-
async def test_approval_workflow_low_risk(client: Client) -> None:
119-
"""Test approval workflow with low risk amount."""
120-
task_queue = f"approval-test-{uuid.uuid4()}"
121-
122-
plugin = LangGraphPlugin(graphs={"approval_workflow": build_approval_graph})
123-
124-
async with Worker(
125-
client,
126-
task_queue=task_queue,
127-
workflows=[ApprovalWorkflow],
128-
activities=[notify_approver],
129-
plugins=[plugin],
130-
):
131-
handle: WorkflowHandle[ApprovalWorkflow, dict] = await client.start_workflow(
132-
ApprovalWorkflow.run,
133-
ApprovalRequest(request_type="supplies", amount=25.0),
134-
id=f"approval-{uuid.uuid4()}",
135-
task_queue=task_queue,
136-
)
137-
138-
# Wait for approval state
139-
import asyncio
140-
141-
for _ in range(20):
142-
status = await handle.query(ApprovalWorkflow.get_status)
143-
if status == "waiting_for_approval":
144-
break
145-
await asyncio.sleep(0.1)
146-
147-
# Verify low risk level
148-
pending = await handle.query(ApprovalWorkflow.get_pending_approval)
149-
assert pending is not None
150-
assert pending["risk_level"] == "low"
151-
152-
# Approve
153-
await handle.signal(
154-
ApprovalWorkflow.provide_approval,
155-
{"approved": True, "reason": "Auto-approved", "approver": "system"},
156-
)
157-
158-
result = await handle.result()
159-
assert result["approved"] is True
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""Tests for the approval_wait_condition LangGraph sample."""
2+
3+
import uuid
4+
5+
from temporalio.client import Client, WorkflowHandle
6+
from temporalio.contrib.langgraph import LangGraphPlugin
7+
from temporalio.worker import Worker
8+
9+
from langgraph_samples.human_in_the_loop.approval_wait_condition.graph import (
10+
build_approval_graph,
11+
notify_approver,
12+
)
13+
from langgraph_samples.human_in_the_loop.approval_wait_condition.workflow import (
14+
ApprovalRequest,
15+
ApprovalWorkflow,
16+
)
17+
18+
19+
async def test_approval_wait_condition_approved(client: Client) -> None:
20+
"""Test approval workflow when request is approved."""
21+
task_queue = f"approval-test-{uuid.uuid4()}"
22+
23+
plugin = LangGraphPlugin(graphs={"approval_workflow": build_approval_graph})
24+
25+
async with Worker(
26+
client,
27+
task_queue=task_queue,
28+
workflows=[ApprovalWorkflow],
29+
activities=[notify_approver],
30+
plugins=[plugin],
31+
):
32+
# Start the workflow
33+
handle: WorkflowHandle[ApprovalWorkflow, dict] = await client.start_workflow(
34+
ApprovalWorkflow.run,
35+
ApprovalRequest(request_type="expense", amount=500.0),
36+
id=f"approval-{uuid.uuid4()}",
37+
task_queue=task_queue,
38+
)
39+
40+
# Wait for the workflow to reach the approval point
41+
import asyncio
42+
43+
for _ in range(20):
44+
status = await handle.query(ApprovalWorkflow.get_status)
45+
if status == "waiting_for_approval":
46+
break
47+
await asyncio.sleep(0.1)
48+
49+
assert status == "waiting_for_approval"
50+
51+
# Query the pending approval
52+
pending = await handle.query(ApprovalWorkflow.get_pending_approval)
53+
assert pending is not None
54+
assert pending["amount"] == 500.0
55+
assert pending["risk_level"] == "medium"
56+
57+
# Send approval signal
58+
await handle.signal(
59+
ApprovalWorkflow.provide_approval,
60+
{"approved": True, "reason": "Looks good", "approver": "manager"},
61+
)
62+
63+
# Wait for result
64+
result = await handle.result()
65+
66+
assert result["approved"] is True
67+
assert result["executed"] is True
68+
assert "Successfully processed" in result["result"]
69+
assert "manager" in result["result"]
70+
71+
72+
async def test_approval_wait_condition_rejected(client: Client) -> None:
73+
"""Test approval workflow when request is rejected."""
74+
task_queue = f"approval-test-{uuid.uuid4()}"
75+
76+
plugin = LangGraphPlugin(graphs={"approval_workflow": build_approval_graph})
77+
78+
async with Worker(
79+
client,
80+
task_queue=task_queue,
81+
workflows=[ApprovalWorkflow],
82+
activities=[notify_approver],
83+
plugins=[plugin],
84+
):
85+
handle: WorkflowHandle[ApprovalWorkflow, dict] = await client.start_workflow(
86+
ApprovalWorkflow.run,
87+
ApprovalRequest(request_type="purchase", amount=5000.0),
88+
id=f"approval-{uuid.uuid4()}",
89+
task_queue=task_queue,
90+
)
91+
92+
# Wait for approval state
93+
import asyncio
94+
95+
for _ in range(20):
96+
status = await handle.query(ApprovalWorkflow.get_status)
97+
if status == "waiting_for_approval":
98+
break
99+
await asyncio.sleep(0.1)
100+
101+
# Verify high risk level for large amount
102+
pending = await handle.query(ApprovalWorkflow.get_pending_approval)
103+
assert pending is not None
104+
assert pending["risk_level"] == "high"
105+
106+
# Reject the request
107+
await handle.signal(
108+
ApprovalWorkflow.provide_approval,
109+
{"approved": False, "reason": "Budget exceeded", "approver": "cfo"},
110+
)
111+
112+
result = await handle.result()
113+
114+
assert result["approved"] is False
115+
assert result["executed"] is False
116+
assert "rejected" in result["result"]
117+
assert "cfo" in result["result"]

0 commit comments

Comments
 (0)