"""Step definitions for cloud_handler_coverage.feature. These steps target specific uncovered lines in cleveragents/resource/handlers/cloud.py: - Lines 249-251: extract_provider hierarchical prefix loop - Line 253: extract_provider returns None for cloud-* generic types - Lines 302-303: resolve_credentials explicit property priority - Lines 331-333: validate_credentials with unknown provider - Lines 420-423: CloudResourceHandler.resolve generic cloud type - Lines 428-431: CloudResourceHandler.resolve unknown provider mapping """ import os from unittest.mock import MagicMock, patch from behave import given, then, when from cleveragents.domain.models.core.resource import ( PhysVirt, Resource, ResourceCapabilities, SandboxStrategy, ) from cleveragents.resource.handlers.cloud import ( CloudResourceHandler, extract_provider, resolve_credentials, validate_credentials, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _VALID_ULID = "01ARZ3NDEKTSV4RRFFQ69G5FAV" def _make_resource(type_name: str, properties: dict | None = None) -> Resource: """Create a minimal Resource for testing.""" return Resource( resource_id=_VALID_ULID, resource_type_name=type_name, classification=PhysVirt.VIRTUAL, properties=properties or {}, sandbox_strategy=SandboxStrategy.NONE, capabilities=ResourceCapabilities(), ) # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("the cloud handler module is imported") def step_cloud_module_imported(context): """Ensure the cloud handler module is importable.""" assert extract_provider is not None assert resolve_credentials is not None assert validate_credentials is not None assert CloudResourceHandler is not None # --------------------------------------------------------------------------- # extract_provider: hierarchical prefix matching (lines 249-251) # --------------------------------------------------------------------------- @when('I extract the provider from type name "{type_name}"') def step_extract_provider(context, type_name): """Call extract_provider with the given type name.""" context.extracted_provider = extract_provider(type_name) @then('the extracted provider should be "{expected}"') def step_verify_extracted_provider(context, expected): """Verify the extracted provider matches the expected value.""" assert context.extracted_provider == expected, ( f"Expected provider '{expected}', got '{context.extracted_provider}'" ) @then("the extracted provider should be None") def step_verify_extracted_provider_none(context): """Verify the extracted provider is None.""" assert context.extracted_provider is None, ( f"Expected None, got '{context.extracted_provider}'" ) # --------------------------------------------------------------------------- # resolve_credentials: explicit properties take priority (lines 302-303) # --------------------------------------------------------------------------- @given('chcov the environment variable "{var_name}" is set to "{var_value}"') def step_set_env_var(context, var_name, var_value): """Set an environment variable, restoring on cleanup.""" original = os.environ.get(var_name) os.environ[var_name] = var_value def restore(): if original is None: os.environ.pop(var_name, None) else: os.environ[var_name] = original context.add_cleanup(restore) @when( 'I resolve credentials for provider "{provider}" with explicit property "{prop}" set to "{value}"' ) def step_resolve_with_explicit(context, provider, prop, value): """Resolve credentials with explicit properties.""" context.resolved_creds = resolve_credentials(provider, {prop: value}) @then('the resolved credential "{field}" should be "{expected}"') def step_verify_resolved_credential(context, field, expected): """Verify a specific resolved credential value.""" actual = context.resolved_creds.get(field) assert actual == expected, ( f"Expected resolved['{field}'] == '{expected}', got '{actual}'" ) # --------------------------------------------------------------------------- # validate_credentials: unknown provider (lines 331-333) # --------------------------------------------------------------------------- @when('I validate credentials for unknown provider "{provider}"') def step_validate_unknown_provider(context, provider): """Call validate_credentials with an unknown provider.""" context.validation_errors = validate_credentials(provider, {}) @then('the validation errors should mention unknown provider "{provider}"') def step_verify_unknown_provider_errors(context, provider): """Verify validation errors mention the unknown provider.""" assert len(context.validation_errors) > 0, "Expected at least one error" joined = " ".join(context.validation_errors) assert f"Unknown cloud provider '{provider}'" in joined, ( f"Expected mention of unknown provider '{provider}' in: {joined}" ) # --------------------------------------------------------------------------- # CloudResourceHandler.resolve: generic cloud-* type (lines 420-423) # --------------------------------------------------------------------------- @given('I have a cloud resource with type "{type_name}"') def step_create_cloud_resource(context, type_name): """Create a Resource with the given type name.""" context.cloud_resource = _make_resource(type_name) @when("I call resolve on the cloud resource handler") def step_call_handler_resolve(context): """Call CloudResourceHandler.resolve and capture the exception.""" handler = CloudResourceHandler() sandbox_mgr = MagicMock() try: handler.resolve( resource=context.cloud_resource, plan_id="plan-001", slot_name="test-slot", sandbox_manager=sandbox_mgr, ) context.handler_error = None context.handler_error_type = None except NotImplementedError as exc: context.handler_error = exc context.handler_error_type = "NotImplementedError" except ValueError as exc: context.handler_error = exc context.handler_error_type = "ValueError" @then('a NotImplementedError should be raised mentioning generic type "{type_name}"') def step_verify_not_implemented_generic(context, type_name): """Verify a NotImplementedError was raised with the correct message.""" assert context.handler_error_type == "NotImplementedError", ( f"Expected NotImplementedError, got {context.handler_error_type}: {context.handler_error}" ) msg = str(context.handler_error) assert type_name in msg, f"Expected '{type_name}' in error message: {msg}" assert "generic" in msg.lower(), f"Expected 'generic' in error message: {msg}" # --------------------------------------------------------------------------- # CloudResourceHandler.resolve: unknown provider mapping (lines 428-431) # --------------------------------------------------------------------------- @given('extract_provider is mocked to return "{fake_provider}"') def step_mock_extract_provider(context, fake_provider): """Patch extract_provider to return a fake provider string.""" context.fake_provider = fake_provider context.extract_patcher = patch( "cleveragents.resource.handlers.cloud.extract_provider", return_value=fake_provider, ) context.extract_patcher.start() context.add_cleanup(context.extract_patcher.stop) @when("I call resolve on the cloud resource handler expecting a ValueError") def step_call_handler_resolve_value_error(context): """Call CloudResourceHandler.resolve and capture the ValueError.""" handler = CloudResourceHandler() sandbox_mgr = MagicMock() try: handler.resolve( resource=context.cloud_resource, plan_id="plan-002", slot_name="test-slot", sandbox_manager=sandbox_mgr, ) context.handler_error = None context.handler_error_type = None except ValueError as exc: context.handler_error = exc context.handler_error_type = "ValueError" except NotImplementedError as exc: context.handler_error = exc context.handler_error_type = "NotImplementedError" @then('a ValueError should be raised mentioning unknown provider "{provider}"') def step_verify_value_error_unknown_provider(context, provider): """Verify a ValueError was raised mentioning the unknown provider.""" assert context.handler_error_type == "ValueError", ( f"Expected ValueError, got {context.handler_error_type}: {context.handler_error}" ) msg = str(context.handler_error) assert provider in msg, f"Expected '{provider}' in error message: {msg}" assert "unknown provider" in msg.lower() or "maps to unknown" in msg.lower(), ( f"Expected 'unknown provider' in error message: {msg}" )