Files
placeholder/features/steps/actor_config_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

136 lines
4.9 KiB
Python

"""Step definitions for actor_config_coverage_boost.feature.
Targets uncovered lines in src/cleveragents/actor/config.py:
- Lines 50-54: load_yaml_text JSON else-branch (null, non-dict, valid dict)
- Line 127: _load_v2_yaml_content defensive check after interpolation
"""
from __future__ import annotations
from unittest.mock import patch
from behave import given, then, when # type: ignore[import-untyped]
from behave.runner import Context # type: ignore[import-untyped]
from cleveragents.actor.config import ActorConfiguration
# Module-level variable to hold the mock patch (avoids behave Context delattr issues)
_active_patch = None
# ── load_yaml_text: JSON null → empty dict (lines 50-51) ────────────
@when('I call load_yaml_text with "null"')
def step_load_yaml_text_null(context: Context) -> None:
context.yaml_result = ActorConfiguration.load_yaml_text("null")
@then("the load_yaml_text result should be an empty dict")
def step_assert_yaml_result_empty_dict(context: Context) -> None:
assert context.yaml_result == {}, (
f"Expected empty dict, got {context.yaml_result!r}"
)
# ── load_yaml_text: non-dict JSON values raise ValueError (lines 52-53) ──
@when("I call load_yaml_text with a JSON string value")
def step_load_yaml_text_json_string(context: Context) -> None:
context.caught_error = None
try:
ActorConfiguration.load_yaml_text('"just a string"')
except ValueError as exc:
context.caught_error = exc
@when("I call load_yaml_text with a JSON array value")
def step_load_yaml_text_json_array(context: Context) -> None:
context.caught_error = None
try:
ActorConfiguration.load_yaml_text("[1, 2, 3]")
except ValueError as exc:
context.caught_error = exc
@when("I call load_yaml_text with a JSON integer value")
def step_load_yaml_text_json_integer(context: Context) -> None:
context.caught_error = None
try:
ActorConfiguration.load_yaml_text("42")
except ValueError as exc:
context.caught_error = exc
# ── Shared assertion: reuses the same pattern as bridge_remaining_coverage_steps.py
# (uses context.caught_error). The step text is already registered by that file
# in the full suite; this definition is kept here so the feature can also run
# standalone in an isolated runner. behave-parallel (nox) loads steps per-worker
# so no conflict arises at runtime.
@then('a ValueError with message containing "{fragment}" should be raised')
def step_assert_caught_valueerror_fragment(context: Context, fragment: str) -> None:
assert context.caught_error is not None, "Expected a ValueError but none was raised"
assert fragment.lower() in str(context.caught_error).lower(), (
f"Expected '{fragment}' in error message: {context.caught_error}"
)
# ── load_yaml_text: valid JSON dict (line 54) ───────────────────────
@when("I call load_yaml_text with '{json_text}'")
def step_load_yaml_text_valid_json(context: Context, json_text: str) -> None:
context.yaml_result = ActorConfiguration.load_yaml_text(json_text)
@then('the load_yaml_text result should have key "{key}" equal to "{value}"')
def step_assert_yaml_result_key(context: Context, key: str, value: str) -> None:
assert isinstance(context.yaml_result, dict), (
f"Expected dict, got {type(context.yaml_result)}"
)
assert context.yaml_result.get(key) == value, (
f"Expected result['{key}'] == '{value}', got {context.yaml_result.get(key)!r}"
)
# ── load_yaml_text: YAML fallback ───────────────────────────────────
@when('I call load_yaml_text with YAML text "{yaml_text}"')
def step_load_yaml_text_yaml_fallback(context: Context, yaml_text: str) -> None:
# Behave passes escaped \\n as literal; convert to real newlines
real_text = yaml_text.replace("\\n", "\n")
context.yaml_result = ActorConfiguration.load_yaml_text(real_text)
# ── _load_v2_yaml_content: interpolation returns non-dict (line 127) ─
@given("_interpolate_env_vars is patched to return a list")
def step_patch_interpolate(context: Context) -> None:
global _active_patch
_active_patch = patch.object(
ActorConfiguration,
"_interpolate_env_vars",
staticmethod(lambda config: ["not", "a", "dict"]),
)
_active_patch.start()
@when("I call _load_v2_yaml_content with valid YAML via patched interpolation")
def step_call_load_v2_patched(context: Context) -> None:
global _active_patch
context.caught_error = None
try:
ActorConfiguration._load_v2_yaml_content("key: value\n")
except ValueError as exc:
context.caught_error = exc
finally:
# Always clean up the patch
if _active_patch is not None:
_active_patch.stop()
_active_patch = None