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