Files
temp/features/steps/plan_preflight_guardrail_coverage_boost_steps.py
freemo a808c395f9 test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

258 lines
8.7 KiB
Python

"""Step definitions for Plan Preflight Guardrail coverage-boost scenarios.
Targets the uncovered lines identified in coverage.xml:
- Lines 52-53: PreflightCheckResult.__repr__
- Lines 86-90: PreflightReport.format_report
- Lines 166-169: check_action_schema with action_name=None
- Line 221: check_skill_tool_existence with missing skills
"""
from __future__ import annotations
from behave import given, then, when
from behave.runner import Context
from cleveragents.application.services.plan_preflight_guardrail import (
PlanPreflightGuardrail,
PreflightCheckName,
PreflightCheckResult,
PreflightRejection,
PreflightReport,
)
# ---------------------------------------------------------------------------
# Givens
# ---------------------------------------------------------------------------
@given("a passing PreflightCheckResult for action_schema_validation")
def step_given_passing_result(context: Context) -> None:
context.cb_check_result = PreflightCheckResult(
PreflightCheckName.ACTION_SCHEMA,
True,
"Action 'deploy' found and valid",
)
@given('a failing PreflightCheckResult for actor_availability with message "{msg}"')
def step_given_failing_result(context: Context, msg: str) -> None:
context.cb_check_result = PreflightCheckResult(
PreflightCheckName.ACTOR_AVAILABILITY,
False,
msg,
)
@given("a PreflightReport with one passing and one failing check result")
def step_given_mixed_report(context: Context) -> None:
report = PreflightReport()
report.add(
PreflightCheckResult(
PreflightCheckName.ACTION_SCHEMA,
True,
"Action 'deploy' found and valid",
)
)
report.add(
PreflightCheckResult(
PreflightCheckName.ACTOR_AVAILABILITY,
False,
"Missing actor roles: estimation",
)
)
context.cb_report = report
@given("a PreflightReport with only passing check results")
def step_given_all_pass_report(context: Context) -> None:
report = PreflightReport()
report.add(
PreflightCheckResult(
PreflightCheckName.ACTION_SCHEMA,
True,
"Action 'deploy' found and valid",
)
)
report.add(
PreflightCheckResult(
PreflightCheckName.AUTOMATION_POLICY,
True,
"Automation profile permits execution",
)
)
context.cb_report = report
@given("a fresh plan preflight guardrail instance")
def step_given_fresh_guardrail(context: Context) -> None:
context.cb_guardrail = PlanPreflightGuardrail()
context.cb_check_result = None
context.cb_rejection = None
context.cb_skill_registry: dict[str, object] = {}
@given('a skill registry containing only "{name}"')
def step_given_skill_registry_one(context: Context, name: str) -> None:
context.cb_skill_registry = {name: {"name": name}}
@given(
"a fresh plan preflight guardrail instance with all registries populated but no action name"
)
def step_given_guardrail_no_action_name(context: Context) -> None:
context.cb_guardrail = PlanPreflightGuardrail()
context.cb_actor_registry = {
role: {"role": role} for role in PlanPreflightGuardrail.ACTOR_ROLES
}
context.cb_automation_profile = {"name": "auto"}
context.cb_rejection = None
# ---------------------------------------------------------------------------
# Whens
# ---------------------------------------------------------------------------
@when("I get the repr of the check result")
def step_when_get_repr(context: Context) -> None:
context.cb_repr_str = repr(context.cb_check_result)
@when("I format the report")
def step_when_format_report(context: Context) -> None:
context.cb_formatted = context.cb_report.format_report()
@when("I check action schema with no action name")
def step_when_check_action_none(context: Context) -> None:
context.cb_check_result = context.cb_guardrail.check_action_schema(
None, {"some-action": {"name": "some-action"}}
)
@when('I check skill/tool existence for skills "{skills_csv}"')
def step_when_check_skills(context: Context, skills_csv: str) -> None:
skill_names = tuple(s.strip() for s in skills_csv.split(","))
context.cb_check_result = context.cb_guardrail.check_skill_tool_existence(
tool_names=(),
tool_registry={},
skill_names=skill_names,
skill_registry=context.cb_skill_registry,
)
@when(
'I check skill/tool existence for tools "{tools}" and skills "{skills}" with empty registries'
)
def step_when_check_both_missing(context: Context, tools: str, skills: str) -> None:
tool_names = tuple(t.strip() for t in tools.split(","))
skill_names = tuple(s.strip() for s in skills.split(","))
context.cb_check_result = context.cb_guardrail.check_skill_tool_existence(
tool_names=tool_names,
tool_registry={},
skill_names=skill_names,
skill_registry={},
)
@when("I run all preflight checks with action_name as None")
def step_when_run_all_none_action(context: Context) -> None:
try:
context.cb_guardrail.run_all_checks(
action_name=None,
action_registry={},
actor_registry=context.cb_actor_registry,
automation_profile=context.cb_automation_profile,
)
context.cb_rejection = None
except PreflightRejection as exc:
context.cb_rejection = exc
# ---------------------------------------------------------------------------
# Thens
# ---------------------------------------------------------------------------
@then('the repr should contain "{text}"')
def step_then_repr_contains(context: Context, text: str) -> None:
assert text in context.cb_repr_str, (
f"Expected '{text}' in repr, got: {context.cb_repr_str}"
)
@then('the formatted report should start with "{prefix}"')
def step_then_report_starts_with(context: Context, prefix: str) -> None:
assert context.cb_formatted.startswith(prefix), (
f"Expected report to start with '{prefix}', got: {context.cb_formatted[:80]}"
)
@then("the formatted report should contain a PASS line")
def step_then_report_has_pass(context: Context) -> None:
assert "[PASS]" in context.cb_formatted, (
f"Expected [PASS] in report, got:\n{context.cb_formatted}"
)
@then("the formatted report should contain a FAIL line")
def step_then_report_has_fail(context: Context) -> None:
assert "[FAIL]" in context.cb_formatted, (
f"Expected [FAIL] in report, got:\n{context.cb_formatted}"
)
@then('the formatted report should not contain "{text}"')
def step_then_report_not_contains(context: Context, text: str) -> None:
assert text not in context.cb_formatted, (
f"Did not expect '{text}' in report, but found it:\n{context.cb_formatted}"
)
@then("the action schema check should fail")
def step_then_action_check_fails(context: Context) -> None:
result = context.cb_check_result
assert result is not None, "No check result recorded"
assert result.passed is False, f"Expected FAIL but got PASS: {result.message}"
assert result.check == PreflightCheckName.ACTION_SCHEMA
@then('the action schema check message should contain "{text}"')
def step_then_action_msg_contains(context: Context, text: str) -> None:
assert text in context.cb_check_result.message, (
f"Expected '{text}' in message, got: {context.cb_check_result.message}"
)
@then("the skill tool check should fail")
def step_then_skill_check_fails(context: Context) -> None:
result = context.cb_check_result
assert result is not None, "No check result recorded"
assert result.passed is False, f"Expected FAIL but got PASS: {result.message}"
assert result.check == PreflightCheckName.SKILL_TOOL_EXISTENCE
@then('the skill tool check message should contain "{text}"')
def step_then_skill_msg_contains(context: Context, text: str) -> None:
assert text in context.cb_check_result.message, (
f"Expected '{text}' in message, got: {context.cb_check_result.message}"
)
@then('a preflight rejection should be raised for "{check_name}"')
def step_then_rejection_for_check(context: Context, check_name: str) -> None:
assert context.cb_rejection is not None, (
"Expected PreflightRejection but none was raised"
)
assert context.cb_rejection.check.value == check_name, (
f"Expected check '{check_name}', got '{context.cb_rejection.check.value}'"
)
@then('the preflight rejection message should contain "{text}"')
def step_then_rejection_msg_contains(context: Context, text: str) -> None:
assert context.cb_rejection is not None, "No rejection recorded"
assert text in str(context.cb_rejection), (
f"Expected '{text}' in rejection message, got: {context.cb_rejection}"
)