forked from HAL9000/cleveragents-core
a808c395f9
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
169 lines
6.1 KiB
Python
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}"
|
|
)
|