Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/acefelix_jarvis.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
13 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "acefelix/jarvis", "files": {"tests/llm/test_config_loading.py": {"1f70cfbf6f3b157d66198268e97d2aa27e738cad": {"t": 1786068624.0302896, "n": 11472, "h": "ad0aa5290e09dc29", "c": "\"\"\"配置加载/迁移单元测试 — T-03 改进项。\n\n测试覆盖:\n- _apply_toml: TOML dict → Settings 字段映射(顶层、子表、权限模式)\n- apply_env_overrides: 环境变量覆盖(JARVIS_* / MY_AGENT_*)\n- save_last_model / save_custom_model: 模型 TOML 持久化\n- _read_toml: UTF-8 BOM 兼容\n\n@author aceFelix\n\"\"\"\n\nfrom __future__ import annotations\n\nimport os\nimport tempfile\nfrom pathlib import Path\nfrom unittest.mock import patch\n\nimport pytest\n\nfrom agent.config.settings import Settings, _apply_toml, _read_toml, load_settings\nfrom agent.config.env import apply_env_overrides\nfrom agent.config.model_registry import save_last_model, save_custom_model\nfrom agent.permissions.modes import PermissionMode\n\n\n# ── _read_toml ──\n\nclass TestReadToml:\n \"\"\"TOML 文件读取测试。\"\"\"\n\n def test_read_nonexistent_file(self) -> None:\n assert _read_toml(Path(\"/nonexistent/settings.toml\")) == {}\n\n def test_read_bom_encoded_file(self) -> None:\n \"\"\"UTF-8 BOM 文件应正确解析。\"\"\"\n with tempfile.NamedTemporaryFile(\n mode=\"wb\", suffix=\".toml\", delete=False\n ) as f:\n f.write(b\"\\xef\\xbb\\xbfmodel = 'gpt-4o'\\nprovider = 'openai'\\n\")\n path = f.name\n try:\n data = _read_toml(Path(path))\n assert data == {\"model\": \"gpt-4o\", \"provider\": \"openai\"}\n finally:\n os.unlink(path)\n\n def test_read_valid_toml(self) -> None:\n with tempfile.NamedTemporaryFile(\n mode=\"w\", suffix=\".toml\", delete=False, encoding=\"utf-8\"\n ) as f:\n f.write(\"provider = 'dashscope'\\nmax_tokens = 4096\\n\")\n path = f.name\n try:\n data = _read_toml(Path(path))\n assert data[\"provider\"] == \"dashscope\"\n assert data[\"max_tokens\"] == 4096\n finally:\n os.unlink(path)\n\n def test_read_empty_file(self) -> None:\n with tempfile.NamedTemporaryFile(\n mode=\"w\", suffix=\".toml\", delete=False, encoding=\"utf-8\"\n ) as f:\n f.write(\"\")\n path = f.name\n try:\n assert _read_toml(Path(path)) == {}\n finally:\n os.unlink(path)\n\n\n# ── _apply_toml ──\n\nclass TestApplyToml:\n \"\"\"TOML → Settings 字段映射测试。\"\"\"\n\n def test_top_level_fields(self) -> None:\n \"\"\"顶层字段应直接映射。\"\"\"\n s = Settings()\n result = _apply_toml(s, {\n \"provider\": \"dashscope\",\n \"model\": \"qwen-plus\",\n \"max_tokens\": 8192,\n \"temperature\": 0.7,\n })\n assert result.provider == \"dashscope\"\n assert result.model == \"qwen-plus\"\n assert result.max_tokens == 8192\n assert result.temperature == 0.7\n\n def test_permission_mode_parsing(self) -> None:\n \"\"\"permission_mode 字符串应解析为枚举。\"\"\"\n s = Settings()\n result = _apply_toml(s, {\"permission_mode\": \"yolo\"})\n assert result.permission_mode == PermissionMode.YOLO\n\n def test_tts_subtable_mapping(self) -> None:\n \"\"\"[tts] 表字段应映射到 tts_* 顶层。\"\"\"\n s = Settings()\n result = _apply_toml(s, {\n \"tts\": {\n \"model\": \"cosyvoice-v3-flash\",\n \"voice\": \"longanlang_v3\",\n \"volume\": 80,\n }\n })\n assert result.tts_model == \"cosyvoice-v3-flash\"\n assert result.tts_voice == \"longanlang_v3\"\n assert result.tts_volume == 80\n\n def test_stt_subtable_mapping(self) -> None:\n s = Settings()\n result = _apply_toml(s, {\n \"stt\": {\n \"model\": \"paraformer-realtime-v2\",\n \"silence_seconds\": 2.0,\n }\n })\n assert result.stt_model == \"paraformer-realtime-v2\"\n assert result.stt_silence_seconds == 2.0\n\n def test_email_subtable_mapping(self) -> None:\n s = Settings()\n result = _apply_toml(s, {\n \"email\": {\n \"enabled\": True,\n \"smtp_host\": \"smtp.163.com\",\n \"smtp_port\": 465,\n }\n })\n assert result.email_enabled is True\n assert result.email_smtp_host == \"smtp.163.com\"\n assert result.email_smtp_port == 465\n\n def test_llm_models_subtable(self) -> None:\n \"\"\"[llm.models] 应映射到 models 字典。\"\"\"\n s = Settings()\n result = _apply_toml(s, {\n \"llm\": {\n \"models\": {\n \"qwen-plus\": \"通义千问 Plus\",\n \"deepseek-chat\": \"DeepSeek Chat\",\n }\n }\n })\n assert result.models == {\n \"qwen-plus\": \"通义千问 Plus\",\n \"deepseek-chat\": \"DeepSeek Chat\",\n }\n\n def test_llm_custom_models_normalization(self) -> None:\n \"\"\"[llm.custom_models] 应规范化 provider_type → api_format 并补充 provider。\"\"\"\n s = Settings()\n result = _apply_toml(s, {\n \"llm\": {\n \"custom_models\": {\n \"my-model\": {\n \"base_url\": \"https://api.example.com/v1\",\n \"api_key\": \"sk-xxx\",\n \"provider_type\": \"openai\", # 旧字段名\n }\n }\n }\n })\n cm = result.custom_models\n assert \"my-model\" in cm\n assert cm[\"my-model\"][\"api_format\"] == \"openai\" # 从 provider_type 规范化\n assert cm[\"my-model\"][\"provider\"] == \"openai\" # 自动补充\n\n def test_empty_data_returns_same_instance(self) -> None:\n \"\"\"空数据应返回原 Settings(无副作用)。\"\"\"\n s = Settings()\n result = _apply_toml(s, {})\n assert result is s\n\n def test_boolean_fields(self) -> None:\n s = Settings(debug=False, verbose=False)\n result = _apply_toml(s, {\"debug\": True, \"verbose\": True})\n assert result.debug is True\n assert result.verbose is True\n\n def test_tools_subtable(self) -> None:\n \"\"\"[tools] 表应映射到 tools_* 字段。\"\"\"\n s = Settings()\n result = _apply_toml(s, {\n \"tools\": {\n \"deferred_loading\": False,\n \"chat_detection\": False,\n }\n })\n assert result.tools_deferred_loading is False\n assert result.tools_chat_detection is False\n\n\n# ── apply_env_overrides ──\n\nclass TestEnvOverrides:\n \"\"\"环境变量覆盖测试。\"\"\"\n\n def test_provider_env_override(self) -> None:\n with patch.dict(os.environ, {\"JARVIS_PROVIDER\": \"deepseek\"}, clear=True):\n s = Settings(provider=\"dashscope\")\n result = apply_env_overrides(s)\n assert result.provider == \"deepseek\"\n\n def test_model_env_override(self) -> None:\n with patch.dict(os.environ, {\"JARVIS_MODEL\": \"gpt-4o\"}, clear=True):\n s = Settings(model=\"qwen\")\n result = apply_env_overrides(s)\n assert result.model == \"gpt-4o\"\n\n def test_permission_mode_env(self) -> None:\n with patch.dict(os.environ, {\"JARVIS_PERMISSION_MODE\": \"yolo\"}, clear=True):\n s = Settings(permission_mode=PermissionMode.DEFAULT)\n result = apply_env_overrides(s)\n assert result.permission_mode == PermissionMode.YOLO\n\n def test_debug_env(self) -> None:\n with patch.dict(os.environ, {\"JARVIS_DEBUG\": \"1\"}, clear=True):\n s = Settings(debug=False)\n result = apply_env_overrides(s)\n assert result.debug is True\n\n def test_api_key_env(self) -> None:\n with patch.dict(os.environ, {\"OPENAI_API_KEY\": \"sk-test123\"}, clear=True):\n s = Settings(api_key=\"\")\n result = apply_env_overrides(s)\n assert result.api_key == \"sk-test123\"\n\n def test_dashscope_api_key_env(self) -> None:\n with patch.dict(os.environ, {\"DASHSCOPE_API_KEY\": \"sk-dash\"}, clear=True):\n s = Settings(dashscope_api_key=\"\", api_key=\"sk-main\")\n result = apply_env_overrides(s)\n assert result.dashscope_api_key == \"sk-dash\"\n # api_key 已有值,不应被覆盖\n assert result.api_key == \"sk-main\"\n\n def test_base_url_env(self) -> None:\n with patch.dict(os.environ, {\"JARVIS_BASE_URL\": \"https://api.example.com/v1\"}, clear=True):\n s = Settings(base_url=\"\")\n result = apply_env_overrides(s)\n assert result.base_url == \"https://api.example.com/v1\"\n\n def test_no_env_vars_no_change(self) -> None:\n with patch.dict(os.environ, {}, clear=True):\n s = Settings(provider=\"dashscope\", model=\"qwen\")\n result = apply_env_overrides(s)\n assert result.provider == \"dashscope\"\n assert result.model == \"qwen\"\n\n def test_my_agent_compat(self) -> None:\n \"\"\"MY_AGENT_* 环境变量兼容性。\"\"\"\n with patch.dict(os.environ, {\"MY_AGENT_PROVIDER\": \"zhipu\"}, clear=True):\n s = Settings(provider=\"dashscope\")\n result = apply_env_overrides(s)\n assert result.provider == \"zhipu\"\n\n\n# ── Model Persistence ──\n\nclass TestModelPersistence:\n \"\"\"模型 TOML 持久化测试。\"\"\"\n\n def test_save_last_model_new_file(self) -> None:\n \"\"\"新文件写入 last_model。\"\"\"\n with tempfile.TemporaryDirectory() as tmpdir:\n home = Path(tmpdir)\n with patch.object(Path, \"home\", return_value=home):\n jarvis_dir = home / \".jarvis\"\n jarvis_dir.mkdir(parents=True, exist_ok=True)\n toml_path = jarvis_dir / \"settings.toml\"\n\n # 首次保存到不存在的文件\n result = save_last_model(\"qwen-plus\")\n assert result is True\n assert toml_path.exists()\n\n content = toml_path.read_text(encoding=\"utf-8\")\n assert 'last_model = \"qwen-plus\"' in content\n\n def test_save_last_model_update_existing(self) -> None:\n \"\"\"已有文件应更新 last_model 而非重复插入。\"\"\"\n with tempfile.TemporaryDirectory() as tmpdir:\n home = Path(tmpdir)\n with patch.object(Path, \"home\", return_value=home):\n jarvis_dir = home / \".jarvis\"\n jarvis_dir.mkdir(parents=True, exist_ok=True)\n toml_path = jarvis_dir / \"settings.toml\"\n toml_path.write_text(\n 'model = \"qwen-plus\"\\nlast_model = \"gpt-4o\"\\n\\n[tts]\\nmodel = \"cosyvoice\"\\n',\n encoding=\"utf-8\",\n )\n\n result = save_last_model(\"deepseek-chat\")\n assert result is True\n\n content = toml_path.read_text(encoding=\"utf-8\")\n assert 'last_model = \"deepseek-chat\"' in content\n # 不应有两个 last_model\n assert content.count(\"last_model\") == 1\n\n def test_save_custom_model_new(self) -> None:\n \"\"\"新增自定义模型到 [llm.custom_models]。\"\"\"\n with tempfile.TemporaryDirectory() as tmpdir:\n home = Path(tmpdir)\n with patch.object(Path, \"home\", return_value=home):\n jarvis_dir = home / \".jarvis\"\n jarvis_dir.mkdir(parents=True, exist_ok=True)\n toml_path = jarvis_dir / \"settings.toml\"\n toml_path.write_text(\n 'model = \"qwen-plus\"\\n\\n[tts]\\nmodel = \"cosyvoice\"\\n',\n encoding=\"utf-8\",\n )\n\n result = save_custom_model(\"my-gpt\", {\n \"base_url\": \"https://api.example.com/v1\",\n \"api_key\": \"sk-xxx\",\n \"provider_type\": \"openai\",\n })\n # save_custom_model 要求文件已存在\n assert result is True\n\n content = toml_path.read_text(encoding=\"utf-8\")\n assert 'my-gpt' in content\n assert 'api_key = \"sk-xxx\"' in content\n"}}}}