"""Step definitions for application_container_coverage_boost.feature. Targets the remaining uncovered lines in ``cleveragents.application.container``: - Lines 187-195: ``_build_checkpoint_service`` function body - Lines 204-211: ``_build_trace_service`` function body """ from __future__ import annotations from typing import Any from unittest.mock import MagicMock from behave import given, then, when from cleveragents.application.container import ( _build_checkpoint_service, _build_session_factory, _build_skill_service, _build_trace_service, reset_container, ) from cleveragents.application.services.checkpoint_service import CheckpointService from cleveragents.application.services.skill_service import SkillService from cleveragents.application.services.trace_service import TraceService from cleveragents.infrastructure.database.llm_trace_repository import ( LLMTraceRepository, ) from cleveragents.infrastructure.database.repositories import CheckpointRepository _IN_MEMORY_URL = "sqlite:///:memory:" # ------------------------------------------------------------------- # Background # ------------------------------------------------------------------- @given("a clean container state for coverage boost tests") def step_clean_container_state(context): """Reset the global container to avoid cross-test interference.""" reset_container() # ------------------------------------------------------------------- # _build_checkpoint_service scenarios # ------------------------------------------------------------------- @given("a mock plan lifecycle service") def step_mock_plan_lifecycle_service(context): """Create a mock PlanLifecycleService for injection.""" context.boost_mock_lifecycle = MagicMock() context.boost_mock_lifecycle.__class__.__name__ = "PlanLifecycleService" @when("I build a checkpoint service with an in-memory database URL") def step_build_checkpoint_service_default(context): """Call _build_checkpoint_service with only a database URL (no lifecycle).""" context.boost_checkpoint_svc = _build_checkpoint_service(_IN_MEMORY_URL) @when("I build a checkpoint service with the mock plan lifecycle service") def step_build_checkpoint_service_with_lifecycle(context): """Call _build_checkpoint_service with a database URL and lifecycle service.""" context.boost_checkpoint_svc = _build_checkpoint_service( _IN_MEMORY_URL, plan_lifecycle_service=context.boost_mock_lifecycle, ) @then("acbs the result should be a CheckpointService instance") def step_verify_checkpoint_service_type(context): """Assert the returned object is a CheckpointService.""" assert isinstance(context.boost_checkpoint_svc, CheckpointService), ( f"Expected CheckpointService, got {type(context.boost_checkpoint_svc).__name__}" ) @then("the checkpoint service should have a repository") def step_verify_checkpoint_has_repository(context): """Assert the service was initialised with a CheckpointRepository.""" repo = context.boost_checkpoint_svc._repository assert repo is not None, "Expected a repository, got None" assert isinstance(repo, CheckpointRepository), ( f"Expected CheckpointRepository, got {type(repo).__name__}" ) @then("the checkpoint service should have no plan lifecycle service") def step_verify_checkpoint_no_lifecycle(context): """Assert the service has no plan_lifecycle_service (default None).""" assert context.boost_checkpoint_svc._plan_lifecycle_service is None @then("the checkpoint service should reference the mock plan lifecycle service") def step_verify_checkpoint_has_lifecycle(context): """Assert the service references the mock lifecycle we injected.""" assert ( context.boost_checkpoint_svc._plan_lifecycle_service is context.boost_mock_lifecycle ) # ------------------------------------------------------------------- # _build_trace_service scenarios # ------------------------------------------------------------------- @given("explicit application settings for trace service") def step_explicit_settings_for_trace(context): """Create a real Settings instance to pass explicitly.""" from cleveragents.config.settings import get_settings context.boost_explicit_settings = get_settings() @when("I build a trace service with an in-memory database URL and no explicit settings") def step_build_trace_service_default(context): """Call _build_trace_service with only a database URL.""" context.boost_trace_svc = _build_trace_service(_IN_MEMORY_URL) @when("I build a trace service with the explicit settings") def step_build_trace_service_with_settings(context): """Call _build_trace_service with a database URL and explicit settings.""" context.boost_trace_svc = _build_trace_service( _IN_MEMORY_URL, settings=context.boost_explicit_settings, ) @then("acbs the result should be a TraceService instance") def step_verify_trace_service_type(context): """Assert the returned object is a TraceService.""" assert isinstance(context.boost_trace_svc, TraceService), ( f"Expected TraceService, got {type(context.boost_trace_svc).__name__}" ) @then("the trace service should have a repository") def step_verify_trace_has_repository(context): """Assert the service was initialised with an LLMTraceRepository.""" repo = context.boost_trace_svc._repository assert repo is not None, "Expected a repository, got None" assert isinstance(repo, LLMTraceRepository), ( f"Expected LLMTraceRepository, got {type(repo).__name__}" ) @then("the trace service should have resolved settings from defaults") def step_verify_trace_default_settings(context): """Assert the service has settings resolved via get_settings() fallback.""" from cleveragents.config.settings import Settings svc_settings = context.boost_trace_svc._settings assert svc_settings is not None, "Expected settings, got None" assert isinstance(svc_settings, Settings), ( f"Expected Settings, got {type(svc_settings).__name__}" ) @then("the trace service should use the explicitly provided settings") def step_verify_trace_explicit_settings(context): """Assert the service uses the exact Settings object we injected.""" assert context.boost_trace_svc._settings is context.boost_explicit_settings, ( "Expected the explicitly provided settings instance" ) # ------------------------------------------------------------------- # _build_session_factory scenarios # ------------------------------------------------------------------- @when("I build a session factory with an in-memory database URL") def step_build_session_factory(context: Any) -> None: """Call _build_session_factory with an in-memory database URL.""" context.boost_session_factory = _build_session_factory(_IN_MEMORY_URL) @then("the result should be a callable sessionmaker") def step_verify_sessionmaker_callable(context: Any) -> None: """Assert the returned object is a callable sessionmaker.""" from sqlalchemy.orm import sessionmaker assert isinstance(context.boost_session_factory, sessionmaker), ( f"Expected sessionmaker, got {type(context.boost_session_factory).__name__}" ) assert callable(context.boost_session_factory) @then("calling the session factory should produce a Session") def step_verify_session_factory_produces_session(context: Any) -> None: """Assert that calling the factory returns a Session instance.""" from sqlalchemy.orm import Session session = context.boost_session_factory() try: assert isinstance(session, Session), ( f"Expected Session, got {type(session).__name__}" ) finally: session.close() @when("I build a session factory with an invalid database URL") def step_build_session_factory_invalid(context: Any) -> None: """Call _build_session_factory with an invalid URL — error deferred to usage.""" # SQLAlchemy lazily connects; the factory itself succeeds, but # calling it to create a session and executing SQL raises. context.boost_session_factory = _build_session_factory( "sqlite:////proc/nonexistent/impossible/db.sqlite" ) @then("a database error should be raised when the factory is called") def step_verify_session_factory_error(context: Any) -> None: """Assert that using the factory with an invalid URL raises an error.""" from sqlalchemy import text as sa_text from sqlalchemy.exc import OperationalError session = context.boost_session_factory() try: # Force a connection attempt by executing a trivial query. session.execute(sa_text("SELECT 1")) raise AssertionError("Expected OperationalError, but no error was raised") except OperationalError: pass # Expected finally: session.close() @when("I resolve session_factory from the container with an in-memory database URL") def step_resolve_container_session_factory(context: Any) -> None: """Resolve session_factory from the DI container using an in-memory URL.""" from unittest.mock import patch with patch.dict("os.environ", {"CLEVERAGENTS_DATABASE_URL": _IN_MEMORY_URL}): from cleveragents.application.container import get_container reset_container() try: container = get_container() context.boost_resolved_factory = container.session_factory() finally: reset_container() @then("the resolved session factory should be callable") def step_verify_resolved_factory_callable(context: Any) -> None: """Assert the container-resolved factory is callable.""" assert callable(context.boost_resolved_factory), ( "Expected a callable session factory from the container" ) @then("calling the resolved session factory should produce a Session") def step_verify_resolved_factory_produces_session(context: Any) -> None: """Assert that calling the resolved factory returns a Session instance.""" from sqlalchemy.orm import Session session = context.boost_resolved_factory() try: assert isinstance(session, Session), ( f"Expected Session, got {type(session).__name__}" ) finally: session.close() # ------------------------------------------------------------------- # _build_skill_service scenarios # ------------------------------------------------------------------- @when("I build a skill service with an in-memory database URL") def step_build_skill_service_default(context): """Call _build_skill_service with a valid in-memory database URL.""" context.boost_skill_svc = _build_skill_service(_IN_MEMORY_URL) @when("I build a skill service with an invalid database URL") def step_build_skill_service_invalid(context): """Mock create_engine to raise OperationalError, triggering the fallback.""" from unittest.mock import patch from sqlalchemy.exc import OperationalError with patch( "sqlalchemy.create_engine", side_effect=OperationalError("mock", {}, Exception("DB unavailable")), ): context.boost_skill_svc = _build_skill_service( "sqlite:////nonexistent/path/db.sqlite" ) @then("the result should be a SkillService instance") def step_verify_skill_service_type(context): """Assert the returned object is a SkillService.""" assert isinstance(context.boost_skill_svc, SkillService), ( f"Expected SkillService, got {type(context.boost_skill_svc).__name__}" ) @then("the skill service should have a repository") def step_verify_skill_has_repository(context): """Assert the service was initialised with a SkillRepository.""" assert context.boost_skill_svc._skill_repo is not None, ( "Expected a SkillRepository, got None" ) @then("the skill service should have no repository") def step_verify_skill_no_repository(context): """Assert the fallback service has no DB-backed repository.""" assert context.boost_skill_svc._skill_repo is None, ( "Expected no SkillRepository for in-memory fallback" )