"""Step definitions for container_coverage_r2.feature. Covers remaining uncovered lines and partial branches in ``cleveragents.application.container``: - Lines 66-67, 69: get_ai_provider non-mock paths - Lines 125-130: _build_project_resource_link_repo - Partial branches at lines 51, 57, 82, 87, 256, 284-285, 288 All step text uses the ``r2cont-`` prefix to avoid collisions with existing step definitions in container_and_repository_coverage_steps.py. """ from __future__ import annotations import os import sys from typing import Any from unittest.mock import MagicMock from behave import given, then, when from behave.runner import Context # ------------------------------------------------------------------- # Helpers # ------------------------------------------------------------------- def _save_env(context: Context, key: str) -> None: """Save an environment variable for later restoration.""" if not hasattr(context, "_r2cont_saved_env"): context._r2cont_saved_env = {} if key not in context._r2cont_saved_env: context._r2cont_saved_env[key] = os.environ.get(key) def _restore_env(context: Context) -> None: """Restore all saved environment variables.""" saved: dict[str, str | None] = getattr(context, "_r2cont_saved_env", {}) for key, original in saved.items(): if original is None: os.environ.pop(key, None) else: os.environ[key] = original def _register_cleanup(context: Context, fn: Any) -> None: """Register a cleanup callable on the behave context.""" if not hasattr(context, "_r2cont_cleanups"): context._r2cont_cleanups = [] context._r2cont_cleanups.append(fn) def _run_cleanups(context: Context) -> None: """Run all registered cleanups (called in after_scenario hook or manually).""" for fn in getattr(context, "_r2cont_cleanups", []): fn() context._r2cont_cleanups = [] # ------------------------------------------------------------------- # Given: get_ai_provider setup # ------------------------------------------------------------------- @given("r2cont- the mock AI environment flag is disabled") def step_disable_mock_ai(context: Context) -> None: """Ensure CLEVERAGENTS_TESTING_USE_MOCK_AI is not set.""" _save_env(context, "CLEVERAGENTS_TESTING_USE_MOCK_AI") os.environ.pop("CLEVERAGENTS_TESTING_USE_MOCK_AI", None) _register_cleanup(context, lambda: _restore_env(context)) @given("r2cont- a provider registry with no configured providers") def step_registry_no_providers(context: Context) -> None: """Create a mock ProviderRegistry that returns no configured providers.""" mock_registry = MagicMock() mock_registry.get_configured_providers.return_value = [] context.r2cont_registry = mock_registry @given("r2cont- a provider registry with configured providers") def step_registry_with_providers(context: Context) -> None: """Create a mock ProviderRegistry that reports configured providers.""" mock_registry = MagicMock() mock_registry.get_configured_providers.return_value = [MagicMock()] mock_provider = MagicMock() mock_registry.create_ai_provider.return_value = mock_provider context.r2cont_registry = mock_registry context.r2cont_expected_provider = mock_provider @given("r2cont- the mock AI environment flag is enabled") def step_enable_mock_ai(context: Context) -> None: """Set the env flag to trigger mock AI loading.""" _save_env(context, "CLEVERAGENTS_TESTING_USE_MOCK_AI") os.environ["CLEVERAGENTS_TESTING_USE_MOCK_AI"] = "true" _register_cleanup(context, lambda: _restore_env(context)) @given("r2cont- the features path is already on sys.path") def step_features_path_on_syspath(context: Context) -> None: """Ensure the features directory IS on sys.path before calling get_ai_provider.""" from pathlib import Path as _Path # Compute the features path the same way container.py does: # Path(__file__).parent.parent.parent.parent / "features" # where __file__ is src/cleveragents/application/container.py from cleveragents.application.container import __file__ as container_file feat_path = str(_Path(container_file).parent.parent.parent.parent / "features") context.r2cont_features_path = feat_path if feat_path not in sys.path: sys.path.insert(0, feat_path) # Record the count so we can verify no duplicates were added context.r2cont_features_path_count_before = sys.path.count(feat_path) def cleanup() -> None: # Remove any extra entries we may have added, restoring original count while sys.path.count(feat_path) > context.r2cont_features_path_count_before: sys.path.remove(feat_path) _register_cleanup(context, cleanup) # ------------------------------------------------------------------- # Given: get_database_url env var setup # ------------------------------------------------------------------- @given("r2cont- no database URL environment variables are set") def step_clear_all_db_env_vars(context: Context) -> None: """Remove both database URL env vars.""" for key in ("CLEVERAGENTS_DATABASE_URL", "CLEVERAGENTS_TEST_DATABASE_URL"): _save_env(context, key) os.environ.pop(key, None) _register_cleanup(context, lambda: _restore_env(context)) @given('r2cont- the env var "{key}" is set to empty') def step_set_env_var_empty(context: Context, key: str) -> None: """Set a specific environment variable to the empty string.""" _save_env(context, key) os.environ[key] = "" _register_cleanup(context, lambda: _restore_env(context)) @given('r2cont- the env var "{key}" is set to "{value}"') def step_set_env_var(context: Context, key: str, value: str) -> None: """Set a specific environment variable.""" _save_env(context, key) os.environ[key] = value _register_cleanup(context, lambda: _restore_env(context)) @given('r2cont- the env var "{key}" is cleared') def step_clear_env_var(context: Context, key: str) -> None: """Clear a specific environment variable.""" _save_env(context, key) os.environ.pop(key, None) _register_cleanup(context, lambda: _restore_env(context)) # ------------------------------------------------------------------- # Given: Container singleton setup # ------------------------------------------------------------------- @given("r2cont- the global container has been reset") def step_reset_global_container(context: Context) -> None: """Reset the global container singleton to None.""" from cleveragents.application.container import reset_container reset_container() _register_cleanup(context, reset_container) # ------------------------------------------------------------------- # When: get_ai_provider # ------------------------------------------------------------------- @when("r2cont- I call get_ai_provider with the mocked registry") def step_call_get_ai_provider_mocked(context: Context) -> None: """Call get_ai_provider passing the mock settings and registry.""" from cleveragents.application.container import get_ai_provider mock_settings = MagicMock() # Ensure Settings.mock_providers is explicitly False so the non-mock # branch is exercised (MagicMock auto-attributes are truthy). mock_settings.mock_providers = False context.r2cont_ai_result = get_ai_provider( settings=mock_settings, provider_registry=context.r2cont_registry, ) @when("r2cont- I call get_ai_provider without registry args") def step_call_get_ai_provider_default(context: Context) -> None: """Call get_ai_provider using defaults (will trigger mock AI path).""" from cleveragents.application.container import get_ai_provider context.r2cont_ai_result = get_ai_provider() # ------------------------------------------------------------------- # When: _build_project_resource_link_repo # ------------------------------------------------------------------- @when("r2cont- I build a project resource link repo with an in-memory database") def step_build_project_resource_link_repo(context: Context) -> None: """Call _build_project_resource_link_repo with sqlite memory URL.""" from cleveragents.application.container import _build_project_resource_link_repo context.r2cont_link_repo = _build_project_resource_link_repo("sqlite:///:memory:") # ------------------------------------------------------------------- # When: get_database_url # ------------------------------------------------------------------- @when("r2cont- I call get_database_url") def step_call_get_database_url(context: Context) -> None: """Call get_database_url and store the result.""" from cleveragents.application.container import get_database_url context.r2cont_db_url = get_database_url() # ------------------------------------------------------------------- # When: get_container # ------------------------------------------------------------------- @when("r2cont- I call get_container twice") def step_call_get_container_twice(context: Context) -> None: """Call get_container twice and store both references.""" from cleveragents.application.container import get_container context.r2cont_container_first = get_container() context.r2cont_container_second = get_container() # ------------------------------------------------------------------- # When: override_providers - non-existent name # ------------------------------------------------------------------- @when("r2cont- I override a provider with a non-existent name") def step_override_nonexistent(context: Context) -> None: """Call override_providers with a name that doesn't exist on Container.""" from cleveragents.application.container import override_providers context.r2cont_override_error = None try: override_providers(this_provider_does_not_exist="some_value") except Exception as exc: context.r2cont_override_error = exc # ------------------------------------------------------------------- # When: override_providers - non-database_url provider # ------------------------------------------------------------------- @when("r2cont- I override the settings provider with a mock value") def step_override_settings(context: Context) -> None: """Override the 'settings' provider (not database_url) to hit the else branch.""" from cleveragents.application.container import override_providers mock_settings = MagicMock() mock_settings._r2cont_marker = "mocked_settings" context.r2cont_mock_settings = mock_settings override_providers(settings=mock_settings) # ------------------------------------------------------------------- # Then: get_ai_provider assertions # ------------------------------------------------------------------- @then("r2cont- the ai provider result should be None") def step_ai_provider_none(context: Context) -> None: """Assert get_ai_provider returned None.""" assert context.r2cont_ai_result is None, ( f"Expected None, got {context.r2cont_ai_result}" ) @then("r2cont- the ai provider result should not be None") def step_ai_provider_not_none(context: Context) -> None: """Assert get_ai_provider returned a provider instance.""" assert context.r2cont_ai_result is not None, "Expected a provider, got None" assert context.r2cont_ai_result is context.r2cont_expected_provider @then("r2cont- the features path should appear exactly once on sys.path") def step_features_path_no_duplicate(context: Context) -> None: """Verify the features path was not duplicated on sys.path.""" count = sys.path.count(context.r2cont_features_path) assert count == context.r2cont_features_path_count_before, ( f"Expected features path count {context.r2cont_features_path_count_before}, " f"got {count}" ) _run_cleanups(context) # ------------------------------------------------------------------- # Then: _build_project_resource_link_repo assertions # ------------------------------------------------------------------- @then("r2cont- the result should be a ProjectResourceLinkRepository instance") def step_verify_link_repo_type(context: Context) -> None: """Assert the returned object is a ProjectResourceLinkRepository.""" from cleveragents.infrastructure.database.repositories import ( ProjectResourceLinkRepository, ) assert isinstance(context.r2cont_link_repo, ProjectResourceLinkRepository), ( f"Expected ProjectResourceLinkRepository, " f"got {type(context.r2cont_link_repo).__name__}" ) # ------------------------------------------------------------------- # Then: get_database_url assertions # ------------------------------------------------------------------- @then('r2cont- the database URL should start with "{prefix}"') def step_db_url_starts_with(context: Context, prefix: str) -> None: """Assert the database URL starts with the given prefix.""" assert context.r2cont_db_url.startswith(prefix), ( f"Expected URL starting with '{prefix}', got '{context.r2cont_db_url}'" ) _run_cleanups(context) @then('r2cont- the database URL should be "{expected}"') def step_db_url_exact(context: Context, expected: str) -> None: """Assert the database URL matches exactly.""" assert context.r2cont_db_url == expected, ( f"Expected '{expected}', got '{context.r2cont_db_url}'" ) _run_cleanups(context) # ------------------------------------------------------------------- # Then: get_container assertions # ------------------------------------------------------------------- @then("r2cont- both calls should return the same container instance") def step_same_container(context: Context) -> None: """Assert both get_container calls returned the same object.""" assert context.r2cont_container_first is context.r2cont_container_second, ( "Expected the same container instance on both calls" ) _run_cleanups(context) # ------------------------------------------------------------------- # Then: override_providers assertions # ------------------------------------------------------------------- @then("r2cont- no error should have been raised") def step_no_override_error(context: Context) -> None: """Assert no exception was raised during override_providers.""" assert context.r2cont_override_error is None, ( f"Unexpected error: {context.r2cont_override_error}" ) _run_cleanups(context) @then("r2cont- the settings provider should return the mock value") def step_settings_overridden(context: Context) -> None: """Assert the settings provider now returns our mock.""" from cleveragents.application.container import get_container container = get_container() result = container.settings() assert result is context.r2cont_mock_settings, ( f"Expected mocked settings, got {result}" ) _run_cleanups(context)