Files

1 line
19 KiB
JSON

{"repo": "arthalter/artcode", "files": {"tests/unit/test_config.py": {"fe684e9dac853f5cfc44d8024083466a0933a363": {"t": 1785746644.823328, "n": 4730, "h": "5d61d12f53e48182", "c": "from __future__ import annotations\n\nimport pytest\n\nfrom pathlib import Path\n\nfrom artcode.config import ArtCodeConfig, DEFAULT_ALLOWED_DIR, load_config, parse_config\nfrom artcode.errors import ConfigError\n\n\ndef valid_raw(allowed_dir: Path | None = None) -> dict:\n raw = {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n if allowed_dir is not None:\n raw[\"tools\"] = {\"allowed_dirs\": [str(allowed_dir)]}\n return raw\n\n\ndef raw_without_tools() -> dict:\n return {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n\n\ndef test_parse_valid_config() -> None:\n config = parse_config(valid_raw())\n\n assert isinstance(config, ArtCodeConfig)\n assert config.protocol == \"openai\"\n assert config.thinking.enabled is True\n assert config.thinking.effort == \"high\"\n assert config.tools.allowed_dirs\n\n\ndef test_load_missing_file(tmp_path) -> None:\n with pytest.raises(ConfigError, match=\"找不到配置文件\"):\n load_config(tmp_path / \"artcode.yaml\")\n\n\ndef test_load_invalid_yaml(tmp_path) -> None:\n path = tmp_path / \"artcode.yaml\"\n path.write_text(\"protocol: [\\n\", encoding=\"utf-8\")\n\n with pytest.raises(ConfigError, match=\"不是合法 YAML\"):\n load_config(path)\n\n\ndef test_top_level_must_be_map() -> None:\n with pytest.raises(ConfigError, match=\"顶层必须是对象\"):\n parse_config([\"not\", \"a\", \"map\"])\n\n\n@pytest.mark.parametrize(\"field\", [\"protocol\", \"model\", \"base_url\", \"api_key\"])\ndef test_missing_required_fields(field: str) -> None:\n raw = valid_raw()\n raw.pop(field)\n\n with pytest.raises(ConfigError, match=f\"配置缺少字段:{field}\"):\n parse_config(raw)\n\n\n@pytest.mark.parametrize(\"field\", [\"model\", \"base_url\", \"api_key\"])\ndef test_empty_required_string_fields(field: str) -> None:\n raw = valid_raw()\n raw[field] = \" \"\n\n with pytest.raises(ConfigError, match=f\"配置字段 {field} 不能为空\"):\n parse_config(raw)\n\n\ndef test_unsupported_protocol() -> None:\n raw = valid_raw()\n raw[\"protocol\"] = \"anthropic\"\n\n with pytest.raises(ConfigError, match=\"当前不支持\"):\n parse_config(raw)\n\n\ndef test_thinking_is_optional_and_defaults_to_off() -> None:\n raw = valid_raw()\n raw.pop(\"thinking\")\n\n config = parse_config(raw)\n\n assert config.thinking.enabled is False\n assert config.thinking.effort == \"high\"\n\n\ndef test_thinking_enabled_must_be_bool() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"enabled\"] = \"true\"\n\n with pytest.raises(ConfigError, match=\"thinking.enabled 必须是布尔值\"):\n parse_config(raw)\n\n\ndef test_thinking_effort_must_be_allowed_value() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"effort\"] = \"max\"\n\n with pytest.raises(ConfigError, match=\"thinking.effort\"):\n parse_config(raw)\n\n\ndef test_safe_status_masks_api_key() -> None:\n config = parse_config(valid_raw())\n status = config.safe_status()\n\n assert status.masked_api_key != config.api_key\n assert config.api_key not in status.masked_api_key\n\n\ndef test_chapter_name_is_ch04() -> None:\n status = parse_config(valid_raw()).safe_status()\n\n assert status.chapter == \"ch04:动手实现 Agent Loop\"\n\n\ndef test_tools_default_to_experiment_dir() -> None:\n config = parse_config(raw_without_tools())\n\n assert config.tools.allowed_dirs == (DEFAULT_ALLOWED_DIR.resolve(),)\n\n\ndef test_tools_allowed_dirs_are_absolute_and_created(tmp_path) -> None:\n allowed_dir = tmp_path / \"sandbox\"\n\n config = parse_config(valid_raw(allowed_dir))\n\n assert config.tools.allowed_dirs == (allowed_dir.resolve(),)\n assert allowed_dir.exists()\n\n\ndef test_tools_allowed_dirs_must_be_absolute() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [\"relative/path\"]}\n\n with pytest.raises(ConfigError, match=\"只支持绝对路径\"):\n parse_config(raw)\n\n\ndef test_tools_allowed_dirs_must_not_contain_empty_path() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [\" \"]}\n\n with pytest.raises(ConfigError, match=\"不能包含空路径\"):\n parse_config(raw)\n\n\ndef test_tools_allowed_dirs_items_must_be_strings() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [123]}\n\n with pytest.raises(ConfigError, match=\"每一项都必须是绝对路径字符串\"):\n parse_config(raw)\n\n\ndef test_safe_status_contains_allowed_dirs(tmp_path) -> None:\n allowed_dir = tmp_path / \"sandbox\"\n status = parse_config(valid_raw(allowed_dir)).safe_status()\n\n assert status.allowed_dirs == (str(allowed_dir.resolve()),)\n"}, "b33b43a92e59cc225169490ea143f3e58676aec2": {"t": 1785766535.4435248, "n": 4730, "h": "5d61d12f53e48182", "c": "from __future__ import annotations\n\nimport pytest\n\nfrom pathlib import Path\n\nfrom artcode.config import ArtCodeConfig, DEFAULT_ALLOWED_DIR, load_config, parse_config\nfrom artcode.errors import ConfigError\n\n\ndef valid_raw(allowed_dir: Path | None = None) -> dict:\n raw = {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n if allowed_dir is not None:\n raw[\"tools\"] = {\"allowed_dirs\": [str(allowed_dir)]}\n return raw\n\n\ndef raw_without_tools() -> dict:\n return {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n\n\ndef test_parse_valid_config() -> None:\n config = parse_config(valid_raw())\n\n assert isinstance(config, ArtCodeConfig)\n assert config.protocol == \"openai\"\n assert config.thinking.enabled is True\n assert config.thinking.effort == \"high\"\n assert config.tools.allowed_dirs\n\n\ndef test_load_missing_file(tmp_path) -> None:\n with pytest.raises(ConfigError, match=\"找不到配置文件\"):\n load_config(tmp_path / \"artcode.yaml\")\n\n\ndef test_load_invalid_yaml(tmp_path) -> None:\n path = tmp_path / \"artcode.yaml\"\n path.write_text(\"protocol: [\\n\", encoding=\"utf-8\")\n\n with pytest.raises(ConfigError, match=\"不是合法 YAML\"):\n load_config(path)\n\n\ndef test_top_level_must_be_map() -> None:\n with pytest.raises(ConfigError, match=\"顶层必须是对象\"):\n parse_config([\"not\", \"a\", \"map\"])\n\n\n@pytest.mark.parametrize(\"field\", [\"protocol\", \"model\", \"base_url\", \"api_key\"])\ndef test_missing_required_fields(field: str) -> None:\n raw = valid_raw()\n raw.pop(field)\n\n with pytest.raises(ConfigError, match=f\"配置缺少字段:{field}\"):\n parse_config(raw)\n\n\n@pytest.mark.parametrize(\"field\", [\"model\", \"base_url\", \"api_key\"])\ndef test_empty_required_string_fields(field: str) -> None:\n raw = valid_raw()\n raw[field] = \" \"\n\n with pytest.raises(ConfigError, match=f\"配置字段 {field} 不能为空\"):\n parse_config(raw)\n\n\ndef test_unsupported_protocol() -> None:\n raw = valid_raw()\n raw[\"protocol\"] = \"anthropic\"\n\n with pytest.raises(ConfigError, match=\"当前不支持\"):\n parse_config(raw)\n\n\ndef test_thinking_is_optional_and_defaults_to_off() -> None:\n raw = valid_raw()\n raw.pop(\"thinking\")\n\n config = parse_config(raw)\n\n assert config.thinking.enabled is False\n assert config.thinking.effort == \"high\"\n\n\ndef test_thinking_enabled_must_be_bool() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"enabled\"] = \"true\"\n\n with pytest.raises(ConfigError, match=\"thinking.enabled 必须是布尔值\"):\n parse_config(raw)\n\n\ndef test_thinking_effort_must_be_allowed_value() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"effort\"] = \"max\"\n\n with pytest.raises(ConfigError, match=\"thinking.effort\"):\n parse_config(raw)\n\n\ndef test_safe_status_masks_api_key() -> None:\n config = parse_config(valid_raw())\n status = config.safe_status()\n\n assert status.masked_api_key != config.api_key\n assert config.api_key not in status.masked_api_key\n\n\ndef test_chapter_name_is_ch04() -> None:\n status = parse_config(valid_raw()).safe_status()\n\n assert status.chapter == \"ch04:动手实现 Agent Loop\"\n\n\ndef test_tools_default_to_experiment_dir() -> None:\n config = parse_config(raw_without_tools())\n\n assert config.tools.allowed_dirs == (DEFAULT_ALLOWED_DIR.resolve(),)\n\n\ndef test_tools_allowed_dirs_are_absolute_and_created(tmp_path) -> None:\n allowed_dir = tmp_path / \"sandbox\"\n\n config = parse_config(valid_raw(allowed_dir))\n\n assert config.tools.allowed_dirs == (allowed_dir.resolve(),)\n assert allowed_dir.exists()\n\n\ndef test_tools_allowed_dirs_must_be_absolute() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [\"relative/path\"]}\n\n with pytest.raises(ConfigError, match=\"只支持绝对路径\"):\n parse_config(raw)\n\n\ndef test_tools_allowed_dirs_must_not_contain_empty_path() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [\" \"]}\n\n with pytest.raises(ConfigError, match=\"不能包含空路径\"):\n parse_config(raw)\n\n\ndef test_tools_allowed_dirs_items_must_be_strings() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [123]}\n\n with pytest.raises(ConfigError, match=\"每一项都必须是绝对路径字符串\"):\n parse_config(raw)\n\n\ndef test_safe_status_contains_allowed_dirs(tmp_path) -> None:\n allowed_dir = tmp_path / \"sandbox\"\n status = parse_config(valid_raw(allowed_dir)).safe_status()\n\n assert status.allowed_dirs == (str(allowed_dir.resolve()),)\n"}, "bd4cde30e76d587b0a38fcf4a7135bb9115854c4": {"t": 1785766536.6938083, "n": 4719, "h": "a09069322e50e78c", "c": "from __future__ import annotations\n\nimport pytest\n\nfrom pathlib import Path\n\nfrom artcode.config import ArtCodeConfig, DEFAULT_ALLOWED_DIR, load_config, parse_config\nfrom artcode.errors import ConfigError\n\n\ndef valid_raw(allowed_dir: Path | None = None) -> dict:\n raw = {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n if allowed_dir is not None:\n raw[\"tools\"] = {\"allowed_dirs\": [str(allowed_dir)]}\n return raw\n\n\ndef raw_without_tools() -> dict:\n return {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n\n\ndef test_parse_valid_config() -> None:\n config = parse_config(valid_raw())\n\n assert isinstance(config, ArtCodeConfig)\n assert config.protocol == \"openai\"\n assert config.thinking.enabled is True\n assert config.thinking.effort == \"high\"\n assert config.tools.allowed_dirs\n\n\ndef test_load_missing_file(tmp_path) -> None:\n with pytest.raises(ConfigError, match=\"找不到配置文件\"):\n load_config(tmp_path / \"artcode.yaml\")\n\n\ndef test_load_invalid_yaml(tmp_path) -> None:\n path = tmp_path / \"artcode.yaml\"\n path.write_text(\"protocol: [\\n\", encoding=\"utf-8\")\n\n with pytest.raises(ConfigError, match=\"不是合法 YAML\"):\n load_config(path)\n\n\ndef test_top_level_must_be_map() -> None:\n with pytest.raises(ConfigError, match=\"顶层必须是对象\"):\n parse_config([\"not\", \"a\", \"map\"])\n\n\n@pytest.mark.parametrize(\"field\", [\"protocol\", \"model\", \"base_url\", \"api_key\"])\ndef test_missing_required_fields(field: str) -> None:\n raw = valid_raw()\n raw.pop(field)\n\n with pytest.raises(ConfigError, match=f\"配置缺少字段:{field}\"):\n parse_config(raw)\n\n\n@pytest.mark.parametrize(\"field\", [\"model\", \"base_url\", \"api_key\"])\ndef test_empty_required_string_fields(field: str) -> None:\n raw = valid_raw()\n raw[field] = \" \"\n\n with pytest.raises(ConfigError, match=f\"配置字段 {field} 不能为空\"):\n parse_config(raw)\n\n\ndef test_unsupported_protocol() -> None:\n raw = valid_raw()\n raw[\"protocol\"] = \"anthropic\"\n\n with pytest.raises(ConfigError, match=\"当前不支持\"):\n parse_config(raw)\n\n\ndef test_thinking_is_optional_and_defaults_to_off() -> None:\n raw = valid_raw()\n raw.pop(\"thinking\")\n\n config = parse_config(raw)\n\n assert config.thinking.enabled is False\n assert config.thinking.effort == \"high\"\n\n\ndef test_thinking_enabled_must_be_bool() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"enabled\"] = \"true\"\n\n with pytest.raises(ConfigError, match=\"thinking.enabled 必须是布尔值\"):\n parse_config(raw)\n\n\ndef test_thinking_effort_must_be_allowed_value() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"effort\"] = \"max\"\n\n with pytest.raises(ConfigError, match=\"thinking.effort\"):\n parse_config(raw)\n\n\ndef test_safe_status_masks_api_key() -> None:\n config = parse_config(valid_raw())\n status = config.safe_status()\n\n assert status.masked_api_key != config.api_key\n assert config.api_key not in status.masked_api_key\n\n\ndef test_chapter_name_is_ch03() -> None:\n status = parse_config(valid_raw()).safe_status()\n\n assert status.chapter == \"ch03:工具系统\"\n\n\ndef test_tools_default_to_experiment_dir() -> None:\n config = parse_config(raw_without_tools())\n\n assert config.tools.allowed_dirs == (DEFAULT_ALLOWED_DIR.resolve(),)\n\n\ndef test_tools_allowed_dirs_are_absolute_and_created(tmp_path) -> None:\n allowed_dir = tmp_path / \"sandbox\"\n\n config = parse_config(valid_raw(allowed_dir))\n\n assert config.tools.allowed_dirs == (allowed_dir.resolve(),)\n assert allowed_dir.exists()\n\n\ndef test_tools_allowed_dirs_must_be_absolute() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [\"relative/path\"]}\n\n with pytest.raises(ConfigError, match=\"只支持绝对路径\"):\n parse_config(raw)\n\n\ndef test_tools_allowed_dirs_must_not_contain_empty_path() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [\" \"]}\n\n with pytest.raises(ConfigError, match=\"不能包含空路径\"):\n parse_config(raw)\n\n\ndef test_tools_allowed_dirs_items_must_be_strings() -> None:\n raw = valid_raw()\n raw[\"tools\"] = {\"allowed_dirs\": [123]}\n\n with pytest.raises(ConfigError, match=\"每一项都必须是绝对路径字符串\"):\n parse_config(raw)\n\n\ndef test_safe_status_contains_allowed_dirs(tmp_path) -> None:\n allowed_dir = tmp_path / \"sandbox\"\n status = parse_config(valid_raw(allowed_dir)).safe_status()\n\n assert status.allowed_dirs == (str(allowed_dir.resolve()),)\n"}, "02797516e21d2d6f9b5def54dec77ab40fec45e8": {"t": 1785766538.309079, "n": 2772, "h": "e8f45eb17bb3c663", "c": "from __future__ import annotations\n\nimport pytest\n\nfrom artcode.config import ArtCodeConfig, load_config, parse_config\nfrom artcode.errors import ConfigError\n\n\ndef valid_raw() -> dict:\n return {\n \"protocol\": \"openai\",\n \"model\": \"deepseek-v4-flash\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key\": \"sk-test-secret-value\",\n \"thinking\": {\"enabled\": True, \"effort\": \"high\"},\n }\n\n\ndef test_parse_valid_config() -> None:\n config = parse_config(valid_raw())\n\n assert isinstance(config, ArtCodeConfig)\n assert config.protocol == \"openai\"\n assert config.thinking.enabled is True\n assert config.thinking.effort == \"high\"\n\n\ndef test_load_missing_file(tmp_path) -> None:\n with pytest.raises(ConfigError, match=\"找不到配置文件\"):\n load_config(tmp_path / \"artcode.yaml\")\n\n\ndef test_load_invalid_yaml(tmp_path) -> None:\n path = tmp_path / \"artcode.yaml\"\n path.write_text(\"protocol: [\\n\", encoding=\"utf-8\")\n\n with pytest.raises(ConfigError, match=\"不是合法 YAML\"):\n load_config(path)\n\n\ndef test_top_level_must_be_map() -> None:\n with pytest.raises(ConfigError, match=\"顶层必须是对象\"):\n parse_config([\"not\", \"a\", \"map\"])\n\n\n@pytest.mark.parametrize(\"field\", [\"protocol\", \"model\", \"base_url\", \"api_key\"])\ndef test_missing_required_fields(field: str) -> None:\n raw = valid_raw()\n raw.pop(field)\n\n with pytest.raises(ConfigError, match=f\"配置缺少字段:{field}\"):\n parse_config(raw)\n\n\n@pytest.mark.parametrize(\"field\", [\"model\", \"base_url\", \"api_key\"])\ndef test_empty_required_string_fields(field: str) -> None:\n raw = valid_raw()\n raw[field] = \" \"\n\n with pytest.raises(ConfigError, match=f\"配置字段 {field} 不能为空\"):\n parse_config(raw)\n\n\ndef test_unsupported_protocol() -> None:\n raw = valid_raw()\n raw[\"protocol\"] = \"anthropic\"\n\n with pytest.raises(ConfigError, match=\"当前不支持\"):\n parse_config(raw)\n\n\ndef test_thinking_is_optional_and_defaults_to_off() -> None:\n raw = valid_raw()\n raw.pop(\"thinking\")\n\n config = parse_config(raw)\n\n assert config.thinking.enabled is False\n assert config.thinking.effort == \"high\"\n\n\ndef test_thinking_enabled_must_be_bool() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"enabled\"] = \"true\"\n\n with pytest.raises(ConfigError, match=\"thinking.enabled 必须是布尔值\"):\n parse_config(raw)\n\n\ndef test_thinking_effort_must_be_allowed_value() -> None:\n raw = valid_raw()\n raw[\"thinking\"][\"effort\"] = \"max\"\n\n with pytest.raises(ConfigError, match=\"thinking.effort\"):\n parse_config(raw)\n\n\ndef test_safe_status_masks_api_key() -> None:\n config = parse_config(valid_raw())\n status = config.safe_status()\n\n assert status.masked_api_key != config.api_key\n assert config.api_key not in status.masked_api_key\n"}}}}