Files
temp/features/steps/uko_l2_detail_level_steps.py

387 lines
14 KiB
Python

"""Behave steps for UKO Layer 2 detail-level map tests.
Covers DetailLevelMap base maps, DetailLevelMapBuilder,
``build_effective_map``, and unknown-level resolution.
All step definitions use the ``for uko_l2`` suffix to avoid
AmbiguousStep collisions with other feature files.
"""
from __future__ import annotations
import copy
from types import MappingProxyType
from _uko_l2_test_helpers import capture_error
from behave import given, then, when
from behave.runner import Context
from cleveragents.acms.uko.detail_level_maps import (
CODE_DETAIL_LEVEL_MAP,
FUNC_DETAIL_LEVEL_MAP,
OO_DETAIL_LEVEL_MAP,
PROC_DETAIL_LEVEL_MAP,
DetailLevelMapBuilder,
build_effective_map,
)
from cleveragents.domain.models.acms.crp import (
DetailLevelCycleError,
DetailLevelMap,
)
# ---------------------------------------------------------------------------
# DetailLevelMap -- base maps
# ---------------------------------------------------------------------------
@given("the uko-code detail level map for uko_l2")
def step_get_code_map(ctx: Context) -> None:
ctx.detail_map = CODE_DETAIL_LEVEL_MAP
@given("the uko-oo detail level map for uko_l2")
def step_get_oo_map(ctx: Context) -> None:
ctx.detail_map = OO_DETAIL_LEVEL_MAP
@given("the uko-func detail level map for uko_l2")
def step_get_func_map(ctx: Context) -> None:
ctx.detail_map = FUNC_DETAIL_LEVEL_MAP
@given("the uko-proc detail level map for uko_l2")
def step_get_proc_map(ctx: Context) -> None:
ctx.detail_map = PROC_DETAIL_LEVEL_MAP
@then('the map should have domain "{expected}" for uko_l2')
def step_check_map_domain(ctx: Context, expected: str) -> None:
assert ctx.detail_map.domain == expected
@then("the map should have max_depth {expected:d} for uko_l2")
def step_check_map_max_depth(ctx: Context, expected: int) -> None:
assert ctx.detail_map.max_depth == expected
@then('the map should resolve "{name}" to {expected:d} for uko_l2')
def step_check_map_resolve_name(ctx: Context, name: str, expected: int) -> None:
assert ctx.detail_map.resolve(name) == expected
@then("the map should have {count:d} levels for uko_l2")
def step_check_map_level_count(ctx: Context, count: int) -> None:
assert len(ctx.detail_map.effective_levels()) == count
@then("the map should resolve integer {value:d} to {expected:d} for uko_l2")
def step_check_map_resolve_int(ctx: Context, value: int, expected: int) -> None:
assert ctx.detail_map.resolve(value) == expected
@then('the map parent domain should be "{expected}" for uko_l2')
def step_check_map_parent_domain(ctx: Context, expected: str) -> None:
assert ctx.detail_map.parent is not None
assert ctx.detail_map.parent.domain == expected
# ---------------------------------------------------------------------------
# DetailLevelMapBuilder
# ---------------------------------------------------------------------------
@given(
'a detail level map builder with parent "{parent}" and domain "{domain}" for uko_l2'
)
def step_create_builder(ctx: Context, parent: str, domain: str) -> None:
parent_map = CODE_DETAIL_LEVEL_MAP if parent == "uko-code:" else None
assert parent_map is not None, f"Unknown parent: {parent}"
ctx.builder = DetailLevelMapBuilder(parent_map, domain)
@when('I insert "{name}" after "{after}" for uko_l2')
def step_builder_insert(ctx: Context, name: str, after: str) -> None:
ctx.builder.insert_after(after, name)
@when("I build the map for uko_l2")
def step_builder_build(ctx: Context) -> None:
ctx.built_map = ctx.builder.build()
@then('the built map should have domain "{expected}" for uko_l2')
def step_check_built_domain(ctx: Context, expected: str) -> None:
assert ctx.built_map.domain == expected
@then('the built map should resolve "{name}" to {expected:d} for uko_l2')
def step_check_built_resolve(ctx: Context, name: str, expected: int) -> None:
assert ctx.built_map.resolve(name) == expected
@then("the built map should have {count:d} levels for uko_l2")
def step_check_built_levels(ctx: Context, count: int) -> None:
assert len(ctx.built_map.effective_levels()) == count
@when('I try to insert after non-existent level "{name}" for uko_l2')
def step_insert_nonexistent(ctx: Context, name: str) -> None:
with capture_error(ctx, ValueError):
ctx.builder.insert_after(name, "TEST")
@then("a value error should be raised for uko_l2")
def step_check_value_error(ctx: Context) -> None:
assert ctx.error is not None
assert isinstance(ctx.error, ValueError)
@when("I try to create a builder with empty domain for uko_l2")
def step_create_builder_empty_domain(ctx: Context) -> None:
with capture_error(ctx, ValueError):
DetailLevelMapBuilder(CODE_DETAIL_LEVEL_MAP, "")
@when('I try to insert empty level name after "{after}" for uko_l2')
def step_insert_empty_name(ctx: Context, after: str) -> None:
with capture_error(ctx, ValueError):
ctx.builder.insert_after(after, "")
@when('I try to insert "{name}" after empty level name for uko_l2')
def step_insert_after_empty(ctx: Context, name: str) -> None:
with capture_error(ctx, ValueError):
ctx.builder.insert_after("", name)
@then('the builder parent domain should be "{expected}" for uko_l2')
def step_check_builder_parent(ctx: Context, expected: str) -> None:
assert ctx.builder.parent.domain == expected
@then('the builder domain should be "{expected}" for uko_l2')
def step_check_builder_domain(ctx: Context, expected: str) -> None:
assert ctx.builder.domain == expected
# ---------------------------------------------------------------------------
# build_effective_map function
# ---------------------------------------------------------------------------
@when("I build an effective map with no insertions for uko_l2")
def step_build_effective_no_insert(ctx: Context) -> None:
ctx.effective = build_effective_map(ctx.detail_map, [])
@when('I build an effective map with insertion "{name}" after "{after}" for uko_l2')
def step_build_effective_insert(ctx: Context, name: str, after: str) -> None:
ctx.effective = build_effective_map(ctx.detail_map, [(after, name)])
@when('I try to build an effective map with insertion after "{after}" for uko_l2')
def step_build_effective_invalid(ctx: Context, after: str) -> None:
with capture_error(ctx, ValueError):
build_effective_map(ctx.detail_map, [(after, "TEST")])
@then("the effective map should have {count:d} entries for uko_l2")
def step_check_effective_count(ctx: Context, count: int) -> None:
assert len(ctx.effective) == count
@then(
'the effective map entry {index:d} should be "{name}" at depth {depth:d} for uko_l2'
)
def step_check_effective_entry(ctx: Context, index: int, name: str, depth: int) -> None:
entry_name, entry_depth = ctx.effective[index]
assert entry_name == name, f"Expected '{name}' but got '{entry_name}'"
assert entry_depth == depth, f"Expected depth {depth} but got {entry_depth}"
# ---------------------------------------------------------------------------
# Unknown level resolution
# ---------------------------------------------------------------------------
@when('I try to resolve unknown level "{name}" for uko_l2')
def step_resolve_unknown(ctx: Context, name: str) -> None:
with capture_error(ctx, ValueError):
ctx.detail_map.resolve(name)
# ---------------------------------------------------------------------------
# Fresh DetailLevelMap (register / mutate)
# ---------------------------------------------------------------------------
@given("a fresh detail level map for uko_l2")
def step_create_fresh_map(ctx: Context) -> None:
ctx.fresh_map = DetailLevelMap(
domain="test:", parent=None, levels={"BASE": 0}, max_depth=10
)
@when('I register level "{name}" with value {value:d} for uko_l2')
def step_register_level(ctx: Context, name: str, value: int) -> None:
ctx.fresh_map.register(name, value)
@then('the fresh map should resolve "{name}" to {expected:d} for uko_l2')
def step_check_fresh_resolve(ctx: Context, name: str, expected: int) -> None:
assert ctx.fresh_map.resolve(name) == expected
@when('I try to register level "{name}" with value {value:d} for uko_l2')
def step_try_register_negative(ctx: Context, name: str, value: int) -> None:
with capture_error(ctx, ValueError):
ctx.fresh_map.register(name, value)
@when("I try to mutate the levels dict for uko_l2")
def step_mutate_levels(ctx: Context) -> None:
with capture_error(ctx, TypeError):
ctx.detail_map.levels["HACK"] = 99
@then("a type error should be raised for uko_l2")
def step_check_type_error(ctx: Context) -> None:
assert ctx.error is not None
assert isinstance(ctx.error, TypeError)
@when('I try to insert duplicate level "{name}" after "{after}" for uko_l2')
def step_try_insert_duplicate(ctx: Context, name: str, after: str) -> None:
with capture_error(ctx, ValueError):
ctx.builder.insert_after(after, name)
@when("I try to build an effective map with duplicate level for uko_l2")
def step_build_effective_duplicate(ctx: Context) -> None:
with capture_error(ctx, ValueError):
build_effective_map(ctx.detail_map, [("MEMBER_LISTING", "MODULE_LISTING")])
@when("I try to mutate effective_levels for uko_l2")
def step_mutate_effective_levels(ctx: Context) -> None:
with capture_error(ctx, TypeError):
effective = ctx.detail_map.effective_levels()
effective["HACK"] = 99 # type: ignore[index] # deliberate mutation test
# ---------------------------------------------------------------------------
# Review findings: H3 — MappingProxyType serialization
# ---------------------------------------------------------------------------
@when("I serialize the detail level map to JSON for uko_l2")
def step_serialize_map_json(ctx: Context) -> None:
ctx.serialized_json = ctx.detail_map.model_dump_json()
@then('the serialized JSON should contain "{expected}" for uko_l2')
def step_check_serialized_json(ctx: Context, expected: str) -> None:
assert expected in ctx.serialized_json
@when("I dump the detail level map to dict for uko_l2")
def step_dump_map_dict(ctx: Context) -> None:
ctx.dumped_dict = ctx.detail_map.model_dump()
@then('the dumped dict levels should contain "{expected}" for uko_l2')
def step_check_dumped_dict(ctx: Context, expected: str) -> None:
assert expected in ctx.dumped_dict["levels"]
# ---------------------------------------------------------------------------
# Review findings: M5 — cycle guard in resolve()
# ---------------------------------------------------------------------------
@given("a detail level map with circular parent for uko_l2")
def step_create_circular_map(ctx: Context) -> None:
# Build two maps that point to each other
map_a = DetailLevelMap(domain="cycle-a:", parent=None, levels={"A": 0}, max_depth=5)
map_b = DetailLevelMap(
domain="cycle-b:", parent=map_a, levels={"B": 1}, max_depth=5
)
# Introduce cycle: map_a.parent -> map_b
object.__setattr__(map_a, "parent", map_b)
ctx.circular_map = map_a
@when('I try to resolve level "{name}" on the circular map for uko_l2')
def step_resolve_circular(ctx: Context, name: str) -> None:
with capture_error(ctx, DetailLevelCycleError):
ctx.circular_map.resolve(name)
@then("a cycle error should be raised for uko_l2")
def step_check_cycle_error(ctx: Context) -> None:
assert ctx.error is not None
assert isinstance(ctx.error, DetailLevelCycleError)
# ---------------------------------------------------------------------------
# Review findings: H2 — levels re-frozen on assignment
# ---------------------------------------------------------------------------
@when("I assign a dict to the levels field for uko_l2")
def step_assign_dict_levels(ctx: Context) -> None:
ctx.fresh_map.levels = {"NEW_LEVEL": 3} # type: ignore[assignment] # deliberate test
ctx.levels_type = type(ctx.fresh_map.levels)
ctx.is_mapping_proxy = isinstance(ctx.fresh_map.levels, MappingProxyType)
@then("the levels should still be an immutable MappingProxy for uko_l2")
def step_check_levels_refrozen(ctx: Context) -> None:
assert ctx.is_mapping_proxy, f"Expected MappingProxyType but got {ctx.levels_type}"
# Also verify it's actually immutable.
# Note: capture_error() is not used here because the pattern is
# "assert that an exception IS raised" (inline assertion), not
# "capture the error for a later @then step".
try:
ctx.fresh_map.levels["HACK"] = 99 # type: ignore[index] # deliberate mutation test
msg = "Expected TypeError on mutation"
raise AssertionError(msg)
except TypeError:
pass
# ---------------------------------------------------------------------------
# Review findings: F7 — cycle guard in effective_levels()
# ---------------------------------------------------------------------------
@when("I try to get effective levels on the circular map for uko_l2")
def step_effective_levels_circular(ctx: Context) -> None:
with capture_error(ctx, DetailLevelCycleError):
ctx.circular_map.effective_levels()
# ---------------------------------------------------------------------------
# Review findings: F13 — deepcopy support
# ---------------------------------------------------------------------------
@when("I deep copy the detail level map for uko_l2")
def step_deep_copy_map(ctx: Context) -> None:
ctx.copied_map = copy.deepcopy(ctx.detail_map)
@then('the copied map should have domain "{expected}" for uko_l2')
def step_check_copied_domain(ctx: Context, expected: str) -> None:
assert ctx.copied_map.domain == expected
@then('the copied map should resolve "{name}" to {expected:d} for uko_l2')
def step_check_copied_resolve(ctx: Context, name: str, expected: int) -> None:
assert ctx.copied_map.resolve(name) == expected
@then("the copied map should not be the same object for uko_l2")
def step_check_copied_identity(ctx: Context) -> None:
assert ctx.copied_map is not ctx.detail_map