"""Robot Framework helper for Tool/Validation domain model smoke tests. Provides a CLI-style interface for Robot to invoke model creation and YAML config loading. Exit code 0 = success, 1 = failure. Usage: python robot/helper_tool_model.py create-tool python robot/helper_tool_model.py create-validation python robot/helper_tool_model.py load-tool-yaml python robot/helper_tool_model.py load-validation-yaml python robot/helper_tool_model.py validate-constraints python robot/helper_tool_model.py validate-invalid-tool """ from __future__ import annotations import sys from pathlib import Path from typing import Any import yaml # Ensure the src directory is on the import path. _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from cleveragents.domain.models.core.tool import ( # noqa: E402 Tool, ToolCapability, ToolSource, ToolType, Validation, ValidationMode, ) def _cmd_create_tool() -> int: """Create a minimal Tool and verify fields.""" tool = Tool( name="smoke/robot-tool", description="Robot smoke test tool", source=ToolSource.BUILTIN, ) assert tool.name == "smoke/robot-tool" assert tool.namespace == "smoke" assert tool.short_name == "robot-tool" assert tool.source == ToolSource.BUILTIN assert tool.tool_type == ToolType.TOOL assert tool.timeout == 300 print(f"tool-model-ok: {tool.name}") return 0 def _cmd_create_validation() -> int: """Create a Validation and verify forced constraints.""" val = Validation( name="smoke/robot-val", description="Robot smoke validation", source=ToolSource.BUILTIN, ) assert val.tool_type == ToolType.VALIDATION assert val.capability.read_only is True assert val.capability.writes is False assert val.capability.checkpointable is False assert val.mode == ValidationMode.REQUIRED print(f"validation-model-ok: {val.name}") return 0 def _cmd_load_tool_yaml(yaml_path: str) -> int: """Load a Tool from a YAML config file and verify.""" path = Path(yaml_path) if not path.exists(): print(f"tool-yaml-fail: file not found: {yaml_path}") return 1 with path.open() as f: config: dict[str, Any] = yaml.safe_load(f) tool = Tool.from_config(config) cli_dict = tool.as_cli_dict() assert "name" in cli_dict assert "source" in cli_dict assert "capability" in cli_dict print(f"tool-yaml-ok: {tool.name} source={tool.source.value}") return 0 def _cmd_load_validation_yaml(yaml_path: str) -> int: """Load a Validation from a YAML config file and verify.""" path = Path(yaml_path) if not path.exists(): print(f"validation-yaml-fail: file not found: {yaml_path}") return 1 with path.open() as f: config: dict[str, Any] = yaml.safe_load(f) val = Validation.from_config(config) assert val.tool_type == ToolType.VALIDATION assert val.capability.read_only is True assert val.capability.writes is False cli_dict = val.as_cli_dict() assert "mode" in cli_dict print(f"validation-yaml-ok: {val.name} mode={val.mode.value}") return 0 def _cmd_validate_constraints() -> int: """Verify that Validation forces read_only and blocks writes.""" val = Validation( name="smoke/constraint-check", description="Constraint test", source=ToolSource.BUILTIN, capability=ToolCapability(writes=True, checkpointable=True), ) # Validation must force these even when given writable capability assert val.capability.read_only is True, "Validation must force read_only=True" assert val.capability.writes is False, "Validation must force writes=False" assert val.capability.checkpointable is False, ( "Validation must force checkpointable=False" ) print("validate-constraints-ok") return 0 def _cmd_validate_invalid_tool() -> int: """Verify that invalid tool config is rejected.""" from pydantic import ValidationError # Test: invalid name pattern try: Tool( name="no-namespace", description="Bad name", source=ToolSource.BUILTIN, ) print("validate-invalid-unexpected-success: bad name accepted") return 1 except ValidationError: pass # Test: custom source without code try: Tool( name="local/test", description="Missing code", source=ToolSource.CUSTOM, ) print("validate-invalid-unexpected-success: missing code accepted") return 1 except ValidationError: pass # Test: read_only with writes try: ToolCapability(read_only=True, writes=True) print("validate-invalid-unexpected-success: read_only+writes accepted") return 1 except ValidationError: pass print("validate-invalid-ok") return 0 def main() -> int: """Entry point called by Robot Framework ``Run Process``.""" if len(sys.argv) < 2: print( "Usage: helper_tool_model.py " " " "[args...]" ) return 1 command = sys.argv[1] if command == "create-tool": return _cmd_create_tool() if command == "create-validation": return _cmd_create_validation() if command == "load-tool-yaml": if len(sys.argv) < 3: print("Usage: helper_tool_model.py load-tool-yaml ") return 1 return _cmd_load_tool_yaml(sys.argv[2]) if command == "load-validation-yaml": if len(sys.argv) < 3: print("Usage: helper_tool_model.py load-validation-yaml ") return 1 return _cmd_load_validation_yaml(sys.argv[2]) if command == "validate-constraints": return _cmd_validate_constraints() if command == "validate-invalid-tool": return _cmd_validate_invalid_tool() print(f"Unknown command: {command}") return 1 if __name__ == "__main__": sys.exit(main())