forked from HAL9000/cleveragents-core
02250473ad
Fix all failing CI quality gates (lint, unit_tests, format) without suppressing any quality enforcement. Root causes and fixes: 1. Format: features/steps/plan_namespaced_name_tdd_steps.py had trailing whitespace; fixed by running ruff format. 2. Unit tests - A2A JSON-RPC 2.0 migration (commit9c6d6915) renamed A2aRequest fields (operation→method, request_id→id, a2a_version→jsonrpc) and A2aResponse fields (status+data→result, request_id→id) but did not update all step files and feature files: - a2a_jsonrpc_wire_format_steps.py: added use_step_matcher('re') and reset to 'parse' at end to prevent parallel test interference - a2a_facade_wiring_steps.py: updated operation= to method=, .status/.data to .result - a2a_facade_steps.py: updated request_id→id, a2a_version→jsonrpc, A2aResponse(request_id=..., status=...) to new API - m6_facade_steps.py: updated all old API usage - devcontainer_cleanup_steps.py: updated A2aRequest(operation=...) - plan_prompt_command_steps.py: updated A2aRequest(operation=...) - wf03_plan_prompt_confidence_steps.py: updated A2aRequest(operation=...) - consolidated_misc.feature: updated old A2aRequest/A2aResponse scenarios 3. Unit tests - Session CLI output changed (commit0d5d9cf0and others): - 'Session Created' → 'Session created' (lowercase) - 'Session Details' → 'Session Summary' - 'Sessions (N total)' → 'Sessions' - session list JSON: top-level 'total' → nested 'summary.total' - Fixed in: session_cli.feature, session_cli_coverage_boost.feature, session_cli_uncovered_branches.feature, session_list_error.feature, tdd_session_create_persist_steps.py 4. Unit tests - Plan list output changed (commit1a07a891): - 'V3 Lifecycle Plans' → 'Plans' - 'Lifecycle Plans' → 'Plans' - Name column removed (restored in source) - Invariants column removed (restored in source) - Project truncation removed (restored in source) - Fixed in: plan_cli_cancel_revert_coverage.feature, plan_lifecycle_cli_coverage.feature, plan_cli_coverage_boost_steps.py, plan.py (source code restored) 5. Unit tests - Plan apply command now requires ULID (commit300a5d6d): - plan_cli_coverage_r3.feature: updated 'PLAN-001' to valid ULID - plan_cli_coverage_r3_steps.py: added --yes flag, added new step for no-eligible-plans path 6. Unit tests - Various source code bugs: - ThoughtBlock: converted from @dataclass to Pydantic BaseModel (architecture test requires all dataclasses to use Pydantic) - session.py: added DatabaseError handling to export, import, tell commands - database.py: fixed rollback_to() to reuse checkpoint connection for writes - database.py: added _get_checkpoint_conn() helper - check-tls-cert.py: fixed SSLCertVerificationError.reason AttributeError 7. Unit tests - Test step bugs: - error_recovery_coverage_boost_steps.py: fixed invalid ULID _PLAN_ID - session_service_coverage_steps.py: fixed 'sha256:' prefix bug in checksum - database_models_new_coverage_steps.py: added 'name' field to session mock - async_audit_recording_steps.py: fixed Settings(audit_async=False) via env var - coverage_threshold_config_steps.py: added --coverage-min pattern support - m5_acms_smoke_steps.py: updated usage hint text - actor_cli_yaml_steps.py: updated 'Removed actor' → 'Actor removed' - aimodelscredentials_steps.py: set context.imported_class in import step - domain_base_model.feature: added missing 'When I examine model_config' step - tui_first_run_steps.py: fixed module reload to restore cleveragents.tui.* modules after test (prevented patch interference in subsequent tests) - tui_first_run_steps.py: added set_search('') step for empty string - resource_handler_base_coverage_r3_steps.py: use _MinimalHandler instead of DatabaseResourceHandler for NotImplementedError tests - resource_handler_crud.feature: updated to test new DatabaseHandler behavior - resource_handler_sandbox.feature: updated to test new DatabaseHandler behavior - tdd_json_decode_crash_persistence.feature: fixed @tdd_bug → @tdd_issue tags 8. Parallel test interference: - All step files using use_step_matcher('re') now reset to 'parse' at end to prevent global matcher state leaking to subsequent step files
340 lines
12 KiB
Python
340 lines
12 KiB
Python
"""Step definitions for resource_handler_base_coverage_r3.feature.
|
|
|
|
Covers uncovered lines in _base.py:
|
|
- L132-134: resolve() RuntimeError when sandbox.context is None
|
|
- L159-160: _require_location() ValueError for no location
|
|
- L224-225: delete() NotImplementedError
|
|
- L238-239: list_children() NotImplementedError
|
|
- L252: diff() NotImplementedError
|
|
- L264-265: discover_children() NotImplementedError
|
|
- L305-307: create_sandbox() RuntimeError when sandbox.context is None
|
|
- L376-382: project_access() ImportError fallback (local mode)
|
|
- L384, L386-391: project_access() ValueError (invalid action)
|
|
|
|
All step text uses the ``rhbcov`` prefix to avoid collisions.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.domain.models.core.resource import (
|
|
PhysVirt,
|
|
Resource,
|
|
ResourceCapabilities,
|
|
SandboxStrategy,
|
|
)
|
|
from cleveragents.resource.handlers._base import BaseResourceHandler
|
|
from cleveragents.resource.handlers.database import DatabaseResourceHandler
|
|
|
|
__all__: list[str] = []
|
|
|
|
|
|
class _MinimalHandler(BaseResourceHandler):
|
|
"""Minimal handler that only implements required abstract methods."""
|
|
|
|
_default_strategy = SandboxStrategy.OVERLAY
|
|
_type_label = "test"
|
|
|
|
def read(self, *, resource: object, path: str = "") -> object:
|
|
raise NotImplementedError("test handler does not support read()")
|
|
|
|
def write(self, *, resource: object, path: str = "", data: bytes = b"") -> object:
|
|
raise NotImplementedError("test handler does not support write()")
|
|
|
|
|
|
_CB32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
|
|
_RHBCOV_COUNTER = 0
|
|
|
|
|
|
def _next_id() -> str:
|
|
"""Generate a valid 26-char Crockford Base32 ID for tests."""
|
|
global _RHBCOV_COUNTER
|
|
_RHBCOV_COUNTER += 1
|
|
n = _RHBCOV_COUNTER + 0xBBC000
|
|
chars: list[str] = []
|
|
for _ in range(26):
|
|
chars.append(_CB32[n % 32])
|
|
n //= 32
|
|
return "".join(reversed(chars))
|
|
|
|
|
|
def _make_resource(
|
|
rtype: str, location: str | None, rid: str | None = None
|
|
) -> Resource:
|
|
return Resource(
|
|
resource_id=rid or _next_id(),
|
|
resource_type_name=rtype,
|
|
classification=PhysVirt.PHYSICAL,
|
|
location=location,
|
|
capabilities=ResourceCapabilities(
|
|
readable=True, writable=True, sandboxable=True
|
|
),
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("rhbcov a database handler with a located resource")
|
|
def step_rhbcov_db_handler_located(context: Context) -> None:
|
|
# Use minimal handler to test base class NotImplementedError defaults
|
|
context.rhbcov_handler = _MinimalHandler()
|
|
context.rhbcov_resource = _make_resource("postgres", "/tmp/rhbcov-fake-db")
|
|
context.rhbcov_error = None
|
|
context.rhbcov_result = None
|
|
|
|
|
|
@given("rhbcov a database handler with a locationless resource")
|
|
def step_rhbcov_db_handler_no_location(context: Context) -> None:
|
|
context.rhbcov_handler = DatabaseResourceHandler()
|
|
context.rhbcov_resource = _make_resource("postgres", None)
|
|
context.rhbcov_error = None
|
|
context.rhbcov_result = None
|
|
|
|
|
|
@given("rhbcov a mock sandbox manager returning context-less sandbox")
|
|
def step_rhbcov_mock_sandbox_mgr_no_context(context: Context) -> None:
|
|
"""Create a mock SandboxManager whose get_or_create_sandbox returns
|
|
a sandbox with context=None, triggering the RuntimeError in resolve().
|
|
"""
|
|
mock_sandbox = MagicMock()
|
|
mock_sandbox.context = None
|
|
mock_sandbox.sandbox_id = "mock-sandbox-001"
|
|
|
|
mock_mgr = MagicMock()
|
|
mock_mgr.get_or_create_sandbox.return_value = mock_sandbox
|
|
|
|
context.rhbcov_sandbox_mgr = mock_mgr
|
|
|
|
|
|
@given(
|
|
"rhbcov a mock sandbox manager returning no existing sandbox and context-less new sandbox"
|
|
)
|
|
def step_rhbcov_mock_sandbox_mgr_create_no_context(context: Context) -> None:
|
|
"""For create_sandbox: get_sandbox returns None (no existing sandbox),
|
|
then get_or_create_sandbox returns a sandbox with context=None.
|
|
"""
|
|
mock_sandbox = MagicMock()
|
|
mock_sandbox.context = None
|
|
mock_sandbox.sandbox_id = "mock-sandbox-002"
|
|
|
|
mock_mgr = MagicMock()
|
|
mock_mgr.get_sandbox.return_value = None
|
|
mock_mgr.get_or_create_sandbox.return_value = mock_sandbox
|
|
|
|
context.rhbcov_sandbox_mgr = mock_mgr
|
|
|
|
|
|
@given("rhbcov the permission service import is patched to raise ImportError")
|
|
def step_rhbcov_patch_permission_import_error(context: Context) -> None:
|
|
"""Patch sys.modules to make the permission_service import fail with
|
|
ImportError inside project_access().
|
|
"""
|
|
patcher = patch.dict(
|
|
sys.modules,
|
|
{
|
|
"cleveragents.application.services.permission_service": None,
|
|
},
|
|
)
|
|
patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
@given("rhbcov the permission service is patched to raise ValueError on action")
|
|
def step_rhbcov_patch_permission_value_error(context: Context) -> None:
|
|
"""Patch the permission service so it imports successfully but
|
|
PermissionAction() raises ValueError for the invalid action string.
|
|
|
|
We patch get_default_permission_service to return a mock service,
|
|
and PermissionAction to raise ValueError when called.
|
|
"""
|
|
# Create a mock module for permission_service
|
|
mock_perm_svc_module = MagicMock()
|
|
mock_perm_svc_module.get_default_permission_service.return_value = MagicMock()
|
|
|
|
# Create a mock module for permission models where PermissionAction raises
|
|
mock_perm_module = MagicMock()
|
|
mock_perm_module.PermissionAction.side_effect = ValueError(
|
|
"'invalid_action_xyz' is not a valid PermissionAction"
|
|
)
|
|
mock_perm_module.PermissionScope = MagicMock()
|
|
|
|
patcher = patch.dict(
|
|
sys.modules,
|
|
{
|
|
"cleveragents.application.services.permission_service": mock_perm_svc_module,
|
|
"cleveragents.domain.models.core.permission": mock_perm_module,
|
|
},
|
|
)
|
|
patcher.start()
|
|
context.add_cleanup(patcher.stop)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("rhbcov I call resolve on the handler")
|
|
def step_rhbcov_call_resolve(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.resolve(
|
|
resource=context.rhbcov_resource,
|
|
plan_id=_next_id(),
|
|
slot_name="test-slot",
|
|
sandbox_manager=context.rhbcov_sandbox_mgr,
|
|
access="read_only",
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when("rhbcov I call _require_location on the handler")
|
|
def step_rhbcov_call_require_location(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler._require_location(
|
|
context.rhbcov_resource
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when("rhbcov I call delete on the base handler")
|
|
def step_rhbcov_call_delete(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.delete(
|
|
resource=context.rhbcov_resource, path="some/path"
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when("rhbcov I call list_children on the base handler")
|
|
def step_rhbcov_call_list_children(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.list_children(
|
|
resource=context.rhbcov_resource
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when("rhbcov I call diff on the base handler")
|
|
def step_rhbcov_call_diff(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.diff(
|
|
resource=context.rhbcov_resource, other_location="/tmp/other"
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when("rhbcov I call discover_children on the base handler")
|
|
def step_rhbcov_call_discover_children(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.discover_children(
|
|
resource=context.rhbcov_resource
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when("rhbcov I call create_sandbox on the handler")
|
|
def step_rhbcov_call_create_sandbox(context: Context) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.create_sandbox(
|
|
resource=context.rhbcov_resource,
|
|
plan_id=_next_id(),
|
|
sandbox_manager=context.rhbcov_sandbox_mgr,
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
@when('rhbcov I call project_access with principal "{principal}" and action "{action}"')
|
|
def step_rhbcov_call_project_access(
|
|
context: Context, principal: str, action: str
|
|
) -> None:
|
|
try:
|
|
context.rhbcov_result = context.rhbcov_handler.project_access(
|
|
resource=context.rhbcov_resource,
|
|
principal=principal,
|
|
action=action,
|
|
)
|
|
except Exception as exc:
|
|
context.rhbcov_error = exc
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('rhbcov a RuntimeError should be stored containing "{fragment}"')
|
|
def step_rhbcov_runtime_error(context: Context, fragment: str) -> None:
|
|
assert context.rhbcov_error is not None, "Expected an error but none was raised"
|
|
assert isinstance(context.rhbcov_error, RuntimeError), (
|
|
f"Expected RuntimeError, got {type(context.rhbcov_error).__name__}: "
|
|
f"{context.rhbcov_error}"
|
|
)
|
|
assert fragment in str(context.rhbcov_error), (
|
|
f"'{fragment}' not found in error message: '{context.rhbcov_error}'"
|
|
)
|
|
|
|
|
|
@then('rhbcov a ValueError should be stored containing "{fragment}"')
|
|
def step_rhbcov_value_error(context: Context, fragment: str) -> None:
|
|
assert context.rhbcov_error is not None, "Expected an error but none was raised"
|
|
assert isinstance(context.rhbcov_error, ValueError), (
|
|
f"Expected ValueError, got {type(context.rhbcov_error).__name__}: "
|
|
f"{context.rhbcov_error}"
|
|
)
|
|
assert fragment in str(context.rhbcov_error), (
|
|
f"'{fragment}' not found in error message: '{context.rhbcov_error}'"
|
|
)
|
|
|
|
|
|
@then('rhbcov a NotImplementedError should be stored containing "{fragment}"')
|
|
def step_rhbcov_not_implemented_error(context: Context, fragment: str) -> None:
|
|
assert context.rhbcov_error is not None, "Expected an error but none was raised"
|
|
assert isinstance(context.rhbcov_error, NotImplementedError), (
|
|
f"Expected NotImplementedError, got "
|
|
f"{type(context.rhbcov_error).__name__}: {context.rhbcov_error}"
|
|
)
|
|
assert fragment in str(context.rhbcov_error), (
|
|
f"'{fragment}' not found in error message: '{context.rhbcov_error}'"
|
|
)
|
|
|
|
|
|
@then("rhbcov the access result should be permitted")
|
|
def step_rhbcov_access_permitted(context: Context) -> None:
|
|
assert context.rhbcov_error is None, f"Unexpected error: {context.rhbcov_error}"
|
|
assert context.rhbcov_result is not None, "Expected an AccessResult, got None"
|
|
assert context.rhbcov_result.permitted is True, (
|
|
f"Expected permitted=True, got {context.rhbcov_result.permitted}"
|
|
)
|
|
|
|
|
|
@then("rhbcov the access result should not be permitted")
|
|
def step_rhbcov_access_not_permitted(context: Context) -> None:
|
|
assert context.rhbcov_error is None, f"Unexpected error: {context.rhbcov_error}"
|
|
assert context.rhbcov_result is not None, "Expected an AccessResult, got None"
|
|
assert context.rhbcov_result.permitted is False, (
|
|
f"Expected permitted=False, got {context.rhbcov_result.permitted}"
|
|
)
|
|
|
|
|
|
@then('rhbcov the access result reason should contain "{fragment}"')
|
|
def step_rhbcov_access_reason(context: Context, fragment: str) -> None:
|
|
assert context.rhbcov_result is not None, "Expected an AccessResult, got None"
|
|
assert fragment in context.rhbcov_result.reason, (
|
|
f"'{fragment}' not found in reason: '{context.rhbcov_result.reason}'"
|
|
)
|