"""Step definitions for ToolLifecycle execute hook tests (issue #2820).""" from typing import Any from behave import then, when from behave.runner import Context from cleveragents.domain.models.core.tool import ( Tool, ToolLifecycle, ToolSource, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _make_builtin_tool(**overrides: Any) -> Tool: """Create a minimal builtin Tool with optional overrides.""" defaults: dict[str, Any] = { "name": "local/test-tool", "description": "A test tool", "source": ToolSource.BUILTIN, } defaults.update(overrides) return Tool(**defaults) # --------------------------------------------------------------------------- # When steps — ToolLifecycle construction # --------------------------------------------------------------------------- @when('I create a tool lifecycle with execute hook "{execute}"') def step_create_lifecycle_with_execute(context: Context, execute: str) -> None: """Create a ToolLifecycle with only the execute hook set.""" context.tool_lifecycle = ToolLifecycle(execute=execute) context.tool_model_error = None @when("I create a tool lifecycle with all four hooks set") def step_create_lifecycle_all_four(context: Context) -> None: """Create a ToolLifecycle with all four lifecycle hooks set.""" context.tool_lifecycle = ToolLifecycle( discover="hooks.discover", activate="hooks.activate", execute="hooks.execute", deactivate="hooks.deactivate", ) context.tool_model_error = None @when("I create a tool lifecycle with no hooks") def step_create_lifecycle_no_hooks(context: Context) -> None: """Create a ToolLifecycle with no hooks (all default to None).""" context.tool_lifecycle = ToolLifecycle() context.tool_model_error = None @when('I create a tool lifecycle with only execute hook "{execute}"') def step_create_lifecycle_only_execute(context: Context, execute: str) -> None: """Create a ToolLifecycle with only the execute hook set (all others None).""" context.tool_lifecycle = ToolLifecycle(execute=execute) context.tool_model_error = None # --------------------------------------------------------------------------- # When steps — Tool.from_config() with lifecycle # --------------------------------------------------------------------------- @when('I load a tool from config with lifecycle execute "{execute}"') def step_load_tool_from_config_with_lifecycle_execute( context: Context, execute: str ) -> None: """Load a Tool from config dict that includes a lifecycle.execute hook.""" config: dict[str, Any] = { "name": "local/lifecycle-tool", "description": "Tool with lifecycle execute hook", "source": "builtin", "lifecycle": { "execute": execute, }, } context.tool_model = Tool.from_config(config) context.tool_model_error = None @when("I load a tool from config with lifecycle execute None") def step_load_tool_from_config_with_lifecycle_execute_none(context: Context) -> None: """Load a Tool from config dict that includes a lifecycle with execute=None.""" config: dict[str, Any] = { "name": "local/lifecycle-tool-none", "description": "Tool with lifecycle execute=None", "source": "builtin", "lifecycle": { "discover": "hooks.discover", }, } context.tool_model = Tool.from_config(config) context.tool_model_error = None # --------------------------------------------------------------------------- # When steps — Tool.as_cli_dict() with lifecycle # --------------------------------------------------------------------------- @when('I create a tool with lifecycle execute "{execute}" and call as_cli_dict') def step_create_tool_with_lifecycle_execute_and_cli_dict( context: Context, execute: str ) -> None: """Create a Tool with a lifecycle.execute hook and call as_cli_dict.""" tool = _make_builtin_tool( lifecycle=ToolLifecycle(execute=execute), ) context.tool_cli_dict = tool.as_cli_dict() context.tool_model_error = None @when("I create a tool with no lifecycle and call as_cli_dict") def step_create_tool_no_lifecycle_and_cli_dict(context: Context) -> None: """Create a Tool with no lifecycle and call as_cli_dict.""" tool = _make_builtin_tool() context.tool_cli_dict = tool.as_cli_dict() context.tool_model_error = None # --------------------------------------------------------------------------- # Then steps — ToolLifecycle field assertions # --------------------------------------------------------------------------- @then('the tool lifecycle execute should be "{expected}"') def step_check_lifecycle_execute(context: Context, expected: str) -> None: """Assert the lifecycle execute hook equals the expected value.""" # Support both context.tool_lifecycle and context.tool_model.lifecycle if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.execute else: assert context.tool_model.lifecycle is not None, ( "Expected tool_model.lifecycle to be set, but it is None" ) actual = context.tool_model.lifecycle.execute assert actual == expected, ( f"Expected lifecycle.execute == {expected!r}, got {actual!r}" ) @then("the tool lifecycle execute should be None") def step_check_lifecycle_execute_none(context: Context) -> None: """Assert the lifecycle execute hook is None.""" if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.execute elif context.tool_model.lifecycle is not None: actual = context.tool_model.lifecycle.execute else: actual = None assert actual is None, f"Expected lifecycle.execute to be None, got {actual!r}" @then("the tool lifecycle discover should be None") def step_check_lifecycle_discover_none(context: Context) -> None: """Assert the lifecycle discover hook is None.""" if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.discover elif context.tool_model.lifecycle is not None: actual = context.tool_model.lifecycle.discover else: actual = None assert actual is None, f"Expected lifecycle.discover to be None, got {actual!r}" @then('the tool lifecycle activate should be "{expected}"') def step_check_lifecycle_activate(context: Context, expected: str) -> None: """Assert the lifecycle activate hook equals the expected value.""" if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.activate else: assert context.tool_model.lifecycle is not None actual = context.tool_model.lifecycle.activate assert actual == expected, ( f"Expected lifecycle.activate == {expected!r}, got {actual!r}" ) @then("the tool lifecycle activate should be None") def step_check_lifecycle_activate_none(context: Context) -> None: """Assert the lifecycle activate hook is None.""" if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.activate elif context.tool_model.lifecycle is not None: actual = context.tool_model.lifecycle.activate else: actual = None assert actual is None, f"Expected lifecycle.activate to be None, got {actual!r}" @then('the tool lifecycle deactivate should be "{expected}"') def step_check_lifecycle_deactivate(context: Context, expected: str) -> None: """Assert the lifecycle deactivate hook equals the expected value.""" if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.deactivate else: assert context.tool_model.lifecycle is not None actual = context.tool_model.lifecycle.deactivate assert actual == expected, ( f"Expected lifecycle.deactivate == {expected!r}, got {actual!r}" ) @then("the tool lifecycle deactivate should be None") def step_check_lifecycle_deactivate_none(context: Context) -> None: """Assert the lifecycle deactivate hook is None.""" if hasattr(context, "tool_lifecycle"): actual = context.tool_lifecycle.deactivate elif context.tool_model.lifecycle is not None: actual = context.tool_model.lifecycle.deactivate else: actual = None assert actual is None, f"Expected lifecycle.deactivate to be None, got {actual!r}" # --------------------------------------------------------------------------- # Then steps — as_cli_dict lifecycle assertions # --------------------------------------------------------------------------- @then('the tool cli dict lifecycle should have key "{key}"') def step_check_cli_dict_lifecycle_has_key(context: Context, key: str) -> None: """Assert the lifecycle dict in as_cli_dict contains the given key.""" assert "lifecycle" in context.tool_cli_dict, ( "Expected 'lifecycle' key in cli dict, but it was absent" ) lc = context.tool_cli_dict["lifecycle"] assert key in lc, ( f"Expected lifecycle dict to have key {key!r}, got keys: {list(lc.keys())}" ) @then('the tool cli dict lifecycle execute should be "{expected}"') def step_check_cli_dict_lifecycle_execute(context: Context, expected: str) -> None: """Assert the lifecycle.execute value in as_cli_dict equals expected.""" assert "lifecycle" in context.tool_cli_dict, ( "Expected 'lifecycle' key in cli dict, but it was absent" ) actual = context.tool_cli_dict["lifecycle"].get("execute") assert actual == expected, ( f"Expected lifecycle.execute == {expected!r}, got {actual!r}" ) @then('the tool cli dict should not have key "{key}"') def step_check_cli_dict_missing_key(context: Context, key: str) -> None: """Assert the cli dict does NOT contain the given key.""" assert key not in context.tool_cli_dict, ( f"Expected cli dict NOT to have key {key!r}, but it was present" )