Files
temp/features/steps/langgraph_nodes_uncovered_lines_steps.py
brent.edwards 17fe46d925 test(behave): fix behave tests
There had been over 100 behave tests failing. There should be none failing now.
2026-02-12 04:47:02 +00:00

280 lines
8.3 KiB
Python

import asyncio
from copy import deepcopy
from behave import given, then, when
from cleveragents.agents.base import Agent
from cleveragents.langgraph.nodes import Node, NodeConfig, NodeType, ToolAgent
from cleveragents.langgraph.state import GraphState
def _make_state(messages=None, metadata=None):
return GraphState(messages=messages or [], metadata=metadata or {})
class RecordingToolAgent(ToolAgent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
self.received_inputs = []
async def process_message(self, message, context=None):
self.received_inputs.append(message)
return self.response
def get_capabilities(self):
return []
class RecordingAgent(Agent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
self.received_inputs = []
self.received_contexts = []
async def process_message(self, message, context=None):
self.received_inputs.append(message)
if context is not None:
self.received_contexts.append(deepcopy(context))
return self.response
def get_capabilities(self):
return []
class ClearingAgent(Agent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
async def process_message(self, message, context=None):
if context is not None:
context.clear()
return self.response
def get_capabilities(self):
return []
class MutatingAgent(Agent):
def __init__(self, name: str, response: str):
super().__init__(name)
self.response = response
async def process_message(self, message, context=None):
if context is not None:
context["mutated"] = "yes"
return self.response
def get_capabilities(self):
return []
@given("a tool node configured")
def step_tool_node_configured(context):
config = NodeConfig(name="tool_node", type=NodeType.TOOL)
context.node = Node(config)
context.state = _make_state()
@when("I execute the tool node")
def step_execute_tool_node(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("it should mark tool executed")
def step_assert_tool_executed(context):
assert context.result.get("tool_executed") is True
assert context.result.get("node") == "tool_node"
@given('a tool agent node using ToolAgent with current_message "{text}"')
def step_toolagent_branch(context, text):
tool_agent = RecordingToolAgent("tool", response="ok")
config = NodeConfig(name="tool_agent", type=NodeType.AGENT, agent="tool")
context.node = Node(config, agents={"tool": tool_agent})
context.state = _make_state(
messages=[{"role": "assistant", "content": "fallback"}],
metadata={"current_message": text},
)
@when("I execute the tool agent node with ToolAgent branch")
def step_execute_toolagent_branch(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then('the ToolAgent should receive input "{text}"')
def step_assert_toolagent_input(context, text):
agent = context.node.agents["tool"]
assert agent.received_inputs[-1] == text
@given("an agent node with only assistant history")
def step_agent_assistant_only(context):
agent = RecordingAgent("rec", response="ok")
config = NodeConfig(name="assistant_only", type=NodeType.AGENT, agent="rec")
context.node = Node(config, agents={"rec": agent})
context.state = _make_state(messages=[{"role": "assistant", "content": "assist"}])
@when("I execute the agent node for assistant fallback")
def step_execute_agent_assistant_only(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("the agent should receive the assistant fallback content")
def step_assert_assistant_fallback(context):
agent = context.node.agents["rec"]
assert agent.received_inputs[-1] == "assist"
@given("an agent that clears context during processing")
def step_agent_clears_context(context):
agent = ClearingAgent("clear", response="done")
config = NodeConfig(name="clear_agent", type=NodeType.AGENT, agent="clear")
context.node = Node(config, agents={"clear": agent})
context.state = _make_state(
messages=[{"role": "user", "content": "hi"}], metadata={"foo": "bar"}
)
@when("I execute the clearing agent node")
def step_execute_clearing_agent(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("only last_agent_node metadata should remain")
def step_assert_cleared_metadata(context):
metadata = context.result.get("metadata", {})
assert metadata == {"last_agent_node": "clear_agent"}
@given("an agent that mutates context to add metadata")
def step_agent_mutates_context(context):
agent = MutatingAgent("mut", response="done")
config = NodeConfig(name="mut_agent", type=NodeType.AGENT, agent="mut")
context.node = Node(config, agents={"mut": agent})
context.state = _make_state(messages=[{"role": "user", "content": "hello"}])
@when("I execute the mutating agent node")
def step_execute_mutating_agent(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("the metadata should include the mutated key")
def step_assert_mutated_metadata(context):
metadata = context.result.get("metadata", {})
assert metadata.get("mutated") == "yes"
@given('a subgraph node named "{subgraph_name}"')
def step_subgraph_node(context, subgraph_name):
config = NodeConfig(name="subgraph", type=NodeType.SUBGRAPH, subgraph=subgraph_name)
context.node = Node(config)
context.state = _make_state()
@when("I execute the subgraph node")
def step_execute_subgraph(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then('it should report the invoked subgraph "{subgraph_name}"')
def step_assert_subgraph(context, subgraph_name):
assert context.result.get("subgraph_invoked") == subgraph_name
@given("a start node")
def step_start_node(context):
config = NodeConfig(name="start", type=NodeType.START)
context.node = Node(config)
context.state = _make_state()
@when("I execute the start node")
def step_execute_start(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("it should indicate graph start")
def step_assert_started(context):
assert context.result.get("started") is True
@given("an end node")
def step_end_node(context):
config = NodeConfig(name="end", type=NodeType.END)
context.node = Node(config)
context.state = _make_state()
@when("I execute the end node")
def step_execute_end(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("it should indicate graph completion")
def step_assert_completed(context):
assert context.result.get("completed") is True
@given("a tool node patched to return non dict")
def step_tool_node_patched(context):
config = NodeConfig(name="patched_tool", type=NodeType.TOOL)
context.node = Node(config)
async def _patched_execute_tool():
return "raw-value"
context.node._execute_tool = _patched_execute_tool # type: ignore[attr-defined]
context.state = _make_state()
@when("I execute the patched tool node")
def step_execute_patched_tool(context):
loop = asyncio.new_event_loop()
try:
context.result = loop.run_until_complete(context.node.execute(context.state))
finally:
loop.close()
@then("the execute result should only include current_node")
def step_assert_current_node_only(context):
assert context.result == {"current_node": "patched_tool"}