"""Step definitions for ContextTierService scope enforcement tests. Extracted from ``scoped_view_steps.py`` to keep step files manageable. All steps prefixed with ``sv`` to avoid AmbiguousStep collisions. """ from __future__ import annotations from typing import Any from behave import given, then, when # type: ignore[import-untyped] from cleveragents.domain.models.acms.scope_resolution import ( resolve_resource_scope, ) from cleveragents.domain.models.acms.scoped_view import ( ScopeViolationError, ) from cleveragents.domain.models.acms.tiers import ( ContextTier, TieredFragment, ) __all__: list[str] = [] def _make_fragment( fid: str, project: str = "", resource: str = "", tier: str = "hot", ) -> TieredFragment: """Create a TieredFragment for testing.""" return TieredFragment( fragment_id=fid, content=f"content-{fid}", tier=ContextTier(tier), project_name=project, resource_id=resource, ) # --------------------------------------------------------------------------- # ContextTierService: scope enforcement # --------------------------------------------------------------------------- @given("sv a ContextTierService") def step_sv_tier_service(context: Any) -> None: from cleveragents.application.services.context_tiers import ContextTierService context.sv_tier_svc = ContextTierService() @when('sv I store fragment "{fid}" project "{proj}" resource "{res}" tier "{tier}"') def step_sv_store_fragment( context: Any, fid: str, proj: str, res: str, tier: str ) -> None: frag = _make_fragment(fid, project=proj, resource=res, tier=tier) context.sv_tier_svc.store(frag) @then("sv get_scoped_by_resource should return {count:d} fragment") def step_sv_scoped_by_resource(context: Any, count: int) -> None: result = context.sv_tier_svc.get_scoped_by_resource(context.sv_scope) assert len(result) == count, f"Expected {count} fragments, got {len(result)}" @then('sv storing fragment with resource "{res}" should raise ScopeViolationError') def step_sv_store_scope_check(context: Any, res: str) -> None: frag = _make_fragment("bad-frag", project="proj-a", resource=res) try: context.sv_tier_svc.store_with_scope_check(frag, context.sv_scope) raise AssertionError("Expected ScopeViolationError") except ScopeViolationError: pass @then( 'sv storing fragment with project "{proj}" and resource "{res}"' " should raise ScopeViolationError" ) def step_sv_store_scope_check_project(context: Any, proj: str, res: str) -> None: frag = _make_fragment("bad-proj-frag", project=proj, resource=res) try: context.sv_tier_svc.store_with_scope_check(frag, context.sv_scope) raise AssertionError("Expected ScopeViolationError for out-of-scope project") except ScopeViolationError: pass @then('sv validating fragment "{fid}" against the scope should pass') def step_sv_validate_fragment(context: Any, fid: str) -> None: context.sv_tier_svc.validate_fragment_scope(fid, context.sv_scope) @then( 'sv validating fragment "{fid}" against the scope should raise ScopeViolationError' ) def step_sv_validate_fragment_fail(context: Any, fid: str) -> None: try: context.sv_tier_svc.validate_fragment_scope(fid, context.sv_scope) raise AssertionError("Expected ScopeViolationError") except ScopeViolationError: pass # --------------------------------------------------------------------------- # Coverage: context_tiers store empty id # --------------------------------------------------------------------------- @then("sv storing fragment with empty id should raise ValueError") def step_sv_store_empty_id(context: Any) -> None: frag = TieredFragment.model_construct( fragment_id="", content="content", tier=ContextTier.HOT, project_name="p", resource_id="R1", ) try: context.sv_tier_svc.store(frag) raise AssertionError("Expected ValueError") except ValueError: pass # --------------------------------------------------------------------------- # Coverage: context_tiers get_scoped_view empty # --------------------------------------------------------------------------- @then("sv get_scoped_view with empty projects should return empty list") def step_sv_scoped_view_empty(context: Any) -> None: result = context.sv_tier_svc.get_scoped_view([]) assert result == [] # --------------------------------------------------------------------------- # Coverage: context_tiers evict_lru empty # --------------------------------------------------------------------------- @then("sv evict_lru on empty cold tier should return empty list") def step_sv_evict_empty(context: Any) -> None: result = context.sv_tier_svc.evict_lru(ContextTier.COLD, 1) assert result == [] # --------------------------------------------------------------------------- # Coverage: context_tiers get_scoped_metrics # --------------------------------------------------------------------------- @then('sv get_scoped_metrics for "{proj}" should show {h:d} hot and {w:d} warm') def step_sv_scoped_metrics(context: Any, proj: str, h: int, w: int) -> None: metrics = context.sv_tier_svc.get_scoped_metrics([proj]) assert metrics.hot_count == h, f"Expected hot={h}, got {metrics.hot_count}" assert metrics.warm_count == w, f"Expected warm={w}, got {metrics.warm_count}" # --------------------------------------------------------------------------- # Coverage: context_tiers validate_fragment_scope not found # --------------------------------------------------------------------------- @then("sv validating non-existent fragment should raise ValueError") def step_sv_validate_nonexistent(context: Any) -> None: try: context.sv_tier_svc.validate_fragment_scope("ghost", context.sv_scope) raise AssertionError("Expected ValueError") except ValueError: pass # --------------------------------------------------------------------------- # Coverage: context_tiers store_with_scope_check happy path # --------------------------------------------------------------------------- @then("sv store_with_scope_check should accept in-scope fragment") def step_sv_store_scope_ok(context: Any) -> None: frag = _make_fragment("ok-frag", project="proj-a", resource="RES01") context.sv_tier_svc.store_with_scope_check(frag, context.sv_scope) result = context.sv_tier_svc.get("ok-frag") assert result is not None # --------------------------------------------------------------------------- # Coverage: context_tiers budget property # --------------------------------------------------------------------------- @then("sv tier service budget should be a TierBudget") def step_sv_budget(context: Any) -> None: from cleveragents.domain.models.acms.tiers import TierBudget budget = context.sv_tier_svc.budget assert isinstance(budget, TierBudget) # --------------------------------------------------------------------------- # Coverage: context_tiers get_for_actor no project filter # --------------------------------------------------------------------------- @then("sv get_for_actor without projects should return a list") def step_sv_actor_no_projects(context: Any) -> None: from cleveragents.domain.models.acms.tiers import ActorRole result = context.sv_tier_svc.get_for_actor(ActorRole.STRATEGIST) assert isinstance(result, list) # --------------------------------------------------------------------------- # Coverage: context_tiers _summarize_for_cold false branch # --------------------------------------------------------------------------- @when('sv I store a short warm fragment "{fid}"') def step_sv_store_short_warm(context: Any, fid: str) -> None: frag = TieredFragment( fragment_id=fid, content="short", tier=ContextTier.WARM, project_name="proj-a", resource_id="RES01", ) context.sv_tier_svc.store(frag) @then('sv demoting "{fid}" to cold should keep content intact') def step_sv_demote_short(context: Any, fid: str) -> None: result = context.sv_tier_svc.demote(fid) assert result is not None assert result.content == "short" assert result.tier == ContextTier.COLD # --------------------------------------------------------------------------- # P0-S2: get_scoped # --------------------------------------------------------------------------- @then('sv get_scoped for "{fid}" should return the fragment') def step_sv_get_scoped_found(context: Any, fid: str) -> None: result = context.sv_tier_svc.get_scoped(fid, context.sv_scope) assert result is not None assert result.fragment_id == fid @then('sv get_scoped for "{fid}" should return None') def step_sv_get_scoped_none(context: Any, fid: str) -> None: result = context.sv_tier_svc.get_scoped(fid, context.sv_scope) assert result is None # --------------------------------------------------------------------------- # P1-SPEC1: DAG expansion # --------------------------------------------------------------------------- @when('sv I resolve scope with registry_lookup expanding "{parent}" to "{child}"') def step_sv_resolve_with_dag(context: Any, parent: str, child: str) -> None: def lookup(rid: str) -> frozenset[str]: if rid == parent: return frozenset([child]) return frozenset() context.sv_scope = resolve_resource_scope( [context.sv_project], registry_lookup=lookup, ) @when( 'sv I store fragment "{fid}" with project "{proj}" and empty resource_id in tier "{tier}"' ) def step_sv_store_empty_rid(context: Any, fid: str, proj: str, tier: str) -> None: frag = _make_fragment(fid, project=proj, resource="", tier=tier) context.sv_tier_svc.store(frag)