"""Additional step implementations for decomposition coverage scenarios. Exercises edge cases and code paths not covered by the primary scenarios: config validation bounds, token estimation errors, token_map parameter, closure trimming/clearing, service wrappers, property access, empty-list helpers, and short-path directory keys. """ from __future__ import annotations from typing import Any from behave import given, then, when # type: ignore[import-untyped] from cleveragents.application.services.decomposition_graph import ( DependencyClosureComputer, ) from cleveragents.application.services.decomposition_models import ( DecompositionConfig, DependencyEdge, DependencyGraph, DependencyType, ) # --------------------------------------------------------------------------- # Additional config validation # --------------------------------------------------------------------------- @when("I create a config with max_files_per_subplan {n:d}") def step_when_create_bad_max_files(context: Any, n: int) -> None: try: DecompositionConfig(max_files_per_subplan=n) except ValueError as exc: context.error = exc @when("I create a config with max_tokens_per_subplan {n:d}") def step_when_create_bad_max_tokens(context: Any, n: int) -> None: try: DecompositionConfig(max_tokens_per_subplan=n) except ValueError as exc: context.error = exc @when("I create a config with min_files_per_subplan {n:d}") def step_when_create_bad_min_files(context: Any, n: int) -> None: try: DecompositionConfig(min_files_per_subplan=n) except ValueError as exc: context.error = exc # --------------------------------------------------------------------------- # estimate_tokens edge cases # --------------------------------------------------------------------------- @when("I estimate tokens for a nonexistent file") def step_when_estimate_nonexistent(context: Any) -> None: from cleveragents.application.services.decomposition_clustering import ( estimate_tokens_for_path, ) context.token_estimate = estimate_tokens_for_path("/no/such/file.py") @then("the token estimate should be {n:d}") def step_then_token_estimate(context: Any, n: int) -> None: assert context.token_estimate == n # --------------------------------------------------------------------------- # cluster_by_size with token_map # --------------------------------------------------------------------------- @given("a set of files with known token counts") def step_given_files_with_tokens(context: Any) -> None: context.sized_files = ["a.py", "b.py", "c.py", "d.py"] context.token_map = {"a.py": 40, "b.py": 60, "c.py": 30, "d.py": 70} @when("I cluster by size with a token_map and max_tokens {n:d}") def step_when_cluster_by_size_tokenmap(context: Any, n: int) -> None: from cleveragents.application.services.decomposition_clustering import ( ClusteringStrategy, ) context.sized_clusters = ClusteringStrategy.cluster_by_size( context.sized_files, n, token_map=context.token_map ) @then("the clusters should respect the token_map values") def step_then_clusters_respect_tokenmap(context: Any) -> None: assert len(context.sized_clusters) >= 2 for cluster in context.sized_clusters: total = sum(context.token_map[f] for f in cluster) assert total <= 100 + 70 # max_tokens + single large file # --------------------------------------------------------------------------- # closure trimming with multiple roots # --------------------------------------------------------------------------- @given("a dependency graph with two roots each reaching 3 nodes") def step_given_two_root_graph(context: Any) -> None: g = DependencyGraph() for n in ("R1", "R1a", "R1b", "R2", "R2a", "R2b"): g.add_node(n) g.add_edge(DependencyEdge("R1", "R1a", DependencyType.IMPORT)) g.add_edge(DependencyEdge("R1", "R1b", DependencyType.IMPORT)) g.add_edge(DependencyEdge("R2", "R2a", DependencyType.IMPORT)) g.add_edge(DependencyEdge("R2", "R2b", DependencyType.IMPORT)) context.graph = g @when("I compute closure from both roots with cutoff {n:d}") def step_when_closure_both_roots_cutoff(context: Any, n: int) -> None: computer = DependencyClosureComputer() context.closure = computer.compute_closure(context.graph, ["R1", "R2"], cutoff=n) # --------------------------------------------------------------------------- # clear cache # --------------------------------------------------------------------------- @when("I compute closure from A and then clear the cache") def step_when_closure_and_clear(context: Any) -> None: computer = DependencyClosureComputer() context.closure_before = computer.compute_closure(context.graph, ["A"]) computer.clear_cache() context.closure_after = computer.compute_closure(context.graph, ["A"]) @then("computing closure from A again should still work") def step_then_closure_after_clear(context: Any) -> None: assert context.closure_before == context.closure_after # --------------------------------------------------------------------------- # service wrapper methods # --------------------------------------------------------------------------- @when("I compute dependency order through the service") def step_when_service_topo(context: Any) -> None: context.service_order = context.svc.compute_dependency_order(context.graph) @then("A should appear before B and C in the service order") def step_then_service_a_before_bc(context: Any) -> None: order = context.service_order assert order.index("A") < order.index("B") assert order.index("A") < order.index("C") @then("B and C should appear before D in the service order") def step_then_service_bc_before_d(context: Any) -> None: order = context.service_order assert order.index("B") < order.index("D") assert order.index("C") < order.index("D") @when("I compute closure through the service from A") def step_when_service_closure(context: Any) -> None: context.service_closure = context.svc.compute_closure(context.graph, ["A"]) @then("the service closure should contain A, B, C, D") def step_then_service_closure_abcd(context: Any) -> None: assert {"A", "B", "C", "D"} <= context.service_closure # --------------------------------------------------------------------------- # decision_service property # --------------------------------------------------------------------------- @then("the decision service property should return the mock") def step_then_ds_property_mock(context: Any) -> None: assert context.svc.decision_service is context.mock_ds @then("the decision service property should return None") def step_then_ds_property_none(context: Any) -> None: assert context.svc.decision_service is None # --------------------------------------------------------------------------- # empty file edge cases # --------------------------------------------------------------------------- @when("I compute dominant extension of an empty list") def step_when_dominant_ext_empty(context: Any) -> None: from cleveragents.application.services.decomposition_service import ( _dominant_extension, ) context.dominant_ext = _dominant_extension([]) @then("the dominant extension should be empty") def step_then_dominant_ext_empty(context: Any) -> None: assert context.dominant_ext == "" @when("I compute common prefix of an empty list") def step_when_common_prefix_empty(context: Any) -> None: from cleveragents.application.services.decomposition_service import ( _common_prefix, ) context.common_pfx = _common_prefix([]) @then("the common prefix should be empty") def step_then_common_prefix_empty(context: Any) -> None: assert context.common_pfx == "" # --------------------------------------------------------------------------- # directory key short path # --------------------------------------------------------------------------- @when("I compute directory key for a single-component path") def step_when_dir_key_short(context: Any) -> None: from cleveragents.application.services.decomposition_clustering import ( _directory_key, ) context.dir_key = _directory_key("file.py", depth=2) @then("the directory key should be empty string") def step_then_dir_key_empty(context: Any) -> None: assert context.dir_key == ""