Files
temp/features/steps/database_models_coverage_r2_steps.py
T
freemo a808c395f9 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

612 lines
20 KiB
Python

"""Step definitions for database_models_coverage_r2.feature.
Targets uncovered lines in
src/cleveragents/infrastructure/database/models.py
identified from build/coverage.xml (round 2).
"""
from __future__ import annotations
import json
from datetime import UTC, datetime
from types import SimpleNamespace
from typing import Any
from behave import given, then, when # type: ignore[import-untyped]
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
NOW = datetime(2025, 6, 1, 12, 0, 0, tzinfo=UTC)
NOW_ISO = NOW.isoformat()
def _make_ulid(seed: int = 1) -> str:
"""Return a fake but valid-length 26-char ULID."""
return f"01ABCDEFGH{seed:016d}"
def _minimal_plan_timestamps() -> SimpleNamespace:
return SimpleNamespace(
created_at=NOW,
updated_at=NOW,
strategize_started_at=None,
strategize_completed_at=None,
execute_started_at=None,
execute_completed_at=None,
apply_started_at=None,
applied_at=None,
)
def _minimal_plan_identity(plan_id: str | None = None) -> SimpleNamespace:
return SimpleNamespace(
plan_id=plan_id or _make_ulid(1),
parent_plan_id=None,
root_plan_id=None,
attempt=1,
)
def _minimal_namespaced_name() -> SimpleNamespace:
return SimpleNamespace(
server=None,
namespace="local",
name="test-action",
__str__=lambda self: f"{self.namespace}/{self.name}",
)
# ---------------------------------------------------------------------------
# LifecycleActionModel — safety_profile round-trip (lines 376, 427)
# ---------------------------------------------------------------------------
@given("a LifecycleActionModel with safety_profile_json populated")
def step_action_model_with_safety(context: Any) -> None:
from cleveragents.infrastructure.database.models import (
LifecycleActionModel,
)
safety_data = {
"require_sandbox": True,
"require_checkpoints": False,
"allow_unsafe_tools": False,
}
model = LifecycleActionModel(
namespaced_name="local/test-action",
namespace="local",
name="test-action",
description="desc",
definition_of_done="dod",
strategy_actor="strat",
execution_actor="exec",
reusable=True,
read_only=False,
state="available",
tags_json="[]",
created_at=NOW_ISO,
updated_at=NOW_ISO,
safety_profile_json=json.dumps(safety_data),
)
# Ensure relationship lists exist
model.arguments_rel = []
model.invariants_rel = []
context.action_model = model
@when("I convert the action model to domain via to_domain")
def step_action_to_domain(context: Any) -> None:
context.domain_action = context.action_model.to_domain()
@then("the domain action has a non-null safety_profile object")
def step_check_safety_profile_not_none(context: Any) -> None:
sp = context.domain_action.safety_profile
assert sp is not None, "safety_profile should be deserialized"
assert sp.require_sandbox is True
@given("an Action domain object with a SafetyProfile attached")
def step_action_domain_with_safety(context: Any) -> None:
from cleveragents.domain.models.core.safety_profile import SafetyProfile
context.domain_action_input = SimpleNamespace(
namespaced_name=_minimal_namespaced_name(),
description="desc",
long_description=None,
definition_of_done="dod",
strategy_actor="strat",
execution_actor="exec",
review_actor=None,
apply_actor=None,
estimation_actor=None,
invariant_actor=None,
automation_profile=None,
safety_profile=SafetyProfile(
require_sandbox=True,
require_checkpoints=False,
allow_unsafe_tools=True,
),
reusable=True,
read_only=False,
inputs_schema=None,
state=SimpleNamespace(value="available"),
created_by=None,
tags=[],
arguments=[],
invariants=[],
created_at=NOW,
updated_at=NOW,
)
@when("I convert the action to a model via from_domain")
def step_action_from_domain(context: Any) -> None:
from cleveragents.infrastructure.database.models import (
LifecycleActionModel,
)
context.action_model_out = LifecycleActionModel.from_domain(
context.domain_action_input,
)
@then("the model safety_profile_json contains serialized SafetyProfile data")
def step_check_safety_json(context: Any) -> None:
raw = context.action_model_out.safety_profile_json
assert raw is not None, "safety_profile_json must not be None"
parsed = json.loads(raw)
assert parsed["require_sandbox"] is True
assert parsed["allow_unsafe_tools"] is True
# ---------------------------------------------------------------------------
# LifecycleActionModel — string arg_type/requirement (lines 466, 469)
# ---------------------------------------------------------------------------
@given(
"an Action domain object whose arguments have plain-string arg_type and requirement"
)
def step_action_plain_string_args(context: Any) -> None:
arg = SimpleNamespace(
name="my_arg",
arg_type="string", # plain string, no .value
requirement="required", # plain string, no .value
description="a test arg",
default_value=None,
min_value=None,
max_value=None,
validation_pattern=None,
)
context.domain_action_input = SimpleNamespace(
namespaced_name=_minimal_namespaced_name(),
description="desc",
long_description=None,
definition_of_done="dod",
strategy_actor="strat",
execution_actor="exec",
review_actor=None,
apply_actor=None,
estimation_actor=None,
invariant_actor=None,
automation_profile=None,
safety_profile=None,
reusable=True,
read_only=False,
inputs_schema=None,
state=SimpleNamespace(value="available"),
created_by=None,
tags=[],
arguments=[arg],
invariants=[],
created_at=NOW,
updated_at=NOW,
)
@when("I convert that action to a model via from_domain")
def step_that_action_from_domain(context: Any) -> None:
from cleveragents.infrastructure.database.models import (
LifecycleActionModel,
)
context.action_model_out = LifecycleActionModel.from_domain(
context.domain_action_input,
)
@then("the model argument rows use the plain-string values directly")
def step_check_arg_strings(context: Any) -> None:
args = context.action_model_out.arguments_rel
assert len(args) == 1
assert args[0].arg_type == "string"
assert args[0].requirement == "required"
# ---------------------------------------------------------------------------
# LifecyclePlanModel — string processing_state (line 882)
# ---------------------------------------------------------------------------
def _base_plan_ns(
*,
processing_state: Any = "queued",
automation_profile: Any = None,
namespaced_name: Any = None,
error_details: Any = None,
) -> SimpleNamespace:
"""Build a minimal Plan-like namespace for from_domain tests."""
ns_name = namespaced_name or _minimal_namespaced_name()
return SimpleNamespace(
identity=_minimal_plan_identity(),
namespaced_name=ns_name,
action_name="local/test-action",
description="plan desc",
definition_of_done="dod",
phase=SimpleNamespace(value="action"),
processing_state=processing_state,
automation_profile=automation_profile,
strategy_actor=None,
execution_actor=None,
review_actor=None,
apply_actor=None,
estimation_actor=None,
invariant_actor=None,
project_links=[],
invariants=[],
arguments={},
arguments_order=[],
changeset_id=None,
sandbox_refs=[],
validation_summary=None,
decision_root_id=None,
timestamps=_minimal_plan_timestamps(),
error_message=None,
error_details=error_details,
created_by=None,
tags=[],
reusable=True,
read_only=False,
)
@given("a Plan domain object whose processing_state is a plain string")
def step_plan_string_state(context: Any) -> None:
context.plan_domain = _base_plan_ns(processing_state="complete")
@when("I convert the plan to a model via from_domain")
def step_plan_from_domain(context: Any) -> None:
from cleveragents.infrastructure.database.models import (
LifecyclePlanModel,
)
context.plan_model_out = LifecyclePlanModel.from_domain(context.plan_domain)
@then("the model processing_state equals the plain string value")
def step_check_plan_state(context: Any) -> None:
assert context.plan_model_out.processing_state == "complete"
# ---------------------------------------------------------------------------
# LifecyclePlanModel — automation_profile (line 890)
# ---------------------------------------------------------------------------
@given("a Plan domain object with a non-null automation_profile")
def step_plan_with_automation_profile(context: Any) -> None:
from cleveragents.domain.models.core.plan import (
AutomationProfileProvenance,
AutomationProfileRef,
)
profile = AutomationProfileRef(
profile_name="strict",
provenance=AutomationProfileProvenance.ACTION,
)
context.plan_domain = _base_plan_ns(automation_profile=profile)
@then("the model automation_profile column contains JSON with profile_name")
def step_check_automation_profile_json(context: Any) -> None:
raw = context.plan_model_out.automation_profile
assert raw is not None
parsed = json.loads(raw)
assert parsed["profile_name"] == "strict"
# ---------------------------------------------------------------------------
# LifecyclePlanModel — fallback namespace (line 906)
# ---------------------------------------------------------------------------
@given("a Plan domain object whose namespaced_name has no namespace attribute")
def step_plan_no_namespace(context: Any) -> None:
# Use a plain string that has no .namespace attribute
context.plan_domain = _base_plan_ns(namespaced_name="local/test-plan")
@then('the model namespace equals "local"')
def step_check_namespace_local(context: Any) -> None:
assert context.plan_model_out.namespace == "local"
# ---------------------------------------------------------------------------
# LifecyclePlanModel — error_details (line 923)
# ---------------------------------------------------------------------------
@given("a Plan domain object with non-null error_details")
def step_plan_with_error_details(context: Any) -> None:
context.plan_domain = _base_plan_ns(
error_details={"code": "E001", "msg": "something failed"},
)
@then("the model error_details_json is a JSON string of the details dict")
def step_check_error_details_json(context: Any) -> None:
raw = context.plan_model_out.error_details_json
assert raw is not None
parsed = json.loads(raw)
assert parsed["code"] == "E001"
assert parsed["msg"] == "something failed"
# ---------------------------------------------------------------------------
# SkillModel — include with overrides (line 2327)
# ---------------------------------------------------------------------------
@given("a Skill domain object with an include that has overrides")
def step_skill_with_include_overrides(context: Any) -> None:
include = SimpleNamespace(
name="other-skill/base",
overrides={"timeout": 120},
)
context.skill_domain = SimpleNamespace(
name="local/my-skill",
description="a skill",
tool_refs=[],
includes=[include],
anonymous_tools=[],
mcp_servers=[],
agent_skills=[],
overrides={},
version=None,
)
@when("I convert the skill to a model via from_domain")
def step_skill_from_domain(context: Any) -> None:
from cleveragents.infrastructure.database.models import SkillModel
context.skill_model_out = SkillModel.from_domain(context.skill_domain)
@then("the include item_config contains the serialized overrides")
def step_check_include_overrides(context: Any) -> None:
items = context.skill_model_out.items_rel
include_items = [i for i in items if i.item_type == "include"]
assert len(include_items) == 1
config = json.loads(include_items[0].item_config)
assert config["overrides"]["timeout"] == 120
# ---------------------------------------------------------------------------
# DecisionModel — string decision_type (line 2698)
# ---------------------------------------------------------------------------
@given("a Decision domain object whose decision_type is a plain string")
def step_decision_string_type(context: Any) -> None:
from cleveragents.domain.models.core.decision import (
ContextSnapshot,
)
context.decision_domain = SimpleNamespace(
decision_id=_make_ulid(10),
plan_id=_make_ulid(11),
parent_decision_id=None,
sequence_number=1,
decision_type="strategy_choice", # plain string, no .value
question="Which strategy?",
chosen_option="Option A",
alternatives_considered=["Option B"],
confidence_score=0.9,
context_snapshot=ContextSnapshot(
hot_context_hash="abc",
hot_context_ref="ref1",
relevant_resources=[],
actor_state_ref="actor1",
),
rationale="because",
actor_reasoning=None,
downstream_decision_ids=[],
downstream_plan_ids=[],
artifacts_produced=[],
created_at=NOW,
is_correction=False,
corrects_decision_id=None,
correction_reason=None,
superseded_by=None,
)
@when("I convert the decision to a model via from_domain")
def step_decision_from_domain(context: Any) -> None:
from cleveragents.infrastructure.database.models import DecisionModel
context.decision_model_out = DecisionModel.from_domain(
context.decision_domain,
)
@then("the model decision_type equals the plain string")
def step_check_decision_type_string(context: Any) -> None:
assert context.decision_model_out.decision_type == "strategy_choice"
# ---------------------------------------------------------------------------
# CheckpointModel — to_domain (lines 2823-2852)
# ---------------------------------------------------------------------------
@given("a CheckpointModel with metadata_json containing reason and phase")
def step_checkpoint_model_with_meta(context: Any) -> None:
from cleveragents.infrastructure.database.models import CheckpointModel
context.checkpoint_model = CheckpointModel(
checkpoint_id=_make_ulid(20),
plan_id=_make_ulid(21),
sandbox_ref="abc123commit",
decision_id=None,
checkpoint_type="pre_write",
resource_id=None,
filesystem_path="checkpoints/cp1",
size_bytes=1024,
created_at=NOW_ISO,
metadata_json=json.dumps(
{
"reason": "before write",
"source_tool": "file_write",
"phase": "execute",
}
),
)
@when("I convert the checkpoint model to domain via to_domain")
def step_checkpoint_to_domain(context: Any) -> None:
context.domain_checkpoint = context.checkpoint_model.to_domain()
@then("the domain checkpoint has the correct metadata fields")
def step_check_checkpoint_metadata(context: Any) -> None:
cp = context.domain_checkpoint
assert cp.checkpoint_id == _make_ulid(20)
assert cp.plan_id == _make_ulid(21)
assert cp.sandbox_ref == "abc123commit"
assert cp.checkpoint_type == "pre_write"
assert cp.filesystem_path == "checkpoints/cp1"
assert cp.size_bytes == 1024
assert cp.metadata.reason == "before write"
assert cp.metadata.source_tool == "file_write"
assert cp.metadata.phase == "execute"
@given("a CheckpointModel with invalid JSON in metadata_json")
def step_checkpoint_bad_json(context: Any) -> None:
from cleveragents.infrastructure.database.models import CheckpointModel
context.checkpoint_model = CheckpointModel(
checkpoint_id=_make_ulid(30),
plan_id=_make_ulid(31),
sandbox_ref="deadbeef",
checkpoint_type="manual",
filesystem_path="",
size_bytes=None,
created_at=NOW_ISO,
metadata_json="NOT VALID JSON {{{",
)
@then("the domain checkpoint metadata is empty defaults")
def step_check_empty_metadata(context: Any) -> None:
cp = context.domain_checkpoint
assert cp.metadata.reason == ""
assert cp.metadata.source_tool == ""
assert cp.metadata.phase == ""
# ---------------------------------------------------------------------------
# CheckpointModel — from_domain (lines 2865-2879)
# ---------------------------------------------------------------------------
@given("a Checkpoint domain object with full metadata")
def step_checkpoint_domain(context: Any) -> None:
from cleveragents.domain.models.core.checkpoint import (
Checkpoint,
CheckpointMetadata,
)
context.checkpoint_domain = Checkpoint(
checkpoint_id=_make_ulid(40),
plan_id=_make_ulid(41),
sandbox_ref="commitsha",
decision_id=_make_ulid(42),
checkpoint_type="post_step",
resource_id=_make_ulid(43),
filesystem_path="cp/path",
size_bytes=2048,
created_at=NOW,
metadata=CheckpointMetadata(
reason="post step save",
source_tool="apply_tool",
phase="apply",
),
)
@when("I convert the checkpoint to a model via from_domain")
def step_checkpoint_from_domain(context: Any) -> None:
from cleveragents.infrastructure.database.models import CheckpointModel
context.checkpoint_model_out = CheckpointModel.from_domain(
context.checkpoint_domain,
)
@then("the model has correct checkpoint_id plan_id and metadata_json")
def step_check_checkpoint_model_fields(context: Any) -> None:
m = context.checkpoint_model_out
assert m.checkpoint_id == _make_ulid(40)
assert m.plan_id == _make_ulid(41)
assert m.sandbox_ref == "commitsha"
assert m.decision_id == _make_ulid(42)
assert m.checkpoint_type == "post_step"
assert m.resource_id == _make_ulid(43)
assert m.filesystem_path == "cp/path"
assert m.size_bytes == 2048
assert m.created_at == NOW_ISO
raw = m.metadata_json
assert raw is not None
parsed = json.loads(raw)
assert parsed["reason"] == "post step save"
assert parsed["source_tool"] == "apply_tool"
assert parsed["phase"] == "apply"
# ---------------------------------------------------------------------------
# get_session (lines 2907-2908)
# ---------------------------------------------------------------------------
@given("an in-memory SQLAlchemy engine")
def step_create_engine(context: Any) -> None:
from cleveragents.infrastructure.database.models import init_database
context.engine = init_database("sqlite:///:memory:")
@when("I call get_session with that engine")
def step_call_get_session(context: Any) -> None:
from cleveragents.infrastructure.database.models import get_session
context.session = get_session(context.engine)
@then("I receive a valid SQLAlchemy session object")
def step_check_session(context: Any) -> None:
from sqlalchemy.orm import Session
assert isinstance(context.session, Session)
# Verify it can execute a simple query
result = context.session.execute(__import__("sqlalchemy").text("SELECT 1"))
assert result.scalar() == 1
context.session.close()