fix(ci): fix remaining Robot Framework integration test failures
- A2A JSON-RPC 2.0 migration: updated 3 robot helpers still using the old API (operation= → method=, resp.status/resp.data → resp.result): helper_m6_autonomy_acceptance.py, helper_wf03_plan_prompt_confidence.py, wf02_test_generation_artifacts.py - Session CLI: updated 'Session Details' → 'Session Summary' panel title assertion in helper_session_cli.py to match current CLI output - Audit wiring: fixed container_wiring test to create DB tables via Base.metadata.create_all() and disable async mode for deterministic verification (container's in-memory DB had no schema) - Missing migration: added m9_001_session_name_column.py to add the 'name' column to sessions table (ORM model had it, Alembic migration was missing, causing 'session create' to fail after 'agents init') All 1908 integration tests now pass (0 failed, 0 skipped). ISSUES CLOSED: #2597
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
"""Add name column to sessions table.
|
||||
|
||||
The ``name`` column was added to the ``SessionModel`` ORM class but the
|
||||
corresponding Alembic migration was missing, causing ``session create``
|
||||
to fail with ``OperationalError: table sessions has no column named name``
|
||||
when the database was created via Alembic migrations (as opposed to
|
||||
``Base.metadata.create_all()`` used by the template DB).
|
||||
|
||||
Revision ID: m9_001_session_name_column
|
||||
Revises: m6_006_estimation_report_json
|
||||
Create Date: 2026-04-04 12:00:00
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "m9_001_session_name_column"
|
||||
down_revision: str | Sequence[str] | None = "m6_006_estimation_report_json"
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add ``name`` column to ``sessions`` table."""
|
||||
op.add_column(
|
||||
"sessions",
|
||||
sa.Column("name", sa.String(255), nullable=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove ``name`` column from ``sessions`` table."""
|
||||
op.drop_column("sessions", "name")
|
||||
@@ -125,10 +125,8 @@ def ignore_non_security() -> None:
|
||||
def container_wiring() -> None:
|
||||
"""Verify DI container registers a functional AuditEventSubscriber.
|
||||
|
||||
Verifies the subscriber type is correct and that the subscriber is
|
||||
wired to both the event bus and audit service from the container.
|
||||
The functional end-to-end audit pipeline is tested separately in
|
||||
e2e_plan_lifecycle (which uses a controlled, non-container wiring).
|
||||
Goes beyond type checking: emits an event through the container's
|
||||
EventBus and verifies it reaches the AuditService via the subscriber.
|
||||
"""
|
||||
from dependency_injector import providers
|
||||
|
||||
@@ -143,7 +141,6 @@ def container_wiring() -> None:
|
||||
# requires the .cleveragents/ directory to exist).
|
||||
container.database_url.override(providers.Object("sqlite:///:memory:"))
|
||||
|
||||
# Verify subscriber type
|
||||
subscriber = container.audit_event_subscriber()
|
||||
if not isinstance(subscriber, AuditEventSubscriber):
|
||||
print(
|
||||
@@ -152,22 +149,29 @@ def container_wiring() -> None:
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Verify the subscriber has the expected audit_service and event_bus attributes
|
||||
# Functional verification: emit through container's EventBus
|
||||
# and verify the event reaches the AuditService.
|
||||
audit_svc = container.audit_service()
|
||||
bus = container.event_bus()
|
||||
if subscriber._audit_service is not audit_svc:
|
||||
print(
|
||||
"FAIL: subscriber._audit_service is not container.audit_service()",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
if subscriber._event_bus is not bus:
|
||||
print(
|
||||
"FAIL: subscriber._event_bus is not container.event_bus()",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# The container's in-memory DB has no tables yet — create them
|
||||
# so the audit service can persist entries.
|
||||
db_session = audit_svc._ensure_session()
|
||||
audit_engine = db_session.get_bind()
|
||||
Base.metadata.create_all(audit_engine)
|
||||
|
||||
# Disable async mode so writes happen synchronously and we can
|
||||
# verify the count immediately without race conditions.
|
||||
audit_svc._async_mode = False
|
||||
|
||||
bus = container.event_bus()
|
||||
bus.emit(DomainEvent(event_type=EventType.PLAN_APPLIED, plan_id="WIRE-001"))
|
||||
count = audit_svc.count()
|
||||
if count < 1:
|
||||
print(
|
||||
f"FAIL: expected >=1 audit entries after emit, got {count}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
print("CONTAINER_OK")
|
||||
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ def facade_session() -> None:
|
||||
facade = A2aLocalFacade()
|
||||
|
||||
resp_create = facade.dispatch(A2aRequest(method="session.create", params={}))
|
||||
assert resp_create.result is not None, f"got error: {resp_create.error}"
|
||||
assert resp_create.result is not None, f"Expected ok, got {resp_create.status}"
|
||||
assert "session_id" in resp_create.result
|
||||
|
||||
resp_close = facade.dispatch(A2aRequest(method="session.close", params={}))
|
||||
|
||||
@@ -148,7 +148,7 @@ def show_valid() -> None:
|
||||
try:
|
||||
result = runner.invoke(session_app, ["show", sid])
|
||||
assert result.exit_code == 0, f"exit={result.exit_code}: {result.output}"
|
||||
assert "Session Details" in result.output
|
||||
assert "Session Summary" in result.output
|
||||
print("session-cli-show-valid-ok")
|
||||
finally:
|
||||
_teardown()
|
||||
|
||||
@@ -70,15 +70,15 @@ def wf03_plan_prompt_facade() -> None:
|
||||
guidance = "Use separate models/ directory instead of inline"
|
||||
|
||||
request = A2aRequest(
|
||||
operation="_cleveragents/plan/prompt",
|
||||
method="_cleveragents/plan/prompt",
|
||||
params={"plan_id": plan_id, "guidance": guidance},
|
||||
)
|
||||
response = facade.dispatch(request)
|
||||
|
||||
if response.status != "ok":
|
||||
_fail(f"Expected status 'ok', got '{response.status}'")
|
||||
if response.result is None:
|
||||
_fail(f"Expected result, got error: {response.error}")
|
||||
|
||||
data = response.data
|
||||
data = response.result
|
||||
if data.get("status") != "guidance_injected":
|
||||
_fail(f"Expected data.status 'guidance_injected', got: {data}")
|
||||
if data.get("plan_id") != plan_id:
|
||||
@@ -254,7 +254,7 @@ def wf03_pause_and_resume() -> None:
|
||||
"This follows the project convention."
|
||||
)
|
||||
prompt_request = A2aRequest(
|
||||
operation="_cleveragents/plan/prompt",
|
||||
method="_cleveragents/plan/prompt",
|
||||
params={
|
||||
"plan_id": paused_plan_id,
|
||||
"guidance": guidance_text,
|
||||
@@ -262,19 +262,19 @@ def wf03_pause_and_resume() -> None:
|
||||
)
|
||||
prompt_response = facade.dispatch(prompt_request)
|
||||
|
||||
if prompt_response.status != "ok":
|
||||
if prompt_response.result is None:
|
||||
_fail(f"Step 3: plan prompt failed: {prompt_response}")
|
||||
|
||||
if prompt_response.data.get("status") != "guidance_injected":
|
||||
_fail(f"Step 3: Expected guidance_injected: {prompt_response.data}")
|
||||
if prompt_response.result.get("status") != "guidance_injected":
|
||||
_fail(f"Step 3: Expected guidance_injected: {prompt_response.result}")
|
||||
|
||||
# Verify guidance propagates through the dispatch path
|
||||
actual_guidance = prompt_response.data.get("guidance")
|
||||
actual_guidance = prompt_response.result.get("guidance")
|
||||
if actual_guidance != guidance_text:
|
||||
_fail(f"Step 3: Expected guidance '{guidance_text}', got: {actual_guidance}")
|
||||
|
||||
# Verify the plan_id from response matches paused context
|
||||
actual_plan_id = prompt_response.data.get("plan_id")
|
||||
actual_plan_id = prompt_response.result.get("plan_id")
|
||||
if actual_plan_id != paused_plan_id:
|
||||
_fail(f"Step 3: Expected plan_id '{paused_plan_id}', got: {actual_plan_id}")
|
||||
|
||||
|
||||
@@ -181,12 +181,12 @@ def wf02_mocked_generation_artifacts() -> None:
|
||||
)
|
||||
artifacts_response = facade.dispatch(
|
||||
A2aRequest(
|
||||
operation="_cleveragents/plan/artifacts",
|
||||
method="_cleveragents/plan/artifacts",
|
||||
params={"plan_id": plan_id},
|
||||
)
|
||||
)
|
||||
assert artifacts_response.status == "ok", artifacts_response.error
|
||||
response_data = artifacts_response.data
|
||||
assert artifacts_response.result is not None, artifacts_response.error
|
||||
response_data = artifacts_response.result
|
||||
assert response_data.get("plan_id") == plan_id
|
||||
assert response_data.get("changeset_id") == changeset_id
|
||||
sandbox_refs = response_data.get("sandbox_refs")
|
||||
|
||||
Reference in New Issue
Block a user