"""Step definitions for config_resolution.feature.""" from __future__ import annotations import os import shutil import tempfile from pathlib import Path from typing import Any from behave import given, then, use_step_matcher, when from behave.runner import Context from cleveragents.application.services.config_service import ConfigService use_step_matcher("re") # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- def _get_service(context: Context) -> ConfigService: """Return the test-scoped ConfigService instance.""" svc: ConfigService = context._config_service return svc def _cleanup(context: Context) -> None: """Clean up temp dir and env vars.""" tmpdir = getattr(context, "_config_tmpdir", None) if tmpdir and os.path.isdir(tmpdir): shutil.rmtree(tmpdir, ignore_errors=True) for var in getattr(context, "_env_vars_set", []): os.environ.pop(var, None) # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("a clean config service test environment") def step_clean_config_service_env(context: Context) -> None: tmpdir = tempfile.mkdtemp() context._config_tmpdir = tmpdir tmp_path = Path(tmpdir) context._config_service = ConfigService( config_dir=tmp_path, config_path=tmp_path / "config.toml", ) context._env_vars_set = [] if not hasattr(context, "_cleanup_handlers"): context._cleanup_handlers = [] context._cleanup_handlers.append(lambda: _cleanup(context)) # --------------------------------------------------------------------------- # Given # --------------------------------------------------------------------------- @given( r'I have written global config key "(?P[^"]+)" with value "(?P[^"]+)"' ) def step_write_global_config(context: Context, key: str, value: str) -> None: svc = _get_service(context) data = svc.read_config() data[key] = value svc.write_config(data) @given( r'I have written project "(?P[^"]+)" config key "(?P[^"]+)" with value "(?P[^"]+)"' ) def step_write_project_config( context: Context, project: str, key: str, value: str ) -> None: svc = _get_service(context) data = svc.read_config() if "project" not in data: data["project"] = {} proj_data: dict[str, Any] = data["project"] if project not in proj_data: proj_data[project] = {} proj_data[project][key] = value svc.write_config(data) @given(r'I write config data with key "(?P[^"]+)" and value "(?P[^"]+)"') def step_write_config_data(context: Context, key: str, value: str) -> None: svc = _get_service(context) svc.set_value(key, value) # --------------------------------------------------------------------------- # When # --------------------------------------------------------------------------- @when(r'I resolve config key "(?P[^"]+)" with CLI value "(?P[^"]+)"') def step_resolve_key_cli(context: Context, key: str, cli_value: str) -> None: svc = _get_service(context) context.resolved = svc.resolve(key, cli_value=cli_value) @when(r'I resolve config key "(?P[^"]+)" for project "(?P[^"]+)"') def step_resolve_key_project(context: Context, key: str, project: str) -> None: svc = _get_service(context) context.resolved = svc.resolve(key, project_name=project) @when(r'I resolve config key "(?P[^"]+)" with verbose mode') def step_resolve_key_verbose(context: Context, key: str) -> None: svc = _get_service(context) context.resolved = svc.resolve(key, verbose=True) @when(r'I resolve config key "(?P[^"]+)"') def step_resolve_key(context: Context, key: str) -> None: svc = _get_service(context) context.resolved = svc.resolve(key) @when(r'I look up the env var for key "(?P[^"]+)"') def step_lookup_env_var(context: Context, key: str) -> None: svc = _get_service(context) context.env_var_name = svc.env_var_for_key(key) @when(r'I attempt to resolve unknown key "(?P[^"]+)"') def step_attempt_resolve_unknown(context: Context, key: str) -> None: svc = _get_service(context) try: svc.resolve(key) context.raised_error = None except ValueError as exc: context.raised_error = exc @when(r'I attempt to validate unknown key "(?P[^"]+)"') def step_attempt_validate_unknown(context: Context, key: str) -> None: try: ConfigService.validate_key(key) context.raised_error = None except ValueError as exc: context.raised_error = exc @when( r'I attempt to validate type for key "(?P[^"]+)" with value "(?P[^"]+)"' ) def step_attempt_validate_type(context: Context, key: str, value: str) -> None: try: ConfigService.validate_type(key, value) context.raised_error = None except (TypeError, ValueError) as exc: context.raised_error = exc @when("I read the config file") def step_read_config_file(context: Context) -> None: svc = _get_service(context) context.config_data = svc.read_config() @when("I write config data to a new directory") def step_write_to_new_dir(context: Context) -> None: base = Path(context._config_tmpdir) / "nested" / "subdir" context._new_config_dir = base svc = ConfigService(config_dir=base, config_path=base / "config.toml") svc.write_config({"test_key": "test_value"}) @when("I resolve all keys") def step_resolve_all(context: Context) -> None: svc = _get_service(context) context.all_resolved = svc.resolve_all() @when(r'I set config value "(?P[^"]+)" to "(?P[^"]+)"') def step_set_config_value(context: Context, key: str, value: str) -> None: svc = _get_service(context) svc.set_value(key, value) @when(r'I get entry for key "(?P[^"]+)"') def step_get_entry(context: Context, key: str) -> None: context.entry = ConfigService.get_entry(key) # --------------------------------------------------------------------------- # Then # --------------------------------------------------------------------------- @then(r'the resolved value should be "(?P[^"]+)"') def step_resolved_value(context: Context, value: str) -> None: assert str(context.resolved.value) == value, ( f"Expected '{value}', got '{context.resolved.value}'" ) @then(r'the resolved source should be "(?P[^"]+)"') def step_resolved_source(context: Context, source: str) -> None: assert context.resolved.source.value == source, ( f"Expected source '{source}', got '{context.resolved.source.value}'" ) @then(r'the env var name should be "(?P[^"]+)"') def step_env_var_name(context: Context, expected: str) -> None: assert context.env_var_name == expected, ( f"Expected '{expected}', got '{context.env_var_name}'" ) @then(r'the config ValueError message should contain "(?P[^"]+)"') def step_config_value_error_raised(context: Context, fragment: str) -> None: assert context.raised_error is not None, "Expected ValueError but none was raised" assert isinstance(context.raised_error, ValueError), ( f"Expected ValueError, got {type(context.raised_error).__name__}" ) assert fragment in str(context.raised_error), ( f"Expected '{fragment}' in message: {context.raised_error}" ) @then(r'the config TypeError message should contain "(?P[^"]+)"') def step_config_type_error_raised(context: Context, fragment: str) -> None: assert context.raised_error is not None, "Expected TypeError but none was raised" assert isinstance(context.raised_error, TypeError), ( f"Expected TypeError, got {type(context.raised_error).__name__}" ) assert fragment in str(context.raised_error), ( f"Expected '{fragment}' in message: {context.raised_error}" ) @then(r"the resolved value should be integer (?P\d+)") def step_resolved_int(context: Context, value: str) -> None: expected = int(value) assert context.resolved.value == expected, ( f"Expected {expected}, got {context.resolved.value}" ) assert isinstance(context.resolved.value, int), ( f"Expected int, got {type(context.resolved.value).__name__}" ) @then("the resolved value should be boolean true") def step_resolved_bool_true(context: Context) -> None: assert context.resolved.value is True, ( f"Expected True, got {context.resolved.value}" ) @then("the resolved value should be boolean false") def step_resolved_bool_false(context: Context) -> None: assert context.resolved.value is False, ( f"Expected False, got {context.resolved.value}" ) @then(r"the resolved value should be float (?P[\d.]+)") def step_resolved_float(context: Context, value: str) -> None: expected = float(value) assert abs(context.resolved.value - expected) < 0.001, ( f"Expected {expected}, got {context.resolved.value}" ) @then(r"the resolution chain should have (?P\d+) entries") def step_chain_length(context: Context, count: str) -> None: expected = int(count) assert len(context.resolved.chain) == expected, ( f"Expected {expected} entries, got {len(context.resolved.chain)}" ) @then(r'the chain should include source "(?P[^"]+)"') def step_chain_includes_source(context: Context, source: str) -> None: sources = [entry["source"] for entry in context.resolved.chain] assert source in sources, f"Expected source '{source}' in chain: {sources}" @then(r'the registry should contain key "(?P[^"]+)"') def step_registry_contains(context: Context, key: str) -> None: registry = ConfigService.registry() assert key in registry, f"Key '{key}' not found in registry" @then( r'the config data should contain key "(?P[^"]+)" with value "(?P[^"]+)"' ) def step_config_data_contains(context: Context, key: str, value: str) -> None: data = context.config_data assert key in data, f"Key '{key}' not in config data: {data}" assert str(data[key]) == value, ( f"Expected '{value}' for key '{key}', got '{data[key]}'" ) @then("the config directory should exist") def step_config_dir_exists(context: Context) -> None: assert context._new_config_dir.exists(), ( f"Config dir {context._new_config_dir} does not exist" ) @then("the result should contain all registered keys") def step_result_has_all_keys(context: Context) -> None: registered = ConfigService.registered_keys() for key in registered: assert key in context.all_resolved, ( f"Key '{key}' missing from resolve_all result" ) @then(r'the entry should have section "(?P
[^"]+)"') def step_entry_section(context: Context, section: str) -> None: assert context.entry is not None, "Entry should not be None" assert context.entry.section == section, ( f"Expected section '{section}', got '{context.entry.section}'" ) @then(r'the entry should have python type "(?P[^"]+)"') def step_entry_type(context: Context, type_name: str) -> None: assert context.entry is not None, "Entry should not be None" assert context.entry.python_type.__name__ == type_name, ( f"Expected type '{type_name}', got '{context.entry.python_type.__name__}'" ) @then("the entry should be None") def step_entry_is_none(context: Context) -> None: assert context.entry is None, f"Expected None, got {context.entry}" # Restore default step matcher for other step files use_step_matcher("parse")