forked from HAL9000/cleveragents-core
55aee7cf22
The step_register_skills_table step called add_skill in a loop but only committed once at the end. Because SkillRepository.create() obtains a new session per call and only flushes (never commits), the intermediate sessions could be garbage-collected before the final commit, rolling back their transactions on the shared SQLite :memory: connection. Moving _commit_pending inside the loop ensures each skill is durably committed before the next session is created. ISSUES CLOSED: #418
770 lines
25 KiB
Python
770 lines
25 KiB
Python
"""Step definitions for correction_service_coverage.feature.
|
|
|
|
Provides full line-and-branch coverage for
|
|
``cleveragents.application.services.correction_service.CorrectionService``.
|
|
|
|
All step-text uses a ``cov-`` prefix to avoid collisions with existing
|
|
correction_flows_steps.py and correction_service_new_coverage_steps.py.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from behave import given, then, when
|
|
|
|
from cleveragents.application.services.correction_service import CorrectionService
|
|
from cleveragents.core.exceptions import ResourceNotFoundError, ValidationError
|
|
from cleveragents.domain.models.core.correction import (
|
|
CorrectionMode,
|
|
CorrectionStatus,
|
|
)
|
|
|
|
# -------------------------------------------------------------------
|
|
# Helpers
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
def _parse_adj(spec: str) -> dict[str, list[str]]:
|
|
"""Parse ``"A->B,C;B->D"`` into an adjacency dict."""
|
|
tree: dict[str, list[str]] = {}
|
|
for seg in spec.split(";"):
|
|
seg = seg.strip()
|
|
if not seg:
|
|
continue
|
|
parent, children_str = seg.split("->")
|
|
tree[parent.strip()] = [c.strip() for c in children_str.split(",")]
|
|
return tree
|
|
|
|
|
|
def _build_chain_cov(root: str, count: int) -> dict[str, list[str]]:
|
|
"""Build a linear chain of *count* total nodes starting at *root*."""
|
|
tree: dict[str, list[str]] = {}
|
|
current = root
|
|
for i in range(1, count):
|
|
child = f"{root}_ch{i}"
|
|
tree[current] = [child]
|
|
current = child
|
|
return tree
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Background / Given
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@given("a coverage test correction service")
|
|
def step_bg_service(context):
|
|
context.cov_svc = CorrectionService()
|
|
context.cov_cid = None
|
|
context.cov_req = None
|
|
context.cov_impact = None
|
|
context.cov_revert_result = None
|
|
context.cov_append_result = None
|
|
context.cov_dispatch_result = None
|
|
context.cov_dry_run = None
|
|
context.cov_retrieved = None
|
|
context.cov_list = None
|
|
context.cov_attempts = None
|
|
context.cov_subtree = None
|
|
context.cov_error = None
|
|
|
|
|
|
@given('a cov-stored revert correction for plan "{plan}" targeting "{target}"')
|
|
def step_stored_revert_cov(context, plan, target):
|
|
req = context.cov_svc.request_correction(
|
|
plan_id=plan,
|
|
target_decision_id=target,
|
|
mode=CorrectionMode.REVERT,
|
|
)
|
|
context.cov_cid = req.correction_id
|
|
context.cov_req = req
|
|
|
|
|
|
@given('a cov-stored append correction for plan "{plan}" targeting "{target}"')
|
|
def step_stored_append_cov(context, plan, target):
|
|
req = context.cov_svc.request_correction(
|
|
plan_id=plan,
|
|
target_decision_id=target,
|
|
mode=CorrectionMode.APPEND,
|
|
)
|
|
context.cov_cid = req.correction_id
|
|
context.cov_req = req
|
|
|
|
|
|
@given("the cov-stored correction has been executed via revert with empty tree")
|
|
def step_pre_execute_revert(context):
|
|
context.cov_svc.execute_revert(context.cov_cid, {})
|
|
|
|
|
|
@given("the cov-stored correction has been executed via append")
|
|
def step_pre_execute_append(context):
|
|
context.cov_svc.execute_append(context.cov_cid)
|
|
|
|
|
|
@given("the cov-stored correction has been analyzed with empty tree")
|
|
def step_pre_analyze(context):
|
|
context.cov_svc.analyze_impact(context.cov_cid, {})
|
|
|
|
|
|
@given('the cov-stored correction status is forced to "{status}"')
|
|
def step_force_status(context, status):
|
|
req = context.cov_svc.get_correction(context.cov_cid)
|
|
req.status = CorrectionStatus(status)
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - request_correction
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when(
|
|
'I cov-request a correction for plan "{plan}" targeting "{target}" in revert mode'
|
|
)
|
|
def step_request_revert(context, plan, target):
|
|
context.cov_req = context.cov_svc.request_correction(
|
|
plan_id=plan,
|
|
target_decision_id=target,
|
|
mode=CorrectionMode.REVERT,
|
|
)
|
|
context.cov_cid = context.cov_req.correction_id
|
|
|
|
|
|
@when(
|
|
'I cov-request a correction for plan "{plan}" targeting "{target}"'
|
|
' in append mode with guidance "{guidance}" and dry_run'
|
|
)
|
|
def step_request_append_guidance_dryrun(context, plan, target, guidance):
|
|
context.cov_req = context.cov_svc.request_correction(
|
|
plan_id=plan,
|
|
target_decision_id=target,
|
|
mode=CorrectionMode.APPEND,
|
|
guidance=guidance,
|
|
dry_run=True,
|
|
)
|
|
context.cov_cid = context.cov_req.correction_id
|
|
|
|
|
|
@when(
|
|
'I cov-attempt to create a correction with plan_id "{plan}" and target "{target}"'
|
|
)
|
|
def step_attempt_create(context, plan, target):
|
|
try:
|
|
context.cov_svc.request_correction(
|
|
plan_id=plan,
|
|
target_decision_id=target,
|
|
mode=CorrectionMode.REVERT,
|
|
)
|
|
context.cov_error = None
|
|
except (ValidationError, Exception) as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
@when('I cov-attempt to create a correction with empty plan_id and target "{target}"')
|
|
def step_attempt_create_empty_plan(context, target):
|
|
try:
|
|
context.cov_svc.request_correction(
|
|
plan_id="",
|
|
target_decision_id=target,
|
|
mode=CorrectionMode.REVERT,
|
|
)
|
|
context.cov_error = None
|
|
except (ValidationError, Exception) as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
@when('I cov-attempt to create a correction with plan_id "{plan}" and empty target')
|
|
def step_attempt_create_empty_target(context, plan):
|
|
try:
|
|
context.cov_svc.request_correction(
|
|
plan_id=plan,
|
|
target_decision_id="",
|
|
mode=CorrectionMode.REVERT,
|
|
)
|
|
context.cov_error = None
|
|
except (ValidationError, Exception) as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - analyze_impact
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-analyze impact with an empty tree")
|
|
def step_analyze_empty(context):
|
|
context.cov_impact = context.cov_svc.analyze_impact(context.cov_cid, {})
|
|
|
|
|
|
@when('I cov-analyze impact with adjacency "{spec}"')
|
|
def step_analyze_adj(context, spec):
|
|
tree = _parse_adj(spec)
|
|
context.cov_impact = context.cov_svc.analyze_impact(context.cov_cid, tree)
|
|
|
|
|
|
@when('I cov-attempt to analyze impact for correction id "{cid}"')
|
|
def step_attempt_analyze(context, cid):
|
|
try:
|
|
context.cov_svc.analyze_impact(cid, {})
|
|
context.cov_error = None
|
|
except ResourceNotFoundError as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - generate_dry_run_report
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-generate dry-run report with an empty tree")
|
|
def step_dryrun_empty(context):
|
|
context.cov_dry_run = context.cov_svc.generate_dry_run_report(context.cov_cid, {})
|
|
|
|
|
|
@when('I cov-generate dry-run report with adjacency "{spec}"')
|
|
def step_dryrun_adj(context, spec):
|
|
tree = _parse_adj(spec)
|
|
context.cov_dry_run = context.cov_svc.generate_dry_run_report(context.cov_cid, tree)
|
|
|
|
|
|
@when('I cov-generate dry-run report with a chain of {n:d} nodes rooted at "{root}"')
|
|
def step_dryrun_chain(context, n, root):
|
|
tree = _build_chain_cov(root, n)
|
|
context.cov_dry_run = context.cov_svc.generate_dry_run_report(context.cov_cid, tree)
|
|
|
|
|
|
@when('I cov-attempt to generate dry-run report for correction id "{cid}"')
|
|
def step_attempt_dryrun(context, cid):
|
|
try:
|
|
context.cov_svc.generate_dry_run_report(cid, {})
|
|
context.cov_error = None
|
|
except ResourceNotFoundError as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - execute_revert
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when('I cov-execute revert with adjacency "{spec}"')
|
|
def step_exec_revert_adj(context, spec):
|
|
tree = _parse_adj(spec)
|
|
context.cov_revert_result = context.cov_svc.execute_revert(context.cov_cid, tree)
|
|
|
|
|
|
@when("I cov-execute revert with empty tree")
|
|
def step_exec_revert_empty(context):
|
|
context.cov_revert_result = context.cov_svc.execute_revert(context.cov_cid, {})
|
|
|
|
|
|
@when("I cov-attempt to execute revert on the stored correction")
|
|
def step_attempt_exec_revert(context):
|
|
try:
|
|
context.cov_svc.execute_revert(context.cov_cid, {})
|
|
context.cov_error = None
|
|
except (ValidationError, ResourceNotFoundError) as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - execute_append
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-execute append for the stored correction")
|
|
def step_exec_append(context):
|
|
context.cov_append_result = context.cov_svc.execute_append(context.cov_cid)
|
|
|
|
|
|
@when("I cov-attempt to execute append on the stored correction")
|
|
def step_attempt_exec_append(context):
|
|
try:
|
|
context.cov_svc.execute_append(context.cov_cid)
|
|
context.cov_error = None
|
|
except (ValidationError, ResourceNotFoundError) as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - execute_correction (dispatch)
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when('I cov-dispatch execute correction with adjacency "{spec}"')
|
|
def step_dispatch_adj(context, spec):
|
|
tree = _parse_adj(spec)
|
|
context.cov_dispatch_result = context.cov_svc.execute_correction(
|
|
context.cov_cid, tree
|
|
)
|
|
|
|
|
|
@when("I cov-dispatch execute correction with no tree")
|
|
def step_dispatch_no_tree(context):
|
|
context.cov_dispatch_result = context.cov_svc.execute_correction(context.cov_cid)
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - get_correction
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-get the stored correction by id")
|
|
def step_get_by_id(context):
|
|
context.cov_retrieved = context.cov_svc.get_correction(context.cov_cid)
|
|
|
|
|
|
@when('I cov-attempt to get correction with id "{cid}"')
|
|
def step_attempt_get(context, cid):
|
|
try:
|
|
context.cov_svc.get_correction(cid)
|
|
context.cov_error = None
|
|
except ResourceNotFoundError as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - list_corrections
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-list all corrections without filter")
|
|
def step_list_all(context):
|
|
context.cov_list = context.cov_svc.list_corrections()
|
|
|
|
|
|
@when('I cov-list corrections for plan "{plan}"')
|
|
def step_list_by_plan(context, plan):
|
|
context.cov_list = context.cov_svc.list_corrections(plan_id=plan)
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - list_attempts
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-list attempts for the stored correction")
|
|
def step_list_attempts(context):
|
|
context.cov_attempts = context.cov_svc.list_attempts(context.cov_cid)
|
|
|
|
|
|
@when('I cov-attempt to list attempts for correction id "{cid}"')
|
|
def step_attempt_list_attempts(context, cid):
|
|
try:
|
|
context.cov_svc.list_attempts(cid)
|
|
context.cov_error = None
|
|
except ResourceNotFoundError as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - cancel_correction
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when("I cov-cancel the stored correction")
|
|
def step_cancel(context):
|
|
context.cov_svc.cancel_correction(context.cov_cid)
|
|
|
|
|
|
@when("I cov-attempt to cancel the stored correction")
|
|
def step_attempt_cancel(context):
|
|
try:
|
|
context.cov_svc.cancel_correction(context.cov_cid)
|
|
context.cov_error = None
|
|
except ValidationError as exc:
|
|
context.cov_error = exc
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# When steps - _compute_affected_subtree
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@when('I cov-compute subtree for "{root}" with empty tree')
|
|
def step_subtree_empty(context, root):
|
|
context.cov_subtree = CorrectionService._compute_affected_subtree(root, {})
|
|
|
|
|
|
@when('I cov-compute subtree for "{root}" with adjacency "{spec}"')
|
|
def step_subtree_adj(context, root, spec):
|
|
tree = _parse_adj(spec)
|
|
context.cov_subtree = CorrectionService._compute_affected_subtree(root, tree)
|
|
|
|
|
|
# ===================================================================
|
|
# Then steps
|
|
# ===================================================================
|
|
|
|
# -------------------------------------------------------------------
|
|
# __init__ assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then("the coverage service corrections dict should be empty")
|
|
def step_init_corrections(context):
|
|
assert len(context.cov_svc._corrections) == 0
|
|
|
|
|
|
@then("the coverage service impacts dict should be empty")
|
|
def step_init_impacts(context):
|
|
assert len(context.cov_svc._impacts) == 0
|
|
|
|
|
|
@then("the coverage service attempts dict should be empty")
|
|
def step_init_attempts(context):
|
|
assert len(context.cov_svc._attempts) == 0
|
|
|
|
|
|
@then("the coverage service results dict should be empty")
|
|
def step_init_results(context):
|
|
assert len(context.cov_svc._results) == 0
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# request_correction assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then("the cov-request should be stored in the service")
|
|
def step_req_stored(context):
|
|
assert context.cov_req is not None
|
|
fetched = context.cov_svc.get_correction(context.cov_req.correction_id)
|
|
assert fetched.correction_id == context.cov_req.correction_id
|
|
|
|
|
|
@then('the cov-request plan_id should be "{val}"')
|
|
def step_req_plan(context, val):
|
|
assert context.cov_req.plan_id == val
|
|
|
|
|
|
@then('the cov-request target_decision_id should be "{val}"')
|
|
def step_req_target(context, val):
|
|
assert context.cov_req.target_decision_id == val
|
|
|
|
|
|
@then('the cov-request mode should be "{val}"')
|
|
def step_req_mode(context, val):
|
|
assert context.cov_req.mode.value == val
|
|
|
|
|
|
@then('the cov-request status should be "{val}"')
|
|
def step_req_status(context, val):
|
|
assert context.cov_req.status.value == val
|
|
|
|
|
|
@then("the cov-request dry_run should be false")
|
|
def step_req_dryrun_false(context):
|
|
assert context.cov_req.dry_run is False
|
|
|
|
|
|
@then("the cov-request dry_run should be true")
|
|
def step_req_dryrun_true(context):
|
|
assert context.cov_req.dry_run is True
|
|
|
|
|
|
@then('the cov-request guidance should be "{val}"')
|
|
def step_req_guidance(context, val):
|
|
assert context.cov_req.guidance == val
|
|
|
|
|
|
@then("the cov-request guidance should be empty")
|
|
def step_req_guidance_empty(context):
|
|
assert context.cov_req.guidance == ""
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Error assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then("a cov ValidationError should have been raised")
|
|
def step_cov_val_error(context):
|
|
assert context.cov_error is not None, "Expected ValidationError but none was raised"
|
|
assert isinstance(context.cov_error, ValidationError), (
|
|
f"Expected ValidationError, got {type(context.cov_error).__name__}"
|
|
)
|
|
|
|
|
|
@then("a cov ResourceNotFoundError should have been raised")
|
|
def step_cov_rnf_error(context):
|
|
assert context.cov_error is not None, (
|
|
"Expected ResourceNotFoundError but none was raised"
|
|
)
|
|
assert isinstance(context.cov_error, ResourceNotFoundError), (
|
|
f"Expected ResourceNotFoundError, got {type(context.cov_error).__name__}"
|
|
)
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Impact assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov-impact affected decisions should be "{expected}"')
|
|
def step_impact_decisions(context, expected):
|
|
expected_list = [d.strip() for d in expected.split(",")]
|
|
assert context.cov_impact.affected_decisions == expected_list, (
|
|
f"Expected {expected_list}, got {context.cov_impact.affected_decisions}"
|
|
)
|
|
|
|
|
|
@then('the cov-impact risk level should be "{val}"')
|
|
def step_impact_risk(context, val):
|
|
assert context.cov_impact.risk_level == val
|
|
|
|
|
|
@then('the cov-impact rollback tier should be "{val}"')
|
|
def step_impact_rollback(context, val):
|
|
assert context.cov_impact.rollback_tier == val
|
|
|
|
|
|
@then("the cov-impact estimated cost should be {cost:g}")
|
|
def step_impact_cost(context, cost):
|
|
assert context.cov_impact.estimated_cost == cost
|
|
|
|
|
|
@then('the cov-impact artifacts to archive should be "{expected}"')
|
|
def step_impact_artifacts(context, expected):
|
|
expected_list = [a.strip() for a in expected.split(",")]
|
|
assert context.cov_impact.artifacts_to_archive == expected_list
|
|
|
|
|
|
@then('the cov-impact affected files should be "{expected}"')
|
|
def step_impact_files(context, expected):
|
|
expected_list = [f.strip() for f in expected.split(",")]
|
|
assert context.cov_impact.affected_files == expected_list
|
|
|
|
|
|
@then("the cov-impact affected decisions count should be {n:d}")
|
|
def step_impact_count(context, n):
|
|
actual = len(context.cov_impact.affected_decisions)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|
|
|
|
|
|
@then('the cov-stored correction status should be "{val}"')
|
|
def step_stored_status(context, val):
|
|
req = context.cov_svc.get_correction(context.cov_cid)
|
|
assert req.status.value == val, f"Expected status '{val}', got '{req.status.value}'"
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Dry-run report assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov dry-run mode should be "{val}"')
|
|
def step_dr_mode(context, val):
|
|
assert context.cov_dry_run.mode.value == val
|
|
|
|
|
|
@then("the cov dry-run warnings should be empty")
|
|
def step_dr_warnings_empty(context):
|
|
assert len(context.cov_dry_run.warnings) == 0, (
|
|
f"Expected no warnings, got {context.cov_dry_run.warnings}"
|
|
)
|
|
|
|
|
|
@then('the cov dry-run warnings should contain "{fragment}"')
|
|
def step_dr_warning_contains(context, fragment):
|
|
joined = " | ".join(context.cov_dry_run.warnings)
|
|
assert fragment in joined, (
|
|
f"'{fragment}' not found in warnings: {context.cov_dry_run.warnings}"
|
|
)
|
|
|
|
|
|
@then('the cov dry-run decisions to invalidate should be "{expected}"')
|
|
def step_dr_invalidate(context, expected):
|
|
expected_list = [d.strip() for d in expected.split(",")]
|
|
assert context.cov_dry_run.decisions_to_invalidate == expected_list
|
|
|
|
|
|
@then("the cov dry-run decisions to invalidate should be empty list")
|
|
def step_dr_invalidate_empty(context):
|
|
assert context.cov_dry_run.decisions_to_invalidate == []
|
|
|
|
|
|
@then("the cov dry-run decisions to invalidate count should be {n:d}")
|
|
def step_dr_invalidate_count(context, n):
|
|
actual = len(context.cov_dry_run.decisions_to_invalidate)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|
|
|
|
|
|
@then("the cov dry-run estimated recompute time should be {t:g}")
|
|
def step_dr_recompute(context, t):
|
|
assert context.cov_dry_run.estimated_recompute_time_seconds == t
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Revert result assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov revert result status should be "{val}"')
|
|
def step_revert_status(context, val):
|
|
assert context.cov_revert_result.status.value == val
|
|
|
|
|
|
@then('the cov revert result reverted decisions should include "{d}"')
|
|
def step_revert_incl(context, d):
|
|
assert d in context.cov_revert_result.reverted_decisions
|
|
|
|
|
|
@then('the cov revert result archived artifacts should include "{a}"')
|
|
def step_revert_artifacts(context, a):
|
|
assert a in context.cov_revert_result.archived_artifacts
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Append result assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov append result status should be "{val}"')
|
|
def step_append_status(context, val):
|
|
assert context.cov_append_result.status.value == val
|
|
|
|
|
|
@then("the cov append result spawned_child_plan_id should not be none")
|
|
def step_append_child(context):
|
|
assert context.cov_append_result.spawned_child_plan_id is not None
|
|
|
|
|
|
@then("the cov append result new_decisions should not be empty")
|
|
def step_append_new_decisions(context):
|
|
assert len(context.cov_append_result.new_decisions) > 0
|
|
|
|
|
|
@then("the cov first attempt details should contain spawned_child_plan_id")
|
|
def step_append_attempt_details(context):
|
|
attempts = context.cov_svc.list_attempts(context.cov_cid)
|
|
assert "spawned_child_plan_id" in attempts[0].details
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Dispatch result assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov dispatch result status should be "{val}"')
|
|
def step_dispatch_status(context, val):
|
|
assert context.cov_dispatch_result.status.value == val
|
|
|
|
|
|
@then('the cov dispatch result reverted decisions should include "{d}"')
|
|
def step_dispatch_reverted(context, d):
|
|
assert d in context.cov_dispatch_result.reverted_decisions
|
|
|
|
|
|
@then("the cov dispatch result spawned_child_plan_id should not be none")
|
|
def step_dispatch_child(context):
|
|
assert context.cov_dispatch_result.spawned_child_plan_id is not None
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# get_correction assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov-retrieved correction plan_id should be "{val}"')
|
|
def step_get_plan(context, val):
|
|
assert context.cov_retrieved.plan_id == val
|
|
|
|
|
|
@then('the cov-retrieved correction target_decision_id should be "{val}"')
|
|
def step_get_target(context, val):
|
|
assert context.cov_retrieved.target_decision_id == val
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# list_corrections assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then("the cov corrections list should have at least {n:d} items")
|
|
def step_list_min(context, n):
|
|
actual = len(context.cov_list)
|
|
assert actual >= n, f"Expected at least {n}, got {actual}"
|
|
|
|
|
|
@then("the cov corrections list should have {n:d} item")
|
|
def step_list_exact_one(context, n):
|
|
actual = len(context.cov_list)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|
|
|
|
|
|
@then("the cov corrections list should have {n:d} items")
|
|
def step_list_exact(context, n):
|
|
actual = len(context.cov_list)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# list_attempts assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then("the cov attempts list should have {n:d} items")
|
|
def step_attempts_count_plural(context, n):
|
|
actual = len(context.cov_attempts)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|
|
|
|
|
|
@then("the cov attempts list should have {n:d} item")
|
|
def step_attempts_count_single(context, n):
|
|
actual = len(context.cov_attempts)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# Attempt detail assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then("the cov attempts for the stored correction should have {n:d} entry")
|
|
def step_attempts_for_stored(context, n):
|
|
attempts = context.cov_svc.list_attempts(context.cov_cid)
|
|
assert len(attempts) == n, f"Expected {n}, got {len(attempts)}"
|
|
|
|
|
|
@then("the cov first attempt should be successful")
|
|
def step_first_attempt_ok(context):
|
|
attempts = context.cov_svc.list_attempts(context.cov_cid)
|
|
assert attempts[0].success is True
|
|
|
|
|
|
@then("the cov first attempt completed_at should be set")
|
|
def step_first_attempt_completed(context):
|
|
attempts = context.cov_svc.list_attempts(context.cov_cid)
|
|
assert attempts[0].completed_at is not None
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# _classify_risk assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('cov classifying risk for {n:d} affected returns "{expected}"')
|
|
def step_classify_risk(context, n, expected):
|
|
result = CorrectionService._classify_risk(n)
|
|
assert result == expected, (
|
|
f"_classify_risk({n}) = '{result}', expected '{expected}'"
|
|
)
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
# _compute_affected_subtree assertions
|
|
# -------------------------------------------------------------------
|
|
|
|
|
|
@then('the cov subtree should be "{expected}"')
|
|
def step_subtree_result(context, expected):
|
|
expected_list = [n.strip() for n in expected.split(",")]
|
|
assert context.cov_subtree == expected_list, (
|
|
f"Expected {expected_list}, got {context.cov_subtree}"
|
|
)
|
|
|
|
|
|
@then("the cov subtree count should be {n:d}")
|
|
def step_subtree_count(context, n):
|
|
actual = len(context.cov_subtree)
|
|
assert actual == n, f"Expected {n}, got {actual}"
|