Files
temp/features/steps/resolver_handler_coverage_boost_steps.py
freemo a808c395f9 test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

169 lines
6.1 KiB
Python

"""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}"
)