"""Behave steps targeting uncovered lines in yaml_template_engine.py. Uncovered lines targeted: 101-105 - fallback parse path after _fix_common_yaml_issues (dict and non-dict) 134-136 - _postprocess_rendered_yaml value_words branch 138-142 - dead-code branch (documented; closest reachable path exercised) 172 - _fix_common_yaml_issues single/zero-colon line passthrough 236-237 - _sum_filter ValueError for non-summable input """ from __future__ import annotations from typing import Any from unittest import mock import yaml from behave import given, then, when # Keep a reference to the module for mock.patch.object import cleveragents.actor.yaml_template_engine as _mod from cleveragents.actor.yaml_template_engine import YAMLTemplateEngine # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("a fresh YAML template engine instance") def step_fresh_engine(context: Any) -> None: context.engine = YAMLTemplateEngine() context.result = None context.error = None # --------------------------------------------------------------------------- # Lines 101-105 - fallback returns dict # --------------------------------------------------------------------------- @given("a YAML template with Jinja2 that renders valid content") def step_template_for_fallback(context: Any) -> None: # Must contain {{ so load_string takes the template path context.yaml_template = "result: {{ val }}" @given("a context that produces parseable YAML on fix retry") def step_context_for_fallback(context: Any) -> None: context.template_ctx = {"val": "hello", "context": {}} @when("I render and parse with a forced first-parse YAMLError returning a dict") def step_render_fallback_dict(context: Any) -> None: """Mock yaml.safe_load so the first call raises YAMLError and the second returns a dict - exercising lines 100-101-105.""" side_effects = [ yaml.YAMLError("simulated first-parse failure"), {"result": "recovered-value"}, ] with mock.patch.object(_mod.yaml, "safe_load", side_effect=side_effects): context.result = context.engine._render_and_parse( context.yaml_template, context.template_ctx ) @then("the fallback parse should return a dict result") def step_assert_fallback_dict(context: Any) -> None: assert isinstance(context.result, dict), ( f"Expected dict, got {type(context.result)}" ) assert context.result == {"result": "recovered-value"} @when("I render and parse with a forced first-parse YAMLError returning a list") def step_render_fallback_list(context: Any) -> None: """Mock yaml.safe_load so the first call raises YAMLError and the second returns a list - exercising lines 100-101-102-103-104.""" side_effects = [ yaml.YAMLError("simulated first-parse failure"), ["not", "a", "dict"], ] try: with mock.patch.object(_mod.yaml, "safe_load", side_effect=side_effects): context.result = context.engine._render_and_parse( context.yaml_template, context.template_ctx ) context.error = None except Exception as exc: context.result = None context.error = exc @then("a ValueError should be raised from the fallback path") def step_assert_fallback_valueerror(context: Any) -> None: assert isinstance(context.error, ValueError), ( f"Expected ValueError, got {type(context.error)}: {context.error}" ) assert "dict" in str(context.error) # The ValueError should chain from the original YAMLError assert context.error.__cause__ is not None # --------------------------------------------------------------------------- # Lines 134-136 - _postprocess_rendered_yaml value_words branch # --------------------------------------------------------------------------- @given("rendered YAML with a single-colon line whose value has multiple words") def step_rendered_single_colon_spaced_value(context: Any) -> None: # line.count(":") == 1, value_part has spaces, ":" not in value_part # This reaches line 134 (value_words = ...) and line 135-136 (any() → False) context.postprocess_input = "config: alpha beta gamma delta" @when("I postprocess the YAML content") def step_postprocess_content(context: Any) -> None: context.postprocess_output = context.engine._postprocess_rendered_yaml( context.postprocess_input ) @then("the value_words branch should be entered and evaluated") def step_assert_value_words_entered(context: Any) -> None: # Because value has no colons in subsequent words, the inner condition is # False and the line passes through unchanged. assert context.postprocess_output.strip() == "config: alpha beta gamma delta" # --------------------------------------------------------------------------- # Lines 138-142 - dead code (closest reachable path) # --------------------------------------------------------------------------- @given('rendered YAML content "{content}" with spaces in value') def step_rendered_content_with_spaces(context: Any, content: str) -> None: context.postprocess_input = content @then("the line should pass through unchanged because value words lack colons") def step_assert_unchanged_no_colon_words(context: Any) -> None: # The guard ":" not in value_part on line 133 prevents any word from having # a colon, so the inner any() on line 135-136 is always False. # The line passes to fixed_lines.append(line) on line 143. assert context.postprocess_input.strip() in context.postprocess_output # --------------------------------------------------------------------------- # Line 172 - _fix_common_yaml_issues passthrough for <=1 colon lines # --------------------------------------------------------------------------- @given('YAML content with a single-colon line "{content}"') def step_yaml_single_colon_line(context: Any, content: str) -> None: context.fix_input = content @when("I apply fix common YAML issues") def step_apply_fix_common(context: Any) -> None: context.fix_output = context.engine._fix_common_yaml_issues(context.fix_input) @then("the single-colon line should be preserved verbatim") def step_assert_single_colon_preserved(context: Any) -> None: assert context.fix_output == context.fix_input @given('YAML content with a zero-colon line "{content}"') def step_yaml_zero_colon_line(context: Any, content: str) -> None: context.fix_input = content @then("the zero-colon line should be preserved verbatim") def step_assert_zero_colon_preserved(context: Any) -> None: assert context.fix_output == context.fix_input # --------------------------------------------------------------------------- # Lines 236-237 - _sum_filter ValueError # --------------------------------------------------------------------------- @given("a sequence of non-summable items") def step_non_summable_sequence(context: Any) -> None: # Passing strings that can't be summed with + starting from 0 context.non_summable = "not-a-sequence" @when("I call the sum filter on the non-summable sequence") def step_call_sum_filter(context: Any) -> None: try: context.result = context.engine._sum_filter(context.non_summable) context.error = None except Exception as exc: context.result = None context.error = exc @then("a ValueError should be raised with a descriptive message") def step_assert_sum_valueerror(context: Any) -> None: assert isinstance(context.error, ValueError), ( f"Expected ValueError, got {type(context.error)}: {context.error}" ) assert "Cannot sum value" in str(context.error) # --------------------------------------------------------------------------- # Integration: end-to-end fallback # --------------------------------------------------------------------------- @given('a template "{template}" with context val set to "{val}"') def step_template_with_val(context: Any, template: str, val: str) -> None: context.yaml_template = template context.template_ctx = {"val": val, "context": {}} @when("rendering triggers a YAMLError on first parse but fix produces a dict") def step_render_e2e_fallback(context: Any) -> None: side_effects = [ yaml.YAMLError("transient parse failure"), {"result": "ok"}, ] with mock.patch.object(_mod.yaml, "safe_load", side_effect=side_effects): context.result = context.engine._render_and_parse( context.yaml_template, context.template_ctx ) @then("the final result should be the fixed dict") def step_assert_e2e_fixed(context: Any) -> None: assert context.result == {"result": "ok"}