forked from HAL9000/cleveragents-core
32b352832f
_get_tool_registry_service in cli/commands/validation.py was manually constructing the full ToolRegistryService dependency graph (create_engine, sessionmaker, ToolRegistryRepository, ValidationAttachmentRepository) instead of delegating to the DI container. This duplicated wiring logic that belongs exclusively in the container and made the function harder to test. Changes: - Add _build_tool_registry_service() builder function to container.py following the established _build_skill_service/_build_session_service pattern - Register tool_registry_service as a Singleton provider in the Container class - Refactor _get_tool_registry_service() to delegate to container.tool_registry_service() — a one-liner consistent with _get_skill_service() in cli/commands/skill.py - Add TDD Behave feature (tdd_di_tool_registry_service.feature) with two scenarios: (1) function delegates to container, (2) container exposes the provider — both scenarios were failing before this fix - Update validation_cli_uncovered_branches_steps.py to match the new container-delegation pattern (mock container.tool_registry_service() instead of container.database_url()) ISSUES CLOSED: #3006
149 lines
5.8 KiB
Python
149 lines
5.8 KiB
Python
"""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}"
|
|
)
|