Files
temp/features/steps/services_init_coverage_r3_steps.py
freemo 31472b5413 test(coverage): add Behave scenarios for 39 under-covered modules
Add Behave feature/step pairs that exercise uncovered branches across handlers, LSP, CLI, and service layers to reach the coverage gate.

ISSUES CLOSED: #1232
2026-03-31 21:47:12 +00:00

171 lines
6.3 KiB
Python

"""Step definitions for services __init__ lazy-import coverage (round 3).
Exercises every lazy-loaded symbol declared in
``cleveragents.application.services._LAZY_IMPORTS`` so that the
``__getattr__`` code path (lines 561-569) is executed at runtime.
Each scenario mocks ``importlib.import_module`` to avoid pulling in the
full dependency graph and simply verifies that the lazy-loading machinery
resolves the requested attribute name correctly.
"""
from __future__ import annotations
import importlib
import sys
import types
from unittest.mock import patch
from behave import given, then, when
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
_PKG = "cleveragents.application.services"
def _get_fresh_module():
"""Return the services __init__ module after purging cached lazy attrs.
We reload the module so that every scenario exercises ``__getattr__``
afresh rather than hitting the ``globals()`` cache set by a previous
lazy load.
"""
mod = sys.modules.get(_PKG)
if mod is None:
mod = importlib.import_module(_PKG)
else:
# Remove any previously-cached lazy symbols from module globals
# so __getattr__ is triggered again.
for attr_name in list(mod._LAZY_IMPORTS):
mod.__dict__.pop(attr_name, None)
return mod
def _make_fake_submodule(attr_name: str) -> types.ModuleType:
"""Build a tiny fake module that carries ``attr_name`` as a sentinel."""
fake = types.ModuleType(f"fake_{attr_name}")
setattr(fake, attr_name, type(attr_name, (), {}))
return fake
# ---------------------------------------------------------------------------
# Given
# ---------------------------------------------------------------------------
@given("svcov3 a fresh services module")
def step_svcov3_fresh_module(context):
"""Prepare a clean services module and set up the import_module mock."""
context.svcov3_mod = _get_fresh_module()
context.svcov3_error = None
context.svcov3_value = None
# Patch importlib.import_module inside the services __init__ so that we
# do not trigger heavy transitive imports. The patched version creates
# lightweight fake modules on the fly.
original_import_module = importlib.import_module
def _patched_import_module(name, package=None):
# Only intercept calls that originate from the lazy-loader.
if package == _PKG and name.startswith("."):
submodule_name = name.lstrip(".")
full_name = f"{_PKG}.{submodule_name}"
# If the real submodule is already in sys.modules use it -
# this avoids breakage when earlier tests legitimately loaded
# the submodule.
if full_name in sys.modules:
return sys.modules[full_name]
# Otherwise try a real import; fall back to a fake module.
try:
return original_import_module(name, package)
except Exception:
# Build a fake module that provides every attr declared
# in _LAZY_IMPORTS for this submodule.
fake = types.ModuleType(full_name)
lazy = context.svcov3_mod._LAZY_IMPORTS
for _sym, (sub, attr) in lazy.items():
if sub == submodule_name:
setattr(fake, attr, type(attr, (), {}))
sys.modules[full_name] = fake
return fake
return original_import_module(name, package)
patcher = patch("importlib.import_module", side_effect=_patched_import_module)
patcher.start()
context.add_cleanup(patcher.stop)
# ---------------------------------------------------------------------------
# When
# ---------------------------------------------------------------------------
@when('svcov3 I access lazy attribute "{attr_name}"')
def step_svcov3_access_lazy(context, attr_name):
"""Trigger ``__getattr__`` for *attr_name* and store the result."""
try:
# Remove from globals to force __getattr__ path
context.svcov3_mod.__dict__.pop(attr_name, None)
context.svcov3_value = getattr(context.svcov3_mod, attr_name)
except Exception as exc:
context.svcov3_error = exc
@when('svcov3 I access lazy attribute "{attr_name}" again')
def step_svcov3_access_lazy_again(context, attr_name):
"""Access the attribute a second time — should come from globals cache."""
try:
context.svcov3_value = getattr(context.svcov3_mod, attr_name)
except Exception as exc:
context.svcov3_error = exc
@when('svcov3 I access an unknown attribute "{attr_name}"')
def step_svcov3_access_unknown(context, attr_name):
"""Try to access an attribute not in _LAZY_IMPORTS."""
try:
getattr(context.svcov3_mod, attr_name)
except AttributeError as exc:
context.svcov3_error = exc
except Exception as exc:
context.svcov3_error = exc
# ---------------------------------------------------------------------------
# Then
# ---------------------------------------------------------------------------
@then('svcov3 the attribute "{attr_name}" should be resolved')
def step_svcov3_resolved(context, attr_name):
"""Assert the lazy attribute was resolved without error."""
assert context.svcov3_error is None, (
f"Expected no error for {attr_name!r}, got: {context.svcov3_error}"
)
assert context.svcov3_value is not None, (
f"Expected a non-None value for {attr_name!r}"
)
@then('svcov3 an AttributeError should be stored for "{attr_name}"')
def step_svcov3_attribute_error(context, attr_name):
"""Assert that an ``AttributeError`` was raised."""
assert isinstance(context.svcov3_error, AttributeError), (
f"Expected AttributeError, got {type(context.svcov3_error).__name__}: "
f"{context.svcov3_error}"
)
assert attr_name in str(context.svcov3_error)
@then("svcov3 __all__ should equal sorted _LAZY_IMPORTS keys")
def step_svcov3_all_matches(context):
"""Verify ``__all__`` is the sorted list of ``_LAZY_IMPORTS`` keys."""
mod = context.svcov3_mod
expected = sorted(mod._LAZY_IMPORTS.keys())
assert mod.__all__ == expected, (
f"__all__ mismatch.\n"
f" Missing from __all__: {set(expected) - set(mod.__all__)}\n"
f" Extra in __all__: {set(mod.__all__) - set(expected)}"
)