Files
cleveragents-core/features/steps/settings_coverage_boost_steps.py
freemo 051ee7c290
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 21s
CI / quality (pull_request) Successful in 31s
CI / typecheck (pull_request) Successful in 47s
CI / security (pull_request) Successful in 52s
CI / build (pull_request) Successful in 56s
CI / e2e_tests (pull_request) Successful in 5m1s
CI / integration_tests (pull_request) Successful in 5m30s
CI / unit_tests (pull_request) Successful in 5m42s
CI / docker (pull_request) Successful in 58s
CI / coverage (pull_request) Successful in 7m35s
CI / build (push) Successful in 21s
CI / docker (push) Has been skipped
CI / benchmark-regression (pull_request) Failing after 49m24s
CI / lint (push) Successful in 22s
CI / quality (push) Successful in 39s
CI / security (push) Successful in 48s
CI / typecheck (push) Successful in 1m26s
CI / benchmark-regression (push) Has been skipped
CI / e2e_tests (push) Successful in 5m53s
CI / coverage (push) Successful in 9m4s
CI / benchmark-publish (push) Successful in 19m10s
CI / integration_tests (push) Failing after 19m18s
CI / unit_tests (push) Failing after 19m20s
test(coverage): add Behave BDD tests to improve coverage across 52 source files
Added 52 new .feature files and corresponding _steps.py files targeting
previously uncovered code paths in the following areas:

- TUI layer: app, commands, persona (state/schema/registry), widgets,
  input (shell_exec, reference_parser)
- Application services: plan lifecycle/service/executor, session,
  project, repo indexing, correction, checkpoint, actor, llm_actors,
  strategy coordinator, resource file watcher, service retry wiring
- CLI commands: session, resource, repl, plan, db, automation_profile
- Domain models: retry_policy, resource_type, cost_budget,
  docker_compose_analyzer, detail_level, _sql_string_aware,
  _postgresql_helpers
- Core: circuit_breaker, retry_service_patterns
- Infrastructure: repositories, transaction_sandbox, strategy_registry,
  plugins/loader, container
- Config: settings
- Agents: plan_generation, context_analysis, auto_debug
- A2A: facade

All new tests follow the Behave/Gherkin BDD standard. Resolved step
definition collisions with unique prefixes. Fixed Alembic fileConfig
logger disabling issue (disable_existing_loggers=False).

ISSUES CLOSED: #1068
2026-03-20 21:22:10 +00:00

216 lines
8.3 KiB
Python

"""Step definitions for settings_coverage_boost.feature.
These steps target specific uncovered lines in settings.py:
- Lines 561-563: _max_delay_ge_base_delay model validator raising ValueError
- Lines 602, 604-608, 610-611: __repr__ method with sensitive-key masking
"""
from __future__ import annotations
import os
from behave import given, then, when
from pydantic import ValidationError
from cleveragents.config.settings import Settings
from cleveragents.shared.redaction import REDACTED
# Provider env var names that might leak into Settings from the test runner.
_PROVIDER_ENV_KEYS = (
"OPENAI_API_KEY",
"ANTHROPIC_API_KEY",
"GOOGLE_API_KEY",
"GOOGLE_GENAI_API_KEY",
"AZURE_OPENAI_API_KEY",
"AZURE_API_KEY",
"OPENROUTER_API_KEY",
"GEMINI_API_KEY",
"GOOGLE_GEMINI_API_KEY",
"HF_TOKEN",
"HUGGINGFACEHUB_API_TOKEN",
"HUGGING_FACE_HUB_TOKEN",
"COHERE_API_KEY",
"PERPLEXITY_API_KEY",
"GROQ_API_KEY",
"TOGETHER_API_KEY",
)
def _save_and_clear_provider_env(context):
"""Save current provider env vars and remove them so Settings sees None."""
saved = {}
for key in _PROVIDER_ENV_KEYS:
val = os.environ.pop(key, None)
if val is not None:
saved[key] = val
context._saved_provider_env = saved
def _restore_provider_env(context):
"""Restore previously saved provider env vars."""
saved = getattr(context, "_saved_provider_env", {})
for key, val in saved.items():
os.environ[key] = val
# ---------------------------------------------------------------------------
# Background
# ---------------------------------------------------------------------------
@given("the settings coverage module is imported")
def step_settings_module_imported(context):
"""Ensure the Settings class is importable."""
assert Settings is not None
# ---------------------------------------------------------------------------
# Scenario: _max_delay_ge_base_delay rejects invalid delay configuration
# (lines 561-563)
# ---------------------------------------------------------------------------
@when("I create Settings with retry_max_delay less than retry_base_delay")
def step_create_settings_bad_delays(context):
"""Attempt to create Settings where retry_max_delay < retry_base_delay."""
context.delay_error = None
# Use env vars because pydantic-settings reads these for Field values
os.environ["CLEVERAGENTS_RETRY_BASE_DELAY"] = "10.0"
os.environ["CLEVERAGENTS_RETRY_MAX_DELAY"] = "1.0"
os.environ["CLEVERAGENTS_ENV"] = "test"
os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
try:
Settings()
except ValidationError as exc:
context.delay_error = exc
finally:
os.environ.pop("CLEVERAGENTS_RETRY_BASE_DELAY", None)
os.environ.pop("CLEVERAGENTS_RETRY_MAX_DELAY", None)
os.environ.pop("CLEVERAGENTS_ENV", None)
os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
@then("a ValidationError should be raised about delay ordering")
def step_check_delay_validation_error(context):
"""Verify that a ValidationError was raised."""
assert context.delay_error is not None, (
"Expected a ValidationError but none was raised"
)
assert isinstance(context.delay_error, ValidationError)
@then("the error message should mention both delay values")
def step_check_delay_error_message(context):
"""Verify the error message contains information about both delays."""
msg = str(context.delay_error)
assert "retry_max_delay" in msg, f"Expected 'retry_max_delay' in: {msg}"
assert "retry_base_delay" in msg, f"Expected 'retry_base_delay' in: {msg}"
# ---------------------------------------------------------------------------
# Scenario: __repr__ masks sensitive field values when set
# (lines 602, 604-608, 610-611)
# ---------------------------------------------------------------------------
@given("I have a Settings instance with openai_api_key set to a secret value")
def step_create_settings_with_secret(context):
"""Create a Settings instance that has a sensitive key populated."""
_save_and_clear_provider_env(context)
os.environ["OPENAI_API_KEY"] = "sk-test-secret-12345"
os.environ["CLEVERAGENTS_ENV"] = "test"
os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
try:
context.settings_repr_target = Settings()
finally:
os.environ.pop("OPENAI_API_KEY", None)
os.environ.pop("CLEVERAGENTS_ENV", None)
os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
_restore_provider_env(context)
@when("I call repr on the settings instance")
def step_call_repr(context):
"""Invoke repr() on the stored Settings instance."""
context.settings_repr_output = repr(context.settings_repr_target)
@then("the repr output should contain REDACTED for the openai_api_key field")
def step_check_repr_redacted(context):
"""Verify the sensitive field is masked."""
expected = f"openai_api_key={REDACTED!r}"
assert expected in context.settings_repr_output, (
f"Expected '{expected}' in repr output:\n{context.settings_repr_output}"
)
@then('the repr output should start with "Settings(" and end with ")"')
def step_check_repr_format(context):
"""Verify overall repr format."""
assert context.settings_repr_output.startswith("Settings("), (
f"Expected repr to start with 'Settings(': {context.settings_repr_output[:50]}"
)
assert context.settings_repr_output.endswith(")"), (
f"Expected repr to end with ')': {context.settings_repr_output[-50:]}"
)
# ---------------------------------------------------------------------------
# Scenario: __repr__ shows non-sensitive fields normally
# (lines 604, 605, 606, 610, 611)
# ---------------------------------------------------------------------------
@given("I have a Settings instance with default values")
def step_create_settings_defaults(context):
"""Create a Settings instance with defaults (no secrets set)."""
_save_and_clear_provider_env(context)
os.environ["CLEVERAGENTS_ENV"] = "test"
os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
try:
context.settings_repr_target = Settings()
finally:
os.environ.pop("CLEVERAGENTS_ENV", None)
os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
_restore_provider_env(context)
@then("the repr output should contain the env field with its actual value")
def step_check_repr_env_field(context):
"""Verify that non-sensitive fields are shown with their actual values."""
assert "env='test'" in context.settings_repr_output, (
f"Expected \"env='test'\" in repr output:\n{context.settings_repr_output}"
)
@then("the repr output should not contain REDACTED for non-sensitive fields")
def step_check_repr_no_unnecessary_redaction(context):
"""Verify that the 'env' field (non-sensitive) is not redacted."""
assert f"env={REDACTED!r}" not in context.settings_repr_output, (
"Did not expect 'env' to be redacted"
)
# ---------------------------------------------------------------------------
# Scenario: __repr__ shows None for sensitive keys that are None
# (lines 606, 607 false-branch, 610)
# ---------------------------------------------------------------------------
@given("I have a Settings instance where all sensitive keys are None")
def step_create_settings_no_secrets(context):
"""Create a Settings with no API keys set (all remain None)."""
_save_and_clear_provider_env(context)
os.environ["CLEVERAGENTS_ENV"] = "test"
os.environ["CLEVERAGENTS_MOCK_PROVIDERS"] = "true"
try:
context.settings_repr_target = Settings()
finally:
os.environ.pop("CLEVERAGENTS_ENV", None)
os.environ.pop("CLEVERAGENTS_MOCK_PROVIDERS", None)
_restore_provider_env(context)
@then("the repr output should show None for sensitive key fields without redaction")
def step_check_repr_none_not_redacted(context):
"""When a sensitive key is None, it should show None rather than REDACTED."""
output = context.settings_repr_output
# azure_api_key defaults to None and should NOT be redacted
assert "azure_api_key=None" in output, (
f"Expected 'azure_api_key=None' in repr output:\n{output}"
)
# Confirm REDACTED is NOT used for a None-valued sensitive key
assert f"azure_api_key={REDACTED!r}" not in output, (
"Did not expect azure_api_key to be redacted when its value is None"
)