Files
cleveragents-core/robot/wf02_test_generation_artifacts.py
freemo c301fc13dd 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
2026-04-04 20:38:16 +00:00

197 lines
6.8 KiB
Python

"""WF02 artifact lifecycle assertions.
Implements artifact generation assertions through lifecycle-produced
changesets and user-facing artifacts output.
"""
from __future__ import annotations
import json
import sys
from io import StringIO
from pathlib import Path
from wf02_test_generation_common import (
assert_invariants_and_conventions,
auth_module_fixture,
create_trusted_plan,
create_wf02_action,
generate_tests_from_mock_provider,
setup_lifecycle,
validated_relative_test_path,
)
from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest
from cleveragents.application.services.plan_apply_service import PlanApplyService
from cleveragents.application.services.plan_execution_context import (
PlanExecutionContext,
)
from cleveragents.domain.models.core.change import (
ChangeEntry,
ChangeOperation,
InMemoryChangeSetStore,
)
from cleveragents.tool.builtins.changeset import ChangeSetCapture
from cleveragents.tool.builtins.file_tools import FILE_WRITE_SPEC
def _capture_changeset_from_file_lifecycle(
plan_id: str,
fixture_root: Path,
generated_files: dict[str, str],
) -> tuple[str, InMemoryChangeSetStore]:
"""Produce a changeset through ChangeSetCapture + file-write lifecycle."""
capture = ChangeSetCapture(
plan_id=plan_id,
resource_id="local/api-service-repo",
sandbox_root=str(fixture_root),
)
wrapped_file_write = capture.wrap_tool(FILE_WRITE_SPEC)
normalized: dict[str, str] = {}
for rel_path, content in generated_files.items():
output_path = validated_relative_test_path(fixture_root, rel_path)
normalized_rel = output_path.relative_to(fixture_root.resolve()).as_posix()
wrapped_file_write.handler(
{
"path": normalized_rel,
"content": content,
"create_dirs": True,
"sandbox_root": str(fixture_root),
}
)
normalized[normalized_rel] = content
lifecycle_changeset = capture.to_spec_changeset()
store = InMemoryChangeSetStore()
execution_context = PlanExecutionContext(
plan_id=plan_id,
automation_profile="trusted",
changeset_store=store,
)
changeset_id = execution_context.start_changeset()
for entry in lifecycle_changeset.entries:
operation = (
ChangeOperation(entry.operation)
if isinstance(entry.operation, str)
else ChangeOperation(entry.operation.value)
)
execution_context.record_change(
ChangeEntry(
plan_id=plan_id,
resource_id=entry.resource_id,
tool_name=entry.tool_name,
operation=operation,
path=entry.path,
before_hash=entry.before_hash,
after_hash=entry.after_hash,
timestamp=entry.timestamp,
)
)
return changeset_id, store
def wf02_mocked_generation_artifacts() -> None:
"""Verify lifecycle-produced artifacts include provider-derived outputs."""
lifecycle = setup_lifecycle()
create_wf02_action(lifecycle)
plan_id = create_trusted_plan(lifecycle)
lifecycle.try_auto_run(plan_id)
with auth_module_fixture() as (fixture_root, baseline_coverage):
assert (fixture_root / "src" / "auth.py").is_file()
assert baseline_coverage < 80.0
generated_files = generate_tests_from_mock_provider(
fixture_root,
"src/auth",
80,
include_coverage_suite=True,
)
assert "tests/test_provider_mock_output.py" in generated_files
changeset_id, store = _capture_changeset_from_file_lifecycle(
plan_id,
fixture_root,
generated_files,
)
plan = lifecycle.get_plan(plan_id)
plan.changeset_id = changeset_id
plan.sandbox_refs = [f"sandbox-{plan_id[:8]}"]
lifecycle.save_plan(plan)
assert_invariants_and_conventions(
fixture_root,
plan_id,
lifecycle,
generated_files,
)
apply_service = PlanApplyService(
lifecycle_service=lifecycle, changeset_store=store
)
capture = StringIO()
old_stdout = sys.stdout
sys.stdout = capture
try:
artifact_payload = apply_service.artifacts(plan_id, fmt="json")
finally:
sys.stdout = old_stdout
rendered = artifact_payload or capture.getvalue().strip()
parsed = json.loads(rendered)
assert parsed["changeset_id"] == changeset_id
raw_files = parsed["files_changed"]
changed_files = {
item["path"] if isinstance(item, dict) else str(item) for item in raw_files
}
expected_files = set(generated_files)
assert len(changed_files) == len(expected_files), (
"Artifact count mismatch: expected "
f"{len(expected_files)} files, got {len(changed_files)}"
)
assert changed_files == expected_files, (
"Artifact membership mismatch: expected "
f"{sorted(expected_files)}, got {sorted(changed_files)}"
)
assert all(path.startswith("tests/") for path in changed_files), (
f"Expected test-only artifacts, got {sorted(changed_files)}"
)
assert all(not path.startswith("src/") for path in changed_files), (
"Invariant violated: production source path detected in artifacts"
)
persisted_changeset = store.get(changeset_id)
assert persisted_changeset is not None
persisted_paths = {entry.path for entry in persisted_changeset.entries}
assert len(persisted_paths) == len(expected_files), (
"Persisted artifact count mismatch: expected "
f"{len(expected_files)} files, got {len(persisted_paths)}"
)
assert persisted_paths == expected_files, (
"Persisted artifact membership mismatch: expected "
f"{sorted(expected_files)}, got {sorted(persisted_paths)}"
)
facade = A2aLocalFacade(
services={"plan_lifecycle_service": lifecycle},
)
artifacts_response = facade.dispatch(
A2aRequest(
method="_cleveragents/plan/artifacts",
params={"plan_id": plan_id},
)
)
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")
assert isinstance(sandbox_refs, list)
assert sandbox_refs, "Expected non-empty sandbox refs in artifacts response"
print("wf02-mocked-generation-artifacts-ok")