Files
wehub-resource-sync e9a2f726c9
CI / test (3.11) (push) Has been cancelled
CI / test (3.12) (push) Has been cancelled
CI / test (3.13) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:29:51 +08:00

2228 lines
89 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""Tests for omlx.settings module."""
import json
import os
import tempfile
from argparse import Namespace
from pathlib import Path
from unittest.mock import patch
import pytest
from omlx.settings import (
BURST_DECODE_MODES,
DEFAULT_BURST_DECODE_MODE,
AuthSettings,
CacheSettings,
ClaudeCodeSettings,
GlobalSettings,
HuggingFaceSettings,
IntegrationSettings,
LoggingSettings,
MCPSettings,
MemorySettings,
ModelSettings,
NetworkSettings,
SamplingSettings,
SchedulerSettings,
ServerSettings,
burst_decode_env,
get_settings,
get_ssd_capacity,
get_system_memory,
init_settings,
reset_settings,
resolve_default_base_path,
)
class TestServerSettings:
"""Tests for ServerSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = ServerSettings()
assert settings.host == "127.0.0.1"
assert settings.port == 8000
assert settings.log_level == "info"
assert settings.cors_origins == ["*"]
assert settings.sse_keepalive_mode == "chunk"
assert settings.auto_start_on_launch is True
assert settings.burst_decode_mode == "balanced"
assert settings.preserve_mid_system_cache is True
def test_custom_values(self):
"""Test custom values."""
settings = ServerSettings(
host="0.0.0.0",
port=9000,
log_level="debug",
cors_origins=["https://example.com"],
)
assert settings.host == "0.0.0.0"
assert settings.port == 9000
assert settings.log_level == "debug"
assert settings.cors_origins == ["https://example.com"]
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = ServerSettings(host="127.0.0.1", port=8000, log_level="info")
result = settings.to_dict()
assert result == {
"host": "127.0.0.1",
"port": 8000,
"log_level": "info",
"cors_origins": ["*"],
"server_aliases": [],
"sse_keepalive_mode": "chunk",
"auto_start_on_launch": True,
"burst_decode_mode": "balanced",
"preserve_mid_system_cache": True,
}
def test_from_dict_sse_keepalive_mode(self):
"""sse_keepalive_mode round-trips through from_dict / to_dict."""
for mode in ("chunk", "comment", "off"):
settings = ServerSettings.from_dict({"sse_keepalive_mode": mode})
assert settings.sse_keepalive_mode == mode
assert settings.to_dict()["sse_keepalive_mode"] == mode
def test_from_dict_burst_decode_mode(self):
"""burst_decode_mode round-trips through from_dict / to_dict."""
for mode in BURST_DECODE_MODES:
settings = ServerSettings.from_dict({"burst_decode_mode": mode})
assert settings.burst_decode_mode == mode
assert settings.to_dict()["burst_decode_mode"] == mode
def test_from_dict_burst_decode_mode_default(self):
"""A settings.json without burst_decode_mode falls back to the default."""
settings = ServerSettings.from_dict({})
assert settings.burst_decode_mode == DEFAULT_BURST_DECODE_MODE
def test_from_dict_preserve_mid_system_cache(self):
"""preserve_mid_system_cache round-trips through from_dict / to_dict."""
settings = ServerSettings.from_dict({"preserve_mid_system_cache": False})
assert settings.preserve_mid_system_cache is False
assert settings.to_dict()["preserve_mid_system_cache"] is False
def test_from_dict_preserve_mid_system_cache_default(self):
"""Missing preserve_mid_system_cache keeps the cache-friendly default."""
settings = ServerSettings.from_dict({})
assert settings.preserve_mid_system_cache is True
def test_from_dict_auto_start_on_launch(self):
"""auto_start_on_launch round-trips through from_dict / to_dict."""
settings = ServerSettings.from_dict({"auto_start_on_launch": False})
assert settings.auto_start_on_launch is False
assert settings.to_dict()["auto_start_on_launch"] is False
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"host": "0.0.0.0", "port": 9000, "log_level": "debug"}
settings = ServerSettings.from_dict(data)
assert settings.host == "0.0.0.0"
assert settings.port == 9000
assert settings.log_level == "debug"
assert settings.cors_origins == ["*"] # default
def test_from_dict_reads_bind_address_fallback(self):
"""bind_address is accepted only as a compatibility fallback."""
settings = ServerSettings.from_dict({"bind_address": "0.0.0.0"})
assert settings.host == "0.0.0.0"
def test_from_dict_host_wins_over_bind_address(self):
"""host remains the canonical persisted/admin API key."""
settings = ServerSettings.from_dict(
{"host": "127.0.0.1", "bind_address": "0.0.0.0"}
)
assert settings.host == "127.0.0.1"
def test_from_dict_with_cors_origins(self):
"""Test creation from dictionary with cors_origins."""
data = {
"host": "0.0.0.0",
"port": 9000,
"cors_origins": ["https://chat.example.com", "http://localhost:3000"],
}
settings = ServerSettings.from_dict(data)
assert settings.cors_origins == [
"https://chat.example.com",
"http://localhost:3000",
]
def test_from_dict_with_defaults(self):
"""Test creation from partial dictionary uses defaults."""
data = {"port": 9000}
settings = ServerSettings.from_dict(data)
assert settings.host == "127.0.0.1" # default
assert settings.port == 9000
assert settings.log_level == "info" # default
assert settings.cors_origins == ["*"] # default
class TestBurstDecodeEnv:
"""Tests for the Burst Decode mode -> OMLX_DECODE_BURST_* env mapping."""
def test_off_disables_bursting(self):
"""'off' caps max_steps at 1, which disables bursting in _step_burst."""
assert burst_decode_env("off")["OMLX_DECODE_BURST_MAX_STEPS"] == "1"
def test_levels_set_single_request_budget(self):
"""light / balanced / aggressive map to the documented budgets."""
assert burst_decode_env("light")["OMLX_DECODE_BURST_BUDGET_SINGLE_S"] == "0.05"
assert (
burst_decode_env("balanced")["OMLX_DECODE_BURST_BUDGET_SINGLE_S"] == "0.1"
)
assert (
burst_decode_env("aggressive")["OMLX_DECODE_BURST_BUDGET_SINGLE_S"] == "0.2"
)
def test_on_levels_keep_burst_enabled(self):
"""The on-levels keep max_steps above the disable threshold."""
for mode in ("light", "balanced", "aggressive"):
assert int(burst_decode_env(mode)["OMLX_DECODE_BURST_MAX_STEPS"]) > 1
def test_unknown_mode_falls_back_to_default(self):
"""An unknown mode never disables bursting; it uses the default."""
assert burst_decode_env("bogus") == burst_decode_env(DEFAULT_BURST_DECODE_MODE)
def test_keys_match_engine_config_env_vars(self):
"""The mapping only sets the env vars EngineConfig actually reads."""
assert set(burst_decode_env("balanced")) == {
"OMLX_DECODE_BURST_MAX_STEPS",
"OMLX_DECODE_BURST_BUDGET_SINGLE_S",
}
class TestModelSettings:
"""Tests for ModelSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = ModelSettings()
assert settings.model_dirs == []
assert settings.model_dir is None
def test_get_model_dirs_default(self):
"""Test default model directories."""
settings = ModelSettings()
base_path = Path("/tmp/omlx")
assert settings.get_model_dirs(base_path) == [Path("/tmp/omlx/models")]
assert settings.get_model_dir(base_path) == Path("/tmp/omlx/models")
def test_get_model_dirs_custom(self):
"""Test custom model directories."""
settings = ModelSettings(model_dirs=["/custom/models"])
base_path = Path("/tmp/omlx")
assert settings.get_model_dirs(base_path) == [Path("/custom/models")]
def test_get_model_dirs_multiple(self):
"""Test multiple model directories."""
settings = ModelSettings(model_dirs=["/path/a", "/path/b"])
base_path = Path("/tmp/omlx")
result = settings.get_model_dirs(base_path)
assert len(result) == 2
assert result[0] == Path("/path/a")
assert result[1] == Path("/path/b")
# get_model_dir returns the first (primary) directory
assert settings.get_model_dir(base_path) == Path("/path/a")
def test_get_model_dirs_with_tilde(self):
"""Test model directory with tilde expansion."""
settings = ModelSettings(model_dirs=["~/models"])
base_path = Path("/tmp/omlx")
result = settings.get_model_dirs(base_path)
assert "~" not in str(result[0]) # Should be expanded
def test_get_model_dirs_backward_compat(self):
"""Test backward compatibility: model_dir fallback when model_dirs is empty."""
settings = ModelSettings(model_dir="/legacy/models")
base_path = Path("/tmp/omlx")
assert settings.get_model_dirs(base_path) == [Path("/legacy/models")]
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = ModelSettings(model_dirs=["/models"])
result = settings.to_dict()
assert result == {
"model_dirs": ["/models"],
"model_dir": "/models",
"model_fallback": False,
"hide_helper_models": False,
}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"model_dirs": ["/models"]}
settings = ModelSettings.from_dict(data)
assert settings.model_dirs == ["/models"]
assert settings.model_fallback is False
def test_from_dict_ignores_legacy_max_model_memory(self):
"""Legacy max_model_memory key in settings.json is silently ignored."""
data = {"model_dirs": ["/models"], "max_model_memory": "32GB"}
settings = ModelSettings.from_dict(data)
assert settings.model_dirs == ["/models"]
assert not hasattr(settings, "max_model_memory")
def test_model_fallback_default(self):
"""Test model_fallback defaults to False."""
settings = ModelSettings()
assert settings.model_fallback is False
def test_model_fallback_to_dict(self):
"""Test model_fallback is included in to_dict."""
settings = ModelSettings(model_dirs=["/models"], model_fallback=True)
result = settings.to_dict()
assert result["model_fallback"] is True
def test_model_fallback_from_dict(self):
"""Test model_fallback is loaded from dict."""
data = {"model_dirs": ["/models"], "model_fallback": True}
settings = ModelSettings.from_dict(data)
assert settings.model_fallback is True
def test_model_fallback_from_dict_missing(self):
"""Test model_fallback defaults to False when missing from dict."""
data = {"model_dirs": ["/models"]}
settings = ModelSettings.from_dict(data)
assert settings.model_fallback is False
def test_from_dict_backward_compat(self):
"""Test from_dict migrates old model_dir to model_dirs."""
data = {"model_dir": "/legacy/models"}
settings = ModelSettings.from_dict(data)
assert settings.model_dirs == ["/legacy/models"]
assert settings.model_dir == "/legacy/models"
class TestSchedulerSettings:
"""Tests for SchedulerSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = SchedulerSettings()
assert settings.max_concurrent_requests == 8
assert settings.embedding_batch_size == 32
def test_custom_values(self):
"""Test custom values."""
settings = SchedulerSettings(
max_concurrent_requests=128, embedding_batch_size=16
)
assert settings.max_concurrent_requests == 128
assert settings.embedding_batch_size == 16
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = SchedulerSettings()
result = settings.to_dict()
assert result == {
"max_concurrent_requests": 8,
"embedding_batch_size": 32,
"chunked_prefill": False,
}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"max_concurrent_requests": 512}
settings = SchedulerSettings.from_dict(data)
assert settings.max_concurrent_requests == 512
assert settings.embedding_batch_size == 32
data = {"max_concurrent_requests": 512, "embedding_batch_size": 24}
settings = SchedulerSettings.from_dict(data)
assert settings.max_concurrent_requests == 512
assert settings.embedding_batch_size == 24
def test_from_dict_backwards_compat(self):
"""Test creation from dictionary with old keys."""
data = {"max_num_seqs": 64}
settings = SchedulerSettings.from_dict(data)
assert settings.max_concurrent_requests == 64
data = {"completion_batch_size": 32}
settings = SchedulerSettings.from_dict(data)
assert settings.max_concurrent_requests == 32
class TestCacheSettings:
"""Tests for CacheSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = CacheSettings()
assert settings.enabled is True
assert settings.ssd_cache_dir is None
assert settings.ssd_cache_max_size == "auto"
assert settings.initial_cache_blocks == 256
def test_get_ssd_cache_dir_default(self):
"""Test default SSD cache directory."""
settings = CacheSettings(ssd_cache_dir=None)
base_path = Path("/tmp/omlx")
assert settings.get_ssd_cache_dir(base_path) == Path("/tmp/omlx/cache")
def test_get_ssd_cache_dir_custom(self):
"""Test custom SSD cache directory."""
settings = CacheSettings(ssd_cache_dir="/custom/cache")
base_path = Path("/tmp/omlx")
assert settings.get_ssd_cache_dir(base_path) == Path("/custom/cache")
def test_get_ssd_cache_max_size_bytes_auto(self):
"""Test auto SSD cache size calculation."""
settings = CacheSettings(ssd_cache_max_size="auto")
base_path = Path("/tmp/omlx")
cache_dir = settings.get_ssd_cache_dir(base_path)
expected = int(get_ssd_capacity(cache_dir) * 0.1)
assert settings.get_ssd_cache_max_size_bytes(base_path) == expected
def test_get_ssd_cache_max_size_bytes_explicit(self):
"""Test explicit SSD cache size."""
settings = CacheSettings(ssd_cache_max_size="100GB")
base_path = Path("/tmp/omlx")
assert settings.get_ssd_cache_max_size_bytes(base_path) == 100 * 1024**3
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = CacheSettings(
enabled=False, ssd_cache_dir="/cache", ssd_cache_max_size="50GB"
)
result = settings.to_dict()
assert result == {
"enabled": False,
"hot_cache_only": False,
"ssd_cache_dir": "/cache",
"ssd_cache_max_size": "50GB",
"hot_cache_max_size": "0",
"initial_cache_blocks": 256,
}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {
"enabled": False,
"ssd_cache_dir": "/cache",
"ssd_cache_max_size": "200GB",
}
settings = CacheSettings.from_dict(data)
assert settings.enabled is False
assert settings.ssd_cache_dir == "/cache"
assert settings.ssd_cache_max_size == "200GB"
assert settings.initial_cache_blocks == 256 # default
def test_from_dict_with_initial_cache_blocks(self):
"""Test creation from dictionary with initial_cache_blocks."""
data = {
"enabled": True,
"initial_cache_blocks": 16384,
}
settings = CacheSettings.from_dict(data)
assert settings.initial_cache_blocks == 16384
def test_from_dict_migrates_hot_cache_auto_to_disabled(self):
"""Legacy hot_cache_max_size=auto should load as disabled."""
settings = CacheSettings.from_dict({"hot_cache_max_size": "auto"})
assert settings.hot_cache_max_size == "0"
def test_initial_cache_blocks_custom(self):
"""Test custom initial_cache_blocks value."""
settings = CacheSettings(initial_cache_blocks=8192)
assert settings.initial_cache_blocks == 8192
result = settings.to_dict()
assert result["initial_cache_blocks"] == 8192
class TestAuthSettings:
"""Tests for AuthSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = AuthSettings()
assert settings.api_key is None
assert settings.secret_key is None
assert settings.sub_keys == []
def test_custom_values(self):
"""Test custom values."""
settings = AuthSettings(api_key="test-secret-key", secret_key="my-secret")
assert settings.api_key == "test-secret-key"
assert settings.secret_key == "my-secret"
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = AuthSettings(api_key="my-key")
result = settings.to_dict()
assert result == {
"api_key": "my-key",
"secret_key": None,
"skip_api_key_verification": False,
"sub_keys": [],
}
def test_to_dict_with_sub_keys(self):
"""Test conversion to dictionary with sub keys."""
from omlx.settings import SubKeyEntry
settings = AuthSettings(
api_key="my-key",
sub_keys=[SubKeyEntry(key="sk1", name="Test", created_at="2024-01-01")],
)
result = settings.to_dict()
assert len(result["sub_keys"]) == 1
assert result["sub_keys"][0]["key"] == "sk1"
assert result["sub_keys"][0]["name"] == "Test"
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"api_key": "loaded-key", "secret_key": "loaded-secret"}
settings = AuthSettings.from_dict(data)
assert settings.api_key == "loaded-key"
assert settings.secret_key == "loaded-secret"
assert settings.sub_keys == []
def test_from_dict_with_sub_keys(self):
"""Test creation from dictionary with sub keys."""
data = {
"api_key": "loaded-key",
"sub_keys": [
{"key": "sk1", "name": "My Key", "created_at": "2024-01-01"},
],
}
settings = AuthSettings.from_dict(data)
assert len(settings.sub_keys) == 1
assert settings.sub_keys[0].key == "sk1"
assert settings.sub_keys[0].name == "My Key"
def test_from_dict_backward_compat(self):
"""Test creation from dictionary without secret_key (backward compat)."""
data = {"api_key": "loaded-key"}
settings = AuthSettings.from_dict(data)
assert settings.api_key == "loaded-key"
assert settings.secret_key is None
assert settings.sub_keys == []
class TestMCPSettings:
"""Tests for MCPSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = MCPSettings()
assert settings.config_path is None
def test_custom_values(self):
"""Test custom values."""
settings = MCPSettings(config_path="/path/to/mcp.json")
assert settings.config_path == "/path/to/mcp.json"
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = MCPSettings(config_path="/mcp/config.json")
result = settings.to_dict()
assert result == {"config_path": "/mcp/config.json"}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"config_path": "/some/path.json"}
settings = MCPSettings.from_dict(data)
assert settings.config_path == "/some/path.json"
class TestHuggingFaceSettings:
"""Tests for HuggingFaceSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = HuggingFaceSettings()
assert settings.endpoint == ""
assert settings.hf_cache_enabled is True
def test_custom_values(self):
"""Test custom values."""
settings = HuggingFaceSettings(
endpoint="https://hf-mirror.com",
hf_cache_enabled=False,
)
assert settings.endpoint == "https://hf-mirror.com"
assert settings.hf_cache_enabled is False
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = HuggingFaceSettings(
endpoint="https://hf-mirror.com",
hf_cache_enabled=False,
)
result = settings.to_dict()
assert result == {
"endpoint": "https://hf-mirror.com",
"hf_cache_enabled": False,
}
def test_to_dict_empty(self):
"""Test conversion to dictionary with empty endpoint."""
settings = HuggingFaceSettings()
result = settings.to_dict()
assert result == {"endpoint": "", "hf_cache_enabled": True}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"endpoint": "https://hf-mirror.com", "hf_cache_enabled": False}
settings = HuggingFaceSettings.from_dict(data)
assert settings.endpoint == "https://hf-mirror.com"
assert settings.hf_cache_enabled is False
def test_from_dict_defaults(self):
"""Test creation from empty dictionary uses defaults."""
settings = HuggingFaceSettings.from_dict({})
assert settings.endpoint == ""
assert settings.hf_cache_enabled is True
class TestNetworkSettings:
"""Tests for NetworkSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = NetworkSettings()
assert settings.http_proxy == ""
assert settings.https_proxy == ""
assert settings.no_proxy == ""
assert settings.ca_bundle == ""
def test_custom_values(self):
"""Test custom values."""
settings = NetworkSettings(
http_proxy="http://proxy:8080",
https_proxy="http://proxy:8080",
no_proxy="localhost,127.0.0.1",
ca_bundle="/tmp/corp-ca.pem",
)
assert settings.http_proxy == "http://proxy:8080"
assert settings.https_proxy == "http://proxy:8080"
assert settings.no_proxy == "localhost,127.0.0.1"
assert settings.ca_bundle == "/tmp/corp-ca.pem"
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = NetworkSettings(
http_proxy="http://proxy:8080",
https_proxy="http://proxy:8080",
no_proxy="localhost",
ca_bundle="/tmp/ca.pem",
)
result = settings.to_dict()
assert result == {
"http_proxy": "http://proxy:8080",
"https_proxy": "http://proxy:8080",
"no_proxy": "localhost",
"ca_bundle": "/tmp/ca.pem",
}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {
"http_proxy": "http://proxy:8080",
"https_proxy": "http://proxy:8080",
"no_proxy": "localhost",
"ca_bundle": "/tmp/ca.pem",
}
settings = NetworkSettings.from_dict(data)
assert settings.http_proxy == "http://proxy:8080"
assert settings.https_proxy == "http://proxy:8080"
assert settings.no_proxy == "localhost"
assert settings.ca_bundle == "/tmp/ca.pem"
class TestLoggingSettings:
"""Tests for LoggingSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = LoggingSettings()
assert settings.log_dir is None
assert settings.retention_days == 7
def test_custom_values(self):
"""Test custom values."""
settings = LoggingSettings(log_dir="/custom/logs", retention_days=14)
assert settings.log_dir == "/custom/logs"
assert settings.retention_days == 14
def test_get_log_dir_default(self):
"""Test default log directory."""
settings = LoggingSettings(log_dir=None)
base_path = Path("/tmp/omlx")
assert settings.get_log_dir(base_path) == Path("/tmp/omlx/logs")
def test_get_log_dir_custom(self):
"""Test custom log directory."""
settings = LoggingSettings(log_dir="/custom/logs")
base_path = Path("/tmp/omlx")
assert settings.get_log_dir(base_path) == Path("/custom/logs")
def test_get_log_dir_with_tilde(self):
"""Test log directory with tilde expansion."""
settings = LoggingSettings(log_dir="~/logs")
base_path = Path("/tmp/omlx")
result = settings.get_log_dir(base_path)
assert "~" not in str(result) # Should be expanded
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = LoggingSettings(log_dir="/logs", retention_days=30)
result = settings.to_dict()
assert result == {"log_dir": "/logs", "retention_days": 30}
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"log_dir": "/my/logs", "retention_days": 14}
settings = LoggingSettings.from_dict(data)
assert settings.log_dir == "/my/logs"
assert settings.retention_days == 14
def test_from_dict_with_defaults(self):
"""Test creation from partial dictionary uses defaults."""
data = {"log_dir": "/custom"}
settings = LoggingSettings.from_dict(data)
assert settings.log_dir == "/custom"
assert settings.retention_days == 7 # default
class TestMemorySettings:
"""Tests for MemorySettings dataclass."""
def test_defaults(self):
"""Memory guard defaults to balanced tier and guard on."""
settings = MemorySettings()
assert settings.prefill_memory_guard is True
assert settings.memory_guard_tier == "balanced"
def test_to_dict(self):
"""Test serialization."""
settings = MemorySettings(memory_guard_tier="safe")
d = settings.to_dict()
assert d["memory_guard_tier"] == "safe"
assert d["prefill_memory_guard"] is True
assert d["soft_threshold"] == 0.85
assert d["hard_threshold"] == 0.95
# Removed fields must not be present.
assert "max_process_memory" not in d
assert "max_process_memory_is_explicit" not in d
def test_to_dict_guard_disabled(self):
"""Test serialization with prefill guard disabled."""
settings = MemorySettings(prefill_memory_guard=False)
d = settings.to_dict()
assert d["prefill_memory_guard"] is False
def test_from_dict(self):
"""Test deserialization picks up tier value."""
settings = MemorySettings.from_dict({"memory_guard_tier": "aggressive"})
assert settings.memory_guard_tier == "aggressive"
assert settings.prefill_memory_guard is True # default
def test_from_dict_defaults(self):
"""Test deserialization with empty dict uses defaults."""
settings = MemorySettings.from_dict({})
assert settings.memory_guard_tier == "balanced"
assert settings.prefill_memory_guard is True
def test_from_dict_invalid_tier_falls_back_to_balanced(self):
"""Unknown tier values silently degrade to balanced."""
settings = MemorySettings.from_dict({"memory_guard_tier": "wild"})
assert settings.memory_guard_tier == "balanced"
def test_from_dict_ignores_legacy_keys(self):
"""Legacy max_process_memory / is_explicit keys in old settings.json are ignored."""
settings = MemorySettings.from_dict(
{
"max_process_memory": "80%",
"max_process_memory_is_explicit": True,
"memory_guard_tier": "safe",
}
)
assert settings.memory_guard_tier == "safe"
assert not hasattr(settings, "max_process_memory")
def test_from_dict_guard_disabled(self):
"""Test deserialization with prefill guard disabled."""
settings = MemorySettings.from_dict({"prefill_memory_guard": False})
assert settings.prefill_memory_guard is False
class TestGlobalSettings:
"""Tests for GlobalSettings class."""
def test_defaults(self):
"""Test default values."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings(base_path=Path(tmpdir))
assert settings.server.host == "127.0.0.1"
assert settings.server.port == 8000
assert settings.memory.memory_guard_tier == "balanced"
assert settings.scheduler.max_concurrent_requests == 8
assert settings.scheduler.embedding_batch_size == 32
assert settings.cache.enabled is True
assert settings.auth.api_key is None
assert settings.mcp.config_path is None
def test_get_effective_model_dirs_includes_hf_cache_between_dirs(
self, tmp_path, monkeypatch
):
"""HF cache is inserted between primary and additional model dirs."""
primary = tmp_path / "primary"
additional = tmp_path / "additional"
hf_cache = tmp_path / "hf" / "hub"
primary.mkdir()
additional.mkdir()
hf_cache.mkdir(parents=True)
monkeypatch.setenv("HF_HUB_CACHE", str(hf_cache))
settings = GlobalSettings(base_path=tmp_path / "omlx")
settings.model.model_dirs = [str(primary), str(additional)]
assert settings.get_effective_model_dirs() == [
primary.resolve(),
hf_cache.resolve(),
additional.resolve(),
]
def test_get_effective_model_dirs_skips_disabled_hf_cache(
self, tmp_path, monkeypatch
):
"""Disabled HF cache is not included in discovery dirs."""
primary = tmp_path / "primary"
hf_cache = tmp_path / "hf" / "hub"
primary.mkdir()
hf_cache.mkdir(parents=True)
monkeypatch.setenv("HF_HUB_CACHE", str(hf_cache))
settings = GlobalSettings(base_path=tmp_path / "omlx")
settings.model.model_dirs = [str(primary)]
settings.huggingface.hf_cache_enabled = False
assert settings.get_effective_model_dirs() == [primary.resolve()]
def test_cli_override_memory_guard_tier(self, tmp_path):
"""CLI memory guard tier should override loaded settings."""
args = Namespace(memory_guard="safe", memory_guard_gb=None)
settings = GlobalSettings.load(base_path=tmp_path, cli_args=args)
assert settings.memory.memory_guard_tier == "safe"
assert settings.memory.memory_guard_custom_ceiling_gb == 0.0
def test_cli_override_memory_guard_gb_sets_custom_tier(self, tmp_path):
"""CLI memory guard GB should select custom tier automatically."""
args = Namespace(memory_guard=None, memory_guard_gb=48.0)
settings = GlobalSettings.load(base_path=tmp_path, cli_args=args)
assert settings.memory.memory_guard_tier == "custom"
assert settings.memory.memory_guard_custom_ceiling_gb == 48.0
def test_load_from_file(self):
"""Test loading settings from JSON file."""
with tempfile.TemporaryDirectory() as tmpdir:
# Create settings file
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps(
{
"version": "1.0",
"server": {"port": 9000},
"auth": {"api_key": "test-key"},
}
)
)
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.port == 9000
assert settings.auth.api_key == "test-key"
def test_load_from_file_all_sections(self):
"""Test loading all settings sections from file."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps(
{
"version": "1.0",
"server": {
"host": "0.0.0.0",
"port": 9000,
"log_level": "debug",
},
"model": {"model_dir": "/models"},
"memory": {"memory_guard_tier": "safe"},
"scheduler": {
"max_concurrent_requests": 128,
"embedding_batch_size": 24,
},
"cache": {
"enabled": False,
"ssd_cache_dir": "/cache",
"ssd_cache_max_size": "50GB",
},
"auth": {"api_key": "secret"},
"mcp": {"config_path": "/mcp.json"},
}
)
)
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.host == "0.0.0.0"
assert settings.server.port == 9000
assert settings.server.log_level == "debug"
assert settings.model.model_dirs == ["/models"] # Migrated from model_dir
assert settings.model.model_dir == "/models" # Backward compat field
assert settings.memory.memory_guard_tier == "safe"
assert settings.scheduler.max_concurrent_requests == 128
assert settings.scheduler.embedding_batch_size == 24
assert settings.cache.enabled is False
assert settings.cache.ssd_cache_dir == "/cache"
assert settings.auth.api_key == "secret"
assert settings.mcp.config_path == "/mcp.json"
def test_load_nonexistent_file_uses_defaults(self):
"""Test loading with no settings file uses defaults."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.host == "127.0.0.1"
assert settings.server.port == 8000
def test_load_invalid_json_uses_defaults(self):
"""Test loading invalid JSON file logs warning and uses defaults."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text("{ invalid json }")
settings = GlobalSettings.load(base_path=tmpdir)
# Should use defaults due to parse error
assert settings.server.port == 8000
def test_save(self):
"""Test saving settings to file."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings(base_path=Path(tmpdir))
settings.server.port = 9001
settings.auth.api_key = "saved-key"
settings.save()
# Verify file was created
settings_file = Path(tmpdir) / "settings.json"
assert settings_file.exists()
# Verify content
data = json.loads(settings_file.read_text())
assert data["version"] == "1.0"
assert data["server"]["port"] == 9001
assert data["auth"]["api_key"] == "saved-key"
def test_save_and_load_cors_origins(self):
"""Test saving and loading cors_origins through settings file."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings(base_path=Path(tmpdir))
settings.server.cors_origins = ["https://chat.example.com"]
settings.save()
loaded = GlobalSettings.load(base_path=tmpdir)
assert loaded.server.cors_origins == ["https://chat.example.com"]
def test_load_cors_origins_from_file(self):
"""Test loading cors_origins from settings.json."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps(
{
"version": "1.0",
"server": {
"port": 8000,
"cors_origins": [
"https://chat.example.com",
"http://localhost:3000",
],
},
}
)
)
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.cors_origins == [
"https://chat.example.com",
"http://localhost:3000",
]
def test_load_without_cors_origins_uses_default(self):
"""Test that missing cors_origins in settings.json uses default ['*']."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps({"version": "1.0", "server": {"port": 9000}})
)
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.cors_origins == ["*"]
def test_save_creates_directory(self):
"""Test save creates base directory if needed."""
with tempfile.TemporaryDirectory() as tmpdir:
base = Path(tmpdir) / "nested" / "omlx"
settings = GlobalSettings(base_path=base)
settings.save()
assert base.exists()
assert (base / "settings.json").exists()
def test_ensure_directories(self):
"""Test directory creation."""
with tempfile.TemporaryDirectory() as tmpdir:
base = Path(tmpdir) / "omlx"
settings = GlobalSettings(base_path=base)
settings.ensure_directories()
assert base.exists()
assert (base / "models").exists()
assert (base / "cache").exists()
assert (base / "logs").exists()
def test_ensure_directories_custom_paths(self):
"""Test directory creation with custom paths."""
with tempfile.TemporaryDirectory() as tmpdir:
base = Path(tmpdir) / "omlx"
custom_models = Path(tmpdir) / "custom_models"
custom_cache = Path(tmpdir) / "custom_cache"
custom_logs = Path(tmpdir) / "custom_logs"
settings = GlobalSettings(base_path=base)
settings.model.model_dir = str(custom_models)
settings.cache.ssd_cache_dir = str(custom_cache)
settings.logging.log_dir = str(custom_logs)
settings.ensure_directories()
assert base.exists()
assert custom_models.exists()
assert custom_cache.exists()
assert custom_logs.exists()
def test_ensure_directories_unavailable_model_dir(self):
"""Test that unavailable model dirs are skipped instead of crashing."""
with tempfile.TemporaryDirectory() as tmpdir:
base = Path(tmpdir) / "omlx"
valid_models = Path(tmpdir) / "valid_models"
unavailable = Path("/Volumes/NonExistentDrive/Models")
settings = GlobalSettings(base_path=base)
settings.model.model_dirs = [str(valid_models), str(unavailable)]
settings.ensure_directories()
assert base.exists()
assert valid_models.exists()
# Unavailable path should be removed from model_dirs
resolved_dirs = settings.model.get_model_dirs(base)
assert len(resolved_dirs) == 1
assert resolved_dirs[0] == valid_models.resolve()
def test_ensure_directories_unreadable_model_dir(self, tmp_path, monkeypatch):
"""Test that existing but unreadable model dirs are skipped."""
base = tmp_path / "omlx"
valid_models = tmp_path / "valid_models"
unreadable = tmp_path / "unreadable_models"
unreadable.mkdir()
original_iterdir = Path.iterdir
def fake_iterdir(path):
if path == unreadable.resolve():
raise PermissionError("Operation not permitted")
return original_iterdir(path)
monkeypatch.setattr(Path, "iterdir", fake_iterdir)
settings = GlobalSettings(base_path=base)
settings.model.model_dirs = [str(valid_models), str(unreadable)]
settings.ensure_directories()
resolved_dirs = settings.model.get_model_dirs(base)
assert resolved_dirs == [valid_models.resolve()]
def test_validate_valid_settings(self):
"""Test validation with valid settings."""
settings = GlobalSettings()
errors = settings.validate()
assert errors == []
def test_validate_context_window_policy(self):
"""Sampling context policy must be positive when set."""
settings = GlobalSettings()
settings.sampling.max_context_window_policy = 128000
assert settings.validate() == []
settings.sampling.max_context_window_policy = 0
errors = settings.validate()
assert any("max_context_window_policy" in e for e in errors)
def test_validate_invalid_port_low(self):
"""Test validation catches port below 1."""
settings = GlobalSettings()
settings.server.port = 0
errors = settings.validate()
assert any("port" in e.lower() for e in errors)
def test_validate_invalid_port_high(self):
"""Test validation catches port above 65535."""
settings = GlobalSettings()
settings.server.port = 70000
errors = settings.validate()
assert any("port" in e.lower() for e in errors)
def test_validate_invalid_log_level(self):
"""Test validation catches invalid log level."""
settings = GlobalSettings()
settings.server.log_level = "invalid"
errors = settings.validate()
assert any("log_level" in e.lower() for e in errors)
def test_validate_valid_log_levels(self):
"""Test validation accepts all valid log levels."""
for level in ["trace", "debug", "info", "warning", "error", "critical"]:
settings = GlobalSettings()
settings.server.log_level = level
errors = settings.validate()
assert errors == []
def test_validate_invalid_sse_keepalive_mode(self):
"""Validation rejects unknown sse_keepalive_mode values."""
settings = GlobalSettings()
settings.server.sse_keepalive_mode = "bogus"
errors = settings.validate()
assert any("sse_keepalive_mode" in e for e in errors)
def test_validate_valid_sse_keepalive_modes(self):
"""Validation accepts chunk / comment / off."""
for mode in ("chunk", "comment", "off"):
settings = GlobalSettings()
settings.server.sse_keepalive_mode = mode
errors = settings.validate()
assert errors == []
def test_validate_memory_guard_tier_valid(self):
"""Test validation accepts each known tier."""
for tier in ("safe", "balanced", "aggressive"):
settings = GlobalSettings()
settings.memory.memory_guard_tier = tier
errors = settings.validate()
assert not any("memory_guard_tier" in e for e in errors)
settings = GlobalSettings()
settings.memory.memory_guard_tier = "custom"
settings.memory.memory_guard_custom_ceiling_gb = 48.0
errors = settings.validate()
assert not any("memory_guard_tier" in e for e in errors)
assert not any("memory_guard_custom_ceiling_gb" in e for e in errors)
def test_validate_memory_guard_tier_invalid(self):
"""Test validation flags unknown tier values."""
settings = GlobalSettings()
settings.memory.memory_guard_tier = "extreme" # type: ignore[assignment]
errors = settings.validate()
assert any("memory_guard_tier" in e for e in errors)
def test_validate_invalid_scheduler_values(self):
"""Test validation catches invalid scheduler values."""
settings = GlobalSettings()
settings.scheduler.max_concurrent_requests = 0
errors = settings.validate()
assert any("max_concurrent_requests" in e.lower() for e in errors)
settings = GlobalSettings()
settings.scheduler.embedding_batch_size = 0
errors = settings.validate()
assert any("embedding_batch_size" in e.lower() for e in errors)
def test_validate_invalid_cache_size(self):
"""Test validation catches invalid cache size."""
settings = GlobalSettings()
settings.cache.ssd_cache_max_size = "not-a-size"
errors = settings.validate()
assert any("ssd_cache_max_size" in e.lower() for e in errors)
def test_validate_hot_cache_size(self):
"""Hot cache accepts explicit sizes only; auto is SSD-cache-only."""
settings = GlobalSettings()
settings.cache.hot_cache_max_size = "0"
assert not any("hot_cache_max_size" in e for e in settings.validate())
settings.cache.hot_cache_max_size = "8GB"
assert not any("hot_cache_max_size" in e for e in settings.validate())
settings.cache.hot_cache_max_size = "auto"
errors = settings.validate()
assert any("hot_cache_max_size" in e for e in errors)
assert any("auto" in e for e in errors)
settings.cache.hot_cache_max_size = "not-a-size"
errors = settings.validate()
assert any("hot_cache_max_size" in e for e in errors)
def test_validate_invalid_initial_cache_blocks(self):
"""Test validation catches invalid initial_cache_blocks."""
settings = GlobalSettings()
settings.cache.initial_cache_blocks = 0
errors = settings.validate()
assert any("initial_cache_blocks" in e.lower() for e in errors)
settings = GlobalSettings()
settings.cache.initial_cache_blocks = -1
errors = settings.validate()
assert any("initial_cache_blocks" in e.lower() for e in errors)
def test_validate_multiple_errors(self):
"""Test validation returns multiple errors."""
settings = GlobalSettings()
settings.server.port = 0
settings.scheduler.max_concurrent_requests = -1
settings.memory.memory_guard_tier = "extreme" # type: ignore[assignment]
errors = settings.validate()
assert len(errors) >= 3
def test_validate_hf_endpoint_empty_ok(self):
"""Test empty HuggingFace endpoint passes validation."""
settings = GlobalSettings()
settings.huggingface.endpoint = ""
errors = settings.validate()
assert not any("huggingface" in e.lower() for e in errors)
def test_validate_hf_endpoint_valid(self):
"""Test valid HuggingFace endpoint passes validation."""
settings = GlobalSettings()
settings.huggingface.endpoint = "https://hf-mirror.com"
errors = settings.validate()
assert not any("huggingface" in e.lower() for e in errors)
def test_validate_hf_endpoint_invalid(self):
"""Test invalid HuggingFace endpoint fails validation."""
settings = GlobalSettings()
settings.huggingface.endpoint = "not-a-url"
errors = settings.validate()
assert any("huggingface" in e.lower() for e in errors)
def test_env_override_server(self):
"""Test environment variable override for server settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{
"OMLX_HOST": "0.0.0.0",
"OMLX_PORT": "9999",
"OMLX_LOG_LEVEL": "debug",
},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.host == "0.0.0.0"
assert settings.server.port == 9999
assert settings.server.log_level == "debug"
def test_env_override_model(self):
"""Test environment variable override for model settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{
"OMLX_MODEL_DIR": "/env/models",
},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.model.model_dir == "/env/models"
def test_env_override_scheduler(self):
"""Test environment variable override for scheduler settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{"OMLX_MAX_CONCURRENT_REQUESTS": "512"},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.scheduler.max_concurrent_requests == 512
def test_env_override_embedding_batch_size(self):
"""Test environment variable override for embedding batch size."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{"OMLX_EMBEDDING_BATCH_SIZE": "24"},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.scheduler.embedding_batch_size == 24
def test_env_override_scheduler_legacy_fallback(self):
"""Test legacy OMLX_MAX_NUM_SEQS env var is accepted as fallback."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{"OMLX_MAX_NUM_SEQS": "256"},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.scheduler.max_concurrent_requests == 256
def test_env_override_cache(self):
"""Test environment variable override for cache settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{
"OMLX_CACHE_ENABLED": "false",
"OMLX_SSD_CACHE_DIR": "/env/cache",
"OMLX_SSD_CACHE_MAX_SIZE": "200GB",
},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.cache.enabled is False
assert settings.cache.ssd_cache_dir == "/env/cache"
assert settings.cache.ssd_cache_max_size == "200GB"
def test_env_override_initial_cache_blocks(self):
"""Test environment variable override for initial_cache_blocks."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{"OMLX_INITIAL_CACHE_BLOCKS": "16384"},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.cache.initial_cache_blocks == 16384
def test_env_override_cache_enabled_values(self):
"""Test various values for OMLX_CACHE_ENABLED."""
with tempfile.TemporaryDirectory() as tmpdir:
for value in ["true", "1", "yes"]:
with patch.dict(os.environ, {"OMLX_CACHE_ENABLED": value}, clear=False):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.cache.enabled is True
for value in ["false", "0", "no"]:
with patch.dict(os.environ, {"OMLX_CACHE_ENABLED": value}, clear=False):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.cache.enabled is False
def test_env_override_auth(self):
"""Test environment variable override for auth settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(os.environ, {"OMLX_API_KEY": "env-key"}, clear=False):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.auth.api_key == "env-key"
def test_env_override_mcp(self):
"""Test environment variable override for MCP settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ, {"OMLX_MCP_CONFIG": "/env/mcp.json"}, clear=False
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.mcp.config_path == "/env/mcp.json"
def test_env_override_hf_endpoint(self):
"""Test environment variable override for HuggingFace settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{"OMLX_HF_ENDPOINT": "https://hf-mirror.com"},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.huggingface.endpoint == "https://hf-mirror.com"
def test_env_override_network(self):
"""Test environment variable override for network proxy settings."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(
os.environ,
{
"OMLX_HTTP_PROXY": "http://proxy.company.com:8080",
"OMLX_HTTPS_PROXY": "http://proxy.company.com:8443",
"OMLX_NO_PROXY": "localhost,127.0.0.1",
"OMLX_CA_BUNDLE": "/tmp/corp-ca.pem",
},
clear=False,
):
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.network.http_proxy == "http://proxy.company.com:8080"
assert settings.network.https_proxy == "http://proxy.company.com:8443"
assert settings.network.no_proxy == "localhost,127.0.0.1"
assert settings.network.ca_bundle == "/tmp/corp-ca.pem"
def test_env_override_invalid_port_logs_warning(self):
"""Test invalid OMLX_PORT logs warning and keeps default."""
with tempfile.TemporaryDirectory() as tmpdir:
with patch.dict(os.environ, {"OMLX_PORT": "not-a-number"}, clear=False):
settings = GlobalSettings.load(base_path=tmpdir)
# Should keep default due to parse error
assert settings.server.port == 8000
def test_env_override_after_file(self):
"""Test env vars override file settings."""
with tempfile.TemporaryDirectory() as tmpdir:
# Create settings file
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps({"version": "1.0", "server": {"port": 9000}})
)
with patch.dict(os.environ, {"OMLX_PORT": "8888"}, clear=False):
settings = GlobalSettings.load(base_path=tmpdir)
# Env should override file
assert settings.server.port == 8888
def test_cli_override(self):
"""Test CLI argument override."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(
port=8888,
host="0.0.0.0",
log_level="warning",
model_dir="/cli/models",
api_key="cli-key",
)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.server.port == 8888
assert settings.server.host == "0.0.0.0"
assert settings.server.log_level == "warning"
assert settings.model.model_dir == "/cli/models"
assert settings.auth.api_key == "cli-key"
def test_cli_override_partial(self):
"""Test CLI override with some None values."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(port=7777, host=None, log_level=None)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.server.port == 7777
assert settings.server.host == "127.0.0.1" # default
assert settings.server.log_level == "info" # default
def test_cli_override_scheduler(self):
"""Test CLI override for scheduler settings."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(max_concurrent_requests=64, embedding_batch_size=12)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.scheduler.max_concurrent_requests == 64
assert settings.scheduler.embedding_batch_size == 12
def test_cli_override_cache(self):
"""Test CLI override for cache settings."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(
cache_enabled=False,
ssd_cache_dir="/cli/cache",
ssd_cache_max_size="500GB",
)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.cache.enabled is False
assert settings.cache.ssd_cache_dir == "/cli/cache"
assert settings.cache.ssd_cache_max_size == "500GB"
def test_cli_override_initial_cache_blocks(self):
"""Test CLI override for initial_cache_blocks."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(initial_cache_blocks=4096)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.cache.initial_cache_blocks == 4096
def test_cli_override_mcp(self):
"""Test CLI override for MCP settings."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(mcp_config="/cli/mcp.json")
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.mcp.config_path == "/cli/mcp.json"
def test_cli_override_network(self):
"""Test CLI override for network proxy settings."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(
http_proxy="http://proxy.company.com:8080",
https_proxy="http://proxy.company.com:8443",
no_proxy="localhost,127.0.0.1",
ca_bundle="/tmp/corp-ca.pem",
)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.network.http_proxy == "http://proxy.company.com:8080"
assert settings.network.https_proxy == "http://proxy.company.com:8443"
assert settings.network.no_proxy == "localhost,127.0.0.1"
assert settings.network.ca_bundle == "/tmp/corp-ca.pem"
def test_validate_invalid_http_proxy(self):
"""Test invalid http_proxy fails validation."""
settings = GlobalSettings()
settings.network.http_proxy = "proxy.company.com:8080"
errors = settings.validate()
assert any("http_proxy" in e.lower() for e in errors)
def test_validate_invalid_https_proxy(self):
"""Test invalid https_proxy fails validation."""
settings = GlobalSettings()
settings.network.https_proxy = "proxy.company.com:8443"
errors = settings.validate()
assert any("https_proxy" in e.lower() for e in errors)
def test_validate_valid_network_proxy(self):
"""Test valid network proxy values pass validation."""
settings = GlobalSettings()
settings.network.http_proxy = "http://proxy.company.com:8080"
settings.network.https_proxy = "http://proxy.company.com:8443"
errors = settings.validate()
assert not any("http_proxy" in e.lower() for e in errors)
assert not any("https_proxy" in e.lower() for e in errors)
def test_priority_cli_over_env_over_file(self):
"""Test that CLI > env > file > defaults priority is respected."""
with tempfile.TemporaryDirectory() as tmpdir:
# Create settings file with port 9000
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps({"version": "1.0", "server": {"port": 9000}})
)
# Set env to port 8888
with patch.dict(os.environ, {"OMLX_PORT": "8888"}, clear=False):
# CLI sets port to 7777
args = Namespace(port=7777)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
# CLI should win
assert settings.server.port == 7777
# Without CLI, env should win over file
settings = GlobalSettings.load(base_path=tmpdir)
# Env is no longer set, so file should be used
assert settings.server.port == 9000
def test_to_dict(self):
"""Test conversion to dictionary."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings(base_path=Path(tmpdir))
settings.server.port = 9000
result = settings.to_dict()
assert result["version"] == "1.0"
assert result["base_path"] == tmpdir
assert result["server"]["port"] == 9000
assert "model" in result
assert "scheduler" in result
assert "cache" in result
assert "auth" in result
assert "mcp" in result
def test_to_scheduler_config(self):
"""Test conversion to SchedulerConfig."""
settings = GlobalSettings()
settings.scheduler.max_concurrent_requests = 128
settings.scheduler.embedding_batch_size = 12
scheduler_config = settings.to_scheduler_config()
assert scheduler_config.max_num_seqs == 128
assert scheduler_config.completion_batch_size == 128
assert scheduler_config.embedding_batch_size == 12
assert scheduler_config.initial_cache_blocks == 256 # default
def test_to_scheduler_config_initial_cache_blocks(self):
"""Test that initial_cache_blocks passes through to SchedulerConfig."""
settings = GlobalSettings()
settings.cache.initial_cache_blocks = 8192
scheduler_config = settings.to_scheduler_config()
assert scheduler_config.initial_cache_blocks == 8192
class TestInitSettings:
"""Tests for init_settings and get_settings."""
def setup_method(self):
"""Reset settings before each test."""
reset_settings()
def teardown_method(self):
"""Reset settings after each test."""
reset_settings()
def test_init_settings(self):
"""Test initializing global settings."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = init_settings(base_path=tmpdir)
assert settings is not None
assert get_settings() is settings
def test_init_settings_with_cli_args(self):
"""Test initializing with CLI arguments."""
with tempfile.TemporaryDirectory() as tmpdir:
args = Namespace(port=9999)
settings = init_settings(base_path=tmpdir, cli_args=args)
assert settings.server.port == 9999
assert get_settings().server.port == 9999
def test_get_settings_uninitialized_raises(self):
"""Test get_settings raises RuntimeError when not initialized."""
with pytest.raises(RuntimeError, match="Settings not initialized"):
get_settings()
def test_reset_settings(self):
"""Test resetting settings allows re-initialization."""
with tempfile.TemporaryDirectory() as tmpdir:
# Initialize first
init_settings(base_path=tmpdir)
assert get_settings() is not None
# Reset
reset_settings()
# Should raise now
with pytest.raises(RuntimeError):
get_settings()
# Can initialize again
settings = init_settings(base_path=tmpdir)
assert settings is not None
def test_multiple_init_overwrites(self):
"""Test calling init_settings multiple times overwrites."""
with (
tempfile.TemporaryDirectory() as tmpdir1,
tempfile.TemporaryDirectory() as tmpdir2,
):
settings1 = init_settings(base_path=tmpdir1)
settings2 = init_settings(base_path=tmpdir2)
assert get_settings() is settings2
# Use resolve() to handle macOS /var -> /private/var symlink
assert get_settings().base_path.resolve() == Path(tmpdir2).resolve()
class TestHelperFunctions:
"""Tests for helper functions."""
def test_get_system_memory(self):
"""Test system memory detection."""
memory = get_system_memory()
assert memory > 0
# Should be at least 1GB on any modern system
assert memory >= 1024**3
def test_get_system_memory_returns_int(self):
"""Test that get_system_memory returns an integer."""
memory = get_system_memory()
assert isinstance(memory, int)
def test_get_system_memory_uses_sysconf_before_compat(self):
"""macOS should not depend on psutil's HOST_VM_INFO64 adapter."""
def fake_sysconf(name):
if name == "SC_PHYS_PAGES":
return 123
if name == "SC_PAGE_SIZE":
return 4096
raise ValueError(name)
with (
patch("omlx.settings.os.sysconf", side_effect=fake_sysconf),
patch(
"omlx.utils.psutil_compat.get_total_memory",
side_effect=AssertionError("compat should not be called"),
),
):
assert get_system_memory() == 123 * 4096
def test_get_system_memory_falls_back_to_compat_when_sysconf_fails(self):
with (
patch("omlx.settings.os.sysconf", side_effect=ValueError("unsupported")),
patch(
"omlx.utils.psutil_compat.get_total_memory",
return_value=32 * 1024**3,
),
):
assert get_system_memory() == 32 * 1024**3
def test_get_ssd_capacity(self):
"""Test SSD capacity detection."""
capacity = get_ssd_capacity(Path("/"))
assert capacity > 0
def test_get_ssd_capacity_returns_int(self):
"""Test that get_ssd_capacity returns an integer."""
capacity = get_ssd_capacity(Path("/"))
assert isinstance(capacity, int)
def test_get_ssd_capacity_nonexistent_path(self):
"""Test SSD capacity for non-existent path uses parent."""
with tempfile.TemporaryDirectory() as tmpdir:
nonexistent = Path(tmpdir) / "does" / "not" / "exist"
capacity = get_ssd_capacity(nonexistent)
assert capacity > 0
def test_get_ssd_capacity_with_string_path(self):
"""Test SSD capacity with string path."""
capacity = get_ssd_capacity("/")
assert capacity > 0
def test_get_ssd_capacity_with_tilde(self):
"""Test SSD capacity with tilde path expansion."""
capacity = get_ssd_capacity("~/")
assert capacity > 0
class TestResolveDefaultBasePath:
"""Tests for resolve_default_base_path()."""
def test_falls_back_to_default_when_nothing_configured(self, monkeypatch):
monkeypatch.delenv("OMLX_BASE_PATH", raising=False)
monkeypatch.setattr(
"omlx.settings.BASE_PATH_BOOTSTRAP_FILE",
Path("/nonexistent/oMLX/base-path"),
)
assert resolve_default_base_path() == Path.home() / ".omlx"
def test_uses_bootstrap_file_when_present(self, monkeypatch, tmp_path):
monkeypatch.delenv("OMLX_BASE_PATH", raising=False)
custom_base = tmp_path / "external-ssd" / "omlx-data"
bootstrap_file = tmp_path / "base-path"
bootstrap_file.write_text(f"{custom_base}\n", encoding="utf-8")
monkeypatch.setattr("omlx.settings.BASE_PATH_BOOTSTRAP_FILE", bootstrap_file)
assert resolve_default_base_path() == custom_base.resolve()
def test_env_var_wins_over_bootstrap_file(self, monkeypatch, tmp_path):
env_base = tmp_path / "env-base"
bootstrap_base = tmp_path / "bootstrap-base"
bootstrap_file = tmp_path / "base-path"
bootstrap_file.write_text(str(bootstrap_base), encoding="utf-8")
monkeypatch.setattr("omlx.settings.BASE_PATH_BOOTSTRAP_FILE", bootstrap_file)
monkeypatch.setenv("OMLX_BASE_PATH", str(env_base))
assert resolve_default_base_path() == env_base.resolve()
def test_empty_bootstrap_file_falls_back_to_default(self, monkeypatch, tmp_path):
monkeypatch.delenv("OMLX_BASE_PATH", raising=False)
bootstrap_file = tmp_path / "base-path"
bootstrap_file.write_text(" \n", encoding="utf-8")
monkeypatch.setattr("omlx.settings.BASE_PATH_BOOTSTRAP_FILE", bootstrap_file)
assert resolve_default_base_path() == Path.home() / ".omlx"
def test_global_settings_load_uses_resolver_when_no_base_path_given(
self, monkeypatch, tmp_path
):
resolved = tmp_path / "resolved-base"
monkeypatch.setattr(
"omlx.settings.resolve_default_base_path", lambda: resolved
)
settings = GlobalSettings.load()
assert settings.base_path == resolved
class TestSettingsVersionMigration:
"""Tests for settings version handling."""
def test_load_different_version_logs_migration(self):
"""Test loading settings with different version logs migration."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps({"version": "0.9", "server": {"port": 9000}})
)
settings = GlobalSettings.load(base_path=tmpdir)
# Should still load the settings
assert settings.server.port == 9000
def test_load_without_version(self):
"""Test loading settings file without version field."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(json.dumps({"server": {"port": 9000}}))
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.port == 9000
class TestSettingsEdgeCases:
"""Tests for edge cases and error handling."""
def test_empty_settings_file(self):
"""Test loading empty settings file."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text("{}")
settings = GlobalSettings.load(base_path=tmpdir)
# Should use all defaults
assert settings.server.port == 8000
assert settings.server.host == "127.0.0.1"
def test_partial_section_in_file(self):
"""Test loading file with partial section data."""
with tempfile.TemporaryDirectory() as tmpdir:
settings_file = Path(tmpdir) / "settings.json"
settings_file.write_text(
json.dumps(
{
"version": "1.0",
"server": {"port": 9000},
# host and log_level should use defaults
}
)
)
settings = GlobalSettings.load(base_path=tmpdir)
assert settings.server.port == 9000
assert settings.server.host == "127.0.0.1" # default
assert settings.server.log_level == "info" # default
def test_cli_args_without_expected_attrs(self):
"""Test CLI args object without all expected attributes."""
with tempfile.TemporaryDirectory() as tmpdir:
# Minimal args object
args = Namespace(port=9000)
settings = GlobalSettings.load(base_path=tmpdir, cli_args=args)
assert settings.server.port == 9000
# Other settings should be defaults
assert settings.server.host == "127.0.0.1"
def test_save_with_unicode_api_key(self):
"""Test saving settings with unicode in values."""
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings(base_path=Path(tmpdir))
settings.auth.api_key = "key-with-unicode-\u4e2d\u6587"
settings.save()
# Reload and verify
loaded = GlobalSettings.load(base_path=tmpdir)
assert loaded.auth.api_key == "key-with-unicode-\u4e2d\u6587"
class TestSamplingSettings:
"""Tests for SamplingSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = SamplingSettings()
# Fallback default kept at 32768 so existing settings.json
# files carrying the historical default keep working unchanged
# after upgrade. ``max_context_window_policy`` is the explicit
# operator policy cap (None by default).
assert settings.max_context_window == 32768
assert settings.max_context_window_policy is None
assert settings.max_tokens == 32768
assert settings.temperature == 1.0
assert settings.top_p == 0.95
assert settings.top_k == 0
assert settings.repetition_penalty == 1.0
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = SamplingSettings(max_context_window=4096)
d = settings.to_dict()
assert d["max_context_window"] == 4096
assert "max_tokens" in d
assert "repetition_penalty" in d
def test_from_dict(self):
"""Test creation from dictionary."""
data = {
"max_context_window": 8192,
"max_tokens": 1024,
"repetition_penalty": 1.2,
}
settings = SamplingSettings.from_dict(data)
assert settings.max_context_window == 8192
assert settings.max_tokens == 1024
assert settings.repetition_penalty == 1.2
def test_from_dict_defaults(self):
"""Test from_dict uses defaults for missing fields."""
settings = SamplingSettings.from_dict({})
assert settings.max_context_window == 32768
assert settings.max_context_window_policy is None
assert settings.repetition_penalty == 1.0
def test_policy_field_round_trip(self):
"""``max_context_window_policy`` must serialize and
deserialize without losing its ``None`` semantics."""
unset = SamplingSettings.from_dict({})
assert unset.max_context_window_policy is None
# to_dict preserves None
d = unset.to_dict()
assert d["max_context_window_policy"] is None
# Setting an explicit value round-trips
with_policy = SamplingSettings.from_dict({"max_context_window_policy": 128_000})
assert with_policy.max_context_window_policy == 128_000
assert with_policy.to_dict()["max_context_window_policy"] == 128_000
class TestClaudeCodeSettings:
"""Tests for ClaudeCodeSettings dataclass."""
def test_defaults(self):
"""Test default values."""
settings = ClaudeCodeSettings()
assert settings.context_scaling_enabled is False
assert settings.target_context_size == 200000
def test_custom_values(self):
"""Test custom values."""
settings = ClaudeCodeSettings(
context_scaling_enabled=True, target_context_size=131072
)
assert settings.context_scaling_enabled is True
assert settings.target_context_size == 131072
def test_to_dict(self):
"""Test conversion to dictionary."""
settings = ClaudeCodeSettings(
context_scaling_enabled=True, target_context_size=100000
)
result = settings.to_dict()
assert result["context_scaling_enabled"] is True
assert result["target_context_size"] == 100000
assert result["mode"] == "cloud"
assert result["opus_model"] is None
assert result["sonnet_model"] is None
assert result["haiku_model"] is None
def test_from_dict(self):
"""Test creation from dictionary."""
data = {"context_scaling_enabled": True, "target_context_size": 131072}
settings = ClaudeCodeSettings.from_dict(data)
assert settings.context_scaling_enabled is True
assert settings.target_context_size == 131072
def test_from_dict_defaults(self):
"""Test from_dict uses defaults for missing fields."""
settings = ClaudeCodeSettings.from_dict({})
assert settings.context_scaling_enabled is False
assert settings.target_context_size == 200000
def test_new_fields_defaults(self):
"""Test that the four new fields have correct defaults."""
settings = ClaudeCodeSettings()
assert settings.mode == "cloud"
assert settings.opus_model is None
assert settings.sonnet_model is None
assert settings.haiku_model is None
def test_new_fields_to_dict(self):
"""Test that to_dict includes all four new fields."""
settings = ClaudeCodeSettings(
mode="local",
opus_model="mlx-community/Qwen3-30B-A3B-4bit",
sonnet_model="mlx-community/Qwen3-14B-4bit",
haiku_model="mlx-community/Qwen3-4B-4bit",
)
result = settings.to_dict()
assert result["mode"] == "local"
assert result["opus_model"] == "mlx-community/Qwen3-30B-A3B-4bit"
assert result["sonnet_model"] == "mlx-community/Qwen3-14B-4bit"
assert result["haiku_model"] == "mlx-community/Qwen3-4B-4bit"
def test_new_fields_from_dict_full(self):
"""Test from_dict with all four new fields present."""
data = {
"mode": "local",
"opus_model": "mlx-community/Qwen3-30B-A3B-4bit",
"sonnet_model": "mlx-community/Qwen3-14B-4bit",
"haiku_model": "mlx-community/Qwen3-4B-4bit",
}
settings = ClaudeCodeSettings.from_dict(data)
assert settings.mode == "local"
assert settings.opus_model == "mlx-community/Qwen3-30B-A3B-4bit"
assert settings.sonnet_model == "mlx-community/Qwen3-14B-4bit"
assert settings.haiku_model == "mlx-community/Qwen3-4B-4bit"
def test_new_fields_from_dict_backward_compat(self):
"""Test from_dict({}) gives correct defaults — simulates old settings.json."""
settings = ClaudeCodeSettings.from_dict({})
assert settings.mode == "cloud"
assert settings.opus_model is None
assert settings.sonnet_model is None
assert settings.haiku_model is None
def test_new_fields_from_dict_null_model(self):
"""Test from_dict with explicit null model values."""
data = {"mode": "cloud", "opus_model": None}
settings = ClaudeCodeSettings.from_dict(data)
assert settings.mode == "cloud"
assert settings.opus_model is None
class TestIntegrationSettings:
"""Tests for IntegrationSettings dataclass.
Upstream ``tests/test_integrations.py::TestIntegrationSettings`` already
covers defaults, basic to_dict, and full/empty from_dict. The local
tests below add: exact dict-shape pinning (so a future field
addition that forgets to_dict raises a loud test failure — see
81dc2d5 for the MemorySettings case), partial-dict fallback,
explicit-null override semantics, and round-trip identity. Plus
upstream's MarkItDown-integration tests merged in below.
"""
def test_to_dict_defaults(self):
settings = IntegrationSettings()
d = settings.to_dict()
# Pin only the integration-model surface — MarkItDown additions
# are covered by ``test_markitdown_defaults`` separately, so we
# check the model fields exactly and leave the rest free to
# grow.
assert d["codex_model"] is None
assert d["opencode_model"] is None
assert d["openclaw_model"] is None
assert d["hermes_model"] is None
assert d["pi_model"] is None
assert d["copilot_model"] is None
assert d["openclaw_tools_profile"] == "coding"
def test_to_dict_custom(self):
settings = IntegrationSettings(
codex_model="qwen-coder-30b",
opencode_model="qwen-coder-7b",
openclaw_model="qwen-coder-3b",
hermes_model="hermes-3-8b",
pi_model="qwen-3-4b",
copilot_model="qwen-coder-1.5b",
openclaw_tools_profile="creative",
)
d = settings.to_dict()
assert d["codex_model"] == "qwen-coder-30b"
assert d["opencode_model"] == "qwen-coder-7b"
assert d["openclaw_model"] == "qwen-coder-3b"
assert d["hermes_model"] == "hermes-3-8b"
assert d["pi_model"] == "qwen-3-4b"
assert d["copilot_model"] == "qwen-coder-1.5b"
assert d["openclaw_tools_profile"] == "creative"
def test_from_dict_partial(self):
"""Missing keys fall back to dataclass defaults."""
settings = IntegrationSettings.from_dict({"pi_model": "qwen-3-4b"})
assert settings.pi_model == "qwen-3-4b"
assert settings.codex_model is None
assert settings.copilot_model is None
assert settings.openclaw_tools_profile == "coding"
def test_from_dict_explicit_null_overrides_default(self):
"""Explicit None for a *_model field must be preserved."""
settings = IntegrationSettings.from_dict({"codex_model": None, "pi_model": "x"})
assert settings.codex_model is None
assert settings.pi_model == "x"
def test_round_trip(self):
"""to_dict → from_dict → to_dict is identity."""
original = IntegrationSettings(
codex_model="m1",
pi_model="m2",
openclaw_tools_profile="custom",
)
round_tripped = IntegrationSettings.from_dict(original.to_dict())
assert round_tripped.to_dict() == original.to_dict()
# --- MarkItDown integration tests merged in from upstream ---
def test_markitdown_defaults(self):
settings = IntegrationSettings()
assert settings.markitdown_enabled is True
assert settings.markitdown_expose_model is False
assert settings.markitdown_max_file_size_mb == 25
assert settings.markitdown_max_files_per_request == 5
assert settings.markitdown_pdf_processing_engine == "markitdown"
def test_markitdown_to_dict(self):
settings = IntegrationSettings(
markitdown_enabled=False,
markitdown_expose_model=False,
markitdown_max_file_size_mb=10,
markitdown_max_files_per_request=2,
markitdown_pdf_processing_engine="OCR-Model",
)
result = settings.to_dict()
assert result["markitdown_enabled"] is False
assert result["markitdown_expose_model"] is False
assert result["markitdown_max_file_size_mb"] == 10
assert result["markitdown_max_files_per_request"] == 2
assert result["markitdown_pdf_processing_engine"] == "OCR-Model"
def test_markitdown_from_dict_backward_compat(self):
settings = IntegrationSettings.from_dict({})
assert settings.markitdown_enabled is True
assert settings.markitdown_expose_model is False
assert settings.markitdown_max_file_size_mb == 25
assert settings.markitdown_max_files_per_request == 5
assert settings.markitdown_pdf_processing_engine == "markitdown"
def test_markitdown_validation(self):
settings = GlobalSettings()
settings.integrations.markitdown_max_file_size_mb = 0
settings.integrations.markitdown_max_files_per_request = 0
settings.integrations.markitdown_pdf_processing_engine = ""
errors = settings.validate()
assert "markitdown_max_file_size_mb must be > 0" in errors
assert "markitdown_max_files_per_request must be > 0" in errors
assert "markitdown_pdf_processing_engine must not be empty" in errors
class TestClaudeCodeValidation:
"""Tests for mode validation in GlobalSettings.validate()."""
def _make_global_settings(self, mode: str) -> GlobalSettings:
"""Create a GlobalSettings with a specific claude_code.mode for testing."""
gs = GlobalSettings.__new__(GlobalSettings)
# Copy defaults from a real instance then override claude_code
real = GlobalSettings()
gs.__dict__.update(real.__dict__)
gs.claude_code = ClaudeCodeSettings(mode=mode)
return gs
def test_validate_mode_cloud_valid(self):
"""Mode 'cloud' passes validation."""
gs = self._make_global_settings("cloud")
errors = gs.validate()
mode_errors = [e for e in errors if "claude_code mode" in e]
assert mode_errors == []
def test_validate_mode_local_valid(self):
"""Mode 'local' passes validation."""
gs = self._make_global_settings("local")
errors = gs.validate()
mode_errors = [e for e in errors if "claude_code mode" in e]
assert mode_errors == []
def test_validate_mode_invalid(self):
"""Invalid mode produces a validation error."""
gs = self._make_global_settings("auto")
errors = gs.validate()
mode_errors = [e for e in errors if "claude_code mode" in e]
assert len(mode_errors) == 1
assert "auto" in mode_errors[0]
def test_validate_mode_empty_string_invalid(self):
"""Empty string mode is invalid."""
gs = self._make_global_settings("")
errors = gs.validate()
mode_errors = [e for e in errors if "claude_code mode" in e]
assert len(mode_errors) == 1
class TestClaudeCodeRouteIntegration:
"""Integration tests for the settings chain: dataclass <-> dict <-> routes."""
def test_claude_code_to_dict_has_six_keys(self):
"""to_dict must include all six keys so GlobalSettings.save() persists them."""
s = ClaudeCodeSettings(
context_scaling_enabled=True,
target_context_size=100000,
mode="local",
opus_model="mlx-community/Qwen3-30B-A3B-4bit",
sonnet_model="mlx-community/Qwen3-14B-4bit",
haiku_model="mlx-community/Qwen3-4B-4bit",
)
d = s.to_dict()
expected_keys = {
"context_scaling_enabled",
"target_context_size",
"mode",
"opus_model",
"sonnet_model",
"haiku_model",
}
assert set(d.keys()) == expected_keys
def test_claude_code_new_fields_round_trip(self):
"""Full round-trip: set values -> to_dict -> from_dict -> values match."""
original = ClaudeCodeSettings(
mode="local",
opus_model="mlx-community/Qwen3-30B-A3B-4bit",
sonnet_model="mlx-community/Qwen3-14B-4bit",
haiku_model="mlx-community/Qwen3-4B-4bit",
)
reloaded = ClaudeCodeSettings.from_dict(original.to_dict())
assert reloaded.mode == "local"
assert reloaded.opus_model == "mlx-community/Qwen3-30B-A3B-4bit"
assert reloaded.sonnet_model == "mlx-community/Qwen3-14B-4bit"
assert reloaded.haiku_model == "mlx-community/Qwen3-4B-4bit"
def test_claude_code_round_trip_null_models(self):
"""Null model fields survive the round-trip."""
original = ClaudeCodeSettings(mode="cloud", opus_model=None)
reloaded = ClaudeCodeSettings.from_dict(original.to_dict())
assert reloaded.mode == "cloud"
assert reloaded.opus_model is None
def test_post_handler_model_fields_set_explicit_null(self):
"""
GlobalSettingsRequest.model_validate with explicit null must include
the field in model_fields_set so the POST handler can clear it.
"""
from omlx.admin.routes import GlobalSettingsRequest
r = GlobalSettingsRequest.model_validate({"claude_code_opus_model": None})
assert "claude_code_opus_model" in r.model_fields_set
assert r.claude_code_opus_model is None
def test_post_handler_model_fields_set_absent_field(self):
"""
GlobalSettingsRequest() with no claude_code_opus_model must NOT include it
in model_fields_set — POST handler must not apply it (leave server value alone).
"""
from omlx.admin.routes import GlobalSettingsRequest
r = GlobalSettingsRequest()
assert "claude_code_opus_model" not in r.model_fields_set
def test_post_handler_model_fields_set_explicit_value(self):
"""
GlobalSettingsRequest with an explicit model ID must include the field
in model_fields_set and carry the value.
"""
from omlx.admin.routes import GlobalSettingsRequest
r = GlobalSettingsRequest(
claude_code_opus_model="mlx-community/Qwen3-30B-A3B-4bit"
)
assert "claude_code_opus_model" in r.model_fields_set
assert r.claude_code_opus_model == "mlx-community/Qwen3-30B-A3B-4bit"
class TestCORSMiddleware:
"""Test that CORS middleware is correctly applied to the server."""
def test_cors_preflight(self):
"""Test that CORS preflight requests get proper response headers."""
from fastapi.testclient import TestClient
from omlx.server import app, init_server
# Reset middleware stack so add_middleware works even if app was
# already started by another test in the same process.
app.middleware_stack = None
with tempfile.TemporaryDirectory() as tmpdir:
settings = GlobalSettings(base_path=Path(tmpdir))
init_server(
model_dirs=[tmpdir],
global_settings=settings,
)
client = TestClient(app)
resp = client.options(
"/v1/models",
headers={
"Origin": "https://example.com",
"Access-Control-Request-Method": "GET",
},
)
assert resp.status_code == 200
assert "access-control-allow-origin" in resp.headers
assert resp.headers["access-control-allow-origin"] == "*"