"""Step definitions for resolver_handler_coverage_boost.feature. Targets uncovered lines in src/cleveragents/resource/handlers/resolver.py: - Lines 72-73: empty module_path or class_name after rsplit on ':' - Lines 84-87: generic Exception (not ModuleNotFoundError) during import - Lines 99-102: Exception during handler class __init__ - Lines 106-107: instance does not satisfy ResourceHandler protocol """ from __future__ import annotations import types from typing import Any from unittest.mock import patch from behave import given, then, when # type: ignore[import-untyped] from cleveragents.resource.handlers.resolver import ( HandlerResolutionError, clear_handler_cache, resolve_handler, ) # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("the handler cache is cleared") def step_clear_cache(context: Any) -> None: clear_handler_cache() context.resolver_error: HandlerResolutionError | None = None context.rigged_ref: str | None = None # --------------------------------------------------------------------------- # Given steps - prepare mock modules for injection via importlib patch # --------------------------------------------------------------------------- @given("a module import that raises a generic RuntimeError") def step_import_raises_runtime_error(context: Any) -> None: """Prepare a handler reference whose import will raise RuntimeError (not ModuleNotFoundError), exercising lines 84-87.""" context.rigged_ref = "fake_boom_module:FakeHandler" original_import = __import__("importlib").import_module def _patched_import(name: str, *args: Any, **kwargs: Any) -> types.ModuleType: if name == "fake_boom_module": raise RuntimeError("simulated import explosion") return original_import(name, *args, **kwargs) context._import_patcher = patch( "cleveragents.resource.handlers.resolver.importlib.import_module", side_effect=_patched_import, ) context._import_patcher.start() @given("a module whose handler class raises TypeError on init") def step_class_init_raises(context: Any) -> None: """Prepare a handler reference whose class __init__ raises, exercising lines 99-102.""" context.rigged_ref = "fake_init_module:BrokenHandler" # Build a synthetic module with a class whose __init__ always fails fake_module = types.ModuleType("fake_init_module") class BrokenHandler: def __init__(self) -> None: raise TypeError("cannot construct this handler") fake_module.BrokenHandler = BrokenHandler # type: ignore[attr-defined] original_import = __import__("importlib").import_module def _patched_import(name: str, *args: Any, **kwargs: Any) -> types.ModuleType: if name == "fake_init_module": return fake_module return original_import(name, *args, **kwargs) context._import_patcher = patch( "cleveragents.resource.handlers.resolver.importlib.import_module", side_effect=_patched_import, ) context._import_patcher.start() @given("a module whose handler class does not satisfy ResourceHandler") def step_class_not_protocol(context: Any) -> None: """Prepare a handler reference whose class does NOT implement the ResourceHandler protocol, exercising lines 106-107.""" context.rigged_ref = "fake_proto_module:NotAHandler" fake_module = types.ModuleType("fake_proto_module") class NotAHandler: """Has no ``resolve`` method - fails the runtime_checkable check.""" pass fake_module.NotAHandler = NotAHandler # type: ignore[attr-defined] original_import = __import__("importlib").import_module def _patched_import(name: str, *args: Any, **kwargs: Any) -> types.ModuleType: if name == "fake_proto_module": return fake_module return original_import(name, *args, **kwargs) context._import_patcher = patch( "cleveragents.resource.handlers.resolver.importlib.import_module", side_effect=_patched_import, ) context._import_patcher.start() # --------------------------------------------------------------------------- # When steps # --------------------------------------------------------------------------- @when('I try to resolve handler ref "{ref}"') def step_try_resolve_ref(context: Any, ref: str) -> None: """Attempt to resolve a literal handler reference string, expecting failure.""" try: resolve_handler(ref) context.resolver_error = None except HandlerResolutionError as exc: context.resolver_error = exc @when("I try to resolve the rigged handler reference") def step_try_resolve_rigged(context: Any) -> None: """Resolve the handler reference prepared by a Given step that patches importlib.import_module.""" assert context.rigged_ref is not None, "No rigged reference was configured" try: resolve_handler(context.rigged_ref) context.resolver_error = None except HandlerResolutionError as exc: context.resolver_error = exc finally: # Always stop the patcher after use if hasattr(context, "_import_patcher") and context._import_patcher is not None: context._import_patcher.stop() context._import_patcher = None # --------------------------------------------------------------------------- # Then steps # --------------------------------------------------------------------------- @then('a handler resolution error should be raised with message "{fragment}"') def step_assert_resolution_error_contains(context: Any, fragment: str) -> None: assert context.resolver_error is not None, ( "Expected HandlerResolutionError but no error was raised" ) assert isinstance(context.resolver_error, HandlerResolutionError), ( f"Expected HandlerResolutionError, got {type(context.resolver_error).__name__}" ) error_text = str(context.resolver_error) assert fragment.lower() in error_text.lower(), ( f"Expected '{fragment}' in error message, got: {error_text}" )