"""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}'" )