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