a7dc917021
ISSUES CLOSED: #319
244 lines
8.8 KiB
Python
244 lines
8.8 KiB
Python
"""Step definitions for secure template rendering tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from behave import given, then, when
|
|
from behave.runner import Context
|
|
|
|
from cleveragents.templates.renderer import TemplateRenderer
|
|
from cleveragents.templates.secure_renderer import (
|
|
SecureTemplateRenderer,
|
|
TemplateConfig,
|
|
TemplateError,
|
|
TemplateSecurityError,
|
|
TemplateSizeError,
|
|
TemplateValidationError,
|
|
render_template_secure,
|
|
validate_template,
|
|
)
|
|
|
|
__all__: list[str] = []
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Background
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given("a secure template test environment")
|
|
def step_given_secure_template_env(context: Context) -> None:
|
|
context.sec_template = ""
|
|
context.sec_context = {} # dict[str, str]
|
|
context.sec_rendered = ""
|
|
context.sec_error = None # TemplateError | None
|
|
context.sec_validation_errors = [] # list[str]
|
|
context.sec_config = None # TemplateConfig | None
|
|
context.sec_allowed_keys = None # frozenset[str] | None
|
|
context.sec_renderer = None # SecureTemplateRenderer | None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Given steps — template text
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@given('the secure template text "{template}"')
|
|
def step_given_template(context: Context, template: str) -> None:
|
|
context.sec_template = template
|
|
|
|
|
|
@given('a secure template context key "{key}" with value "{value}"')
|
|
def step_given_context_key(context: Context, key: str, value: str) -> None:
|
|
context.sec_context[key] = value
|
|
|
|
|
|
@given('a secure template with allowed keys "{keys}"')
|
|
def step_given_allowed_keys(context: Context, keys: str) -> None:
|
|
context.sec_allowed_keys = frozenset(k.strip() for k in keys.split(","))
|
|
|
|
|
|
@given("the secure template has no key restrictions")
|
|
def step_given_template_open(context: Context) -> None:
|
|
context.sec_allowed_keys = None
|
|
|
|
|
|
@given("a secure template that is {n} characters long")
|
|
def step_given_long_template(context: Context, n: str) -> None:
|
|
context.sec_template = "x" * int(n)
|
|
|
|
|
|
@given("a secure template config with max output length of {n}")
|
|
def step_given_max_output_config(context: Context, n: str) -> None:
|
|
context.sec_config = TemplateConfig(max_output_length=int(n))
|
|
|
|
|
|
@given("a secure template config with logging disabled")
|
|
def step_given_config_logging_disabled(context: Context) -> None:
|
|
context.sec_config = TemplateConfig(
|
|
allowed_keys=context.sec_allowed_keys or frozenset({"name"}),
|
|
log_rejected=False,
|
|
)
|
|
|
|
|
|
@given("a secure template config that permits unknown keys")
|
|
def step_given_config_permit_unknown(context: Context) -> None:
|
|
context.sec_config = TemplateConfig(
|
|
allowed_keys=frozenset({"name"}),
|
|
reject_unknown_keys=False,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# When steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@when("I render the secure template")
|
|
def step_when_render_securely(context: Context) -> None:
|
|
config = context.sec_config
|
|
if config is None and context.sec_allowed_keys is not None:
|
|
config = TemplateConfig(allowed_keys=context.sec_allowed_keys)
|
|
renderer = SecureTemplateRenderer(config=config)
|
|
context.sec_rendered = renderer.render(context.sec_template, context.sec_context)
|
|
|
|
|
|
@when("I render the secure template expecting a security error")
|
|
def step_when_render_security_error(context: Context) -> None:
|
|
config = context.sec_config or TemplateConfig()
|
|
renderer = SecureTemplateRenderer(config=config)
|
|
try:
|
|
renderer.render(context.sec_template, context.sec_context)
|
|
context.sec_error = None
|
|
except TemplateError as exc:
|
|
assert isinstance(exc, TemplateSecurityError), (
|
|
f"Expected TemplateSecurityError, got {type(exc).__name__}: {exc}"
|
|
)
|
|
context.sec_error = exc
|
|
|
|
|
|
@when("I render the secure template expecting a size error")
|
|
def step_when_render_size_error(context: Context) -> None:
|
|
config = context.sec_config or TemplateConfig()
|
|
renderer = SecureTemplateRenderer(config=config)
|
|
try:
|
|
renderer.render(context.sec_template, context.sec_context)
|
|
context.sec_error = None
|
|
except TemplateError as exc:
|
|
assert isinstance(exc, TemplateSizeError), (
|
|
f"Expected TemplateSizeError, got {type(exc).__name__}: {exc}"
|
|
)
|
|
context.sec_error = exc
|
|
|
|
|
|
@when("I render the secure template expecting a validation error")
|
|
def step_when_render_validation_error(context: Context) -> None:
|
|
config = context.sec_config
|
|
if config is None and context.sec_allowed_keys is not None:
|
|
config = TemplateConfig(allowed_keys=context.sec_allowed_keys)
|
|
elif config is None:
|
|
config = TemplateConfig()
|
|
renderer = SecureTemplateRenderer(config=config)
|
|
try:
|
|
renderer.render(context.sec_template, context.sec_context)
|
|
context.sec_error = None
|
|
except TemplateError as exc:
|
|
assert isinstance(exc, TemplateValidationError), (
|
|
f"Expected TemplateValidationError, got {type(exc).__name__}: {exc}"
|
|
)
|
|
context.sec_error = exc
|
|
|
|
|
|
@when("I validate the secure template")
|
|
def step_when_validate_template(context: Context) -> None:
|
|
allowed = context.sec_allowed_keys or frozenset()
|
|
context.sec_validation_errors = validate_template(
|
|
context.sec_template, allowed_keys=allowed
|
|
)
|
|
|
|
|
|
@when("I render via the secure convenience function")
|
|
def step_when_render_convenience(context: Context) -> None:
|
|
context.sec_rendered = render_template_secure(
|
|
context.sec_template, context.sec_context
|
|
)
|
|
|
|
|
|
@when("I render via the secure convenience function expecting an error")
|
|
def step_when_render_convenience_error(context: Context) -> None:
|
|
try:
|
|
render_template_secure(context.sec_template, context.sec_context)
|
|
context.sec_error = None
|
|
except TemplateError as exc:
|
|
context.sec_error = exc
|
|
|
|
|
|
@when("I create a renderer with those settings")
|
|
def step_when_create_renderer(context: Context) -> None:
|
|
config = context.sec_config
|
|
if config is None and context.sec_allowed_keys is not None:
|
|
config = TemplateConfig(allowed_keys=context.sec_allowed_keys)
|
|
context.sec_renderer = SecureTemplateRenderer(config=config)
|
|
|
|
|
|
@when("I render via the legacy TemplateRenderer")
|
|
def step_when_render_legacy(context: Context) -> None:
|
|
renderer = TemplateRenderer()
|
|
context.sec_rendered = renderer.render(context.sec_template, context.sec_context)
|
|
|
|
|
|
@when("I render via the legacy TemplateRenderer with config")
|
|
def step_when_render_legacy_with_config(context: Context) -> None:
|
|
config = context.sec_config
|
|
if config is None and context.sec_allowed_keys is not None:
|
|
config = TemplateConfig(allowed_keys=context.sec_allowed_keys)
|
|
renderer = TemplateRenderer(config=config)
|
|
context.sec_rendered = renderer.render(context.sec_template, context.sec_context)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Then steps
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@then('the secure template output should be "{expected}"')
|
|
def step_then_rendered_output(context: Context, expected: str) -> None:
|
|
assert context.sec_rendered == expected, (
|
|
f"Expected {expected!r}, got {context.sec_rendered!r}"
|
|
)
|
|
|
|
|
|
@then('the secure template error should mention "{text}"')
|
|
def step_then_error_mentions(context: Context, text: str) -> None:
|
|
assert context.sec_error is not None, "Expected a template error"
|
|
assert text in str(context.sec_error), (
|
|
f"Expected '{text}' in error: {context.sec_error}"
|
|
)
|
|
|
|
|
|
@then("the secure template validation should be empty")
|
|
def step_then_validation_empty(context: Context) -> None:
|
|
assert context.sec_validation_errors == [], (
|
|
f"Expected no errors, got: {context.sec_validation_errors}"
|
|
)
|
|
|
|
|
|
@then("the secure template validation should not be empty")
|
|
def step_then_validation_not_empty(context: Context) -> None:
|
|
assert len(context.sec_validation_errors) > 0, (
|
|
"Expected validation errors but got none"
|
|
)
|
|
|
|
|
|
@then('the secure template validation should mention "{text}"')
|
|
def step_then_validation_mentions(context: Context, text: str) -> None:
|
|
combined = " ".join(context.sec_validation_errors)
|
|
assert text in combined, f"Expected '{text}' in: {context.sec_validation_errors}"
|
|
|
|
|
|
@then('the renderer config allowed keys should contain "{key}"')
|
|
def step_then_config_has_key(context: Context, key: str) -> None:
|
|
cfg = context.sec_renderer.config # exercises the .config property (line 155)
|
|
assert key in cfg.allowed_keys, (
|
|
f"Expected '{key}' in allowed_keys, got: {cfg.allowed_keys}"
|
|
)
|