Files
cleveragents-core/features/steps/project_context_policy_steps.py

348 lines
12 KiB
Python

"""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())}"
)