forked from HAL9000/cleveragents-core
a808c395f9
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
258 lines
8.7 KiB
Python
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}"
|
|
)
|