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