"""Step definitions for config_service_coverage_boost.feature. Targets uncovered lines in config_service.py: - Line 1267: resolve() verbose + cli_value (CLI_FLAG chain entry with value) - Lines 1279-1283: resolve() verbose + env var (ENV_VAR chain entry with value) - Lines 1404-1405: set_project_value() with empty project_name - Line 1415: set_project_value() non-dict project section - Line 1418: set_project_value() non-dict project overrides - Line 1450: get_project_overrides() non-dict project section - Line 1453: get_project_overrides() non-dict project data """ from __future__ import annotations import os import tempfile from pathlib import Path from typing import Any from unittest.mock import patch from behave import given, then, when from cleveragents.application.services.config_service import ( ConfigLevel, ConfigService, ) # --------------------------------------------------------------------------- # Background # --------------------------------------------------------------------------- @given("a fresh temporary config directory for coverage boost tests") def step_fresh_temp_dir(context: Any) -> None: context.cb_tmpdir = Path(tempfile.mkdtemp()) context.cb_config_dir = context.cb_tmpdir / "cfg" context.cb_config_path = context.cb_config_dir / "config.toml" context.cb_svc = None context.cb_result = None context.cb_error = None context.cb_env_patches: list[Any] = [] # --------------------------------------------------------------------------- # Given helpers # --------------------------------------------------------------------------- @given("a coverage boost ConfigService with empty config") def step_svc_empty(context: Any) -> None: context.cb_config_dir.mkdir(parents=True, exist_ok=True) context.cb_svc = ConfigService( config_dir=context.cb_config_dir, config_path=context.cb_config_path, ) @given('the coverage boost env var "{name}" is injected as "{value}"') def step_set_env_var(context: Any, name: str, value: str) -> None: patcher = patch.dict(os.environ, {name: value}) patcher.start() context.cb_env_patches.append(patcher) @given("a coverage boost ConfigService with project section set to a string") def step_svc_project_section_string(context: Any) -> None: context.cb_config_dir.mkdir(parents=True, exist_ok=True) # Write a TOML file where "project" is a plain string, not a table. # tomlkit won't let us write project = "bad" then treat it as a table, # so we write raw TOML manually. context.cb_config_path.write_text('project = "not_a_dict"\n') context.cb_svc = ConfigService( config_dir=context.cb_config_dir, config_path=context.cb_config_path, ) @given( 'a coverage boost ConfigService with project overrides for "{proj}" set to a string' ) def step_svc_project_overrides_string(context: Any, proj: str) -> None: context.cb_config_dir.mkdir(parents=True, exist_ok=True) # Write TOML where [project] exists but project. is a string. raw = f'[project]\n{proj} = "not_a_dict"\n' context.cb_config_path.write_text(raw) context.cb_svc = ConfigService( config_dir=context.cb_config_dir, config_path=context.cb_config_path, ) # --------------------------------------------------------------------------- # When steps # --------------------------------------------------------------------------- @when('I resolve "{key}" with cli_value "{cli_val}" and verbose True') def step_resolve_cli_verbose(context: Any, key: str, cli_val: str) -> None: context.cb_result = context.cb_svc.resolve(key, cli_value=cli_val, verbose=True) @when('I resolve "{key}" with verbose True and no cli_value') def step_resolve_verbose_no_cli(context: Any, key: str) -> None: context.cb_result = context.cb_svc.resolve(key, verbose=True) @when( 'I call set_project_value with empty project name and key "{key}" value {value:d}' ) def step_set_project_value_empty_name(context: Any, key: str, value: int) -> None: try: context.cb_svc.set_project_value("", key, value) context.cb_error = None except (ValueError, TypeError) as exc: context.cb_error = exc @when('I call set_project_value for project "{proj}" key "{key}" value {value:d}') def step_set_project_value_normal( context: Any, proj: str, key: str, value: int ) -> None: try: context.cb_svc.set_project_value(proj, key, value) context.cb_error = None except (ValueError, TypeError) as exc: context.cb_error = exc @when('I call get_project_overrides for project "{proj}"') def step_get_project_overrides(context: Any, proj: str) -> None: context.cb_result = context.cb_svc.get_project_overrides(proj) # --------------------------------------------------------------------------- # Then steps # --------------------------------------------------------------------------- @then('the chain should contain a CLI_FLAG entry with value "{expected}"') def step_verify_cli_flag_chain_entry(context: Any, expected: str) -> None: chain = context.cb_result.chain cli_entries = [e for e in chain if e["source"] == ConfigLevel.CLI_FLAG.value] assert len(cli_entries) == 1, f"Expected 1 CLI_FLAG entry, got {len(cli_entries)}" assert cli_entries[0]["value"] == expected, ( f"Expected CLI_FLAG value '{expected}', got '{cli_entries[0]['value']}'" ) @then( 'the chain should contain an ENV_VAR entry with value "{expected}" ' 'and env_name "{env_name}"' ) def step_verify_env_var_chain_entry(context: Any, expected: str, env_name: str) -> None: # Clean up env patches after capturing the resolve result for p in context.cb_env_patches: p.stop() context.cb_env_patches.clear() chain = context.cb_result.chain env_entries = [e for e in chain if e["source"] == ConfigLevel.ENV_VAR.value] assert len(env_entries) == 1, f"Expected 1 ENV_VAR entry, got {len(env_entries)}" entry = env_entries[0] assert entry["value"] == expected, ( f"Expected ENV_VAR value '{expected}', got '{entry['value']}'" ) assert entry["env_name"] == env_name, ( f"Expected env_name '{env_name}', got '{entry['env_name']}'" ) @then('a ValueError with message "{fragment}" should be raised') def step_verify_valueerror_message(context: Any, fragment: str) -> None: assert context.cb_error is not None, "Expected a ValueError but none was raised" assert isinstance(context.cb_error, ValueError), ( f"Expected ValueError, got {type(context.cb_error).__name__}" ) assert fragment in str(context.cb_error), ( f"Expected '{fragment}' in error message, got: {context.cb_error}" ) @then("the call should succeed without error") def step_verify_no_error(context: Any) -> None: assert context.cb_error is None, f"Expected no error but got: {context.cb_error}" @then('get_project_overrides for "{proj}" should contain key "{key}"') def step_verify_overrides_contain_key(context: Any, proj: str, key: str) -> None: overrides = context.cb_svc.get_project_overrides(proj) assert key in overrides, ( f"Expected '{key}' in project overrides, got: {list(overrides.keys())}" ) @then("the coverage boost result should be an empty dictionary") def step_verify_empty_dict(context: Any) -> None: assert context.cb_result == {}, f"Expected empty dict, got: {context.cb_result}"