55aee7cf22
CI / lint (pull_request) Successful in 18s
CI / quality (pull_request) Successful in 21s
CI / benchmark-publish (pull_request) Has been skipped
CI / build (pull_request) Successful in 14s
CI / security (pull_request) Successful in 49s
CI / typecheck (pull_request) Successful in 56s
CI / integration_tests (pull_request) Successful in 2m35s
CI / unit_tests (pull_request) Successful in 14m30s
CI / docker (pull_request) Successful in 55s
CI / benchmark-regression (pull_request) Successful in 21m11s
CI / coverage (pull_request) Successful in 34m22s
CI / lint (push) Successful in 12s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 17s
CI / security (push) Successful in 29s
CI / typecheck (push) Successful in 1m0s
CI / benchmark-regression (push) Has been skipped
CI / integration_tests (push) Successful in 4m54s
CI / benchmark-publish (push) Successful in 11m48s
CI / unit_tests (push) Successful in 16m30s
CI / docker (push) Successful in 28s
CI / coverage (push) Successful in 25m45s
The step_register_skills_table step called add_skill in a loop but only committed once at the end. Because SkillRepository.create() obtains a new session per call and only flushes (never commits), the intermediate sessions could be garbage-collected before the final commit, rolling back their transactions on the shared SQLite :memory: connection. Moving _commit_pending inside the loop ensures each skill is durably committed before the next session is created. ISSUES CLOSED: #418
278 lines
13 KiB
Gherkin
278 lines
13 KiB
Gherkin
Feature: ConfigService full coverage
|
|
As a developer
|
|
I want every line of config_service.py tested
|
|
So that the module has complete test coverage
|
|
|
|
# ---------- ConfigLevel enum ----------
|
|
|
|
Scenario: ConfigLevel enum has five precedence levels
|
|
Then ConfigLevel should have values CLI_FLAG ENV_VAR PROJECT GLOBAL DEFAULT
|
|
|
|
# ---------- ConfigEntry and ResolvedValue dataclasses ----------
|
|
|
|
Scenario: ConfigEntry dataclass stores all metadata fields
|
|
Given a ConfigEntry with key "test.key" and type str
|
|
Then the ConfigEntry fields should match the provided values
|
|
|
|
Scenario: ResolvedValue dataclass stores resolution result
|
|
Given a ResolvedValue with key "test.key" and source DEFAULT
|
|
Then the ResolvedValue fields should match including an empty chain by default
|
|
|
|
# ---------- _env_name helper ----------
|
|
|
|
Scenario: _env_name builds uppercase CLEVERAGENTS env var name
|
|
When I call _env_name with section "core" and key "log_level"
|
|
Then the env name should be "CLEVERAGENTS_CORE_LOG_LEVEL"
|
|
|
|
# ---------- _register and _build_catalog ----------
|
|
|
|
Scenario: _register adds an entry to the global registry
|
|
When I register a custom key "custom.test_key" with type int and default 42
|
|
Then the registry should contain "custom.test_key" with default 42
|
|
|
|
Scenario: _register uses explicit env_var when provided
|
|
When I register a key "custom.explicit_env" with explicit env_var "MY_CUSTOM_VAR"
|
|
Then the entry "custom.explicit_env" should have env_var "MY_CUSTOM_VAR"
|
|
|
|
Scenario: _build_catalog populates all expected config sections
|
|
Then the registry should contain keys from sections core plan provider sandbox context index
|
|
|
|
Scenario: _build_catalog registers the expected number of keys
|
|
Then the registry should contain at least 20 keys
|
|
|
|
# ---------- ConfigService instantiation ----------
|
|
|
|
Scenario: ConfigService uses custom config_dir and config_path
|
|
Given a temporary directory for config service
|
|
When I create a ConfigService with custom paths
|
|
Then the service should use the custom config directory and path
|
|
|
|
Scenario: ConfigService uses defaults when no paths provided
|
|
When I create a ConfigService with default paths
|
|
Then the service config path should end with ".cleveragents/config.toml"
|
|
|
|
# ---------- registry(), get_entry(), registered_keys() ----------
|
|
|
|
Scenario: registry returns a copy of all registered entries
|
|
When I call ConfigService.registry
|
|
Then it should return a dict with the same keys as the internal registry
|
|
|
|
Scenario: get_entry returns the entry for a known key
|
|
When I call ConfigService.get_entry with "core.log_level"
|
|
Then it should return a ConfigEntry with key "core.log_level"
|
|
|
|
Scenario: get_entry returns None for an unknown key
|
|
When I call ConfigService.get_entry with "nonexistent.key"
|
|
Then it should return None
|
|
|
|
Scenario: registered_keys returns a sorted list of all keys
|
|
When I call ConfigService.registered_keys
|
|
Then it should return a sorted list containing "core.log_level"
|
|
|
|
# ---------- read_config ----------
|
|
|
|
Scenario: read_config returns empty dict when file does not exist
|
|
Given a temporary directory for config service
|
|
When I create a ConfigService pointing to a missing config file
|
|
Then read_config should return an empty dict
|
|
|
|
Scenario: read_config parses an existing TOML file
|
|
Given a temporary directory for config service
|
|
And a TOML config file with key "core.log_level" set to "DEBUG"
|
|
When I create a ConfigService pointing to that TOML file
|
|
Then read_config should return a dict with "core.log_level" equal to "DEBUG"
|
|
|
|
# ---------- write_config ----------
|
|
|
|
Scenario: write_config creates directories and writes a new TOML file
|
|
Given a temporary directory for config service
|
|
When I create a ConfigService with a nested non-existent config directory
|
|
And I call write_config with key "greeting" value "hello"
|
|
Then the TOML file should exist and contain key "greeting" with value "hello"
|
|
|
|
Scenario: write_config merges data into an existing TOML file
|
|
Given a temporary directory for config service
|
|
And a TOML config file with key "existing" set to "value1"
|
|
When I create a ConfigService pointing to that TOML file
|
|
And I call write_config with key "new_key" value "value2"
|
|
Then read_config should return a dict containing both "existing" and "new_key"
|
|
|
|
# ---------- set_value ----------
|
|
|
|
Scenario: set_value persists a single key-value pair
|
|
Given a temporary directory for config service
|
|
When I create a ConfigService with custom paths
|
|
And I call set_value with key "mykey" and value "myval"
|
|
Then read_config should return a dict with "mykey" equal to "myval"
|
|
|
|
# ---------- validate_key ----------
|
|
|
|
Scenario: validate_key returns entry for a known key
|
|
When I call validate_key with "core.log_level"
|
|
Then it should return the ConfigEntry for "core.log_level" without error
|
|
|
|
Scenario: validate_key raises ValueError for an unknown key
|
|
When I call validate_key with "totally.unknown"
|
|
Then a ValueError should be raised mentioning "Unknown configuration key"
|
|
|
|
# ---------- validate_type ----------
|
|
|
|
Scenario: validate_type returns value unchanged when type already matches
|
|
When I call validate_type with key "core.log_level" and value "INFO"
|
|
Then it should return "INFO" unchanged
|
|
|
|
Scenario: validate_type coerces a string to int
|
|
When I call validate_type with key "core.server_port" and string value "9090"
|
|
Then it should return the integer 9090
|
|
|
|
Scenario: validate_type coerces a string to float
|
|
When I call validate_type with key "provider.temperature" and string value "0.5"
|
|
Then it should return the float 0.5
|
|
|
|
Scenario: validate_type coerces non-string to str
|
|
When I call validate_type with key "core.log_level" and integer value 123
|
|
Then it should return the string "123"
|
|
|
|
Scenario: validate_type coerces "true" string to bool True
|
|
When I call validate_type with key "core.debug_enabled" and string value "true"
|
|
Then it should return boolean True
|
|
|
|
Scenario: validate_type coerces "1" string to bool True
|
|
When I call validate_type with key "core.debug_enabled" and string value "1"
|
|
Then it should return boolean True
|
|
|
|
Scenario: validate_type coerces "yes" string to bool True
|
|
When I call validate_type with key "core.debug_enabled" and string value "yes"
|
|
Then it should return boolean True
|
|
|
|
Scenario: validate_type coerces "false" string to bool False
|
|
When I call validate_type with key "core.debug_enabled" and string value "false"
|
|
Then it should return boolean False
|
|
|
|
Scenario: validate_type coerces "0" string to bool False
|
|
When I call validate_type with key "core.debug_enabled" and string value "0"
|
|
Then it should return boolean False
|
|
|
|
Scenario: validate_type coerces "no" string to bool False
|
|
When I call validate_type with key "core.debug_enabled" and string value "no"
|
|
Then it should return boolean False
|
|
|
|
Scenario: validate_type raises TypeError for invalid bool string
|
|
When I call validate_type with key "core.debug_enabled" and string value "maybe"
|
|
Then a TypeError should be raised mentioning "Cannot convert"
|
|
|
|
Scenario: validate_type raises TypeError for non-coercible int value
|
|
When I call validate_type with key "core.server_port" and string value "not_a_number"
|
|
Then a TypeError should be raised mentioning "Type mismatch"
|
|
|
|
Scenario: validate_type raises TypeError for non-bool non-string to bool key
|
|
When I call validate_type with key "core.debug_enabled" and a list value
|
|
Then a TypeError should be raised mentioning "Type mismatch"
|
|
|
|
Scenario: validate_type raises ValueError for completely unknown key
|
|
When I call validate_type with unknown key "fake.unknown" and value "x"
|
|
Then a ValueError should be raised mentioning "Cannot validate type for unknown key"
|
|
|
|
# ---------- resolve: five-level chain ----------
|
|
|
|
Scenario: resolve returns default value when no overrides exist
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I resolve key "core.log_level" with no overrides
|
|
Then the resolved value should be "INFO" from source DEFAULT
|
|
|
|
Scenario: resolve returns CLI flag value when provided
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I resolve key "core.log_level" with cli_value "TRACE"
|
|
Then the resolved value should be "TRACE" from source CLI_FLAG
|
|
|
|
Scenario: resolve returns environment variable when no CLI flag
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
And the env var "CLEVERAGENTS_CORE_LOG_LEVEL" is injected with "WARNING"
|
|
When I resolve key "core.log_level" with no overrides
|
|
Then the resolved value should be "WARNING" from source ENV_VAR
|
|
|
|
Scenario: CLI flag takes precedence over environment variable
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
And the env var "CLEVERAGENTS_CORE_LOG_LEVEL" is injected with "WARNING"
|
|
When I resolve key "core.log_level" with cli_value "TRACE"
|
|
Then the resolved value should be "TRACE" from source CLI_FLAG
|
|
|
|
Scenario: resolve returns project-scoped value when set
|
|
Given a temporary directory for config service
|
|
And a ConfigService with project-scoped config for key "core.log_level" value "PROJECT_DEBUG" under project "myproj"
|
|
When I resolve key "core.log_level" with project_name "myproj"
|
|
Then the resolved value should be "PROJECT_DEBUG" from source PROJECT
|
|
|
|
Scenario: resolve returns global config value when no higher overrides
|
|
Given a temporary directory for config service
|
|
And a ConfigService with global config key "core.log_level" set to "GLOBAL_WARN"
|
|
When I resolve key "core.log_level" with no overrides
|
|
Then the resolved value should be "GLOBAL_WARN" from source GLOBAL
|
|
|
|
Scenario: resolve populates chain when verbose is True
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I resolve key "core.log_level" with verbose True and no overrides
|
|
Then the resolved chain should have 5 entries covering all levels
|
|
|
|
Scenario: resolve verbose chain includes env_name for ENV_VAR level
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I resolve key "core.log_level" with verbose True and no overrides
|
|
Then the verbose chain ENV_VAR entry should include env_name "CLEVERAGENTS_CORE_LOG_LEVEL"
|
|
|
|
Scenario: resolve verbose chain includes path for GLOBAL level
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I resolve key "core.log_level" with verbose True and no overrides
|
|
Then the verbose chain GLOBAL entry should include the config file path
|
|
|
|
Scenario: resolve verbose chain shows project value when project-scoped wins
|
|
Given a temporary directory for config service
|
|
And a ConfigService with project-scoped config for key "core.log_level" value "PROJECT_DEBUG" under project "myproj"
|
|
When I resolve key "core.log_level" with project_name "myproj" and verbose True
|
|
Then the resolved value should be "PROJECT_DEBUG" from source PROJECT
|
|
And the verbose chain PROJECT entry should have value "PROJECT_DEBUG"
|
|
|
|
Scenario: resolve verbose chain shows global value when global config wins
|
|
Given a temporary directory for config service
|
|
And a ConfigService with global config key "core.log_level" set to "GLOBAL_WARN"
|
|
When I resolve key "core.log_level" with verbose True and no overrides
|
|
Then the resolved value should be "GLOBAL_WARN" from source GLOBAL
|
|
And the verbose chain GLOBAL entry should have value "GLOBAL_WARN"
|
|
|
|
Scenario: resolve skips project scope for non-scopable keys
|
|
Given a temporary directory for config service
|
|
And a ConfigService with project-scoped config for key "core.database_url" value "pg://proj" under project "myproj"
|
|
When I resolve key "core.database_url" with project_name "myproj"
|
|
Then the resolved value should not be "pg://proj"
|
|
And the resolved source should be DEFAULT or GLOBAL
|
|
|
|
# ---------- env_var_for_key ----------
|
|
|
|
Scenario: env_var_for_key returns the env var name for a valid key
|
|
When I call env_var_for_key with "core.log_level"
|
|
Then it should return "CLEVERAGENTS_CORE_LOG_LEVEL"
|
|
|
|
Scenario: env_var_for_key raises ValueError for an unknown key
|
|
When I call env_var_for_key with "nonexistent.key"
|
|
Then a ValueError should be raised mentioning "Unknown configuration key"
|
|
|
|
# ---------- resolve_all ----------
|
|
|
|
Scenario: resolve_all returns a resolved value for every registered key
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I call resolve_all with no overrides
|
|
Then the result should contain an entry for every registered key
|
|
|
|
Scenario: resolve_all applies CLI overrides to matching keys
|
|
Given a temporary directory for config service
|
|
And a ConfigService with empty config for resolve tests
|
|
When I call resolve_all with cli_override "core.log_level" set to "FATAL"
|
|
Then the resolve_all result for "core.log_level" should have value "FATAL" and source CLI_FLAG
|