"""Step definitions for ProjectContextPolicy domain model tests.""" 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.models.core.context_policy import ( ContextView, ProjectContextPolicy, ) # ------------------------------------------------------------------- # Helpers # ------------------------------------------------------------------- def _default_view(**overrides: Any) -> ContextView: """Create a ContextView with sensible defaults.""" defaults: dict[str, Any] = {} defaults.update(overrides) return ContextView(**defaults) # ------------------------------------------------------------------- # Empty policy defaults # ------------------------------------------------------------------- @when("I create an empty project context policy") def step_create_empty_policy(context: Context) -> None: context.policy = ProjectContextPolicy() context.resolved_view = context.policy.default_view @then("the default view should include all resources") def step_default_includes_all_resources(context: Context) -> None: assert context.resolved_view.include_resources == [] @then("the default view should include all paths") def step_default_includes_all_paths(context: Context) -> None: assert context.resolved_view.include_paths == [] @then("the default view should have no file size limit") def step_default_no_file_size_limit(context: Context) -> None: assert context.resolved_view.max_file_size is None @then("the default view should have no total size limit") def step_default_no_total_size_limit(context: Context) -> None: assert context.resolved_view.max_total_size is None # ------------------------------------------------------------------- # View inheritance — Given steps # ------------------------------------------------------------------- @given("a policy with only a default view") def step_policy_default_only(context: Context) -> None: context.default_view = ContextView( include_resources=["db-*"], max_file_size=1024, ) context.policy = ProjectContextPolicy( default_view=context.default_view, ) @given("a policy with default and strategize views") def step_policy_default_and_strategize(context: Context) -> None: context.default_view = ContextView( include_resources=["db-*"], ) context.strategize_view = ContextView( include_resources=["db-*", "cache-*"], ) context.policy = ProjectContextPolicy( default_view=context.default_view, strategize_view=context.strategize_view, ) @given("a policy with default strategize and execute views") def step_policy_default_strat_exec(context: Context) -> None: context.default_view = ContextView( include_resources=["db-*"], ) context.strategize_view = ContextView( include_resources=["db-*", "cache-*"], ) context.execute_view = ContextView( include_resources=["db-*", "cache-*", "api-*"], ) context.policy = ProjectContextPolicy( default_view=context.default_view, strategize_view=context.strategize_view, execute_view=context.execute_view, ) @given("a policy with a custom execute view") def step_policy_custom_execute(context: Context) -> None: context.default_view = ContextView( include_resources=["db-*"], ) context.execute_view = ContextView( include_resources=["all-*"], max_file_size=2048, ) context.policy = ProjectContextPolicy( default_view=context.default_view, execute_view=context.execute_view, ) # ------------------------------------------------------------------- # View inheritance — When/Then steps # ------------------------------------------------------------------- @when('I resolve the view for phase "{phase}"') def step_resolve_view(context: Context, phase: str) -> None: context.resolved_view = context.policy.resolve_view(phase) @then("the resolved view should be the default view") def step_resolved_is_default(context: Context) -> None: assert context.resolved_view is context.default_view @then("the resolved view should be the strategize view") def step_resolved_is_strategize(context: Context) -> None: assert context.resolved_view is context.strategize_view @then("the resolved view should be the execute view") def step_resolved_is_execute(context: Context) -> None: assert context.resolved_view is context.execute_view # ------------------------------------------------------------------- # Invalid phase # ------------------------------------------------------------------- @when('I try to resolve the view for phase "{phase}"') def step_try_resolve_invalid_phase(context: Context, phase: str) -> None: context.policy_error = None try: context.policy.resolve_view(phase) except ValueError as exc: context.policy_error = str(exc) @then("a context policy error should be raised") def step_policy_error_raised(context: Context) -> None: assert context.policy_error is not None, "Expected an error but none was raised" @then('the context policy error should mention "{text}"') def step_error_mentions(context: Context, text: str) -> None: assert text in context.policy_error, ( f"Expected '{text}' in error: {context.policy_error}" ) # ------------------------------------------------------------------- # Include/exclude resources # ------------------------------------------------------------------- @when('I create a context view with include resources "{patterns}"') def step_create_view_include_resources(context: Context, patterns: str) -> None: names = [p.strip() for p in patterns.split(",")] context.ctx_view = ContextView(include_resources=names) @then("the context view should have {count:d} include resources") def step_view_include_resource_count(context: Context, count: int) -> None: assert len(context.ctx_view.include_resources) == count @when('I create a context view with exclude resources "{patterns}"') def step_create_view_exclude_resources(context: Context, patterns: str) -> None: names = [p.strip() for p in patterns.split(",")] context.ctx_view = ContextView(exclude_resources=names) @then("the context view should have {count:d} exclude resource") def step_view_exclude_resource_count_singular(context: Context, count: int) -> None: assert len(context.ctx_view.exclude_resources) == count @when('I create a context view with include "{inc}" and exclude "{exc}"') def step_create_view_include_exclude(context: Context, inc: str, exc: str) -> None: context.ctx_view = ContextView( include_resources=[inc], exclude_resources=[exc], ) @then("the context view should have {count:d} include resource") def step_view_include_resource_count_singular(context: Context, count: int) -> None: assert len(context.ctx_view.include_resources) == count # ------------------------------------------------------------------- # Include/exclude paths # ------------------------------------------------------------------- @when('I create a context view with include paths "{globs}"') def step_create_view_include_paths(context: Context, globs: str) -> None: paths = [g.strip() for g in globs.split(",")] context.ctx_view = ContextView(include_paths=paths) @then("the context view should have {count:d} include paths") def step_view_include_path_count(context: Context, count: int) -> None: assert len(context.ctx_view.include_paths) == count @when('I create a context view with exclude paths "{globs}"') def step_create_view_exclude_paths(context: Context, globs: str) -> None: paths = [g.strip() for g in globs.split(",")] context.ctx_view = ContextView(exclude_paths=paths) @then("the context view should have {count:d} exclude paths") def step_view_exclude_path_count(context: Context, count: int) -> None: assert len(context.ctx_view.exclude_paths) == count # ------------------------------------------------------------------- # Size limit validation # ------------------------------------------------------------------- @when("I create a context view with max file size {size:d}") def step_create_view_max_file_size(context: Context, size: int) -> None: context.ctx_view = ContextView(max_file_size=size) @then("the context view max file size should be {size:d}") def step_view_max_file_size(context: Context, size: int) -> None: assert context.ctx_view.max_file_size == size @when("I create a context view with no file size limit") def step_create_view_no_file_limit(context: Context) -> None: context.ctx_view = ContextView() @then("the context view max file size should be None") def step_view_max_file_size_none(context: Context) -> None: assert context.ctx_view.max_file_size is None @when("I try to create a context view with max file size {size:d}") def step_try_create_view_bad_file_size(context: Context, size: int) -> None: context.policy_error = None try: ContextView(max_file_size=size) except ValidationError as exc: context.policy_error = str(exc) @when("I create a context view with max total size {size:d}") def step_create_view_max_total_size(context: Context, size: int) -> None: context.ctx_view = ContextView(max_total_size=size) @then("the context view max total size should be {size:d}") def step_view_max_total_size(context: Context, size: int) -> None: assert context.ctx_view.max_total_size == size @when("I try to create a context view with max total size {size:d}") def step_try_create_view_bad_total_size(context: Context, size: int) -> None: context.policy_error = None try: ContextView(max_total_size=size) except ValidationError as exc: context.policy_error = str(exc) # ------------------------------------------------------------------- # Serialization round-trip # ------------------------------------------------------------------- @given("a fully populated context policy") def step_full_policy(context: Context) -> None: context.policy = ProjectContextPolicy( default_view=ContextView( include_resources=["db-*"], exclude_resources=["db-test"], include_paths=["src/**"], exclude_paths=["*.pyc"], max_file_size=1048576, max_total_size=10485760, ), strategize_view=ContextView( include_resources=["db-*", "cache-*"], ), execute_view=ContextView( include_paths=["src/**", "lib/**"], ), apply_view=ContextView( exclude_paths=["tests/**"], ), ) @when("I serialize and deserialize the policy") def step_roundtrip(context: Context) -> None: json_str = context.policy.model_dump_json() context.roundtrip_policy = ProjectContextPolicy.model_validate_json(json_str) @then("the deserialized policy should match the original") def step_roundtrip_match(context: Context) -> None: assert context.roundtrip_policy == context.policy # ------------------------------------------------------------------- # ContextView model_dump keys # ------------------------------------------------------------------- @when("I create a context view with defaults") def step_create_default_view(context: Context) -> None: context.ctx_view = ContextView() context.ctx_view_dump = context.ctx_view.model_dump() @then('the context view dump should have key "{key}"') def step_view_dump_has_key(context: Context, key: str) -> None: assert key in context.ctx_view_dump, ( f"Key '{key}' not in dump: {list(context.ctx_view_dump.keys())}" )