"""Step definitions for definition-of-done gating tests.""" from __future__ import annotations from behave import given, then, when from behave.runner import Context from cleveragents.domain.models.core.definition_of_done import ( DoDCriterion, DoDResult, DoDStatus, DoDSummary, TextMatchEvaluator, parse_dod_criteria, render_dod_template, ) __all__: list[str] = [] # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("a definition of done test environment") def step_given_dod_env(context: Context) -> None: context.dod_criteria = None # list[DoDCriterion] | None context.dod_rendered = None # str | None context.dod_results = None # list[DoDResult] | None context.dod_context = {} # dict[str, Any] context.dod_summary = None # DoDSummary | None # --------------------------------------------------------------------------- # Given steps # --------------------------------------------------------------------------- @given('DoD criteria from "{text}"') def step_given_criteria_from(context: Context, text: str) -> None: resolved = text.replace("\\n", "\n") context.dod_criteria = parse_dod_criteria(resolved) @given('evaluation context with key "{key}" value "{value}"') def step_given_eval_context(context: Context, key: str, value: str) -> None: context.dod_context[key] = value @given("an empty evaluation context") def step_given_empty_context(context: Context) -> None: context.dod_context = {} @given('a DoD criterion with text "{text}" and pattern "{pattern}"') def step_given_criterion_with_pattern( context: Context, text: str, pattern: str ) -> None: context.dod_criteria = [DoDCriterion(index=0, text=text, pattern=pattern)] @given("a DoDSummary with {p} passed and {f} failed") def step_given_summary(context: Context, p: str, f: str) -> None: results: list[DoDResult] = [] for i in range(int(p)): results.append( DoDResult( criterion=DoDCriterion(index=i, text=f"crit-{i}"), status=DoDStatus.PASSED, reasoning="ok", ) ) for i in range(int(f)): results.append( DoDResult( criterion=DoDCriterion(index=int(p) + i, text=f"fail-{i}"), status=DoDStatus.FAILED, reasoning="not ok", ) ) context.dod_summary = DoDSummary( plan_id="01DODTEST0000000000001", definition_of_done="test dod", results=results, ) @given("an empty DoDSummary") def step_given_empty_summary(context: Context) -> None: context.dod_summary = DoDSummary( plan_id="01DODTEST0000000000001", definition_of_done="empty", results=[], ) @given("a DoDSummary with all criteria skipped") def step_given_all_skipped_summary(context: Context) -> None: results = [ DoDResult( criterion=DoDCriterion(index=i, text=f"skip-{i}"), status=DoDStatus.SKIPPED, reasoning="skipped", ) for i in range(2) ] context.dod_summary = DoDSummary( plan_id="01DODTEST0000000000001", definition_of_done="skip test", results=results, ) @given("a DoD template that is {n} characters long") def step_given_long_dod_template(context: Context, n: str) -> None: context.dod_oversized_template = "x" * int(n) @given('a DoD criterion with text "{text}" and pattern that is {n} chars') def step_given_long_pattern(context: Context, text: str, n: str) -> None: context.dod_criteria = [ DoDCriterion(index=0, text=text, pattern="a" * int(n)), ] @given("a short DoD template with a huge argument value") def step_given_huge_value_template(context: Context) -> None: context.dod_short_template = "Result: {val}" context.dod_huge_value = "x" * 60_000 # --------------------------------------------------------------------------- # When steps # --------------------------------------------------------------------------- @when('I parse the DoD text "{text}"') def step_when_parse(context: Context, text: str) -> None: resolved = text.replace("\\n", "\n") context.dod_criteria = parse_dod_criteria(resolved) @when("I parse an empty DoD text") def step_when_parse_empty(context: Context) -> None: context.dod_criteria = parse_dod_criteria("") @when('I render the DoD template "{template}" with argument "{key}" as "{value}"') def step_when_render(context: Context, template: str, key: str, value: str) -> None: context.dod_rendered = render_dod_template(template, {key: value}) @when( 'I render the DoD template "{template}" with arguments "{k1}"="{v1}" and "{k2}"="{v2}"' ) def step_when_render_multi( context: Context, template: str, k1: str, v1: str, k2: str, v2: str ) -> None: context.dod_rendered = render_dod_template(template, {k1: v1, k2: v2}) @when('I render the oversized DoD template with argument "{key}" as "{value}"') def step_when_render_oversized(context: Context, key: str, value: str) -> None: try: context.dod_rendered = render_dod_template( context.dod_oversized_template, {key: value} ) context.dod_render_error = None except ValueError as exc: context.dod_render_error = exc @when("I render the DoD template that produces oversized output") def step_when_render_oversized_output(context: Context) -> None: try: context.dod_rendered = render_dod_template( context.dod_short_template, {"val": context.dod_huge_value} ) context.dod_render_error = None except ValueError as exc: context.dod_render_error = exc @when("I evaluate the criteria with TextMatchEvaluator") def step_when_evaluate(context: Context) -> None: evaluator = TextMatchEvaluator() context.dod_results = evaluator.evaluate(context.dod_criteria, context.dod_context) # --------------------------------------------------------------------------- # Then steps # --------------------------------------------------------------------------- @then("there should be {n} criteria parsed") def step_then_criteria_count(context: Context, n: str) -> None: assert context.dod_criteria is not None assert len(context.dod_criteria) == int(n), ( f"Expected {n} criteria, got {len(context.dod_criteria)}" ) @then('criterion {i} text should be "{text}"') def step_then_criterion_text(context: Context, i: str, text: str) -> None: assert context.dod_criteria is not None idx = int(i) assert idx < len(context.dod_criteria) assert context.dod_criteria[idx].text == text, ( f"Expected text '{text}', got '{context.dod_criteria[idx].text}'" ) @then('the rendered DoD should be "{expected}"') def step_then_rendered(context: Context, expected: str) -> None: assert context.dod_rendered == expected, ( f"Expected '{expected}', got '{context.dod_rendered}'" ) @then('the rendered DoD should contain "{text}"') def step_then_rendered_contains(context: Context, text: str) -> None: assert context.dod_rendered is not None assert text in context.dod_rendered @then("all criteria should have passed") def step_then_all_passed(context: Context) -> None: assert context.dod_results is not None for r in context.dod_results: assert r.status == DoDStatus.PASSED, ( f"Criterion '{r.criterion.text}' has status '{r.status.value}'" ) @then('criterion {i} should have status "{status}"') def step_then_criterion_status(context: Context, i: str, status: str) -> None: assert context.dod_results is not None idx = int(i) assert idx < len(context.dod_results) assert context.dod_results[idx].status == DoDStatus(status), ( f"Expected status '{status}', got '{context.dod_results[idx].status.value}'" ) @then('criterion {i} reasoning should contain "{text}"') def step_then_criterion_reasoning(context: Context, i: str, text: str) -> None: assert context.dod_results is not None idx = int(i) assert text in context.dod_results[idx].reasoning, ( f"Expected reasoning to contain '{text}', " f"got: {context.dod_results[idx].reasoning}" ) @then("the summary should report all passed") def step_then_summary_all_passed(context: Context) -> None: assert context.dod_summary is not None assert context.dod_summary.all_passed @then("the summary should not report all passed") def step_then_summary_not_all_passed(context: Context) -> None: assert context.dod_summary is not None assert not context.dod_summary.all_passed @then("the DoD summary total should be {n}") def step_then_dod_summary_total(context: Context, n: str) -> None: assert context.dod_summary is not None assert context.dod_summary.total == int(n) @then("the summary failed count should be {n}") def step_then_summary_failed(context: Context, n: str) -> None: assert context.dod_summary is not None assert context.dod_summary.failed == int(n) @then("the summary should be empty") def step_then_summary_empty(context: Context) -> None: assert context.dod_summary is not None assert context.dod_summary.is_empty @then('the summary CLI dict should have key "{key}"') def step_then_cli_dict_key(context: Context, key: str) -> None: assert context.dod_summary is not None cli_dict = context.dod_summary.to_cli_dict() assert key in cli_dict @then("the validation summary should have required_passed {n}") def step_then_val_summary_passed(context: Context, n: str) -> None: assert context.dod_summary is not None vs = context.dod_summary.to_validation_summary() assert vs["required_passed"] == int(n) @then("the validation summary should have required_failed {n}") def step_then_val_summary_failed(context: Context, n: str) -> None: assert context.dod_summary is not None vs = context.dod_summary.to_validation_summary() assert vs["required_failed"] == int(n) @then("the validation summary should have dod_evaluated true") def step_then_val_summary_dod(context: Context) -> None: assert context.dod_summary is not None vs = context.dod_summary.to_validation_summary() assert vs["dod_evaluated"] is True @then("the summary skipped count should be {n}") def step_then_summary_skipped(context: Context, n: str) -> None: assert context.dod_summary is not None assert context.dod_summary.skipped == int(n), ( f"Expected skipped={n}, got {context.dod_summary.skipped}" ) @then('the DoD render should fail with "{text}"') def step_then_dod_render_error(context: Context, text: str) -> None: assert context.dod_render_error is not None, "Expected a ValueError" assert text in str(context.dod_render_error), ( f"Expected '{text}' in error: {context.dod_render_error}" )