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:
2026-04-04 13:51:25 +00:00
committed by Forgejo
parent 0599079fe6
commit c301fc13dd
6 changed files with 75 additions and 34 deletions
@@ -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")
+23 -19
View File
@@ -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")
+1 -1
View File
@@ -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={}))
+1 -1
View File
@@ -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()
+10 -10
View File
@@ -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}")
+3 -3
View File
@@ -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")