forked from HAL9000/cleveragents-core
5e96b4bf80
Implement the spec's 6-level execution environment precedence chain (spec lines 19324-19386): 1. Plan override (priority=override) — always wins 2. Project override (priority=override) — wins over devcontainer 3. Nearest-ancestor devcontainer — auto-discovered 4. Plan fallback (priority=fallback) — defers to devcontainer 5. Project fallback (priority=fallback) — defers to closer scopes 6. Host default — final fallback - New resolve_with_precedence() API on ExecutionEnvironmentResolver - Added execution_env_priority field to ContextConfig (project model) - has_devcontainer() helper for devcontainer-instance detection - Legacy 4-level resolve() preserved for backward compatibility - _parse_priority() defaults missing priority to FALLBACK - 13 new Behave scenarios testing all 6 levels + edge cases - Updated CHANGELOG ISSUES CLOSED: #877
152 lines
5.4 KiB
Python
152 lines
5.4 KiB
Python
"""Step definitions for a2a_cli_facade_integration.feature.
|
|
|
|
Tests the integration between CLI commands and the A2A local facade,
|
|
verifying that the facade bootstrap, dispatch helpers, and
|
|
notification wrappers work correctly.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
|
|
try:
|
|
from cleveragents.a2a.cli_bootstrap import get_facade, reset_facade
|
|
from cleveragents.a2a.facade import A2aLocalFacade
|
|
from cleveragents.a2a.models import A2aRequest
|
|
except ImportError:
|
|
pass # a2a module not available
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a facade created via the CLI bootstrap")
|
|
def step_create_bootstrap_facade(context: Any) -> None:
|
|
reset_facade()
|
|
context.facade = get_facade()
|
|
|
|
|
|
@given("a facade with a mock session service")
|
|
def step_facade_with_mock_session(context: Any) -> None:
|
|
mock_session_svc = MagicMock()
|
|
mock_session_svc.create.return_value = MagicMock(
|
|
session_id="test-session-001",
|
|
actor_name="stub",
|
|
)
|
|
context.facade = A2aLocalFacade(services={"session_service": mock_session_svc})
|
|
|
|
|
|
@given("a facade with a mock plan lifecycle service")
|
|
def step_facade_with_mock_plan(context: Any) -> None:
|
|
mock_plan_svc = MagicMock()
|
|
mock_plan_svc.get_plan.return_value = MagicMock(
|
|
identity=MagicMock(plan_id="test-plan-001"),
|
|
phase=MagicMock(value="execute"),
|
|
state=MagicMock(value="complete"),
|
|
)
|
|
context.facade = A2aLocalFacade(services={"plan_lifecycle_service": mock_plan_svc})
|
|
|
|
|
|
@given("a facade that raises an error on dispatch")
|
|
def step_facade_raises_error(context: Any) -> None:
|
|
context.error_facade = MagicMock(spec=A2aLocalFacade)
|
|
context.error_facade.dispatch.side_effect = RuntimeError("test error")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I dispatch a session.create operation via the CLI facade helper")
|
|
def step_dispatch_session_create(context: Any) -> None:
|
|
request = A2aRequest(operation="session.create", params={"actor": "stub"})
|
|
context.response = context.facade.dispatch(request)
|
|
|
|
|
|
@when("I dispatch a plan.create operation via the CLI facade helper")
|
|
def step_dispatch_plan_create(context: Any) -> None:
|
|
request = A2aRequest(operation="plan.create", params={"action_name": "test"})
|
|
context.response = context.facade.dispatch(request)
|
|
|
|
|
|
@when("I dispatch a plan.execute operation via the CLI facade helper")
|
|
def step_dispatch_plan_execute(context: Any) -> None:
|
|
request = A2aRequest(operation="plan.execute", params={"plan_id": "test-plan-001"})
|
|
context.response = context.facade.dispatch(request)
|
|
|
|
|
|
@when("I dispatch a plan.apply operation via the CLI facade helper")
|
|
def step_dispatch_plan_apply(context: Any) -> None:
|
|
request = A2aRequest(operation="plan.apply", params={"plan_id": "test-plan-001"})
|
|
context.response = context.facade.dispatch(request)
|
|
|
|
|
|
@when("I dispatch a plan.status operation via the CLI facade helper")
|
|
def step_dispatch_plan_status(context: Any) -> None:
|
|
request = A2aRequest(operation="plan.status", params={"plan_id": "test-plan-001"})
|
|
context.response = context.facade.dispatch(request)
|
|
|
|
|
|
@when("I dispatch a plan.diff operation via the CLI facade helper")
|
|
def step_dispatch_plan_diff(context: Any) -> None:
|
|
request = A2aRequest(operation="plan.diff", params={"plan_id": "test-plan-001"})
|
|
context.response = context.facade.dispatch(request)
|
|
|
|
|
|
@when('I call the plan notify_facade helper with operation "{operation}"')
|
|
def step_call_notify_facade(context: Any, operation: str) -> None:
|
|
# Patch the bootstrap module so _notify_facade gets the error facade
|
|
with patch(
|
|
"cleveragents.a2a.cli_bootstrap.get_facade",
|
|
return_value=context.error_facade,
|
|
):
|
|
from cleveragents.a2a.cli_bootstrap import reset_facade
|
|
|
|
reset_facade()
|
|
from cleveragents.cli.commands.plan import _notify_facade
|
|
|
|
# This should not raise even though the facade errors
|
|
context.notify_exception = None
|
|
try:
|
|
_notify_facade(operation, {"plan_id": "test"})
|
|
except Exception as e:
|
|
context.notify_exception = e
|
|
reset_facade()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then("the facade should support all 42 operations")
|
|
def step_check_42_operations(context: Any) -> None:
|
|
ops = context.facade.list_operations()
|
|
assert len(ops) == 42, f"Expected 42 operations, got {len(ops)}: {ops}"
|
|
|
|
|
|
@then("the facade should be cached on subsequent calls")
|
|
def step_check_facade_cached(context: Any) -> None:
|
|
facade2 = get_facade()
|
|
assert facade2 is context.facade, "Facade should be the same cached instance"
|
|
reset_facade()
|
|
|
|
|
|
@then("the facade should return an ok response")
|
|
def step_check_ok_response(context: Any) -> None:
|
|
assert context.response.status == "ok", (
|
|
f"Expected 'ok', got '{context.response.status}'"
|
|
)
|
|
|
|
|
|
@then("no exception should propagate to the caller")
|
|
def step_check_no_exception(context: Any) -> None:
|
|
assert context.notify_exception is None, (
|
|
f"Expected no exception, got: {context.notify_exception}"
|
|
)
|