051ee7c290
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 47s
CI / security (pull_request) Successful in 52s
CI / build (pull_request) Successful in 56s
CI / e2e_tests (pull_request) Successful in 5m1s
CI / integration_tests (pull_request) Successful in 5m30s
CI / unit_tests (pull_request) Successful in 5m42s
CI / docker (pull_request) Successful in 58s
CI / coverage (pull_request) Successful in 7m35s
CI / build (push) Successful in 21s
CI / docker (push) Has been skipped
CI / benchmark-regression (pull_request) Failing after 49m24s
CI / lint (push) Successful in 22s
CI / quality (push) Successful in 39s
CI / security (push) Successful in 48s
CI / typecheck (push) Successful in 1m26s
CI / benchmark-regression (push) Has been skipped
CI / e2e_tests (push) Successful in 5m53s
CI / coverage (push) Successful in 9m4s
CI / benchmark-publish (push) Successful in 19m10s
CI / integration_tests (push) Failing after 19m18s
CI / unit_tests (push) Failing after 19m20s
Added 52 new .feature files and corresponding _steps.py files targeting previously uncovered code paths in the following areas: - TUI layer: app, commands, persona (state/schema/registry), widgets, input (shell_exec, reference_parser) - Application services: plan lifecycle/service/executor, session, project, repo indexing, correction, checkpoint, actor, llm_actors, strategy coordinator, resource file watcher, service retry wiring - CLI commands: session, resource, repl, plan, db, automation_profile - Domain models: retry_policy, resource_type, cost_budget, docker_compose_analyzer, detail_level, _sql_string_aware, _postgresql_helpers - Core: circuit_breaker, retry_service_patterns - Infrastructure: repositories, transaction_sandbox, strategy_registry, plugins/loader, container - Config: settings - Agents: plan_generation, context_analysis, auto_debug - A2A: facade All new tests follow the Behave/Gherkin BDD standard. Resolved step definition collisions with unique prefixes. Fixed Alembic fileConfig logger disabling issue (disable_existing_loggers=False). ISSUES CLOSED: #1068
146 lines
6.0 KiB
Python
146 lines
6.0 KiB
Python
"""Step definitions for detail_level_coverage.feature.
|
|
|
|
These steps target specific uncovered lines in detail_level.py:
|
|
- Line 157: __setattr__ defense-in-depth re-freeze when model validator is bypassed
|
|
- Lines 166-170: __copy__ (copy.copy) support
|
|
- Line 181: __deepcopy__ with memo=None creating a fresh memo dict
|
|
- Lines 250-252: register() raising ValueError when depth exceeds max_depth
|
|
"""
|
|
|
|
import copy
|
|
import json
|
|
from types import MappingProxyType
|
|
from unittest.mock import patch
|
|
|
|
from behave import given, then, when
|
|
from pydantic import BaseModel
|
|
|
|
from cleveragents.domain.models.acms.detail_level import (
|
|
DetailLevelMap,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
@given("the detail level module is imported")
|
|
def step_detail_level_module_imported(context):
|
|
"""Ensure the module is importable."""
|
|
assert DetailLevelMap is not None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Shared construction step
|
|
# ---------------------------------------------------------------------------
|
|
@given(
|
|
'a DetailLevelMap with domain "{domain}" and levels {levels_json} and max_depth {max_depth:d}'
|
|
)
|
|
def step_create_detail_level_map(context, domain, levels_json, max_depth):
|
|
"""Construct a DetailLevelMap from the scenario parameters."""
|
|
levels = json.loads(levels_json)
|
|
context.dlm = DetailLevelMap(
|
|
domain=domain,
|
|
levels=levels,
|
|
max_depth=max_depth,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scenario: copy.copy (lines 166-170)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I shallow-copy the DetailLevelMap")
|
|
def step_shallow_copy(context):
|
|
"""Perform a shallow copy via copy.copy()."""
|
|
context.dlm_copy = copy.copy(context.dlm)
|
|
|
|
|
|
@then("the copy should be a distinct DetailLevelMap instance")
|
|
def step_verify_distinct_copy(context):
|
|
"""Verify the copy is a new object."""
|
|
assert context.dlm_copy is not context.dlm
|
|
assert isinstance(context.dlm_copy, DetailLevelMap)
|
|
|
|
|
|
@then("the copy should have the same domain and levels as the original")
|
|
def step_verify_copy_data(context):
|
|
"""Verify the copy has identical data."""
|
|
assert context.dlm_copy.domain == context.dlm.domain
|
|
assert dict(context.dlm_copy.levels) == dict(context.dlm.levels)
|
|
assert context.dlm_copy.max_depth == context.dlm.max_depth
|
|
# Levels should be frozen in the copy as well
|
|
assert isinstance(context.dlm_copy.levels, MappingProxyType)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scenario: __deepcopy__ with None memo (line 181)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I call __deepcopy__ with None memo")
|
|
def step_deepcopy_none_memo(context):
|
|
"""Call __deepcopy__ directly with memo=None to hit line 181."""
|
|
context.dlm_deepcopy = context.dlm.__deepcopy__(None)
|
|
|
|
|
|
@then("the deep copy should be a valid DetailLevelMap with the same data")
|
|
def step_verify_deepcopy(context):
|
|
"""Verify the deep copy is valid and matches the original."""
|
|
assert isinstance(context.dlm_deepcopy, DetailLevelMap)
|
|
assert context.dlm_deepcopy is not context.dlm
|
|
assert context.dlm_deepcopy.domain == context.dlm.domain
|
|
assert dict(context.dlm_deepcopy.levels) == dict(context.dlm.levels)
|
|
assert context.dlm_deepcopy.max_depth == context.dlm.max_depth
|
|
assert isinstance(context.dlm_deepcopy.levels, MappingProxyType)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scenario: register rejects depth > max_depth (lines 250-252)
|
|
# ---------------------------------------------------------------------------
|
|
@when('I register a level "{name}" with depth {depth:d}')
|
|
def step_register_exceeding_depth(context, name, depth):
|
|
"""Attempt to register a level whose depth exceeds max_depth."""
|
|
try:
|
|
context.dlm.register(name, depth)
|
|
context.register_error = None
|
|
except ValueError as exc:
|
|
context.register_error = exc
|
|
|
|
|
|
@then("a ValueError should be raised mentioning exceeds max_depth")
|
|
def step_verify_register_error(context):
|
|
"""Verify the ValueError was raised with the expected message."""
|
|
assert context.register_error is not None, "Expected ValueError but none was raised"
|
|
msg = str(context.register_error)
|
|
assert "exceeds" in msg, f"Expected 'exceeds' in error message, got: {msg}"
|
|
assert "max_depth" in msg, f"Expected 'max_depth' in error message, got: {msg}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Scenario: __setattr__ defense-in-depth re-freeze (line 157)
|
|
# ---------------------------------------------------------------------------
|
|
@when("I assign a plain dict to levels while bypassing Pydantic model validation")
|
|
def step_assign_bypassing_pydantic(context):
|
|
"""Bypass Pydantic's model validator so __setattr__ defense-in-depth kicks in.
|
|
|
|
We mock BaseModel.__setattr__ to do a plain object.__setattr__,
|
|
which skips Pydantic's validate_assignment (and therefore the
|
|
_freeze_levels model validator). The defense-in-depth check in
|
|
DetailLevelMap.__setattr__ then detects a non-frozen dict and
|
|
re-freezes it via line 157.
|
|
"""
|
|
|
|
def plain_setattr(self, name, value):
|
|
object.__setattr__(self, name, value)
|
|
|
|
with patch.object(BaseModel, "__setattr__", plain_setattr):
|
|
# This calls DetailLevelMap.__setattr__ which calls the mocked
|
|
# super().__setattr__ (plain assignment), then the defense check.
|
|
context.dlm.levels = {"X": 1, "Y": 2}
|
|
|
|
|
|
@then("levels should still be a MappingProxyType after the defense-in-depth re-freeze")
|
|
def step_verify_refreeze(context):
|
|
"""Verify that line 157 executed and re-froze the levels."""
|
|
assert isinstance(context.dlm.levels, MappingProxyType), (
|
|
f"Expected MappingProxyType, got {type(context.dlm.levels)}"
|
|
)
|
|
assert dict(context.dlm.levels) == {"X": 1, "Y": 2}
|