c301fc13dd
- 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
197 lines
6.8 KiB
Python
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")
|