fix(config): add server_url, server_token, format, and default_estimation_actor fields to Settings #3176
@@ -0,0 +1,89 @@
|
||||
Feature: Settings class spec-required env var fields
|
||||
The Settings class must expose fields for all spec-required environment
|
||||
variables: CLEVERAGENTS_SERVER_URL, CLEVERAGENTS_SERVER_TOKEN,
|
||||
CLEVERAGENTS_FORMAT, and CLEVERAGENTS_DEFAULT_ESTIMATION_ACTOR.
|
||||
|
||||
Background:
|
||||
Given no CleverAgents spec-field env vars are set
|
||||
|
||||
# ── server_url ──────────────────────────────────────────────────────────────
|
||||
|
||||
Scenario: server_url defaults to None when env var is absent
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_url should be None
|
||||
|
||||
Scenario: server_url reads CLEVERAGENTS_SERVER_URL from environment
|
||||
Given the environment variable "CLEVERAGENTS_SERVER_URL" is set to "https://ca.example.com"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_url should be "https://ca.example.com"
|
||||
|
||||
Scenario: server_url accepts any string value
|
||||
Given the environment variable "CLEVERAGENTS_SERVER_URL" is set to "http://localhost:9000"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_url should be "http://localhost:9000"
|
||||
|
||||
# ── server_token ─────────────────────────────────────────────────────────────
|
||||
|
||||
Scenario: server_token defaults to None when env var is absent
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_token should be None
|
||||
|
||||
Scenario: server_token reads CLEVERAGENTS_SERVER_TOKEN from environment
|
||||
Given the environment variable "CLEVERAGENTS_SERVER_TOKEN" is set to "secret-token-abc"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_token should be "secret-token-abc"
|
||||
|
||||
Scenario: server_token accepts any string value
|
||||
Given the environment variable "CLEVERAGENTS_SERVER_TOKEN" is set to "bearer-xyz-789"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_token should be "bearer-xyz-789"
|
||||
|
||||
# ── format ───────────────────────────────────────────────────────────────────
|
||||
|
||||
Scenario: format defaults to None when env var is absent
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.format should be None
|
||||
|
||||
Scenario: format reads CLEVERAGENTS_FORMAT from environment
|
||||
Given the environment variable "CLEVERAGENTS_FORMAT" is set to "json"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.format should be "json"
|
||||
|
||||
Scenario: format accepts any string value
|
||||
Given the environment variable "CLEVERAGENTS_FORMAT" is set to "markdown"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.format should be "markdown"
|
||||
|
||||
# ── default_estimation_actor ─────────────────────────────────────────────────
|
||||
|
||||
Scenario: default_estimation_actor defaults to None when env var is absent
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.default_estimation_actor should be None
|
||||
|
||||
Scenario: default_estimation_actor reads CLEVERAGENTS_DEFAULT_ESTIMATION_ACTOR from environment
|
||||
Given the environment variable "CLEVERAGENTS_DEFAULT_ESTIMATION_ACTOR" is set to "gpt-estimator"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.default_estimation_actor should be "gpt-estimator"
|
||||
|
||||
Scenario: default_estimation_actor accepts any string value
|
||||
Given the environment variable "CLEVERAGENTS_DEFAULT_ESTIMATION_ACTOR" is set to "claude-estimator"
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.default_estimation_actor should be "claude-estimator"
|
||||
|
||||
# ── model_fields presence ────────────────────────────────────────────────────
|
||||
|
||||
Scenario: all four spec fields appear in Settings.model_fields
|
||||
When I inspect Settings.model_fields
|
||||
Then "server_url" should be present in model_fields
|
||||
And "server_token" should be present in model_fields
|
||||
And "format" should be present in model_fields
|
||||
And "default_estimation_actor" should be present in model_fields
|
||||
|
||||
# ── type annotation ──────────────────────────────────────────────────────────
|
||||
|
||||
Scenario: all four spec fields are typed as str or None
|
||||
When I instantiate Settings for spec fields
|
||||
Then settings.server_url should be of type str or None
|
||||
And settings.server_token should be of type str or None
|
||||
And settings.format should be of type str or None
|
||||
And settings.default_estimation_actor should be of type str or None
|
||||
@@ -0,0 +1,155 @@
|
||||
"""Step definitions for Settings spec-required env var fields (issue #2866)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from behave import given, then, when
|
||||
|
||||
from cleveragents.config.settings import Settings
|
||||
|
||||
_SPEC_FIELD_ENV_VARS = [
|
||||
"CLEVERAGENTS_SERVER_URL",
|
||||
"CLEVERAGENTS_SERVER_TOKEN",
|
||||
"CLEVERAGENTS_FORMAT",
|
||||
"CLEVERAGENTS_DEFAULT_ESTIMATION_ACTOR",
|
||||
]
|
||||
|
||||
|
||||
@given("no CleverAgents spec-field env vars are set")
|
||||
def step_clear_spec_field_env_vars(context):
|
||||
"""Remove all spec-field env vars so Settings starts from a clean slate."""
|
||||
for key in _SPEC_FIELD_ENV_VARS:
|
||||
os.environ.pop(key, None)
|
||||
if not hasattr(context, "env_vars_to_clean"):
|
||||
context.env_vars_to_clean = []
|
||||
context.env_vars_to_clean.extend(_SPEC_FIELD_ENV_VARS)
|
||||
|
||||
|
||||
@when("I instantiate Settings for spec fields")
|
||||
def step_instantiate_settings_for_spec_fields(context):
|
||||
"""Create a fresh Settings instance (bypassing the singleton)."""
|
||||
Settings._instance = None
|
||||
context.settings = Settings()
|
||||
|
||||
|
||||
@when("I inspect Settings.model_fields")
|
||||
def step_inspect_model_fields(context):
|
||||
"""Capture the model_fields mapping for assertion."""
|
||||
context.model_fields = Settings.model_fields
|
||||
|
||||
|
||||
# ── server_url assertions ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@then("settings.server_url should be None")
|
||||
def step_server_url_is_none(context):
|
||||
assert context.settings.server_url is None, (
|
||||
f"Expected server_url to be None, got {context.settings.server_url!r}"
|
||||
)
|
||||
|
||||
|
||||
@then('settings.server_url should be "{expected}"')
|
||||
def step_server_url_equals(context, expected):
|
||||
assert context.settings.server_url == expected, (
|
||||
f"Expected server_url={expected!r}, got {context.settings.server_url!r}"
|
||||
)
|
||||
|
||||
|
||||
# ── server_token assertions ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
@then("settings.server_token should be None")
|
||||
def step_server_token_is_none(context):
|
||||
assert context.settings.server_token is None, (
|
||||
f"Expected server_token to be None, got {context.settings.server_token!r}"
|
||||
)
|
||||
|
||||
|
||||
@then('settings.server_token should be "{expected}"')
|
||||
def step_server_token_equals(context, expected):
|
||||
assert context.settings.server_token == expected, (
|
||||
f"Expected server_token={expected!r}, got {context.settings.server_token!r}"
|
||||
)
|
||||
|
||||
|
||||
# ── format assertions ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@then("settings.format should be None")
|
||||
def step_format_is_none(context):
|
||||
assert context.settings.format is None, (
|
||||
f"Expected format to be None, got {context.settings.format!r}"
|
||||
)
|
||||
|
||||
|
||||
@then('settings.format should be "{expected}"')
|
||||
def step_format_equals(context, expected):
|
||||
assert context.settings.format == expected, (
|
||||
f"Expected format={expected!r}, got {context.settings.format!r}"
|
||||
)
|
||||
|
||||
|
||||
# ── default_estimation_actor assertions ──────────────────────────────────────
|
||||
|
||||
|
||||
@then("settings.default_estimation_actor should be None")
|
||||
def step_default_estimation_actor_is_none(context):
|
||||
assert context.settings.default_estimation_actor is None, (
|
||||
"Expected default_estimation_actor to be None, "
|
||||
f"got {context.settings.default_estimation_actor!r}"
|
||||
)
|
||||
|
||||
|
||||
@then('settings.default_estimation_actor should be "{expected}"')
|
||||
def step_default_estimation_actor_equals(context, expected):
|
||||
assert context.settings.default_estimation_actor == expected, (
|
||||
f"Expected default_estimation_actor={expected!r}, "
|
||||
f"got {context.settings.default_estimation_actor!r}"
|
||||
)
|
||||
|
||||
|
||||
# ── model_fields presence assertions ─────────────────────────────────────────
|
||||
|
||||
|
||||
@then('"{field_name}" should be present in model_fields')
|
||||
def step_field_in_model_fields(context, field_name):
|
||||
assert field_name in context.model_fields, (
|
||||
f"Expected '{field_name}' to be in Settings.model_fields, "
|
||||
f"but only found: {sorted(context.model_fields.keys())}"
|
||||
)
|
||||
|
||||
|
||||
# ── type annotation assertions ───────────────────────────────────────────────
|
||||
|
||||
|
||||
@then("settings.server_url should be of type str or None")
|
||||
def step_server_url_type(context):
|
||||
value = context.settings.server_url
|
||||
assert value is None or isinstance(value, str), (
|
||||
f"Expected server_url to be str | None, got {type(value)}"
|
||||
)
|
||||
|
||||
|
||||
@then("settings.server_token should be of type str or None")
|
||||
def step_server_token_type(context):
|
||||
value = context.settings.server_token
|
||||
assert value is None or isinstance(value, str), (
|
||||
f"Expected server_token to be str | None, got {type(value)}"
|
||||
)
|
||||
|
||||
|
||||
@then("settings.format should be of type str or None")
|
||||
def step_format_type(context):
|
||||
value = context.settings.format
|
||||
assert value is None or isinstance(value, str), (
|
||||
f"Expected format to be str | None, got {type(value)}"
|
||||
)
|
||||
|
||||
|
||||
@then("settings.default_estimation_actor should be of type str or None")
|
||||
def step_default_estimation_actor_type(context):
|
||||
value = context.settings.default_estimation_actor
|
||||
assert value is None or isinstance(value, str), (
|
||||
f"Expected default_estimation_actor to be str | None, got {type(value)}"
|
||||
)
|
||||
@@ -111,11 +111,45 @@ class Settings(BaseSettings):
|
||||
default=False,
|
||||
validation_alias=AliasChoices("CLEVERAGENTS_SERVER_RELOAD"),
|
||||
)
|
||||
server_url: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("CLEVERAGENTS_SERVER_URL"),
|
||||
description=(
|
||||
"URL for the CleverAgents server endpoint, enabling server mode. "
|
||||
"Maps to the spec's server.url config key."
|
||||
),
|
||||
)
|
||||
server_token: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("CLEVERAGENTS_SERVER_TOKEN"),
|
||||
description=(
|
||||
"Authentication token for connecting to the CleverAgents server. "
|
||||
"Maps to the spec's server.token config key."
|
||||
),
|
||||
)
|
||||
debug_enabled: bool = Field(
|
||||
default=False,
|
||||
validation_alias=AliasChoices("CLEVERAGENTS_DEBUG_ENABLED"),
|
||||
)
|
||||
|
||||
# Core output and estimation configuration
|
||||
format: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("CLEVERAGENTS_FORMAT"),
|
||||
description=(
|
||||
"Default output format (e.g. 'json', 'markdown', 'text'). "
|
||||
"Overrides the core.format config key when set."
|
||||
),
|
||||
)
|
||||
default_estimation_actor: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("CLEVERAGENTS_DEFAULT_ESTIMATION_ACTOR"),
|
||||
description=(
|
||||
"Default actor used for estimation tasks. "
|
||||
"Maps to the spec's core.default_estimation_actor config key."
|
||||
),
|
||||
)
|
||||
|
||||
# Logging/paths
|
||||
log_level: str = Field(
|
||||
default="INFO",
|
||||
|
||||
Reference in New Issue
Block a user