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
136 lines
4.9 KiB
Python
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
|