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
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
216 lines
8.3 KiB
Python
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"
|
|
)
|