Files
cleveragents-core/features/steps/uow_coverage_boost_steps.py
T
freemo a808c395f9
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / build (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 17s
CI / typecheck (pull_request) Successful in 35s
CI / security (pull_request) Successful in 50s
CI / unit_tests (pull_request) Successful in 2m46s
CI / integration_tests (pull_request) Successful in 3m16s
CI / docker (pull_request) Successful in 40s
CI / coverage (pull_request) Successful in 5m6s
CI / lint (push) Successful in 13s
CI / quality (push) Successful in 16s
CI / build (push) Successful in 18s
CI / security (push) Successful in 32s
CI / typecheck (push) Successful in 35s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m52s
CI / integration_tests (push) Successful in 3m8s
CI / docker (push) Successful in 39s
CI / coverage (push) Successful in 5m53s
CI / benchmark-publish (push) Successful in 16m55s
CI / benchmark-regression (pull_request) Successful in 33m0s
test(coverage): add Behave BDD tests to improve unit test coverage across 53 source modules
Add 53 new .feature files and corresponding step definition files targeting
uncovered lines identified in build/coverage.xml. Fix AmbiguousStep conflicts
in 7 pre-existing step files by disambiguating step text.

New tests cover: ACP clients/facade, actor CLI/config, application container,
ACMS service/strategies, async worker, automation profile CLI, autonomy
guardrail, bridge, change model, config CLI/service, context service,
cross-plan correction, database models, decision service, decomposition
clustering/service, discovery handler, langchain chat provider, langgraph
nodes, materializers, multi-project service, plan apply/CLI/lifecycle/model/
preflight/resume/service, PostgreSQL analyzer, project CLI/context CLI,
provider registry, reactive application/route, repositories, resolver handler,
resource registry service, resume model, retry patterns, sandbox protocol,
server CLI, skill CLI/service, skills registry, subplan execution/service,
system CLI, UKO loader, UoW, and YAML template engine.

Closes #645
2026-03-09 13:01:58 -04:00

302 lines
12 KiB
Python

"""Step definitions for UnitOfWork coverage boost.
Targets uncovered lines in
``src/cleveragents/infrastructure/database/unit_of_work.py``:
- Lines 71-76: fresh in-memory engine creation (MEMORY_ENGINES cache miss)
- Lines 90-93: non-SQLite engine creation
- Lines 244-248: checkpoints property lazy init
- Line 312: refresh() method
"""
from __future__ import annotations
from unittest.mock import MagicMock, patch
from behave import given, then, when
from behave.runner import Context
from cleveragents.infrastructure.database.unit_of_work import (
UnitOfWork,
UnitOfWorkContext,
)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_mock_session() -> MagicMock:
"""Create a MagicMock that mimics a SQLAlchemy Session."""
session = MagicMock()
session.add = MagicMock()
session.flush = MagicMock()
session.refresh = MagicMock()
session.commit = MagicMock()
session.rollback = MagicMock()
session.close = MagicMock()
return session
# =========================================================================
# Non-SQLite engine (lines 90-93)
# =========================================================================
@given("a UnitOfWork configured with a non-SQLite database URL")
def step_uow_non_sqlite_url(context: Context) -> None:
"""Create a UnitOfWork with a PostgreSQL-style URL (never actually connects)."""
context.uow = UnitOfWork(
database_url="postgresql://user:pass@localhost:5432/testdb"
)
@when("I access the engine property with mocked create_engine")
def step_access_engine_property_mocked(context: Context) -> None:
"""Access the engine property, mocking _ensure_database_initialized and create_engine."""
from sqlalchemy.engine import Engine
mock_engine = MagicMock(spec=Engine)
with (
patch.object(context.uow, "_ensure_database_initialized"),
patch(
"cleveragents.infrastructure.database.unit_of_work.create_engine",
return_value=mock_engine,
) as mock_create,
):
engine = context.uow.engine
context.result_engine = engine
context.mock_create_engine = mock_create
@then("the engine should be created via the non-SQLite code path")
def step_engine_created_non_sqlite(context: Context) -> None:
"""Verify that create_engine was called (meaning the non-SQLite branch ran)."""
assert context.mock_create_engine.called, "create_engine was not called"
@then("the engine should not use SQLite-specific connect_args")
def step_engine_no_sqlite_args(context: Context) -> None:
"""Verify the call did not include connect_args or isolation_level."""
call_kwargs = context.mock_create_engine.call_args[1]
assert "connect_args" not in call_kwargs, (
f"connect_args should not be present for non-SQLite, got {call_kwargs}"
)
assert "isolation_level" not in call_kwargs, (
f"isolation_level should not be present for non-SQLite, got {call_kwargs}"
)
# =========================================================================
# Fresh in-memory engine (lines 71-76)
# =========================================================================
@given("the in-memory engine cache is cleared")
def step_clear_memory_engine_cache(context: Context) -> None:
"""Clear the MEMORY_ENGINES cache so the cache-miss branch is exercised."""
from cleveragents.infrastructure.database.engine_cache import MEMORY_ENGINES
# Save and clear so the test exercises the cache-miss path
context.saved_memory_engines = dict(MEMORY_ENGINES)
MEMORY_ENGINES.clear()
@given("a UnitOfWork configured with an in-memory SQLite URL")
def step_uow_in_memory_sqlite(context: Context) -> None:
"""Create a UnitOfWork targeting in-memory SQLite."""
context.uow = UnitOfWork(database_url="sqlite:///:memory:")
@when("I access the in-memory engine property with mocked migrations")
def step_access_in_memory_engine_mocked_migrations(context: Context) -> None:
"""Access the engine property, mocking only _ensure_database_initialized.
The real create_engine runs so that MEMORY_ENGINES is actually populated
with a real engine, exercising lines 71-76.
"""
with patch.object(context.uow, "_ensure_database_initialized"):
engine = context.uow.engine
context.result_engine = engine
@then("a new in-memory engine should be created and cached")
def step_engine_created_and_cached(context: Context) -> None:
"""Verify the engine was stored in MEMORY_ENGINES."""
from cleveragents.infrastructure.database.engine_cache import MEMORY_ENGINES
assert "sqlite:///:memory:" in MEMORY_ENGINES, (
"Expected sqlite:///:memory: in MEMORY_ENGINES cache"
)
assert context.result_engine is MEMORY_ENGINES["sqlite:///:memory:"], (
"Engine returned should be the same as the cached one"
)
@then("the cached engine should use SERIALIZABLE isolation")
def step_cached_engine_serializable(context: Context) -> None:
"""Verify the created engine is cached and has the correct URL.
The SERIALIZABLE isolation level is set at creation time. We verify the
engine is present and has the correct URL as a proxy.
"""
from cleveragents.infrastructure.database.engine_cache import MEMORY_ENGINES
engine = MEMORY_ENGINES.get("sqlite:///:memory:")
assert engine is not None, "Engine should be present in cache"
assert str(engine.url) == "sqlite:///:memory:", (
f"Expected sqlite:///:memory: URL, got {engine.url}"
)
# Restore saved cache state
if hasattr(context, "saved_memory_engines"):
MEMORY_ENGINES.clear()
MEMORY_ENGINES.update(context.saved_memory_engines)
# =========================================================================
# Checkpoint repository (lines 244-248)
# =========================================================================
@given("a UnitOfWorkContext backed by a mock session")
def step_uow_context_mock_session(context: Context) -> None:
"""Create a UnitOfWorkContext with a mock session."""
context.mock_session = _make_mock_session()
context.uow_ctx = UnitOfWorkContext(context.mock_session)
@when("I access the checkpoints repository from the context")
def step_access_checkpoints_repo(context: Context) -> None:
"""Access the checkpoints property."""
context.checkpoints_result = context.uow_ctx.checkpoints
@then("the checkpoints repository should be a CheckpointRepository instance")
def step_checkpoints_is_correct_type(context: Context) -> None:
"""Verify the result is a CheckpointRepository."""
from cleveragents.infrastructure.database.repositories import (
CheckpointRepository,
)
assert isinstance(context.checkpoints_result, CheckpointRepository), (
f"Expected CheckpointRepository, got {type(context.checkpoints_result)}"
)
@when("I access the checkpoints repository from the context twice")
def step_access_checkpoints_repo_twice(context: Context) -> None:
"""Access the checkpoints property twice."""
context.checkpoints_first = context.uow_ctx.checkpoints
context.checkpoints_second = context.uow_ctx.checkpoints
@then("both accesses should return the same CheckpointRepository instance")
def step_checkpoints_cached(context: Context) -> None:
"""Verify both accesses return the same object (lazy caching)."""
assert context.checkpoints_first is context.checkpoints_second, (
"Expected same CheckpointRepository instance on repeated access"
)
# =========================================================================
# refresh() method (line 312)
# =========================================================================
@when("I call refresh with a dummy entity")
def step_call_refresh(context: Context) -> None:
"""Call refresh() with a dummy entity object."""
context.dummy_entity = MagicMock(name="DummyEntity")
context.uow_ctx.refresh(context.dummy_entity)
@then("session.refresh should have been called with that entity")
def step_verify_session_refresh_called(context: Context) -> None:
"""Verify the underlying session.refresh was called with the entity."""
context.mock_session.refresh.assert_called_once_with(context.dummy_entity)
# =========================================================================
# Non-SQLite engine options (lines 90-93 deeper verification)
# =========================================================================
@given("a UnitOfWork configured with a postgresql-style database URL")
def step_uow_postgresql_url(context: Context) -> None:
"""Create a UnitOfWork with a PostgreSQL URL."""
context.uow = UnitOfWork(
database_url="postgresql+psycopg2://user:pass@db.example.com:5432/myapp"
)
@when("I trigger engine creation with mocked dependencies")
def step_trigger_engine_creation_mocked(context: Context) -> None:
"""Access engine property with mocked create_engine and migration runner."""
from sqlalchemy.engine import Engine
mock_engine = MagicMock(spec=Engine)
with (
patch.object(context.uow, "_ensure_database_initialized"),
patch(
"cleveragents.infrastructure.database.unit_of_work.create_engine",
return_value=mock_engine,
) as mock_create,
):
engine = context.uow.engine
context.result_engine = engine
context.mock_create_engine = mock_create
@then("create_engine should have been called without isolation_level")
def step_create_engine_no_isolation_level(context: Context) -> None:
"""Verify create_engine was called without isolation_level kwarg."""
call_kwargs = context.mock_create_engine.call_args[1]
assert "isolation_level" not in call_kwargs, (
f"isolation_level should not be set for non-SQLite, got {call_kwargs}"
)
@then("create_engine should have been called with future=True")
def step_create_engine_future_true(context: Context) -> None:
"""Verify create_engine was called with future=True."""
call_kwargs = context.mock_create_engine.call_args[1]
assert call_kwargs.get("future") is True, f"Expected future=True, got {call_kwargs}"
# =========================================================================
# In-memory engine cache hit (lines 70-78)
# =========================================================================
@given("a first UnitOfWork has already created an in-memory engine")
def step_first_uow_creates_engine(context: Context) -> None:
"""Create a UnitOfWork and access its engine, populating MEMORY_ENGINES."""
uow1 = UnitOfWork(database_url="sqlite:///:memory:")
with patch.object(uow1, "_ensure_database_initialized"):
engine1 = uow1.engine
context.first_engine = engine1
@when("a second UnitOfWork accesses its engine property")
def step_second_uow_accesses_engine(context: Context) -> None:
"""Create a second UnitOfWork and access its engine property."""
uow2 = UnitOfWork(database_url="sqlite:///:memory:")
with patch.object(uow2, "_ensure_database_initialized"):
engine2 = uow2.engine
context.second_engine = engine2
@then("the second engine should be the same object as the first")
def step_second_engine_same_as_first(context: Context) -> None:
"""Both UnitOfWork instances should share the same cached engine."""
from cleveragents.infrastructure.database.engine_cache import MEMORY_ENGINES
assert context.second_engine is context.first_engine, (
"Expected same engine object from MEMORY_ENGINES cache"
)
# Restore cache
if hasattr(context, "saved_memory_engines"):
MEMORY_ENGINES.clear()
MEMORY_ENGINES.update(context.saved_memory_engines)