Files
cleveragents-core/features/steps/definition_of_done_steps.py
2026-02-25 10:04:31 +00:00

328 lines
11 KiB
Python

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