fe69d132f1
ISSUES CLOSED: #178
328 lines
11 KiB
Python
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}"
|
|
)
|