Files
wehub-resource-sync 0418dc5cf9
CI / Python tests (3.10) (push) Has been cancelled
CI / Python tests (3.11) (push) Has been cancelled
CI / Python quality (push) Has been cancelled
CI / Frontend typecheck (push) Has been cancelled
Autopilot Pages / deploy (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:45:00 +08:00

915 lines
38 KiB
Python

"""Tests for openharness.config.settings."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from openharness.auth.storage import store_credential
from openharness.config.settings import (
ProviderProfile,
Settings,
display_model_setting,
load_settings,
normalize_anthropic_model_name,
save_settings,
strip_ansi_escape_sequences,
_apply_env_overrides,
)
class TestSettings:
def test_defaults(self):
s = Settings()
assert s.api_key == ""
assert s.model == "claude-sonnet-4-6"
assert s.max_tokens == 16384
assert s.timeout == 30.0
assert s.max_turns == 200
assert s.fast_mode is False
assert s.permission.mode == "default"
assert s.sandbox.enabled is False
assert s.sandbox.filesystem.allow_write == ["."]
assert s.web.resolution_mode == "auto"
assert s.web.synthetic_dns_cidrs == []
def test_resolve_api_key_from_instance(self):
s = Settings(api_key="sk-test-123")
assert s.resolve_api_key() == "sk-test-123"
def test_resolve_api_key_from_env(self, monkeypatch):
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-env-456")
s = Settings()
assert s.resolve_api_key() == "sk-env-456"
def test_resolve_api_key_prefers_openharness_env(self, monkeypatch):
monkeypatch.setenv("OPENHARNESS_ANTHROPIC_API_KEY", "sk-oh-456")
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-env-456")
s = Settings()
assert s.resolve_api_key() == "sk-oh-456"
def test_resolve_api_key_instance_takes_precedence(self, monkeypatch):
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-env-456")
s = Settings(api_key="sk-instance-789")
assert s.resolve_api_key() == "sk-instance-789"
def test_resolve_api_key_missing_raises(self, monkeypatch):
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
s = Settings()
with pytest.raises(ValueError, match="No API key found"):
s.resolve_api_key()
def test_merge_cli_overrides(self):
s = Settings()
updated = s.merge_cli_overrides(model="claude-opus-4-20250514", verbose=True, api_key=None)
assert updated.model == "claude-opus-4-20250514"
assert updated.verbose is True
# api_key=None should not override the default
assert updated.api_key == ""
def test_merge_cli_overrides_applies_permission_mode(self):
s = Settings()
updated = s.merge_cli_overrides(permission_mode="full_auto")
assert updated.permission.mode == "full_auto"
assert s.permission.mode == "default"
def test_web_settings_env_overrides(self, monkeypatch):
monkeypatch.setenv("OPENHARNESS_WEB_PROXY", "http://proxy.example.com:7890")
monkeypatch.setenv("OPENHARNESS_WEB_RESOLUTION_MODE", "synthetic_dns")
monkeypatch.setenv("OPENHARNESS_WEB_SYNTHETIC_DNS_CIDRS", "100.64.0.0/10,203.0.113.0/24")
updated = _apply_env_overrides(Settings())
assert updated.web.proxy == "http://proxy.example.com:7890"
assert updated.web.resolution_mode == "synthetic_dns"
assert updated.web.synthetic_dns_cidrs == ["100.64.0.0/10", "203.0.113.0/24"]
def test_merge_cli_overrides_returns_new_instance(self):
s = Settings()
updated = s.merge_cli_overrides(model="claude-opus-4-20250514")
assert s.model != updated.model
assert s is not updated
def test_resolve_auth_prefers_env_over_flat_api_key_for_openai(self, monkeypatch):
"""When api_format=openai, resolve_auth() should use OPENAI_API_KEY
from the environment rather than the flat api_key field which may
contain an Anthropic key from settings.json."""
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-correct")
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
s = Settings(api_key="sk-ant-wrong-provider", api_format="openai")
s = s.sync_active_profile_from_flat_fields()
auth = s.resolve_auth()
assert auth.value == "sk-openai-correct"
assert "OPENAI" in auth.source
def test_resolve_auth_prefers_openharness_env_for_openai(self, monkeypatch):
monkeypatch.setenv("OPENHARNESS_OPENAI_API_KEY", "sk-oh-openai")
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai-correct")
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
s = Settings(api_key="sk-ant-wrong-provider", api_format="openai")
s = s.sync_active_profile_from_flat_fields()
auth = s.resolve_auth()
assert auth.value == "sk-oh-openai"
assert auth.source == "env:OPENHARNESS_OPENAI_API_KEY"
def test_resolve_auth_falls_back_to_flat_api_key(self, monkeypatch):
"""When no provider-specific env var is set, resolve_auth() should
still fall back to the flat api_key field."""
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
s = Settings(api_key="sk-fallback-key")
s = s.sync_active_profile_from_flat_fields()
auth = s.resolve_auth()
assert auth.value == "sk-fallback-key"
def test_env_overrides_picks_up_openai_base_url(self, tmp_path: Path, monkeypatch):
"""_apply_env_overrides should pick up OPENAI_BASE_URL for relay
providers that use OpenAI-compatible format."""
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
monkeypatch.delenv("OPENHARNESS_BASE_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv("OPENAI_BASE_URL", "https://relay.example.com/v1")
monkeypatch.setenv("OPENAI_API_KEY", "sk-relay-key")
path = tmp_path / "settings.json"
path.write_text(json.dumps({}))
s = load_settings(path)
assert s.base_url == "https://relay.example.com/v1"
def test_load_settings_uses_profile_specific_openharness_env_key(self, tmp_path: Path, monkeypatch):
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-wrong")
monkeypatch.setenv("OPENHARNESS_OPENAI_API_KEY", "sk-oh-openai")
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
path = tmp_path / "settings.json"
path.write_text(
Settings(active_profile="openai-compatible").model_dump_json(),
encoding="utf-8",
)
s = load_settings(path)
assert s.active_profile == "openai-compatible"
assert s.api_key == "sk-oh-openai"
def test_load_settings_ignores_wrong_provider_native_env_key(self, tmp_path: Path, monkeypatch):
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-wrong")
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
path = tmp_path / "settings.json"
path.write_text(
Settings(active_profile="openai-compatible").model_dump_json(),
encoding="utf-8",
)
s = load_settings(path)
assert s.active_profile == "openai-compatible"
assert s.api_key == ""
def test_env_overrides_pick_up_compact_threshold_settings(self, tmp_path: Path, monkeypatch):
monkeypatch.setenv("OPENHARNESS_CONTEXT_WINDOW_TOKENS", "123456")
monkeypatch.setenv("OPENHARNESS_AUTO_COMPACT_THRESHOLD_TOKENS", "120000")
path = tmp_path / "settings.json"
path.write_text(json.dumps({}))
s = load_settings(path)
assert s.context_window_tokens == 123456
assert s.auto_compact_threshold_tokens == 120000
def test_anthropic_base_url_takes_precedence_over_openai(self, tmp_path: Path, monkeypatch):
"""ANTHROPIC_BASE_URL should take precedence over OPENAI_BASE_URL."""
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv("ANTHROPIC_BASE_URL", "https://anthropic-relay.example.com")
monkeypatch.setenv("OPENAI_BASE_URL", "https://openai-relay.example.com/v1")
path = tmp_path / "settings.json"
path.write_text(json.dumps({}))
s = load_settings(path)
assert s.base_url == "https://anthropic-relay.example.com"
class TestLoadSaveSettings:
def test_load_missing_file_returns_defaults(self, tmp_path: Path, monkeypatch):
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
monkeypatch.delenv("OPENHARNESS_BASE_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_MODEL", raising=False)
monkeypatch.delenv("OPENHARNESS_MODEL", raising=False)
path = tmp_path / "nonexistent.json"
s = load_settings(path)
assert s == Settings().materialize_active_profile()
def test_load_existing_file(self, tmp_path: Path, monkeypatch):
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_MODEL", raising=False)
monkeypatch.delenv("OPENHARNESS_MODEL", raising=False)
path = tmp_path / "settings.json"
path.write_text(json.dumps({"model": "claude-opus-4-20250514", "verbose": True, "fast_mode": True}))
s = load_settings(path)
assert s.model == "claude-opus-4-20250514"
assert s.verbose is True
assert s.fast_mode is True
assert s.api_key == "" # default preserved
def test_save_and_load_roundtrip(self, tmp_path: Path, monkeypatch):
monkeypatch.delenv("OPENHARNESS_ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENHARNESS_OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
monkeypatch.delenv("ANTHROPIC_BASE_URL", raising=False)
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
monkeypatch.delenv("ANTHROPIC_MODEL", raising=False)
monkeypatch.delenv("OPENHARNESS_MODEL", raising=False)
path = tmp_path / "settings.json"
original = Settings(api_key="sk-roundtrip", model="claude-opus-4-20250514", verbose=True)
save_settings(original, path)
loaded = load_settings(path)
assert loaded.api_key == original.api_key
assert loaded.model == original.model
assert loaded.verbose == original.verbose
def test_load_migrates_flat_provider_settings_to_profile(self, tmp_path: Path):
path = tmp_path / "settings.json"
path.write_text(
json.dumps(
{
"api_format": "anthropic",
"provider": "anthropic",
"model": "kimi-k2.5",
"base_url": "https://api.moonshot.cn/anthropic",
}
),
encoding="utf-8",
)
loaded = load_settings(path)
profile_name, profile = loaded.resolve_profile()
assert profile_name == "anthropic"
assert profile.base_url == "https://api.moonshot.cn/anthropic"
assert profile.resolved_model == "kimi-k2.5"
assert loaded.base_url == "https://api.moonshot.cn/anthropic"
assert loaded.model == "kimi-k2.5"
def test_materialize_active_profile_uses_profile_model(self):
settings = Settings(
active_profile="codex",
profiles={
"codex": ProviderProfile(
label="Codex Subscription",
provider="openai_codex",
api_format="openai",
auth_source="codex_subscription",
default_model="gpt-5.4",
last_model="gpt-5",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.provider == "openai_codex"
assert materialized.api_format == "openai"
assert materialized.model == "gpt-5"
def test_materialize_active_profile_projects_compact_threshold_settings(self):
settings = Settings(
active_profile="openai-compatible",
profiles={
"openai-compatible": ProviderProfile(
label="OpenAI-Compatible API",
provider="openai",
api_format="openai",
auth_source="openai_api_key",
default_model="gpt-5.4",
context_window_tokens=100000,
auto_compact_threshold_tokens=90000,
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.context_window_tokens == 100000
assert materialized.auto_compact_threshold_tokens == 90000
def test_merge_cli_active_profile_does_not_inherit_flat_provider_fields(self):
settings = Settings(
active_profile="moonshot",
provider="moonshot",
api_format="openai",
base_url="https://api.moonshot.cn/v1",
model="kimi-k2.5",
profiles={
"moonshot": ProviderProfile(
label="Moonshot",
provider="moonshot",
api_format="openai",
auth_source="moonshot_api_key",
default_model="kimi-k2.5",
last_model="kimi-k2.5",
base_url="https://api.moonshot.cn/v1",
),
"codex": ProviderProfile(
label="Codex Subscription",
provider="openai_codex",
api_format="openai",
auth_source="codex_subscription",
default_model="gpt-5.4",
last_model="gpt-5.4",
),
},
)
updated = settings.merge_cli_overrides(active_profile="codex")
profile_name, profile = updated.resolve_profile()
assert profile_name == "codex"
assert updated.provider == "openai_codex"
assert updated.base_url is None
assert updated.model == "gpt-5.4"
assert profile.provider == "openai_codex"
assert profile.auth_source == "codex_subscription"
def test_merge_cli_active_profile_with_model_keeps_target_profile_auth(self):
settings = Settings(
active_profile="moonshot",
provider="moonshot",
api_format="openai",
base_url="https://api.moonshot.cn/v1",
model="kimi-k2.5",
profiles={
"moonshot": ProviderProfile(
label="Moonshot",
provider="moonshot",
api_format="openai",
auth_source="moonshot_api_key",
default_model="kimi-k2.5",
last_model="kimi-k2.5",
base_url="https://api.moonshot.cn/v1",
),
"codex": ProviderProfile(
label="Codex Subscription",
provider="openai_codex",
api_format="openai",
auth_source="codex_subscription",
default_model="gpt-5.4",
last_model="gpt-5.4",
),
},
)
updated = settings.merge_cli_overrides(active_profile="codex", model="gpt-5.5")
profile_name, profile = updated.resolve_profile()
assert profile_name == "codex"
assert updated.provider == "openai_codex"
assert updated.api_format == "openai"
assert updated.base_url is None
assert updated.model == "gpt-5.5"
assert profile.provider == "openai_codex"
assert profile.auth_source == "codex_subscription"
assert profile.last_model == "gpt-5.5"
def test_merge_cli_active_profile_keeps_profile_compact_threshold_settings(self):
settings = Settings(
active_profile="moonshot",
context_window_tokens=64000,
auto_compact_threshold_tokens=60000,
profiles={
"moonshot": ProviderProfile(
label="Moonshot",
provider="moonshot",
api_format="openai",
auth_source="moonshot_api_key",
default_model="kimi-k2.5",
last_model="kimi-k2.5",
base_url="https://api.moonshot.cn/v1",
context_window_tokens=64000,
auto_compact_threshold_tokens=60000,
),
"openai-compatible": ProviderProfile(
label="OpenAI-Compatible API",
provider="openai",
api_format="openai",
auth_source="openai_api_key",
default_model="gpt-5.4",
last_model="gpt-5.4",
base_url="https://relay.example.com/v1",
context_window_tokens=200000,
auto_compact_threshold_tokens=180000,
),
},
)
updated = settings.merge_cli_overrides(active_profile="openai-compatible")
assert updated.base_url == "https://relay.example.com/v1"
assert updated.context_window_tokens == 200000
assert updated.auto_compact_threshold_tokens == 180000
def test_claude_profile_materializes_alias_to_concrete_model(self):
settings = Settings(
active_profile="claude-subscription",
profiles={
"claude-subscription": ProviderProfile(
label="Claude Subscription",
provider="anthropic_claude",
api_format="anthropic",
auth_source="claude_subscription",
default_model="sonnet",
last_model="opus",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "claude-opus-4-6"
def test_claude_profile_normalizes_prefixed_model_name(self):
settings = Settings(
active_profile="claude-subscription",
profiles={
"claude-subscription": ProviderProfile(
label="Claude Subscription",
provider="anthropic_claude",
api_format="anthropic",
auth_source="claude_subscription",
default_model="claude-sonnet-4-6",
last_model="anthropic/claude-sonnet-4-20250514",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "claude-sonnet-4-20250514"
def test_claude_profile_normalizes_dotted_model_name(self):
settings = Settings(
active_profile="claude-api",
profiles={
"claude-api": ProviderProfile(
label="Claude API",
provider="anthropic",
api_format="anthropic",
auth_source="anthropic_api_key",
default_model="claude-sonnet-4-6",
last_model="claude-opus-4.6",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "claude-opus-4-6"
def test_display_model_setting_uses_default_alias(self):
profile = ProviderProfile(
label="Claude API",
provider="anthropic",
api_format="anthropic",
auth_source="anthropic_api_key",
default_model="claude-sonnet-4-6",
last_model=None,
)
assert display_model_setting(profile) == "default"
def test_opusplan_resolves_by_permission_mode(self):
settings = Settings(
permission={"mode": "plan"},
active_profile="claude-api",
profiles={
"claude-api": ProviderProfile(
label="Claude API",
provider="anthropic",
api_format="anthropic",
auth_source="anthropic_api_key",
default_model="claude-sonnet-4-6",
last_model="opusplan",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "claude-opus-4-6"
def test_resolve_auth_prefers_profile_scoped_credential_for_custom_compatible_profile(self, tmp_path: Path, monkeypatch):
monkeypatch.setenv("OPENHARNESS_CONFIG_DIR", str(tmp_path))
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-global-env")
store_credential("profile:kimi-anthropic", "api_key", "sk-profile-specific", use_keyring=False)
settings = Settings(
active_profile="kimi-anthropic",
profiles={
"kimi-anthropic": ProviderProfile(
label="Kimi Anthropic",
provider="anthropic",
api_format="anthropic",
auth_source="anthropic_api_key",
default_model="kimi-k2.5",
base_url="https://api.moonshot.cn/anthropic",
credential_slot="kimi-anthropic",
)
},
)
resolved = settings.resolve_auth()
assert resolved.value == "sk-profile-specific"
assert resolved.source == "file:profile:kimi-anthropic"
def test_normalize_anthropic_model_name_matches_hermes_behavior():
assert normalize_anthropic_model_name("anthropic/claude-sonnet-4-20250514") == "claude-sonnet-4-20250514"
assert normalize_anthropic_model_name("claude-opus-4.6") == "claude-opus-4-6"
def test_save_creates_parent_dirs(self, tmp_path: Path):
path = tmp_path / "deep" / "nested" / "settings.json"
save_settings(Settings(), path)
assert path.exists()
def test_load_with_permission_settings(self, tmp_path: Path):
path = tmp_path / "settings.json"
path.write_text(
json.dumps(
{
"permission": {
"mode": "full_auto",
"allowed_tools": ["Bash", "Read"],
}
}
)
)
s = load_settings(path)
assert s.permission.mode == "full_auto"
assert s.permission.allowed_tools == ["Bash", "Read"]
def test_load_applies_env_overrides(self, tmp_path: Path, monkeypatch):
path = tmp_path / "settings.json"
path.write_text(json.dumps({"model": "from-file", "base_url": "https://file.example"}))
monkeypatch.setenv("ANTHROPIC_MODEL", "from-env-model")
monkeypatch.setenv("ANTHROPIC_BASE_URL", "https://env.example/anthropic")
monkeypatch.setenv("OPENHARNESS_TIMEOUT", "42.5")
monkeypatch.setenv("OPENHARNESS_MAX_TURNS", "42")
monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-env-override")
monkeypatch.setenv("OPENHARNESS_SANDBOX_ENABLED", "true")
monkeypatch.setenv("OPENHARNESS_SANDBOX_FAIL_IF_UNAVAILABLE", "1")
s = load_settings(path)
assert s.model == "from-env-model"
assert s.base_url == "https://env.example/anthropic"
assert s.timeout == 42.5
assert s.max_turns == 42
assert s.api_key == "sk-env-override"
assert s.sandbox.enabled is True
assert s.sandbox.fail_if_unavailable is True
def test_load_with_sandbox_settings(self, tmp_path: Path):
path = tmp_path / "settings.json"
path.write_text(
json.dumps(
{
"sandbox": {
"enabled": True,
"enabled_platforms": ["linux", "wsl"],
"network": {"allowed_domains": ["github.com"]},
"filesystem": {"allow_write": [".", "/tmp"], "deny_write": [".env"]},
}
}
)
)
s = load_settings(path)
assert s.sandbox.enabled is True
assert s.sandbox.enabled_platforms == ["linux", "wsl"]
assert s.sandbox.network.allowed_domains == ["github.com"]
assert s.sandbox.filesystem.allow_write == [".", "/tmp"]
assert s.sandbox.filesystem.deny_write == [".env"]
class TestAnsiEscapeSequences:
"""Tests for ANSI escape sequence handling in settings."""
def test_strip_ansi_escape_sequences(self):
"""Test that ANSI escape sequences are properly stripped."""
# Normal model name should pass through unchanged
assert strip_ansi_escape_sequences("claude-opus-4-6") == "claude-opus-4-6"
# Bold formatting should be stripped
assert strip_ansi_escape_sequences("\x1b[1mclaude-opus-4-6\x1b[0m") == "claude-opus-4-6"
# Green + bold formatting should be stripped
assert strip_ansi_escape_sequences("\x1b[32m\x1b[1mclaude-opus-4-6\x1b[0m") == "claude-opus-4-6"
# Only bold prefix
assert strip_ansi_escape_sequences("\x1b[1mclaude-opus-4-6") == "claude-opus-4-6"
# Only reset suffix
assert strip_ansi_escape_sequences("claude-opus-4-6\x1b[0m") == "claude-opus-4-6"
# Empty string should return empty string
assert strip_ansi_escape_sequences("") == ""
# None should return None
assert strip_ansi_escape_sequences(None) is None
def test_env_override_strips_ansi_from_model(self, monkeypatch):
"""Test that ANSI escape sequences are stripped from ANTHROPIC_MODEL env var."""
monkeypatch.setenv("ANTHROPIC_MODEL", "\x1b[1mclaude-opus-4-6\x1b[0m")
s = Settings()
updated = _apply_env_overrides(s)
assert updated.model == "claude-opus-4-6"
def test_env_override_strips_ansi_from_openharness_model(self, monkeypatch):
"""Test that ANSI escape sequences are stripped from OPENHARNESS_MODEL env var."""
monkeypatch.setenv("OPENHARNESS_MODEL", "\x1b[32mclaude-sonnet-4-6\x1b[0m")
s = Settings()
updated = _apply_env_overrides(s)
assert updated.model == "claude-sonnet-4-6"
def test_merge_cli_overrides_strips_ansi_from_model(self):
"""Test that ANSI escape sequences are stripped from CLI model override."""
s = Settings()
updated = s.merge_cli_overrides(model="\x1b[1mclaude-opus-4-6\x1b[0m")
assert updated.model == "claude-opus-4-6"
class TestMiniMaxProvider:
"""Tests for MiniMax provider profile and auth integration."""
def test_minimax_in_default_provider_profiles(self):
from openharness.config.settings import default_provider_profiles
profiles = default_provider_profiles()
assert "minimax" in profiles
profile = profiles["minimax"]
assert profile.provider == "minimax"
assert profile.api_format == "openai"
assert profile.auth_source == "minimax_api_key"
assert profile.default_model == "MiniMax-M2.7"
assert profile.base_url == "https://api.minimax.io/v1"
def test_auth_source_provider_name_minimax(self):
from openharness.config.settings import auth_source_provider_name
assert auth_source_provider_name("minimax_api_key") == "minimax"
def test_default_auth_source_for_minimax_provider(self):
from openharness.config.settings import default_auth_source_for_provider
assert default_auth_source_for_provider("minimax") == "minimax_api_key"
def test_resolve_auth_reads_minimax_api_key_env(self, monkeypatch):
monkeypatch.setenv("MINIMAX_API_KEY", "minimax-test-key")
settings = Settings(
active_profile="minimax",
profiles={
"minimax": ProviderProfile(
label="MiniMax",
provider="minimax",
api_format="openai",
auth_source="minimax_api_key",
default_model="MiniMax-M2.7",
base_url="https://api.minimax.io/v1",
)
},
)
resolved = settings.resolve_auth()
assert resolved.value == "minimax-test-key"
assert "MINIMAX_API_KEY" in resolved.source
def test_minimax_profile_materializes_default_model(self):
settings = Settings(
active_profile="minimax",
profiles={
"minimax": ProviderProfile(
label="MiniMax",
provider="minimax",
api_format="openai",
auth_source="minimax_api_key",
default_model="MiniMax-M2.7",
base_url="https://api.minimax.io/v1",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "MiniMax-M2.7"
assert materialized.provider == "minimax"
assert materialized.api_format == "openai"
class TestNvidiaProvider:
"""Tests for NVIDIA NIM provider profile and auth integration."""
def test_nvidia_in_default_provider_profiles(self):
from openharness.config.settings import default_provider_profiles
profiles = default_provider_profiles()
assert "nvidia" in profiles
profile = profiles["nvidia"]
assert profile.provider == "nvidia"
assert profile.api_format == "openai"
assert profile.auth_source == "nvidia_api_key"
assert profile.default_model == "openai/gpt-oss-120b"
assert profile.base_url == "https://integrate.api.nvidia.com/v1"
def test_auth_source_provider_name_nvidia(self):
from openharness.config.settings import auth_source_provider_name
assert auth_source_provider_name("nvidia_api_key") == "nvidia"
def test_default_auth_source_for_nvidia_provider(self):
from openharness.config.settings import default_auth_source_for_provider
assert default_auth_source_for_provider("nvidia") == "nvidia_api_key"
def test_resolve_auth_reads_nvidia_api_key_env(self, monkeypatch):
monkeypatch.setenv("NVIDIA_API_KEY", "nvidia-test-key")
settings = Settings(
active_profile="nvidia",
profiles={
"nvidia": ProviderProfile(
label="NVIDIA NIM",
provider="nvidia",
api_format="openai",
auth_source="nvidia_api_key",
default_model="openai/gpt-oss-120b",
base_url="https://integrate.api.nvidia.com/v1",
)
},
)
resolved = settings.resolve_auth()
assert resolved.value == "nvidia-test-key"
assert "NVIDIA_API_KEY" in resolved.source
def test_nvidia_profile_materializes_default_model(self):
settings = Settings(
active_profile="nvidia",
profiles={
"nvidia": ProviderProfile(
label="NVIDIA NIM",
provider="nvidia",
api_format="openai",
auth_source="nvidia_api_key",
default_model="openai/gpt-oss-120b",
base_url="https://integrate.api.nvidia.com/v1",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "openai/gpt-oss-120b"
assert materialized.provider == "nvidia"
assert materialized.api_format == "openai"
class TestQwenProvider:
"""Tests for Qwen (DashScope) provider profile and auth integration."""
def test_qwen_in_default_provider_profiles(self):
from openharness.config.settings import default_provider_profiles
profiles = default_provider_profiles()
assert "qwen" in profiles
profile = profiles["qwen"]
assert profile.provider == "dashscope"
assert profile.api_format == "openai"
assert profile.auth_source == "dashscope_api_key"
assert profile.default_model == "qwen-plus"
assert profile.base_url == "https://dashscope.aliyuncs.com/compatible-mode/v1"
def test_auth_source_provider_name_qwen(self):
from openharness.config.settings import auth_source_provider_name
assert auth_source_provider_name("dashscope_api_key") == "dashscope"
def test_default_auth_source_for_qwen_provider(self):
from openharness.config.settings import default_auth_source_for_provider
assert default_auth_source_for_provider("dashscope") == "dashscope_api_key"
def test_resolve_auth_reads_qwen_api_key_env(self, monkeypatch):
monkeypatch.setenv("DASHSCOPE_API_KEY", "dashscope-test-key")
settings = Settings(
active_profile="qwen",
profiles={
"qwen": ProviderProfile(
label="Qwen (DashScope)",
provider="dashscope",
api_format="openai",
auth_source="dashscope_api_key",
default_model="qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
},
)
resolved = settings.resolve_auth()
assert resolved.value == "dashscope-test-key"
assert "DASHSCOPE_API_KEY" in resolved.source
def test_display_model_setting_for_qwen(self):
from openharness.config.settings import display_model_setting
profile = ProviderProfile(
label="Qwen (DashScope)",
provider="dashscope",
api_format="openai",
auth_source="dashscope_api_key",
default_model="qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
assert display_model_setting(profile) == "qwen-plus"
def test_materialize_active_profile_qwen(self):
settings = Settings(
active_profile="qwen",
profiles={
"qwen": ProviderProfile(
label="Qwen (DashScope)",
provider="dashscope",
api_format="openai",
auth_source="dashscope_api_key",
default_model="qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "qwen-plus"
assert materialized.provider == "dashscope"
assert materialized.api_format == "openai"
class TestModelScopeProvider:
"""Tests for ModelScope provider profile and auth integration."""
def test_modelscope_in_default_provider_profiles(self):
from openharness.config.settings import default_provider_profiles
profiles = default_provider_profiles()
assert "modelscope" in profiles
profile = profiles["modelscope"]
assert profile.provider == "modelscope"
assert profile.api_format == "openai"
assert profile.auth_source == "modelscope_api_key"
assert profile.default_model == "deepseek-ai/DeepSeek-V4-Flash"
assert profile.base_url == "https://api-inference.modelscope.cn/v1"
def test_auth_source_provider_name_modelscope(self):
from openharness.config.settings import auth_source_provider_name
assert auth_source_provider_name("modelscope_api_key") == "modelscope"
def test_default_auth_source_for_modelscope_provider(self):
from openharness.config.settings import default_auth_source_for_provider
assert default_auth_source_for_provider("modelscope") == "modelscope_api_key"
def test_resolve_auth_reads_modelscope_api_key_env(self, monkeypatch):
monkeypatch.setenv("MODELSCOPE_API_KEY", "modelscope-test-key")
settings = Settings(
active_profile="modelscope",
profiles={
"modelscope": ProviderProfile(
label="ModelScope",
provider="modelscope",
api_format="openai",
auth_source="modelscope_api_key",
default_model="deepseek-ai/DeepSeek-V4-Flash",
base_url="https://api-inference.modelscope.cn/v1",
)
},
)
resolved = settings.resolve_auth()
assert resolved.value == "modelscope-test-key"
assert "MODELSCOPE_API_KEY" in resolved.source
def test_modelscope_profile_materializes_default_model(self):
settings = Settings(
active_profile="modelscope",
profiles={
"modelscope": ProviderProfile(
label="ModelScope",
provider="modelscope",
api_format="openai",
auth_source="modelscope_api_key",
default_model="deepseek-ai/DeepSeek-V4-Flash",
base_url="https://api-inference.modelscope.cn/v1",
)
},
)
materialized = settings.materialize_active_profile()
assert materialized.model == "deepseek-ai/DeepSeek-V4-Flash"
assert materialized.provider == "modelscope"
assert materialized.api_format == "openai"