Files
cleveragents-core/features/steps/_inheritance_test_helpers.py
T
hamza.khyari 23d8a53f6a fix(resource): address review findings for resource type inheritance
Fix 25 of 27 review findings from PR #618 code review:

P1 (Must Fix):
- F3: Fix silent data corruption in _merge_collection for properties
  dict fields (was falling through to string list merge)
- F1: Split 1023-line step file into 3 files + helper module (all <500)
- F4: Add SELECT FOR UPDATE lock on parent type in register_type to
  prevent TOCTOU race in concurrent registrations
- F6: Fix docs claiming exceptions inherit from CleverAgentsError
  (they inherit from ValueError)
- F7: Fix docs incorrectly describing validate_chain return type

P2 (Should Fix):
- F9: Replace dict[str, Any] with TypeRegistryMap type alias
- F10: Replace import logging with structlog in inheritance.py
- F11: Add __all__ to inheritance.py
- F13: Fix _load_type_registry to derive built_in from namespace column
- F14: Add warning log for unregistered types in resolve_inheritance_chain
- F15: Return defensive copies from resolve_fields
- F16: Add chain validation to bootstrap_builtin_types
- F17: Narrow except Exception to specific types in step files
- F18: Add side-effect verification scenarios after error cases
- F19: Always include inherits key in JSON output for consistent schema
- F20: Log actual exception instead of hardcoded string in CLI

P3 (Nit):
- F21: Reject whitespace-only inherits values in validate_chain
- F23: Wrap chain errors in HandlerResolutionError in resolver
- F24: Return defensive copies from as_cli_dict
- F25: Replace tautological assertion with ResourceHandler isinstance
- F26: Add whitespace inherits test scenario
- F27: Fix find_subtypes docstring to note it excludes ancestor_name

Deferred:
- F2: type: ignore in step files — pyright only checks src/, matches
  existing project pattern (91 occurrences in resource_dag_steps.py)
- F5: CLI integration tests require full DI container setup
- F8: resource_registry_service.py size is pre-existing (971 on master)
- F12: Coverage boost file changes are test adaptations, not scope creep
- F22: FK constraint intentionally omitted per docs (SQLite compat)
2026-03-10 03:39:32 +00:00

93 lines
3.1 KiB
Python

"""Shared helpers for resource-type-inheritance BDD step files.
These helpers are used by the split step modules:
- resource_type_inheritance_chain_steps.py
- resource_type_inheritance_merge_steps.py
- resource_type_inheritance_extra_steps.py
"""
from __future__ import annotations
from typing import Any
from cleveragents.tool.registry import ToolRegistry
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
_NOOP_HANDLER = lambda **kw: None # noqa: E731
def _make_root_entry(
*,
description: str = "Root type",
handler: str | None = None,
cli_args: list[dict[str, Any]] | None = None,
child_types: list[str] | None = None,
parent_types: list[str] | None = None,
) -> dict[str, Any]:
"""Create a minimal root type registry entry (no inherits)."""
entry: dict[str, Any] = {"description": description}
if handler is not None:
entry["handler"] = handler
if cli_args is not None:
entry["cli_args"] = cli_args
if child_types is not None:
entry["child_types"] = child_types
if parent_types is not None:
entry["parent_types"] = parent_types
return entry
def _make_child_entry(
parent: str,
*,
description: str | None = None,
handler: str | None = None,
cli_args: list[dict[str, Any]] | None = None,
cli_args_replace: bool = False,
child_types: list[str] | None = None,
child_types_replace: bool = False,
parent_types: list[str] | None = None,
parent_types_replace: bool = False,
) -> dict[str, Any]:
"""Create a child type registry entry pointing at *parent*."""
entry: dict[str, Any] = {"inherits": parent}
if description is not None:
entry["description"] = description
if handler is not None:
entry["handler"] = handler
if cli_args is not None:
entry["cli_args"] = cli_args
if cli_args_replace:
entry["cli_args_replace"] = True
if child_types is not None:
entry["child_types"] = child_types
if child_types_replace:
entry["child_types_replace"] = True
if parent_types is not None:
entry["parent_types"] = parent_types
if parent_types_replace:
entry["parent_types_replace"] = True
return entry
def _names_from_csv(csv: str) -> list[str]:
"""Split a comma-separated string into stripped tokens."""
return [t.strip() for t in csv.split(",") if t.strip()]
def _ensure_registry(context: Any) -> dict[str, Any]:
"""Return (and lazily create) the type registry on *context*."""
if not hasattr(context, "type_inherit_registry"):
context.type_inherit_registry = {} # type: ignore[attr-defined]
return context.type_inherit_registry # type: ignore[attr-defined]
def _ensure_tool_registry(context: Any) -> ToolRegistry:
"""Return (and lazily create) the ToolRegistry on *context*."""
if not hasattr(context, "type_inherit_tool_registry"):
context.type_inherit_tool_registry = ToolRegistry() # type: ignore[attr-defined]
return context.type_inherit_tool_registry # type: ignore[attr-defined]