Files
cleveragents-core/features/stream_router_remaining_coverage.feature
T
CoreRasurae f2f7aa5dc9 feat(core): add v3 lifecycle models, automation levels, subplan support, and security hardening
Implement multiple Stage A/B/E/SEC milestones for the v3 lifecycle system:
- Stage A5.3+A5.4: Add LifecycleActionModel and LifecyclePlanModel SQLAlchemy
  models with to_domain()/from_domain() conversion methods
- Stage A5.6: Implement ActionRepository with full CRUD, namespace/state
  queries, referential integrity checks, and retry decorator
- Stage E1: Add subplan domain models (ExecutionMode, SubplanMergeStrategy,
  SubplanConfig, SubplanStatus, SubplanAttempt, SubplanFailureHandler) with
  computed properties on Plan (is_subplan, is_root_plan, depth, has_subplans)
- Stage A6: Add AutomationLevel enum (MANUAL, REVIEW_BEFORE_APPLY,
  FULL_AUTOMATION), settings integration, PlanLifecycleService auto-progression,
  pause/resume, and CLI commands (--automation-level, set-automation-level)
- Stage SEC1: Remove eval()/exec() from stream_router.py, replace with named
  operation and transform registries; code blocks and unregistered transforms
  now raise StreamRoutingError
- Add langchain-anthropic dependency
- Update BDD tests for security changes and relax ADR directory requirement
2026-02-12 20:19:42 +00:00

158 lines
7.5 KiB
Gherkin

Feature: Reactive Stream Router Edge Cases
As an actor orchestration system
I want the stream router to handle invalid inputs, missing components, and edge cases safely
So that stream pipelines degrade gracefully and report clear errors
# Tool agent operation safety
Scenario: Registering an operation with non-alphanumeric characters is rejected
Given a fresh SimpleToolAgent registry
When I register an operation named "bad-name!" with a lambda
Then a ValueError should be raised about operation name format
Scenario: A tool agent with a malformed tool entry passes content through unchanged
Given a SimpleToolAgent whose tools list contains a non-dict entry
When I process content "hello" through that SimpleToolAgent
Then the SimpleToolAgent should return "hello" unchanged
Scenario: A tool agent with an unrecognised operation name passes content through unchanged
Given a SimpleToolAgent with an unknown operation "nonexistent_op"
When I process content "data" through that SimpleToolAgent
Then the SimpleToolAgent should return "data" unchanged
Scenario: A tool agent safely returns empty output when an operation throws
Given a SimpleToolAgent with an operation that raises an exception
When I process content "boom" through that SimpleToolAgent
Then the SimpleToolAgent should return an empty string
# Transform registration safety
Scenario: Registering a transform with non-alphanumeric characters is rejected
Given a fresh ReactiveStreamRouter registry
When I register a transform named "bad@name" with a lambda
Then a ValueError should be raised about transform name format
# Operator construction validation
Scenario: A transform operator created without a function or type is rejected
Given a reactive stream router for coverage
And a stream named "transform_test" exists on the router
When I create a transform operator with neither fn nor type
Then a StreamRoutingError should be raised about missing transform params
# LLM agent provider resolution
Scenario: An LLM agent resolves its provider without forwarding absent optional parameters
Given a SimpleLLMAgent with no temperature or max_tokens or max_retries
And a stub provider registry is active
When I resolve the LLM on that agent
Then the LLM should be resolved without optional kwargs
Scenario: An LLM agent with no system prompt sends only the user message to the model
Given a SimpleLLMAgent with an empty system prompt
And a stub provider registry is active
When I process content "test input" through that SimpleLLMAgent
Then the LLM should receive only a HumanMessage
# LangGraph bridge delegation
Scenario: A graph operator delegates to the bridge factory when the method exists
Given a reactive stream router for coverage
And a LangGraph bridge stub with a graph_execute factory is registered
When I create a graph_execute operator via the bridge
Then the bridge factory method should produce a valid operator
Scenario: A graph operator raises a clear error when the bridge lacks the factory method
Given a reactive stream router for coverage
And a LangGraph bridge stub without the requested factory method is registered
When I attempt to create a graph_execute operator via the bridge
Then a StreamRoutingError should be raised about LangGraph bridge
# Switch operator resilience
Scenario: A switch case pipeline tolerates an operator that resolves to nothing
Given a reactive stream router for coverage
And a stream named "switch_null_op" exists on the router
When I run a switch operator whose case pipeline includes a None operator
Then the switch should still emit the message through the pipeline
Scenario: A switch case with a nonexistent target stream falls through to the default
Given a reactive stream router for coverage
And a stream named "switch_missing_target" exists on the router
When I run a switch with a matching case whose target stream does not exist
Then the switch should fall through to the default
Scenario: A switch default pipeline tolerates an operator that resolves to nothing
Given a reactive stream router for coverage
And a stream named "switch_default_null" exists on the router
When I run a switch operator whose default pipeline includes a None operator
Then the switch should still emit the message through the default pipeline
# Message transformation edge cases
Scenario: A replace transform on a non-dictionary message passes it through unchanged
Given a reactive stream router for coverage
When I apply a replace transform to a non-dict message
Then the message should be returned unchanged
Scenario: An extract-field transform retrieves the named value from a dictionary message
Given a reactive stream router for coverage
When I apply an extract_field transform to a dict message with the field
Then the extracted field value should be returned
Scenario: An extract-field transform on a non-dictionary message passes it through
Given a reactive stream router for coverage
When I apply an extract_field transform to a non-dict message
Then the non-dict message should be returned from extract_field
Scenario: An unrecognised transform type passes the message through unchanged
Given a reactive stream router for coverage
When I apply a transform with unknown type to a message
Then the original message should be returned
# Condition evaluation edge cases
Scenario: A field condition matches when the dictionary message contains the field
Given a reactive stream router for coverage
When I evaluate a field condition against a dict message containing the field
Then the condition should return true
Scenario: A field condition does not match when the dictionary message lacks the field
Given a reactive stream router for coverage
When I evaluate a field condition against a dict message missing the field
Then the condition should return false
Scenario: A field condition does not match against a non-dictionary message
Given a reactive stream router for coverage
When I evaluate a field condition against a non-dict message
Then the condition should return false
# Stream splitting and merging edge cases
Scenario: Splitting a stream ignores condition targets that do not exist
Given a reactive stream router for coverage
And a stream named "split_source" exists on the router
When I split the stream with a condition targeting a nonexistent stream
Then no error should occur and the message should not route
Scenario: Merging streams into a nonexistent target auto-creates it
Given a reactive stream router for coverage
And a stream named "merge_src_a" exists on the router
And a stream named "merge_src_b" exists on the router
When I merge streams into a target that does not exist yet
Then the target stream should be created and receive merged messages
Scenario: Merging streams silently skips source streams that do not exist
Given a reactive stream router for coverage
And a stream named "merge_src_c" exists on the router
When I merge streams including a nonexistent source into an existing target
Then only the existing source should be subscribed
# Router disposal
Scenario: Disposing the router tolerates streams that lack a dispose method
Given a reactive stream router for coverage
And a stream that lacks a dispose method is injected into the router
When I dispose the router
Then the router should be fully cleared without errors