"""Step definitions for application_container_coverage_boost_r2.feature. Targets uncovered lines in ``cleveragents.application.container``: - Lines 201-206: ``_build_repo_indexing_service`` - Lines 213-218: ``_build_resource_registry_service`` - Lines 221-230: ``_build_namespaced_project_repo`` - Lines 269-278: ``_resolve_auto_reindex`` - Lines 283-287: ``_build_analyzer_registry`` - Lines 290-298: ``_build_resource_file_watcher`` - Lines 316-357: ``_build_session_service`` All step text uses the ``r2cov-`` prefix to avoid collisions with existing step definitions. """ from __future__ import annotations from dataclasses import dataclass from typing import Any from unittest.mock import MagicMock, patch from behave import given, then, when from behave.runner import Context from cleveragents.application.container import ( _build_analyzer_registry, _build_namespaced_project_repo, _build_repo_indexing_service, _build_resource_file_watcher, _build_resource_registry_service, _build_session_service, _resolve_auto_reindex, reset_container, ) from cleveragents.application.services.repo_indexing_service import RepoIndexingService from cleveragents.application.services.resource_file_watcher import ResourceFileWatcher from cleveragents.application.services.resource_registry_service import ( ResourceRegistryService, ) from cleveragents.application.services.session_service import PersistentSessionService from cleveragents.domain.models.acms.analyzers import AnalyzerRegistry from cleveragents.infrastructure.database.repositories import ( NamespacedProjectRepository, ) _IN_MEMORY_URL = "sqlite:///:memory:" # ------------------------------------------------------------------- # Background # ------------------------------------------------------------------- @given("a clean container state for r2 coverage tests") def step_clean_container_r2(context: Context) -> None: """Reset the global container to avoid cross-test interference.""" reset_container() # ------------------------------------------------------------------- # _build_repo_indexing_service (lines 201-206) # ------------------------------------------------------------------- @when("r2cov- I build a repo indexing service with an in-memory database") def step_build_repo_indexing_service(context: Context) -> None: """Call _build_repo_indexing_service with in-memory SQLite. We patch RepoIndexingService to avoid needing real DB tables (cleanup_stale_indexing runs in __init__), while still executing the engine/sessionmaker creation on lines 201-206. """ with patch("cleveragents.application.container.RepoIndexingService") as mock_cls: # Make the mock constructor return a stub that remembers the factory def capture_factory(session_factory: Any) -> MagicMock: inst = MagicMock(spec=RepoIndexingService) inst._session_factory = session_factory inst._mock_marker = True return inst mock_cls.side_effect = capture_factory context.r2cov_repo_indexing_svc = _build_repo_indexing_service(_IN_MEMORY_URL) @then("r2cov- the result should be a RepoIndexingService instance") def step_verify_repo_indexing_svc(context: Context) -> None: """Assert a service-like object was returned (mocked to avoid DB).""" svc = context.r2cov_repo_indexing_svc assert svc is not None, "Expected a service instance, got None" assert hasattr(svc, "_session_factory"), "Service should have _session_factory" @then("r2cov- the service should have a session factory") def step_verify_repo_indexing_session_factory(context: Context) -> None: """Assert the session factory was passed to the service.""" svc = context.r2cov_repo_indexing_svc assert svc._session_factory is not None, "Expected a session_factory, got None" assert callable(svc._session_factory), "session_factory should be callable" # ------------------------------------------------------------------- # _build_resource_registry_service (lines 213-218) # ------------------------------------------------------------------- @when("r2cov- I build a resource registry service with an in-memory database") def step_build_resource_registry_service(context: Context) -> None: """Call _build_resource_registry_service with in-memory SQLite. Patch ResourceRegistryService constructor to avoid DB table requirements. """ with patch( "cleveragents.application.container.ResourceRegistryService" ) as mock_cls: def capture_factory(session_factory: Any) -> MagicMock: inst = MagicMock(spec=ResourceRegistryService) inst._session_factory = session_factory return inst mock_cls.side_effect = capture_factory context.r2cov_resource_registry_svc = _build_resource_registry_service( _IN_MEMORY_URL ) @then("r2cov- the result should be a ResourceRegistryService instance") def step_verify_resource_registry_svc(context: Context) -> None: """Assert a service-like object was returned.""" svc = context.r2cov_resource_registry_svc assert svc is not None, "Expected a service instance, got None" assert hasattr(svc, "_session_factory"), "Service should have _session_factory" # ------------------------------------------------------------------- # _build_namespaced_project_repo (lines 221-230) # ------------------------------------------------------------------- @when("r2cov- I build a namespaced project repo with an in-memory database") def step_build_namespaced_project_repo(context: Context) -> None: """Call _build_namespaced_project_repo with in-memory SQLite. Patch NamespacedProjectRepository constructor to avoid DB table requirements. """ with patch( "cleveragents.application.container.NamespacedProjectRepository" ) as mock_cls: def capture_factory(session_factory: Any) -> MagicMock: inst = MagicMock(spec=NamespacedProjectRepository) inst._session_factory = session_factory return inst mock_cls.side_effect = capture_factory context.r2cov_namespaced_project_repo = _build_namespaced_project_repo( _IN_MEMORY_URL ) @then("r2cov- the result should be a NamespacedProjectRepository instance") def step_verify_namespaced_project_repo(context: Context) -> None: """Assert a repo-like object was returned.""" repo = context.r2cov_namespaced_project_repo assert repo is not None, "Expected a repo instance, got None" assert hasattr(repo, "_session_factory"), "Repo should have _session_factory" # ------------------------------------------------------------------- # _resolve_auto_reindex (lines 269-278) # ------------------------------------------------------------------- @dataclass class _FakeResolvedValue: """Minimal stand-in for ConfigService.resolve() return value.""" value: Any @given("r2cov- ConfigService resolve returns a truthy value for auto-reindex") def step_config_resolve_truthy(context: Context) -> None: """Patch ConfigService so resolve returns a truthy value.""" mock_svc = MagicMock() mock_svc.resolve.return_value = _FakeResolvedValue(value=True) context.r2cov_config_patcher = patch( "cleveragents.application.container.ConfigService", return_value=mock_svc, ) # Import ConfigService lazily inside _resolve_auto_reindex, so we # need to patch it in the container module's import path. # The function does: # from cleveragents.application.services.config_service import ConfigService # But since it's a local import, we patch the source module. context.r2cov_config_patcher = patch( "cleveragents.application.services.config_service.ConfigService", return_value=mock_svc, ) context.r2cov_config_patcher.start() def cleanup() -> None: context.r2cov_config_patcher.stop() context.add_cleanup(cleanup) @given("r2cov- ConfigService resolve returns None for auto-reindex") def step_config_resolve_none(context: Context) -> None: """Patch ConfigService so resolve returns None value (defaults to True).""" mock_svc = MagicMock() mock_svc.resolve.return_value = _FakeResolvedValue(value=None) context.r2cov_config_patcher = patch( "cleveragents.application.services.config_service.ConfigService", return_value=mock_svc, ) context.r2cov_config_patcher.start() def cleanup() -> None: context.r2cov_config_patcher.stop() context.add_cleanup(cleanup) @given("r2cov- ConfigService resolve returns a falsy value for auto-reindex") def step_config_resolve_falsy(context: Context) -> None: """Patch ConfigService so resolve returns a falsy (False) value.""" mock_svc = MagicMock() mock_svc.resolve.return_value = _FakeResolvedValue(value=False) context.r2cov_config_patcher = patch( "cleveragents.application.services.config_service.ConfigService", return_value=mock_svc, ) context.r2cov_config_patcher.start() def cleanup() -> None: context.r2cov_config_patcher.stop() context.add_cleanup(cleanup) @given("r2cov- ConfigService raises an exception during resolve") def step_config_resolve_exception(context: Context) -> None: """Patch ConfigService so its constructor raises, hitting the except branch.""" context.r2cov_config_patcher = patch( "cleveragents.application.services.config_service.ConfigService", side_effect=RuntimeError("config unavailable"), ) context.r2cov_config_patcher.start() def cleanup() -> None: context.r2cov_config_patcher.stop() context.add_cleanup(cleanup) @when("r2cov- I call _resolve_auto_reindex") def step_call_resolve_auto_reindex(context: Context) -> None: """Call _resolve_auto_reindex and capture the result.""" context.r2cov_auto_reindex_result = _resolve_auto_reindex() @then("r2cov- the result should be True") def step_verify_result_true(context: Context) -> None: """Assert the result is True.""" assert context.r2cov_auto_reindex_result is True, ( f"Expected True, got {context.r2cov_auto_reindex_result!r}" ) @then("r2cov- the result should be False") def step_verify_result_false(context: Context) -> None: """Assert the result is False.""" assert context.r2cov_auto_reindex_result is False, ( f"Expected False, got {context.r2cov_auto_reindex_result!r}" ) # ------------------------------------------------------------------- # _build_analyzer_registry (lines 283-287) # ------------------------------------------------------------------- @when("r2cov- I build an analyzer registry") def step_build_analyzer_registry(context: Context) -> None: """Call _build_analyzer_registry directly.""" context.r2cov_analyzer_registry = _build_analyzer_registry() @then("r2cov- the result should be an AnalyzerRegistry instance") def step_verify_analyzer_registry(context: Context) -> None: """Assert the returned object is an AnalyzerRegistry.""" assert isinstance(context.r2cov_analyzer_registry, AnalyzerRegistry), ( f"Expected AnalyzerRegistry, got " f"{type(context.r2cov_analyzer_registry).__name__}" ) @then("r2cov- the registry should have a PythonAnalyzer for .py files") def step_verify_python_analyzer_registered(context: Context) -> None: """Assert a PythonAnalyzer is registered for .py extension.""" from cleveragents.domain.models.acms.python_analyzer import PythonAnalyzer registry: AnalyzerRegistry = context.r2cov_analyzer_registry analyzer = registry.get_for_extension(".py") assert analyzer is not None, "Expected an analyzer for .py, got None" assert isinstance(analyzer, PythonAnalyzer), ( f"Expected PythonAnalyzer, got {type(analyzer).__name__}" ) # ------------------------------------------------------------------- # _build_resource_file_watcher (lines 290-298) # ------------------------------------------------------------------- @given("r2cov- a mock event bus for the file watcher") def step_mock_event_bus_for_watcher(context: Context) -> None: """Create a mock ReactiveEventBus for ResourceFileWatcher construction.""" context.r2cov_mock_event_bus = MagicMock() @when("r2cov- I build a resource file watcher with the mock event bus") def step_build_resource_file_watcher(context: Context) -> None: """Call _build_resource_file_watcher with a mock event bus.""" context.r2cov_file_watcher = _build_resource_file_watcher( event_bus=context.r2cov_mock_event_bus, ) @then("r2cov- the result should be a ResourceFileWatcher instance") def step_verify_file_watcher(context: Context) -> None: """Assert the returned object is a ResourceFileWatcher.""" assert isinstance(context.r2cov_file_watcher, ResourceFileWatcher), ( f"Expected ResourceFileWatcher, got {type(context.r2cov_file_watcher).__name__}" ) @then("r2cov- the watcher should have auto_reindex enabled") def step_verify_watcher_auto_reindex(context: Context) -> None: """Assert the watcher was constructed with auto_reindex=True.""" watcher: ResourceFileWatcher = context.r2cov_file_watcher assert watcher._auto_reindex is True, ( f"Expected auto_reindex=True, got {watcher._auto_reindex!r}" ) # ------------------------------------------------------------------- # _build_session_service (lines 316-357) # ------------------------------------------------------------------- @given("r2cov- a mock event bus for the session service") def step_mock_event_bus_for_session(context: Context) -> None: """Create a mock ReactiveEventBus for session service construction.""" context.r2cov_session_event_bus = MagicMock() @when("r2cov- I build a session service with an in-memory database and no event bus") def step_build_session_service_no_bus(context: Context) -> None: """Call _build_session_service with in-memory SQLite and no event bus.""" context.r2cov_session_svc = _build_session_service(_IN_MEMORY_URL) @when( "r2cov- I build a session service with an in-memory database and the mock event bus" ) def step_build_session_service_with_bus(context: Context) -> None: """Call _build_session_service with in-memory SQLite and a mock event bus.""" context.r2cov_session_svc = _build_session_service( _IN_MEMORY_URL, event_bus=context.r2cov_session_event_bus ) @then("r2cov- the result should be a PersistentSessionService instance") def step_verify_session_svc(context: Context) -> None: """Assert the returned object is a PersistentSessionService.""" assert isinstance(context.r2cov_session_svc, PersistentSessionService), ( f"Expected PersistentSessionService, got " f"{type(context.r2cov_session_svc).__name__}" )