"""Step definitions for features/context_fragment_models.feature. Tests the ScoredFragment pipeline model and ContextFragment strategy_source field directly in-memory -- no database required. """ from __future__ import annotations from typing import Any from behave import given, then, when from behave.runner import Context from pydantic import ValidationError from cleveragents.domain.contexts.fragment import ScoredFragment from cleveragents.domain.models.core.context_fragment import ( ContextFragment, FragmentProvenance, ) # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _DEFAULT_PROVENANCE = FragmentProvenance(resource_uri="test://default") def _make_fragment(**kwargs: Any) -> ContextFragment: """Create a ContextFragment with sensible test defaults.""" kwargs.setdefault("uko_node", "test://default") kwargs.setdefault("content", "test content") kwargs.setdefault("token_count", 0) kwargs.setdefault("provenance", _DEFAULT_PROVENANCE) return ContextFragment(**kwargs) # --------------------------------------------------------------------------- # Given steps # --------------------------------------------------------------------------- @given( 'a context fragment with uko_node "{uko}" and content "{content}"' " and token_count {tokens:d} and relevance {rel:g}" ) def step_given_fragment_with_relevance( context: Context, uko: str, content: str, tokens: int, rel: float, ) -> None: context.fragment = _make_fragment( uko_node=uko, content=content, token_count=tokens, relevance_score=rel, ) @given( 'a context fragment with uko_node "{uko}" and content "{content}"' ' and token_count {tokens:d} and strategy_source "{strategy}"' ) def step_given_fragment_with_strategy( context: Context, uko: str, content: str, tokens: int, strategy: str, ) -> None: context.fragment = _make_fragment( uko_node=uko, content=content, token_count=tokens, strategy_source=strategy, ) @given('two scored fragments with uko_node "{uko}" and detail_depth {depth:d}') def step_given_two_same_scored_fragments( context: Context, uko: str, depth: int, ) -> None: frag_a = _make_fragment( uko_node=uko, content="content A", detail_depth=depth, token_count=10, ) frag_b = _make_fragment( uko_node=uko, content="content B", detail_depth=depth, token_count=20, ) context.scored_a = ScoredFragment.from_fragment(frag_a, composite_score=0.5) context.scored_b = ScoredFragment.from_fragment(frag_b, composite_score=0.9) @given('a scored fragment with uko_node "{uko}" and detail_depth {depth:d}') def step_given_scored_fragment_first( context: Context, uko: str, depth: int, ) -> None: frag = _make_fragment( uko_node=uko, content="content", detail_depth=depth, token_count=10, ) context.scored_a = ScoredFragment.from_fragment(frag, composite_score=0.5) @given('another scored fragment with uko_node "{uko}" and detail_depth {depth:d}') def step_given_scored_fragment_second( context: Context, uko: str, depth: int, ) -> None: frag = _make_fragment( uko_node=uko, content="content", detail_depth=depth, token_count=10, ) context.scored_b = ScoredFragment.from_fragment(frag, composite_score=0.7) @given( 'a scored fragment with uko_node "{uko}" and detail_depth {depth:d}' " and score {score:g}" ) def step_given_scored_fragment_with_score( context: Context, uko: str, depth: int, score: float, ) -> None: frag = _make_fragment( uko_node=uko, content="content", detail_depth=depth, token_count=10, ) context.scored_a = ScoredFragment.from_fragment(frag, composite_score=score) @given( 'another scored fragment with uko_node "{uko}" and detail_depth {depth:d}' " and score {score:g}" ) def step_given_another_scored_fragment_with_score( context: Context, uko: str, depth: int, score: float, ) -> None: frag = _make_fragment( uko_node=uko, content="content", detail_depth=depth, token_count=10, ) context.scored_b = ScoredFragment.from_fragment(frag, composite_score=score) @given('three scored fragments where two share uko_node "{uko}" detail_depth {depth:d}') def step_given_three_scored_fragments( context: Context, uko: str, depth: int, ) -> None: frag_a = _make_fragment( uko_node=uko, content="content A", detail_depth=depth, token_count=10, ) frag_b = _make_fragment( uko_node=uko, content="content B", detail_depth=depth, token_count=20, ) frag_c = _make_fragment( uko_node="project://app/other.py", content="content C", detail_depth=depth, token_count=30, ) context.scored_list = [ ScoredFragment.from_fragment(frag_a, composite_score=0.5), ScoredFragment.from_fragment(frag_b, composite_score=0.9), ScoredFragment.from_fragment(frag_c, composite_score=0.7), ] # --------------------------------------------------------------------------- # When steps # --------------------------------------------------------------------------- @when("I create a scored fragment with composite_score {score:g}") def step_when_create_scored(context: Context, score: float) -> None: try: context.scored = ScoredFragment( fragment=context.fragment, composite_score=score, ) context.error = None except (ValidationError, ValueError) as exc: context.scored = None context.error = exc @when( "I create a scored fragment with composite_score {score:g}" " and rank {rank:d} and breakdown:" ) def step_when_create_scored_with_breakdown( context: Context, score: float, rank: int, ) -> None: breakdown: dict[str, float] = {} assert context.table is not None for row in context.table: breakdown[row["component"]] = float(row["score"]) context.scored = ScoredFragment( fragment=context.fragment, composite_score=score, score_breakdown=breakdown, rank=rank, ) @when("I create a scored fragment from relevance") def step_when_create_from_relevance(context: Context) -> None: context.scored = ScoredFragment.from_relevance(context.fragment) @when("I create a scored fragment from factory with score {score:g} and rank {rank:d}") def step_when_create_from_factory( context: Context, score: float, rank: int, ) -> None: context.scored = ScoredFragment.from_fragment( context.fragment, composite_score=score, rank=rank, ) @when("I create a scored fragment with an invalid breakdown component {score:g}") def step_when_create_invalid_breakdown(context: Context, score: float) -> None: try: context.scored = ScoredFragment( fragment=context.fragment, composite_score=0.5, score_breakdown={"bad": score}, ) context.error = None except (ValidationError, ValueError) as exc: context.scored = None context.error = exc @when("I create a scored fragment with composite_score {score:g} and rank {rank:d}") def step_when_create_scored_with_rank( context: Context, score: float, rank: int, ) -> None: try: context.scored = ScoredFragment( fragment=context.fragment, composite_score=score, rank=rank, ) context.error = None except (ValidationError, ValueError) as exc: context.scored = None context.error = exc @when("I add them to a set") def step_when_add_to_set(context: Context) -> None: context.scored_set = set(context.scored_list) # --------------------------------------------------------------------------- # Then steps # --------------------------------------------------------------------------- @then("the scored fragment composite_score should be {expected:g}") def step_then_composite_score(context: Context, expected: float) -> None: assert context.scored is not None, "No scored fragment created" assert context.scored.composite_score == expected, ( f"Expected composite_score={expected}, got {context.scored.composite_score}" ) @then("the scored fragment rank should be {expected:d}") def step_then_rank(context: Context, expected: int) -> None: assert context.scored is not None, "No scored fragment created" assert context.scored.rank == expected, ( f"Expected rank={expected}, got {context.scored.rank}" ) @then("the scored fragment score_breakdown should be empty") def step_then_breakdown_empty(context: Context) -> None: assert context.scored is not None, "No scored fragment created" assert context.scored.score_breakdown == {}, ( f"Expected empty breakdown, got {context.scored.score_breakdown}" ) @then('the scored fragment uko_node should be "{expected}"') def step_then_scored_uko_node(context: Context, expected: str) -> None: assert context.scored is not None, "No scored fragment created" assert context.scored.uko_node == expected, ( f"Expected uko_node={expected}, got {context.scored.uko_node}" ) @then("the scored fragment detail_depth should be {expected:d}") def step_then_scored_detail_depth(context: Context, expected: int) -> None: assert context.scored is not None, "No scored fragment created" assert context.scored.detail_depth == expected, ( f"Expected detail_depth={expected}, got {context.scored.detail_depth}" ) @then("the scored fragment token_count should be {expected:d}") def step_then_scored_token_count(context: Context, expected: int) -> None: assert context.scored is not None, "No scored fragment created" assert context.scored.token_count == expected, ( f"Expected token_count={expected}, got {context.scored.token_count}" ) @then('the scored fragment breakdown "{key}" should be {expected:g}') def step_then_breakdown_value(context: Context, key: str, expected: float) -> None: assert context.scored is not None, "No scored fragment created" assert key in context.scored.score_breakdown, ( f"Key '{key}' not in score_breakdown: {context.scored.score_breakdown}" ) actual = context.scored.score_breakdown[key] assert actual == expected, f"Expected breakdown['{key}']={expected}, got {actual}" @then("the two scored fragments should be equal") def step_then_scored_equal(context: Context) -> None: assert context.scored_a == context.scored_b, "Expected scored fragments to be equal" @then("the two scored fragments should not be equal") def step_then_scored_not_equal(context: Context) -> None: assert context.scored_a != context.scored_b, ( "Expected scored fragments to NOT be equal" ) @then("the two scored fragments should have the same hash") def step_then_scored_same_hash(context: Context) -> None: assert hash(context.scored_a) == hash(context.scored_b), ( "Expected scored fragments to have the same hash" ) @then("the set should contain {expected:d} unique scored fragments") def step_then_set_size(context: Context, expected: int) -> None: assert len(context.scored_set) == expected, ( f"Expected {expected} unique fragments, got {len(context.scored_set)}" ) @then("modifying the scored fragment composite_score should raise an error") def step_then_frozen(context: Context) -> None: try: context.scored.composite_score = 0.99 # type: ignore[misc] raise AssertionError("Expected ValidationError for frozen model") except ValidationError: pass @then("the fragment strategy_source should be empty") def step_then_strategy_source_empty(context: Context) -> None: assert context.fragment.strategy_source == "", ( f"Expected empty strategy_source, got '{context.fragment.strategy_source}'" ) @then('the fragment strategy_source should be "{expected}"') def step_then_strategy_source(context: Context, expected: str) -> None: assert context.fragment.strategy_source == expected, ( f"Expected strategy_source='{expected}', " f"got '{context.fragment.strategy_source}'" )