"""Step definitions for validation CLI uncovered branches. Covers two gaps in ``cleveragents.cli.commands.validation``: 1. ``_get_tool_registry_service()`` (lines 75-95) — the full construction path that imports from the container, creates an engine/session-factory, builds repositories, and returns a ``ToolRegistryService``. 2. ``detach`` command branch L356→360 — when the user confirms the detach prompt but ``detach_validation`` returns ``False`` (attachment not found). """ from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch from behave import given, then, when from behave.runner import Context from typer.testing import CliRunner from cleveragents.cli.commands.validation import app as validation_app _runner = CliRunner() # --------------------------------------------------------------------------- # Patch targets # --------------------------------------------------------------------------- # We patch get_container *inside* the validation module's lazy import so the # real function body executes but uses our mock container. _PATCH_GET_CONTAINER = "cleveragents.application.container.get_container" # For the detach scenario we still patch the whole helper to isolate DB access. _PATCH_VAL_SVC = "cleveragents.cli.commands.validation._get_tool_registry_service" # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("a validation cli branch test runner with mocks") def step_validation_cli_branch_background(context: Context) -> None: context.vcb_runner = _runner context.vcb_result = None context.vcb_returned_service: Any = None context.vcb_mock_service: MagicMock | None = None # --------------------------------------------------------------------------- # Scenario 1: _get_tool_registry_service construction path (L75-95) # --------------------------------------------------------------------------- @given("the validation cli branch DI container provides a database url") def step_vcb_di_container(context: Context) -> None: """Set up a mock container whose ``tool_registry_service()`` returns a real ToolRegistryService backed by an in-memory SQLite database. After Forgejo #3006, ``_get_tool_registry_service`` delegates directly to ``container.tool_registry_service()`` — no manual engine/sessionmaker construction occurs in the function itself. """ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from cleveragents.application.services.tool_registry_service import ( ToolRegistryService, ) from cleveragents.infrastructure.database.repositories import ( ToolRegistryRepository, ValidationAttachmentRepository, ) engine = create_engine("sqlite:///:memory:", echo=False) factory = sessionmaker(bind=engine, expire_on_commit=False) tool_repo = ToolRegistryRepository(session_factory=factory) attachment_repo = ValidationAttachmentRepository(session_factory=factory) real_service = ToolRegistryService( tool_repo=tool_repo, attachment_repo=attachment_repo, ) mock_container = MagicMock() mock_container.tool_registry_service.return_value = real_service context.vcb_mock_container = mock_container @when("the validation cli branch _get_tool_registry_service is called") def step_vcb_call_get_tool_registry_service(context: Context) -> None: from cleveragents.cli.commands.validation import _get_tool_registry_service with patch(_PATCH_GET_CONTAINER, return_value=context.vcb_mock_container): context.vcb_returned_service = _get_tool_registry_service() @then("the validation cli branch returned service should be a ToolRegistryService") def step_vcb_verify_service_type(context: Context) -> None: from cleveragents.application.services.tool_registry_service import ( ToolRegistryService, ) assert context.vcb_returned_service is not None, ( "_get_tool_registry_service returned None" ) assert isinstance(context.vcb_returned_service, ToolRegistryService), ( f"Expected ToolRegistryService, got {type(context.vcb_returned_service)}" ) # --------------------------------------------------------------------------- # Scenario 2: detach — user confirms but attachment not found (L356→360) # --------------------------------------------------------------------------- @given("the validation cli branch detach service returns false") def step_vcb_detach_service_false(context: Context) -> None: """Prepare a mock service whose ``detach_validation`` returns ``False``.""" svc = MagicMock() svc.detach_validation.return_value = False context.vcb_mock_service = svc @when('the validation cli branch detach is invoked with confirmation for "{att_id}"') def step_vcb_detach_with_confirmation(context: Context, att_id: str) -> None: """Invoke the detach command *without* ``--yes`` and feed ``y`` to the confirmation prompt so the code reaches ``service.detach_validation`` which returns ``False`` — covering L356→360.""" with patch(_PATCH_VAL_SVC, return_value=context.vcb_mock_service): context.vcb_result = _runner.invoke( validation_app, ["detach", att_id], input="y\n", ) @then("the validation cli branch detach result should abort with not found") def step_vcb_detach_abort_not_found(context: Context) -> None: result = context.vcb_result assert result is not None, "No CLI result captured" # The command should abort (non-zero exit) assert result.exit_code != 0, ( f"Expected non-zero exit, got {result.exit_code}. Output: {result.output}" ) assert "Attachment not found" in result.output, ( f"Expected 'Attachment not found' in output, got: {result.output}" )