forked from HAL9000/cleveragents-core
b2e923173f
Consolidate 141 BDD feature files that each complete in under 0.1 seconds into 25 domain-grouped feature files, reducing subprocess count from 339 to ~223. Each consolidated file groups scenarios from the same domain/module that share step definitions and fixtures. All scenarios are preserved with clear comment headers indicating their original source file. This reduces subprocess overhead by ~116 invocations (141 original files replaced by 25 consolidated files), targeting the 42% of subprocess count that contributed only 0.2% of actual test runtime. ISSUES CLOSED: #485
474 lines
18 KiB
Gherkin
474 lines
18 KiB
Gherkin
Feature: Consolidated Langgraph
|
|
Combined scenarios from: langgraph_bridge_coverage, langgraph_dynamic_router_coverage, langgraph_graph_coverage, langgraph_nodes_additional_coverage, langgraph_nodes_coverage, langgraph_nodes_uncovered_lines, langgraph_state_additional_coverage, langgraph_state_branch_coverage, pure_graph_coverage, agent_langgraph_port
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_bridge_coverage.feature
|
|
# Feature: LangGraph bridge coverage
|
|
# ============================================================
|
|
|
|
Scenario: Cover bridge lifecycle and operators
|
|
Given I set up a langgraph bridge test harness
|
|
When I create a bridge with mocked graph configuration
|
|
And I exercise bridge graph and stream creation
|
|
And I exercise graph executor branches
|
|
And I exercise state utilities and node operator
|
|
And I exercise conditional routing helpers
|
|
And I exercise router node helpers and cleanup
|
|
Then bridge coverage steps should complete successfully
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_dynamic_router_coverage.feature
|
|
# Feature: Dynamic Router Coverage
|
|
# ============================================================
|
|
|
|
Scenario: Default route when no rules are provided
|
|
Given a dynamic router with no rules
|
|
When I route the message
|
|
"""
|
|
{"text": "hello"}
|
|
"""
|
|
Then the router should return the default end edge
|
|
|
|
|
|
Scenario: Route to target when no condition is required
|
|
Given a dynamic router with a rule targeting "next" and no condition
|
|
When I route the message
|
|
"""
|
|
{"text": "any"}
|
|
"""
|
|
Then the router should return a single edge to "next"
|
|
|
|
|
|
Scenario: Route to target when condition matches
|
|
Given a dynamic router with a rule targeting "match_target" when message type is "match"
|
|
When I route the message
|
|
"""
|
|
{"type": "match", "text": "payload"}
|
|
"""
|
|
Then the router should return a single edge to "match_target"
|
|
|
|
|
|
Scenario: Fall back to end when condition fails
|
|
Given a dynamic router with a rule targeting "conditional" when message type is "expected"
|
|
When I route the message
|
|
"""
|
|
{"type": "other", "text": "payload"}
|
|
"""
|
|
Then the router should return the default end edge
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_graph_coverage.feature
|
|
# Feature: LangGraph uncovered branches
|
|
# ============================================================
|
|
|
|
Scenario: Scheduler falls back when no running loop exists
|
|
Given a langgraph config with only start and end nodes
|
|
And asyncio has no running loop
|
|
When I construct a LangGraph without providing a scheduler
|
|
Then it should create an AsyncIOScheduler and initialize streams
|
|
|
|
|
|
Scenario: Execute dispatches to start stream and returns state
|
|
Given a langgraph instance with a fresh state manager
|
|
When I execute the graph with input data
|
|
Then the start stream should receive the state and execution returns a GraphState
|
|
|
|
|
|
Scenario: Start raises when start stream is missing
|
|
Given a langgraph instance with a removed start stream
|
|
When I call start on the graph
|
|
Then a start stream error should be raised
|
|
|
|
|
|
Scenario: Parallel groups disabled and cycles detected
|
|
Given a langgraph config with parallel execution disabled and a cycle
|
|
When I construct the LangGraph
|
|
Then the graph should report cycles and empty parallel groups
|
|
|
|
|
|
Scenario: Node executor updates state and history
|
|
Given a langgraph instance with a patched state manager
|
|
When I invoke the builtin node executor
|
|
Then it should update state and record execution history
|
|
|
|
|
|
Scenario: Topological levels enumerate layered nodes
|
|
Given a langgraph config with layered nodes
|
|
When I compute the topological levels
|
|
Then the levels should list each layer
|
|
|
|
|
|
Scenario: Execution history getter returns copy
|
|
Given a langgraph instance with recorded history
|
|
When I request execution history
|
|
Then it should return a copy of the recorded history
|
|
|
|
|
|
Scenario: Initialization respects explicit guard nodes
|
|
Given a langgraph config with explicit start and end nodes
|
|
When I construct the LangGraph with that config
|
|
Then initialization should reuse the provided start and end nodes
|
|
|
|
|
|
Scenario: Node subscriptions invoke handlers on stream events
|
|
Given a langgraph instance prepared to observe node streams
|
|
When the node stream emits next error and completion events
|
|
Then the subscription handlers should be exercised
|
|
|
|
|
|
Scenario: Parallel groups computed for converging branches
|
|
Given a langgraph config with converging branches
|
|
When I construct the LangGraph for parallel groups
|
|
Then parallel groups should reflect staged execution order
|
|
|
|
|
|
Scenario: Validation fails when guard nodes missing
|
|
Given a langgraph instance missing required guard nodes
|
|
When I validate the graph explicitly
|
|
Then a guard validation error should be raised
|
|
|
|
|
|
Scenario: Start uses default state and stop resets running flag
|
|
Given a langgraph instance ready to start
|
|
When I start without input then stop and fetch state
|
|
Then it should use default state and update running flags
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_nodes_additional_coverage.feature
|
|
# Feature: Additional LangGraph nodes coverage
|
|
# ============================================================
|
|
|
|
Scenario: Conversation history handles empty messages
|
|
Given a node with default history limits
|
|
When I prepare the conversation history for empty input
|
|
Then it should return empty history without truncation
|
|
|
|
|
|
Scenario: Conversation history falls back on invalid limits
|
|
Given a node with invalid history metadata and two messages
|
|
When I prepare the conversation history with invalid limits
|
|
Then it should return full history without truncation
|
|
|
|
|
|
Scenario: Agent node without configured agent returns failure metadata
|
|
Given an agent node without an agent configured
|
|
When I execute the node expecting an agent configuration error
|
|
Then the result should include failed_node and error text
|
|
|
|
|
|
|
|
Scenario: Agent receives empty input when no messages exist
|
|
Given an agent node with no prior messages
|
|
When I execute the agent node with empty history
|
|
Then the agent should receive empty input and track node metadata
|
|
|
|
|
|
Scenario: Agent merges nested metadata context and flags truncation
|
|
Given an agent node with nested metadata context and long history
|
|
When I execute the agent node with nested context
|
|
Then the agent context should merge nested values and mark truncation
|
|
|
|
|
|
Scenario: Agent error path captures processing failures
|
|
Given an agent node whose handler raises an exception
|
|
When I execute the failing agent node
|
|
Then the agent response should contain the error string
|
|
|
|
|
|
Scenario: Function node retries and reports missing function
|
|
Given a function node missing a target with retry policy
|
|
When I execute the function node with retries
|
|
Then it should report failure after retries
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_nodes_coverage.feature
|
|
# Feature: LangGraph nodes coverage
|
|
# ============================================================
|
|
|
|
Scenario: Conversation history truncates by message count
|
|
Given a node with history limit 2 messages
|
|
And a conversation history of 3 short messages
|
|
When I prepare the conversation history
|
|
Then it should return 2 messages and indicate truncation
|
|
|
|
|
|
Scenario: Agent node falls back when agent instance is missing
|
|
Given an agent node referencing a missing agent
|
|
When I execute the agent node
|
|
Then it should return a not found assistant message
|
|
|
|
|
|
Scenario: Tool agent uses current_message metadata for input
|
|
Given a tool agent node with a current_message "override" and long history
|
|
When I execute the tool agent node
|
|
Then the tool agent should receive input "override"
|
|
And the response metadata should include last_agent_node
|
|
|
|
|
|
Scenario: Function node runs async function
|
|
Given a function node with an async function
|
|
When I execute the function node
|
|
Then the function result message should be "async-result"
|
|
|
|
|
|
Scenario: Function node awaits coroutine returned from sync function
|
|
Given a function node with a sync function that returns a coroutine
|
|
When I execute the function node
|
|
Then the function result message should be "sync-inner"
|
|
|
|
|
|
Scenario: Conditional node detects assistant field match
|
|
Given a conditional node looking for field "status" equals "OK"
|
|
When I execute the conditional node with matching assistant message
|
|
Then the condition_result should be true and metadata flagged
|
|
|
|
|
|
Scenario: Message router selects rule based on field condition
|
|
Given a message router node with a rule matching field "topic" value "urgent"
|
|
When I execute the message router with current_message containing topic "urgent"
|
|
Then it should route to "urgent_node"
|
|
|
|
|
|
Scenario: Edge condition checks equals against latest message
|
|
Given an edge with equals condition on field "content" value "done"
|
|
When I evaluate the edge condition with matching last message
|
|
Then the edge condition should be true
|
|
And evaluating with no messages should also default to true
|
|
|
|
|
|
Scenario: Eval condition supports equals and field comparisons
|
|
Given I evaluate equals condition on message "abc"
|
|
Then the equals evaluation should return true
|
|
When I evaluate field condition on message dict with foo value "bar"
|
|
Then the field evaluation should return true
|
|
When I evaluate field condition on non-dict message
|
|
Then it should return false
|
|
|
|
|
|
Scenario: Node parallel execution flag reflects configuration
|
|
Given a node configured for parallel execution
|
|
Then can_execute_parallel should return true
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_nodes_uncovered_lines.feature
|
|
# Feature: LangGraph nodes uncovered lines
|
|
# ============================================================
|
|
|
|
Scenario: Tool node marks execution
|
|
Given a tool node configured
|
|
When I execute the tool node
|
|
Then it should mark tool executed
|
|
|
|
|
|
Scenario: ToolAgent branch uses current_message for input
|
|
Given a tool agent node using ToolAgent with current_message "override"
|
|
When I execute the tool agent node with ToolAgent branch
|
|
Then the ToolAgent should receive input "override"
|
|
|
|
|
|
Scenario: Agent falls back to last assistant message when no user entries exist
|
|
Given an agent node with only assistant history
|
|
When I execute the agent node for assistant fallback
|
|
Then the agent should receive the assistant fallback content
|
|
|
|
|
|
Scenario: Agent clearing context skips metadata merge
|
|
Given an agent that clears context during processing
|
|
When I execute the clearing agent node
|
|
Then only last_agent_node metadata should remain
|
|
|
|
|
|
Scenario: Agent mutation adds metadata updates
|
|
Given an agent that mutates context to add metadata
|
|
When I execute the mutating agent node
|
|
Then the metadata should include the mutated key
|
|
|
|
|
|
Scenario: Subgraph node reports invocation
|
|
Given a subgraph node named "child_subgraph"
|
|
When I execute the subgraph node
|
|
Then it should report the invoked subgraph "child_subgraph"
|
|
|
|
|
|
Scenario: Start node indicates start
|
|
Given a start node
|
|
When I execute the start node
|
|
Then it should indicate graph start
|
|
|
|
|
|
Scenario: End node indicates completion
|
|
Given an end node
|
|
When I execute the end node
|
|
Then it should indicate graph completion
|
|
|
|
|
|
Scenario: Non-dict tool result leaves base state updates
|
|
Given a tool node patched to return non dict
|
|
When I execute the patched tool node
|
|
Then the execute result should only include current_node
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_state_additional_coverage.feature
|
|
# Feature: LangGraph state additional coverage
|
|
# ============================================================
|
|
|
|
Scenario: Merge mode trims messages beyond retention limit
|
|
Given the state management system is available
|
|
Given I have a GraphState starting with 49 messages
|
|
And I have merge updates containing 5 new messages
|
|
When I merge the updates into the state
|
|
Then the message list should be trimmed to 50 messages
|
|
And the newest message should be retained after trimming
|
|
|
|
|
|
Scenario: Append mode trims messages beyond retention limit
|
|
Given the state management system is available
|
|
Given the state management system is available
|
|
Given I have a GraphState starting with 50 messages
|
|
And I have append updates containing 5 new messages
|
|
When I append the updates into the state
|
|
Then the message list should be trimmed to 50 messages after append
|
|
And the newest appended message should be retained after trimming
|
|
|
|
|
|
|
|
Scenario: Time travel history is recorded and trimmed
|
|
Given the state management system is available
|
|
Given I have a StateManager with time travel enabled and history size 2
|
|
When I perform 4 sequential state updates with time travel
|
|
Then only the two most recent history snapshots should remain
|
|
And the earliest snapshots should be discarded
|
|
|
|
|
|
Scenario: Automatic checkpoint is saved at interval
|
|
Given the state management system is available
|
|
Given I have a StateManager with checkpointing enabled and interval 1
|
|
When I perform 2 sequential state updates with checkpointing
|
|
Then a checkpoint file should exist in the directory
|
|
And checkpoint saving should have been triggered
|
|
|
|
|
|
Scenario: Time travel clamps when steps exceed history
|
|
Given the state management system is available
|
|
Given I have a StateManager with time travel history of 2 snapshots
|
|
When I request time travel 5 steps back
|
|
Then time travel should return the earliest available snapshot
|
|
And the returned state should reflect the earliest snapshot
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: langgraph_state_branch_coverage.feature
|
|
# Feature: LangGraph state branch coverage
|
|
# ============================================================
|
|
|
|
Scenario: Replace mode overwrites existing fields
|
|
Given the state management system is available
|
|
Given I have a GraphState with metadata {"original": 1} and current node "old"
|
|
When I replace the state with updates containing metadata {"new": 2} and current node "new"
|
|
Then the state's metadata should equal {"new": 2}
|
|
And the state's current node should equal "new"
|
|
|
|
|
|
Scenario: Merge mode merges metadata dictionaries
|
|
Given the state management system is available
|
|
Given I have a GraphState with metadata {"alpha": 1}
|
|
And I have merge metadata updates {"beta": 2}
|
|
When I merge metadata updates into the state
|
|
Then the state's metadata should contain keys "alpha" and "beta"
|
|
|
|
|
|
Scenario: Append mode appends a single message item
|
|
Given the state management system is available
|
|
Given I have a GraphState with 1 message
|
|
When I append a single message with id 99 into the state
|
|
Then the state's messages should end with id 99
|
|
And the message list length should be 2
|
|
|
|
|
|
Scenario: Save checkpoint without directory is a no-op
|
|
Given the state management system is available
|
|
Given I have a StateManager without checkpointing
|
|
When I invoke checkpoint saving manually
|
|
Then the update count should remain at 0
|
|
|
|
|
|
Scenario: Load checkpoint restores state and counters
|
|
Given the state management system is available
|
|
Given I have a checkpoint file with execution count 7 and metadata {"restored": true}
|
|
When I load the checkpoint into a StateManager
|
|
Then the manager state should include metadata {"restored": true}
|
|
And the manager update count should be 7
|
|
|
|
|
|
Scenario: Latest checkpoint returns None when missing
|
|
Given the state management system is available
|
|
Given I have a StateManager without checkpointing
|
|
When I request the latest checkpoint
|
|
Then no checkpoint path should be returned
|
|
|
|
|
|
Scenario: Latest checkpoint selects the most recent file
|
|
Given the state management system is available
|
|
Given I have a StateManager with two checkpoints written at different times
|
|
When I request the latest checkpoint
|
|
Then the newest checkpoint path should be returned
|
|
|
|
|
|
Scenario: Time travel without history returns None
|
|
Given the state management system is available
|
|
Given I have a StateManager with time travel disabled
|
|
When I request time travel
|
|
Then the returned time travel state should be None
|
|
|
|
|
|
Scenario: Clear history and reset restore initial state
|
|
Given the state management system is available
|
|
Given I have a StateManager with time travel history entries
|
|
When I clear the history and then reset with metadata {"reset": true}
|
|
Then the history should be empty after reset
|
|
And the execution count should be zero after reset
|
|
And the state metadata should include {"reset": true}
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: pure_graph_coverage.feature
|
|
# Feature: PureGraph coverage
|
|
# ============================================================
|
|
|
|
Scenario: Topological order keeps start and end boundaries
|
|
Given a pure graph with nodes "alpha" and "beta"
|
|
When I list its topological order
|
|
Then the order should be start alpha beta end
|
|
|
|
|
|
Scenario: Execute applies registered node functions sequentially
|
|
Given a pure graph with function nodes "double" and "increment"
|
|
And registered functions that double then increment
|
|
When I execute the pure graph starting with 1
|
|
Then the final result should be 3
|
|
And the functions should run in declaration order
|
|
|
|
|
|
Scenario: Execute skips unregistered functions
|
|
Given a pure graph with a missing function node
|
|
When I execute the pure graph starting with 5
|
|
Then the result should remain 5
|
|
|
|
|
|
Scenario: Nodes without functions do not change value
|
|
Given a pure graph with non-functional nodes "alpha" and "beta"
|
|
When I execute the pure graph starting with 7
|
|
Then the result should remain 7
|
|
|
|
|
|
# ============================================================
|
|
# Originally from: agent_langgraph_port.feature
|
|
# Feature: Actor-first port of v2 agent and LangGraph suites
|
|
# ============================================================
|