test(actor): add step definitions for actor examples validation

Create features/steps/actor_examples_steps.py with step implementations:
- Given steps for actor patterns (strategist/executor/reviewer/validator)
- When steps for schema validation from string and file
- Then steps for field validation and pattern verification
- Graph/route validation (nodes, edges, conditionals, subgraphs)
- Error assertions and documentation completeness checks

All steps use unique patterns to avoid AmbiguousStep conflicts.
This commit is contained in:
2026-02-18 09:36:14 +00:00
parent 02466864ef
commit c6157745e7
+832
View File
@@ -0,0 +1,832 @@
"""Step definitions for actor YAML examples validation.
Tests for features/actor_examples.feature — validates that all actor examples
are correct, loadable, and demonstrate the expected patterns (strategist,
executor, reviewer, tool-only, validation, graphs, hierarchical).
"""
from __future__ import annotations
from pathlib import Path
from behave import given, then, when # type: ignore[import-untyped]
from behave.runner import Context # type: ignore[import-untyped]
from pydantic import ValidationError
from cleveragents.actor.schema import ActorConfigSchema, NodeType
# ────────────────────────────────────────────────────────────
# Given Steps - Background
# ────────────────────────────────────────────────────────────
@given("the CleverAgents actor schema validation is available")
def step_given_schema_available(context: Context) -> None:
"""Verify that ActorConfigSchema is importable and ready."""
context.schema_available = ActorConfigSchema is not None
assert context.schema_available, "ActorConfigSchema should be available"
@given("the actor YAML file {file_path:QuotedString}")
def step_given_actor_yaml_file_examples(context: Context, file_path: str) -> None:
"""Load actor YAML file path for validation."""
# Strip quotes if any
file_path = file_path.strip('"').strip("'")
context.actor_yaml_file = file_path
context.actor_yaml_string = None # Clear any previous string
# ────────────────────────────────────────────────────────────
# Given Steps - Actor Pattern YAMLs
# ────────────────────────────────────────────────────────────
@given("an actor YAML string representing a strategist actor")
def step_given_strategist_actor(context: Context) -> None:
"""Provide strategist actor YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string representing an executor actor")
def step_given_executor_actor(context: Context) -> None:
"""Provide executor actor YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string representing a reviewer actor")
def step_given_reviewer_actor(context: Context) -> None:
"""Provide reviewer actor YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with tool-only configuration")
def step_given_tool_only_actor(context: Context) -> None:
"""Provide tool-only actor YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with git tools")
def step_given_git_tools_actor(context: Context) -> None:
"""Provide git tools actor YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string representing a validator actor")
def step_given_validator_actor(context: Context) -> None:
"""Provide validator actor YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with linear graph workflow")
def step_given_linear_graph_workflow(context: Context) -> None:
"""Provide linear graph workflow YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with conditional routing")
def step_given_conditional_routing_examples(context: Context) -> None:
"""Provide conditional routing YAML from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with tool nodes")
def step_given_tool_nodes(context: Context) -> None:
"""Provide YAML with tool nodes from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with subgraph")
def step_given_subgraph_examples(context: Context) -> None:
"""Provide YAML with subgraph from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with retry logic")
def step_given_retry_logic(context: Context) -> None:
"""Provide YAML with retry logic from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with multi-level hierarchy")
def step_given_multi_level_hierarchy(context: Context) -> None:
"""Provide multi-level hierarchy YAML from docstring."""
context.actor_yaml_string = context.text
# ────────────────────────────────────────────────────────────
# Given Steps - Invalid Examples
# ────────────────────────────────────────────────────────────
@given("an actor YAML string missing required name field")
def step_given_missing_name(context: Context) -> None:
"""Provide YAML missing name field from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with invalid name")
def step_given_invalid_name(context: Context) -> None:
"""Provide YAML with invalid name from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string for LLM without model")
def step_given_llm_without_model(context: Context) -> None:
"""Provide LLM YAML without model from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string for TOOL without tools")
def step_given_tool_without_tools(context: Context) -> None:
"""Provide TOOL YAML without tools from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string for GRAPH without route")
def step_given_graph_without_route(context: Context) -> None:
"""Provide GRAPH YAML without route from docstring."""
context.actor_yaml_string = context.text
@given("an actor YAML string with cyclic graph")
def step_given_cyclic_graph(context: Context) -> None:
"""Provide YAML with cyclic graph from docstring."""
context.actor_yaml_string = context.text
# ────────────────────────────────────────────────────────────
# Given Steps - Directory and Documentation
# ────────────────────────────────────────────────────────────
@given("the examples/actors directory")
def step_given_examples_directory(context: Context) -> None:
"""Set context to examples/actors directory."""
context.examples_dir = Path("examples/actors")
assert context.examples_dir.exists(), (
f"Directory {context.examples_dir} should exist"
)
@given("the documentation file {doc_path:QuotedString}")
def step_given_documentation_file(context: Context, doc_path: str) -> None:
"""Load documentation file for verification."""
doc_path = doc_path.strip('"').strip("'")
context.doc_file = Path(doc_path)
assert context.doc_file.exists(), (
f"Documentation file {context.doc_file} should exist"
)
context.doc_content = context.doc_file.read_text()
# ────────────────────────────────────────────────────────────
# When Steps
# ────────────────────────────────────────────────────────────
@when("I validate the actor schema")
def step_when_validate_actor_schema_examples(context: Context) -> None:
"""Validate actor schema from YAML string."""
try:
context.actor_config = ActorConfigSchema.from_yaml(context.actor_yaml_string)
context.validation_error = None
except ValidationError as e:
context.actor_config = None
context.validation_error = e
except Exception as e:
context.actor_config = None
context.validation_error = e
@when("I validate the actor schema from file")
def step_when_validate_from_file_examples(context: Context) -> None:
"""Validate actor schema from YAML file."""
try:
context.actor_config = ActorConfigSchema.from_yaml_file(context.actor_yaml_file)
context.validation_error = None
except ValidationError as e:
context.actor_config = None
context.validation_error = e
except Exception as e:
context.actor_config = None
context.validation_error = e
# ────────────────────────────────────────────────────────────
# Then Steps - Validation Success/Failure
# ────────────────────────────────────────────────────────────
@then("the actor schema validation should succeed")
def step_then_validation_succeeds_examples(context: Context) -> None:
"""Assert validation succeeded."""
if context.validation_error:
raise AssertionError(
f"Expected validation to succeed but got error: {context.validation_error}"
)
assert context.actor_config is not None, "Actor config should be populated"
@then("the actor schema validation should fail")
def step_then_validation_fails_examples(context: Context) -> None:
"""Assert validation failed."""
assert context.validation_error is not None, "Expected validation to fail"
assert context.actor_config is None, "Actor config should be None on failure"
# ────────────────────────────────────────────────────────────
# Then Steps - Basic Field Checks
# ────────────────────────────────────────────────────────────
@then("the actor config name should be {expected_name:QuotedString}")
def step_then_name_is_examples(context: Context, expected_name: str) -> None:
"""Verify actor name matches expected value."""
expected_name = expected_name.strip('"').strip("'")
assert context.actor_config.name == expected_name, (
f"Expected name '{expected_name}', got '{context.actor_config.name}'"
)
@then("the actor config type should be {expected_type:QuotedString}")
def step_then_type_is_examples(context: Context, expected_type: str) -> None:
"""Verify actor type matches expected value."""
expected_type = expected_type.strip('"').strip("'")
actual_type = (
context.actor_config.type.value
if hasattr(context.actor_config.type, "value")
else str(context.actor_config.type)
)
assert actual_type == expected_type, (
f"Expected type '{expected_type}', got '{actual_type}'"
)
@then("the actor config model should be {expected_model:QuotedString}")
def step_then_model_is_examples(context: Context, expected_model: str) -> None:
"""Verify actor model matches expected value."""
expected_model = expected_model.strip('"').strip("'")
assert context.actor_config.model == expected_model, (
f"Expected model '{expected_model}', got '{context.actor_config.model}'"
)
@then("the actor config description should contain {expected_text:QuotedString}")
def step_then_description_contains_examples(
context: Context, expected_text: str
) -> None:
"""Verify description contains expected text."""
expected_text = expected_text.strip('"').strip("'")
assert expected_text.lower() in context.actor_config.description.lower(), (
f"Expected description to contain '{expected_text}', "
f"got '{context.actor_config.description}'"
)
@then("the actor config system_prompt should contain {expected_text:QuotedString}")
def step_then_system_prompt_contains_examples(
context: Context, expected_text: str
) -> None:
"""Verify system prompt contains expected text."""
expected_text = expected_text.strip('"').strip("'")
assert context.actor_config.system_prompt is not None, (
"System prompt should not be None"
)
assert expected_text.lower() in context.actor_config.system_prompt.lower(), (
f"Expected system_prompt to contain '{expected_text}'"
)
@then("the actor config context_view should be {expected_view:QuotedString}")
def step_then_context_view_is_examples(context: Context, expected_view: str) -> None:
"""Verify context_view matches expected value."""
expected_view = expected_view.strip('"').strip("'")
actual_view = (
context.actor_config.context_view.value
if hasattr(context.actor_config.context_view, "value")
else str(context.actor_config.context_view)
)
assert actual_view == expected_view, (
f"Expected context_view '{expected_view}', got '{actual_view}'"
)
@then("the actor config name should match pattern {pattern:QuotedString}")
def step_then_name_matches_pattern_examples(context: Context, pattern: str) -> None:
"""Verify actor name matches regex pattern."""
import re
pattern = pattern.strip('"').strip("'")
assert re.match(pattern, context.actor_config.name), (
f"Name '{context.actor_config.name}' does not match pattern '{pattern}'"
)
# ────────────────────────────────────────────────────────────
# Then Steps - Memory Configuration
# ────────────────────────────────────────────────────────────
@then("the actor memory enabled should be {expected_bool}")
def step_then_memory_enabled_examples(context: Context, expected_bool: str) -> None:
"""Verify memory enabled flag."""
expected = expected_bool.lower() == "true"
assert context.actor_config.memory is not None, "Memory config should not be None"
assert context.actor_config.memory.enabled == expected, (
f"Expected memory.enabled={expected}, got {context.actor_config.memory.enabled}"
)
@then("the actor memory max_messages should be {expected:d}")
def step_then_memory_max_messages_examples(context: Context, expected: int) -> None:
"""Verify memory max_messages."""
assert context.actor_config.memory is not None, "Memory config should not be None"
assert context.actor_config.memory.max_messages == expected, (
f"Expected memory.max_messages={expected}, "
f"got {context.actor_config.memory.max_messages}"
)
@then("the actor memory max_tokens should be {expected:d}")
def step_then_memory_max_tokens_examples(context: Context, expected: int) -> None:
"""Verify memory max_tokens."""
assert context.actor_config.memory is not None, "Memory config should not be None"
assert context.actor_config.memory.max_tokens == expected, (
f"Expected memory.max_tokens={expected}, got {context.actor_config.memory.max_tokens}"
)
@then("the actor memory max_messages should be at least {minimum:d}")
def step_then_memory_max_messages_at_least_examples(
context: Context, minimum: int
) -> None:
"""Verify memory max_messages is at least the specified minimum."""
assert context.actor_config.memory is not None, "Memory config should not be None"
assert context.actor_config.memory.max_messages >= minimum, (
f"Expected memory.max_messages >= {minimum}, "
f"got {context.actor_config.memory.max_messages}"
)
# ────────────────────────────────────────────────────────────
# Then Steps - Context Configuration
# ────────────────────────────────────────────────────────────
@then("the actor context should include {count:d} files")
def step_then_context_includes_files_examples(context: Context, count: int) -> None:
"""Verify context includes specified number of files."""
assert context.actor_config.context is not None, "Context config should not be None"
assert len(context.actor_config.context.include_files) == count, (
f"Expected {count} include_files, "
f"got {len(context.actor_config.context.include_files)}"
)
@then("the actor context should include {count:d} directory")
def step_then_context_includes_dirs_examples(context: Context, count: int) -> None:
"""Verify context includes specified number of directories."""
assert context.actor_config.context is not None, "Context config should not be None"
assert len(context.actor_config.context.include_dirs) == count, (
f"Expected {count} include_dirs, got {len(context.actor_config.context.include_dirs)}"
)
@then("the actor context max_context_tokens should be {expected:d}")
def step_then_context_max_tokens_examples(context: Context, expected: int) -> None:
"""Verify context max_context_tokens."""
assert context.actor_config.context is not None, "Context config should not be None"
assert context.actor_config.context.max_context_tokens == expected, (
f"Expected context.max_context_tokens={expected}, "
f"got {context.actor_config.context.max_context_tokens}"
)
# ────────────────────────────────────────────────────────────
# Then Steps - Tools
# ────────────────────────────────────────────────────────────
@then("the actor config should have {count:d} tools")
def step_then_has_exact_tool_count_examples(context: Context, count: int) -> None:
"""Verify actor has exact number of tools."""
tools = context.actor_config.tools or []
assert len(tools) == count, f"Expected {count} tools, got {len(tools)}"
@then("the actor config should have at least {minimum:d} tool")
@then("the actor config should have at least {minimum:d} tools")
def step_then_has_minimum_tools_examples(context: Context, minimum: int) -> None:
"""Verify actor has at least minimum number of tools."""
tools = context.actor_config.tools or []
assert len(tools) >= minimum, f"Expected at least {minimum} tools, got {len(tools)}"
@then("the actor config should have read-only tools")
def step_then_has_read_only_tools_examples(context: Context) -> None:
"""Verify actor has read-only tools (files/read, list, etc)."""
tools = context.actor_config.tools or []
assert len(tools) > 0, "Actor should have tools"
# Check that tools include read operations
tool_names = [
str(tool) if isinstance(tool, str) else tool.get("name", "") for tool in tools
]
has_read_tools = any(
"read" in name.lower() or "list" in name.lower() for name in tool_names
)
assert has_read_tools, "Actor should have read-only tools"
@then("the actor config should have write-capable tools")
def step_then_has_write_tools_examples(context: Context) -> None:
"""Verify actor has write-capable tools."""
tools = context.actor_config.tools or []
assert len(tools) > 0, "Actor should have tools"
tool_names = [
str(tool) if isinstance(tool, str) else tool.get("name", "") for tool in tools
]
has_write_tools = any(
"write" in name.lower() or "create" in name.lower() or "delete" in name.lower()
for name in tool_names
)
assert has_write_tools, "Actor should have write-capable tools"
@then("the actor config should have linting tools")
def step_then_has_linting_tools_examples(context: Context) -> None:
"""Verify actor has linting tools."""
tools = context.actor_config.tools or []
assert len(tools) > 0, "Actor should have tools"
tool_names = [
str(tool) if isinstance(tool, str) else tool.get("name", "") for tool in tools
]
has_linting = any(
"lint" in name.lower() or "ruff" in name.lower() for name in tool_names
)
assert has_linting, "Actor should have linting tools"
@then("the actor config should have validation tools")
def step_then_has_validation_tools_examples(context: Context) -> None:
"""Verify actor has validation tools."""
tools = context.actor_config.tools or []
assert len(tools) > 0, "Actor should have tools"
tool_names = [
str(tool) if isinstance(tool, str) else tool.get("name", "") for tool in tools
]
has_validation = any(
"lint" in name.lower() or "test" in name.lower() or "security" in name.lower()
for name in tool_names
)
assert has_validation, "Actor should have validation tools"
@then("the actor config should not have model field")
def step_then_no_model_field_examples(context: Context) -> None:
"""Verify actor does not have model field."""
assert context.actor_config.model is None, "TOOL actor should not have model field"
@then("the actor config should not have system_prompt field")
def step_then_no_system_prompt_field_examples(context: Context) -> None:
"""Verify actor does not have system_prompt field."""
assert context.actor_config.system_prompt is None, (
"TOOL actor should not have system_prompt field"
)
# ────────────────────────────────────────────────────────────
# Then Steps - Graph/Route Configuration
# ────────────────────────────────────────────────────────────
@then("the actor route should have at least {minimum:d} nodes")
def step_then_route_has_minimum_nodes_examples(context: Context, minimum: int) -> None:
"""Verify route has at least minimum number of nodes."""
assert context.actor_config.route is not None, "Route should not be None"
assert len(context.actor_config.route.nodes) >= minimum, (
f"Expected at least {minimum} nodes, got {len(context.actor_config.route.nodes)}"
)
@then("the actor route should have at least {minimum:d} edges")
def step_then_route_has_minimum_edges_examples(context: Context, minimum: int) -> None:
"""Verify route has at least minimum number of edges."""
assert context.actor_config.route is not None, "Route should not be None"
assert len(context.actor_config.route.edges) >= minimum, (
f"Expected at least {minimum} edges, got {len(context.actor_config.route.edges)}"
)
@then("the actor route should have an entry_node")
def step_then_route_has_entry_node_examples(context: Context) -> None:
"""Verify route has entry_node defined."""
assert context.actor_config.route is not None, "Route should not be None"
assert context.actor_config.route.entry_node is not None, (
"entry_node should be defined"
)
assert len(context.actor_config.route.entry_node) > 0, (
"entry_node should not be empty"
)
@then("the actor route should have exit_nodes")
def step_then_route_has_exit_nodes_examples(context: Context) -> None:
"""Verify route has exit_nodes defined."""
assert context.actor_config.route is not None, "Route should not be None"
assert context.actor_config.route.exit_nodes is not None, (
"exit_nodes should be defined"
)
assert len(context.actor_config.route.exit_nodes) > 0, (
"exit_nodes should not be empty"
)
@then("the actor route entry_node should be {expected_node:QuotedString}")
def step_then_entry_node_is_examples(context: Context, expected_node: str) -> None:
"""Verify entry_node matches expected value."""
expected_node = expected_node.strip('"').strip("'")
assert context.actor_config.route.entry_node == expected_node, (
f"Expected entry_node '{expected_node}', got '{context.actor_config.route.entry_node}'"
)
@then("the actor route should have {count:d} exit node")
@then("the actor route should have {count:d} exit nodes")
def step_then_has_exit_node_count_examples(context: Context, count: int) -> None:
"""Verify route has specified number of exit nodes."""
assert context.actor_config.route is not None, "Route should not be None"
assert len(context.actor_config.route.exit_nodes) == count, (
f"Expected {count} exit nodes, got {len(context.actor_config.route.exit_nodes)}"
)
@then("the actor route should have {count:d} nodes")
def step_then_route_has_exact_nodes_examples(context: Context, count: int) -> None:
"""Verify route has exact number of nodes."""
assert context.actor_config.route is not None, "Route should not be None"
assert len(context.actor_config.route.nodes) == count, (
f"Expected {count} nodes, got {len(context.actor_config.route.nodes)}"
)
@then("the actor route should have conditional node")
def step_then_has_conditional_node_examples(context: Context) -> None:
"""Verify route has at least one conditional node."""
assert context.actor_config.route is not None, "Route should not be None"
conditional_nodes = [
node
for node in context.actor_config.route.nodes
if node.type == NodeType.CONDITIONAL
]
assert len(conditional_nodes) > 0, "Route should have at least one conditional node"
@then("the actor route should have multiple exit nodes")
def step_then_has_multiple_exit_nodes_examples(context: Context) -> None:
"""Verify route has multiple exit nodes."""
assert context.actor_config.route is not None, "Route should not be None"
assert len(context.actor_config.route.exit_nodes) > 1, (
f"Expected multiple exit nodes, got {len(context.actor_config.route.exit_nodes)}"
)
@then("the actor route should have tool node")
def step_then_has_tool_node_examples(context: Context) -> None:
"""Verify route has at least one tool node."""
assert context.actor_config.route is not None, "Route should not be None"
tool_nodes = [
node for node in context.actor_config.route.nodes if node.type == NodeType.TOOL
]
assert len(tool_nodes) > 0, "Route should have at least one tool node"
@then("the actor route should have agent nodes")
def step_then_has_agent_nodes_examples(context: Context) -> None:
"""Verify route has agent nodes."""
assert context.actor_config.route is not None, "Route should not be None"
agent_nodes = [
node for node in context.actor_config.route.nodes if node.type == NodeType.AGENT
]
assert len(agent_nodes) > 0, "Route should have at least one agent node"
@then("the actor route should have subgraph nodes")
def step_then_has_subgraph_nodes_examples(context: Context) -> None:
"""Verify route has subgraph nodes."""
assert context.actor_config.route is not None, "Route should not be None"
subgraph_nodes = [
node
for node in context.actor_config.route.nodes
if node.type == NodeType.SUBGRAPH
]
assert len(subgraph_nodes) > 0, "Route should have at least one subgraph node"
@then("the actor route should have parallel execution config")
def step_then_has_parallel_config_examples(context: Context) -> None:
"""Verify route has parallel execution configuration."""
assert context.actor_config.route is not None, "Route should not be None"
# Check if any subgraph node has parallel config
subgraph_nodes = [
node
for node in context.actor_config.route.nodes
if node.type == NodeType.SUBGRAPH
]
has_parallel = any(
node.config and node.config.get("parallel") is True for node in subgraph_nodes
)
assert has_parallel, "Route should have parallel execution configuration"
@then("the actor route should have retry loop")
def step_then_has_retry_loop_examples(context: Context) -> None:
"""Verify route has retry loop structure."""
assert context.actor_config.route is not None, "Route should not be None"
# Check for conditional nodes with retry logic
conditional_nodes = [
node
for node in context.actor_config.route.nodes
if node.type == NodeType.CONDITIONAL
]
assert len(conditional_nodes) > 0, "Route should have conditional nodes for retry"
# Check for edges forming a loop
node_ids = {node.id for node in context.actor_config.route.nodes}
has_loop = False
for edge in context.actor_config.route.edges:
if edge.from_node in node_ids and edge.to_node in node_ids:
# Simple check: if we can go back to an earlier node
from_idx = next(
(
i
for i, n in enumerate(context.actor_config.route.nodes)
if n.id == edge.from_node
),
-1,
)
to_idx = next(
(
i
for i, n in enumerate(context.actor_config.route.nodes)
if n.id == edge.to_node
),
-1,
)
if to_idx < from_idx:
has_loop = True
break
assert has_loop, "Route should have retry loop (backward edge)"
@then("the actor route should have escalation path")
def step_then_has_escalation_path_examples(context: Context) -> None:
"""Verify route has escalation path."""
assert context.actor_config.route is not None, "Route should not be None"
# Check for tool nodes with escalation or notification in config
tool_nodes = [
node for node in context.actor_config.route.nodes if node.type == NodeType.TOOL
]
has_escalation = any(
"escalat" in str(node.config).lower() or "alert" in str(node.config).lower()
for node in tool_nodes
)
assert has_escalation, "Route should have escalation path"
@then("the actor route should have multiple subgraph levels")
def step_then_has_multiple_subgraph_levels_examples(context: Context) -> None:
"""Verify route has multiple levels of subgraphs."""
assert context.actor_config.route is not None, "Route should not be None"
subgraph_nodes = [
node
for node in context.actor_config.route.nodes
if node.type == NodeType.SUBGRAPH
]
assert len(subgraph_nodes) >= 2, (
f"Expected at least 2 subgraph nodes for multiple levels, "
f"got {len(subgraph_nodes)}"
)
@then("the actor route should support parallel execution")
def step_then_supports_parallel_execution_examples(context: Context) -> None:
"""Verify route supports parallel execution."""
assert context.actor_config.route is not None, "Route should not be None"
subgraph_nodes = [
node
for node in context.actor_config.route.nodes
if node.type == NodeType.SUBGRAPH and node.config
]
has_parallel_config = any(
node.config.get("parallel") is True or "max_parallel" in node.config
for node in subgraph_nodes
)
assert has_parallel_config, "Route should support parallel execution"
# ────────────────────────────────────────────────────────────
# Then Steps - Environment Variables
# ────────────────────────────────────────────────────────────
@then("the actor config should have environment variables")
def step_then_has_env_vars_examples(context: Context) -> None:
"""Verify actor has environment variables defined."""
assert context.actor_config.env_vars is not None, "env_vars should not be None"
assert len(context.actor_config.env_vars) > 0, "env_vars should not be empty"
# ────────────────────────────────────────────────────────────
# Then Steps - Error Messages
# ────────────────────────────────────────────────────────────
@then("the error message should contain {expected_text:QuotedString}")
def step_then_error_contains_examples(context: Context, expected_text: str) -> None:
"""Verify error message contains expected text."""
expected_text = expected_text.strip('"').strip("'")
assert context.validation_error is not None, "Should have validation error"
error_str = str(context.validation_error).lower()
assert expected_text.lower() in error_str, (
f"Expected error to contain '{expected_text}', got: {context.validation_error}"
)
# ────────────────────────────────────────────────────────────
# Then Steps - Example Files and Documentation
# ────────────────────────────────────────────────────────────
@then("there should be exactly {count:d} example YAML files")
def step_then_example_count_examples(context: Context, count: int) -> None:
"""Verify exact number of example YAML files."""
yaml_files = list(context.examples_dir.glob("*.yaml"))
assert len(yaml_files) == count, (
f"Expected {count} YAML files, found {len(yaml_files)}: {[f.name for f in yaml_files]}"
)
@then("the example files should include {filename:QuotedString}")
def step_then_example_includes_file_examples(context: Context, filename: str) -> None:
"""Verify specific example file exists."""
filename = filename.strip('"').strip("'")
file_path = context.examples_dir / filename
assert file_path.exists(), (
f"Example file {filename} should exist in {context.examples_dir}"
)
@then("the documentation should reference {filename:QuotedString}")
def step_then_doc_references_file_examples(context: Context, filename: str) -> None:
"""Verify documentation references specific file."""
filename = filename.strip('"').strip("'")
assert filename in context.doc_content, f"Documentation should reference {filename}"
@then("the documentation should have examples for strategist actors")
def step_then_doc_has_strategist_examples(context: Context) -> None:
"""Verify documentation has strategist examples."""
assert "strategist" in context.doc_content.lower(), (
"Documentation should have strategist actor examples"
)
@then("the documentation should have examples for executor actors")
def step_then_doc_has_executor_examples(context: Context) -> None:
"""Verify documentation has executor examples."""
assert "executor" in context.doc_content.lower(), (
"Documentation should have executor actor examples"
)
@then("the documentation should have examples for reviewer actors")
def step_then_doc_has_reviewer_examples(context: Context) -> None:
"""Verify documentation has reviewer examples."""
assert "reviewer" in context.doc_content.lower(), (
"Documentation should have reviewer actor examples"
)
@then("the documentation should have examples for tool-only actors")
def step_then_doc_has_tool_only_examples(context: Context) -> None:
"""Verify documentation has tool-only examples."""
assert (
"tool-only" in context.doc_content.lower()
or "tool only" in context.doc_content.lower()
), "Documentation should have tool-only actor examples"
@then("the documentation should have examples for validation actors")
def step_then_doc_has_validation_examples(context: Context) -> None:
"""Verify documentation has validation examples."""
assert "validation" in context.doc_content.lower(), (
"Documentation should have validation actor examples"
)
@then("the documentation should have examples for hierarchical graphs")
def step_then_doc_has_hierarchical_examples(context: Context) -> None:
"""Verify documentation has hierarchical graph examples."""
assert "hierarchical" in context.doc_content.lower(), (
"Documentation should have hierarchical graph examples"
)