Files
quercus/tests/core/test_config.py
T
Mike-Solar e0db5ab2cf P1: host-agnostic quercus-core with agent loop, providers, and tool schemas
- Core types: Rational time, EntityId, ActionBatch, PngBytes (no Pillow dep)
- Config: ~/.quercus/config.toml with 0600 perms, env var override for CI
- LLMProvider: Claude (anthropic SDK) and OpenAI-compatible endpoint
  (covers custom gateways and llama.cpp server mode); graceful degradation
  without API keys
- Tool schemas: 26 curated tools with JSON Schema and read_only flags
- AgentLoop: sync dialogue loop with confirmation gate (read-only batches
  bypass, rejected mutations are reported back to the LLM), PNG frame
  feedback for multimodal providers
- Session log: JSONL recording with frame sha256 hashes, replayable
- HostAdapter ABC (signatures only, aligned with OPP/1 method families)
- Mock LLM provider (scripted/replay/record) for network-free CI

Tests: 69 passed via uv run pytest (no network access)
2026-08-25 02:06:10 +08:00

84 lines
3.1 KiB
Python

"""配置:0600 权限、环境变量覆盖优先级、密钥不落日志。"""
import stat
import pytest
from quercus_core.config import (
ENV_ANTHROPIC_API_KEY,
ENV_OPENAI_API_KEY,
ENV_OPENAI_BASE_URL,
Config,
config_home,
config_summary,
default_config_path,
default_sessions_dir,
load_config,
save_config,
)
class TestSaveLoad:
def test_save_creates_0600(self, tmp_path):
path = tmp_path / "cfg" / "config.toml"
save_config(Config(anthropic_api_key="sk-ant-secret"), path)
assert path.exists()
assert stat.S_IMODE(path.stat().st_mode) == 0o600
def test_roundtrip(self, tmp_path):
path = tmp_path / "config.toml"
save_config(
Config(
anthropic_api_key="sk-a",
openai_api_key="sk-o",
openai_base_url="https://gw.example/v1",
),
path,
)
cfg = load_config(path)
assert cfg.anthropic_api_key == "sk-a"
assert cfg.openai_api_key == "sk-o"
assert cfg.openai_base_url == "https://gw.example/v1"
assert cfg.has_anthropic_key and cfg.has_openai_key
def test_env_overrides_file(self, tmp_path, monkeypatch):
path = tmp_path / "config.toml"
save_config(Config(anthropic_api_key="file-key", openai_api_key="file-o"), path)
monkeypatch.setenv(ENV_ANTHROPIC_API_KEY, "env-key")
cfg = load_config(path)
assert cfg.anthropic_api_key == "env-key" # 环境变量 > 配置文件
assert cfg.openai_api_key == "file-o" # 未覆盖的保持文件值
def test_env_without_file(self, tmp_path, monkeypatch):
monkeypatch.setenv(ENV_OPENAI_API_KEY, "env-o")
monkeypatch.setenv(ENV_OPENAI_BASE_URL, "http://localhost:8080/v1")
monkeypatch.delenv(ENV_ANTHROPIC_API_KEY, raising=False)
cfg = load_config(tmp_path / "missing.toml")
assert cfg.openai_api_key == "env-o"
assert cfg.openai_base_url == "http://localhost:8080/v1"
assert cfg.anthropic_api_key is None
def test_no_config_no_env(self, tmp_path, monkeypatch):
monkeypatch.delenv(ENV_ANTHROPIC_API_KEY, raising=False)
monkeypatch.delenv(ENV_OPENAI_API_KEY, raising=False)
monkeypatch.delenv(ENV_OPENAI_BASE_URL, raising=False)
cfg = load_config(tmp_path / "missing.toml")
assert cfg.anthropic_api_key is None
assert cfg.openai_api_key is None
assert cfg.openai_base_url is None
assert not cfg.has_anthropic_key and not cfg.has_openai_key
class TestHome:
def test_home_override(self, tmp_home):
assert config_home() == tmp_home / ".quercus"
assert default_config_path() == tmp_home / ".quercus" / "config.toml"
assert default_sessions_dir() == tmp_home / ".quercus" / "sessions"
def test_config_summary_hides_secrets():
cfg = Config(anthropic_api_key="top-secret-value", openai_api_key="second-secret")
summary = config_summary(cfg)
assert summary["anthropic_configured"] is True
assert summary["openai_configured"] is True
assert "secret" not in str(summary)