1 line
70 KiB
JSON
1 line
70 KiB
JSON
{"repo": "beckyeeky/pixiv-xp-pusher", "files": {"tests/test_config.py": {"a21763934c59e4d58625f80c5188e2083f565fdc": {"t": 1785746877.0965247, "n": 7931, "h": "234064473637452d", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_config_removes_legacy_push_cron(self):\n cfg = config.normalize_config({\"scheduler\": {\"cron\": \"0 12 * * *\"}})\n\n self.assertNotIn(\"cron\", cfg[\"scheduler\"])\n\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(\n {provider[\"type\"] for provider in cfg[\"providers\"].values()},\n {\"pixiv\", \"danbooru\"},\n )\n self.assertEqual(cfg[\"models\"], {})\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_normalize_search_first_grounded_judge_provider_pools(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"brave_1\": {\"type\": \"brave_search\", \"api_key\": \"brave-key\"},\n \"tavily_1\": {\"type\": \"tavily_search\", \"api_key\": \"tavily-key\"},\n \"deepseek\": {\"type\": \"deepseek\", \"api_key\": \"llm-key\"},\n },\n \"models\": {\"flash\": {\"provider\": \"deepseek\", \"model\": \"deepseek-v4-flash\"}},\n \"tag_classifier\": {\n \"judges\": [\"flash\"],\n \"maintenance\": {\"min_profile_weight\": \"-1.5\"},\n \"grounded_judge\": {\n \"backend\": \"search_first\",\n \"search_classifier_model\": \"flash\",\n \"brave_providers\": [\"brave_1\", \"missing\"],\n \"tavily_providers\": [\"tavily_1\"],\n },\n },\n })\n\n grounded = cfg[\"tag_classifier\"][\"grounded_judge\"]\n self.assertEqual(grounded[\"backend\"], \"search_first\")\n self.assertEqual(grounded[\"search_classifier_model\"], \"flash\")\n self.assertEqual(grounded[\"brave_providers\"], [\"brave_1\"])\n self.assertEqual(grounded[\"tavily_providers\"], [\"tavily_1\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [])\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"min_profile_weight\"], 1.5)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"旧 profiler.ai key 可供 scorer 迁移继承;分类器改由 Provider 持有凭据\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_normalize_providers_models_and_resolves_judge_model_references(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"gateway\": {\n \"type\": \"openai_compatible\",\n \"base_url\": \"https://a.example/v1\",\n \"api_key\": \"provider-key\",\n },\n },\n \"models\": {\n \"fast\": {\"provider\": \"gateway\", \"model\": \"model-a\"},\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"models\"][\"fast\"][\"provider\"], \"gateway\")\n self.assertEqual(cfg[\"providers\"][\"gateway\"][\"api_key\"], \"provider-key\")\n\n def test_normalize_migrates_current_single_model_classifier_to_provider_and_model(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"provider\"], \"tag_classifier_provider\")\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"existing-key\")\n\n def test_normalize_migrates_single_model_that_inherits_profiler_key(self):\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"shared-key\"}},\n \"tag_classifier\": {\"base_url\": \"https://judge.example/v1\", \"model\": \"existing-model\"},\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"shared-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "6a609d640d17478b546220a04bc086df98e0f7c5": {"t": 1785768374.6709392, "n": 7931, "h": "234064473637452d", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_config_removes_legacy_push_cron(self):\n cfg = config.normalize_config({\"scheduler\": {\"cron\": \"0 12 * * *\"}})\n\n self.assertNotIn(\"cron\", cfg[\"scheduler\"])\n\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(\n {provider[\"type\"] for provider in cfg[\"providers\"].values()},\n {\"pixiv\", \"danbooru\"},\n )\n self.assertEqual(cfg[\"models\"], {})\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_normalize_search_first_grounded_judge_provider_pools(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"brave_1\": {\"type\": \"brave_search\", \"api_key\": \"brave-key\"},\n \"tavily_1\": {\"type\": \"tavily_search\", \"api_key\": \"tavily-key\"},\n \"deepseek\": {\"type\": \"deepseek\", \"api_key\": \"llm-key\"},\n },\n \"models\": {\"flash\": {\"provider\": \"deepseek\", \"model\": \"deepseek-v4-flash\"}},\n \"tag_classifier\": {\n \"judges\": [\"flash\"],\n \"maintenance\": {\"min_profile_weight\": \"-1.5\"},\n \"grounded_judge\": {\n \"backend\": \"search_first\",\n \"search_classifier_model\": \"flash\",\n \"brave_providers\": [\"brave_1\", \"missing\"],\n \"tavily_providers\": [\"tavily_1\"],\n },\n },\n })\n\n grounded = cfg[\"tag_classifier\"][\"grounded_judge\"]\n self.assertEqual(grounded[\"backend\"], \"search_first\")\n self.assertEqual(grounded[\"search_classifier_model\"], \"flash\")\n self.assertEqual(grounded[\"brave_providers\"], [\"brave_1\"])\n self.assertEqual(grounded[\"tavily_providers\"], [\"tavily_1\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [])\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"min_profile_weight\"], 1.5)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"旧 profiler.ai key 可供 scorer 迁移继承;分类器改由 Provider 持有凭据\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_normalize_providers_models_and_resolves_judge_model_references(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"gateway\": {\n \"type\": \"openai_compatible\",\n \"base_url\": \"https://a.example/v1\",\n \"api_key\": \"provider-key\",\n },\n },\n \"models\": {\n \"fast\": {\"provider\": \"gateway\", \"model\": \"model-a\"},\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"models\"][\"fast\"][\"provider\"], \"gateway\")\n self.assertEqual(cfg[\"providers\"][\"gateway\"][\"api_key\"], \"provider-key\")\n\n def test_normalize_migrates_current_single_model_classifier_to_provider_and_model(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"provider\"], \"tag_classifier_provider\")\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"existing-key\")\n\n def test_normalize_migrates_single_model_that_inherits_profiler_key(self):\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"shared-key\"}},\n \"tag_classifier\": {\"base_url\": \"https://judge.example/v1\", \"model\": \"existing-model\"},\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"shared-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "73289708faa81a833ad175e073565554cb7ee895": {"t": 1785768375.9869308, "n": 6543, "h": "a918cad1976affc9", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_config_removes_legacy_push_cron(self):\n cfg = config.normalize_config({\"scheduler\": {\"cron\": \"0 12 * * *\"}})\n\n self.assertNotIn(\"cron\", cfg[\"scheduler\"])\n\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(\n {provider[\"type\"] for provider in cfg[\"providers\"].values()},\n {\"pixiv\", \"danbooru\"},\n )\n self.assertEqual(cfg[\"models\"], {})\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"旧 profiler.ai key 可供 scorer 迁移继承;分类器改由 Provider 持有凭据\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_normalize_providers_models_and_resolves_judge_model_references(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"gateway\": {\n \"type\": \"openai_compatible\",\n \"base_url\": \"https://a.example/v1\",\n \"api_key\": \"provider-key\",\n },\n },\n \"models\": {\n \"fast\": {\"provider\": \"gateway\", \"model\": \"model-a\"},\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"models\"][\"fast\"][\"provider\"], \"gateway\")\n self.assertEqual(cfg[\"providers\"][\"gateway\"][\"api_key\"], \"provider-key\")\n\n def test_normalize_migrates_current_single_model_classifier_to_provider_and_model(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"provider\"], \"tag_classifier_provider\")\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"existing-key\")\n\n def test_normalize_migrates_single_model_that_inherits_profiler_key(self):\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"shared-key\"}},\n \"tag_classifier\": {\"base_url\": \"https://judge.example/v1\", \"model\": \"existing-model\"},\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"shared-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "c038ba9f2de97fa9561739f9bd2c59c7deb74465": {"t": 1785768377.460313, "n": 6351, "h": "6002bcc52d4da565", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(\n {provider[\"type\"] for provider in cfg[\"providers\"].values()},\n {\"pixiv\", \"danbooru\"},\n )\n self.assertEqual(cfg[\"models\"], {})\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"旧 profiler.ai key 可供 scorer 迁移继承;分类器改由 Provider 持有凭据\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_normalize_providers_models_and_resolves_judge_model_references(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"gateway\": {\n \"type\": \"openai_compatible\",\n \"base_url\": \"https://a.example/v1\",\n \"api_key\": \"provider-key\",\n },\n },\n \"models\": {\n \"fast\": {\"provider\": \"gateway\", \"model\": \"model-a\"},\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"models\"][\"fast\"][\"provider\"], \"gateway\")\n self.assertEqual(cfg[\"providers\"][\"gateway\"][\"api_key\"], \"provider-key\")\n\n def test_normalize_migrates_current_single_model_classifier_to_provider_and_model(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"provider\"], \"tag_classifier_provider\")\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"existing-key\")\n\n def test_normalize_migrates_single_model_that_inherits_profiler_key(self):\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"shared-key\"}},\n \"tag_classifier\": {\"base_url\": \"https://judge.example/v1\", \"model\": \"existing-model\"},\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"shared-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "805ed3f1c0de4c0f928690f30c276a0ddd07fefe": {"t": 1785768378.6896238, "n": 6352, "h": "9f7868f0b0d9e5f4", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(\n {provider[\"type\"] for provider in cfg[\"providers\"].values()},\n {\"pixiv\", \"danbooru\"},\n )\n self.assertEqual(cfg[\"models\"], {})\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer 自动继承;分类器改由 Provider 持有凭据\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_normalize_providers_models_and_resolves_judge_model_references(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"gateway\": {\n \"type\": \"openai_compatible\",\n \"base_url\": \"https://a.example/v1\",\n \"api_key\": \"provider-key\",\n },\n },\n \"models\": {\n \"fast\": {\"provider\": \"gateway\", \"model\": \"model-a\"},\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"models\"][\"fast\"][\"provider\"], \"gateway\")\n self.assertEqual(cfg[\"providers\"][\"gateway\"][\"api_key\"], \"provider-key\")\n\n def test_normalize_migrates_current_single_model_classifier_to_provider_and_model(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"provider\"], \"tag_classifier_provider\")\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"existing-key\")\n\n def test_normalize_migrates_single_model_that_inherits_profiler_key(self):\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"shared-key\"}},\n \"tag_classifier\": {\"base_url\": \"https://judge.example/v1\", \"model\": \"existing-model\"},\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"shared-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "af448d8a829416ca195b283291ad615964b88bca": {"t": 1785768379.849882, "n": 5783, "h": "45f8b636b8faf5c0", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"providers\"], {})\n self.assertEqual(cfg[\"models\"], {})\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer 自动继承;分类器改由 Provider 持有凭据\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertNotIn(\"api_key\", cfg[\"tag_classifier\"])\n\n def test_normalize_providers_models_and_resolves_judge_model_references(self):\n cfg = config.normalize_config({\n \"providers\": {\n \"gateway\": {\n \"type\": \"openai_compatible\",\n \"base_url\": \"https://a.example/v1\",\n \"api_key\": \"provider-key\",\n },\n },\n \"models\": {\n \"fast\": {\"provider\": \"gateway\", \"model\": \"model-a\"},\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"models\"][\"fast\"][\"provider\"], \"gateway\")\n self.assertEqual(cfg[\"providers\"][\"gateway\"][\"api_key\"], \"provider-key\")\n\n def test_normalize_migrates_current_single_model_classifier_to_provider_and_model(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"provider\"], \"tag_classifier_provider\")\n self.assertEqual(cfg[\"models\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n self.assertEqual(cfg[\"providers\"][\"tag_classifier_provider\"][\"api_key\"], \"existing-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "c8a6d4f899e4a65b672057a2b4581aa3b2097b6f": {"t": 1785768381.6442916, "n": 6347, "h": "815347032c55ae98", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"base_url\"], \"https://api.deepseek.com/v1\")\n self.assertEqual(cfg[\"tag_classifier\"][\"model\"], \"deepseek-v4-flash\")\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer + tag_classifier 自动继承\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-shared\")\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-own\")\n\n def test_normalize_judge_profiles_and_resolves_references(self):\n cfg = config.normalize_config({\n \"profiler\": {\n \"ai\": {\"api_key\": \"shared-key\"},\n \"danbooru_login\": \"profile-login\",\n \"danbooru_api_key\": \"profile-key\",\n },\n \"judge_profiles\": {\n \"fast\": {\n \"base_url\": \"https://a.example/v1\",\n \"model\": \"model-a\",\n },\n },\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\"fast\", \"missing\"],\n \"danbooru\": {\"enabled\": True},\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n self.assertEqual(cfg[\"judge_profiles\"][\"fast\"][\"api_key\"], \"shared-key\")\n self.assertEqual(cfg[\"judge_profiles\"][\"fast\"][\"name\"], \"fast\")\n self.assertEqual(cfg[\"tag_classifier\"][\"danbooru\"][\"login\"], \"profile-login\")\n self.assertEqual(cfg[\"tag_classifier\"][\"danbooru\"][\"api_key\"], \"profile-key\")\n\n def test_normalize_drops_legacy_inline_judge_objects(self):\n cfg = config.normalize_config({\n \"judge_profiles\": {\"fast\": {\"api_key\": \"key\"}},\n \"tag_classifier\": {\"judges\": [{\"name\": \"old\", \"api_key\": \"key\"}, \"fast\"]},\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"fast\"])\n\n def test_normalize_migrates_current_single_model_classifier_to_a_profile(self):\n cfg = config.normalize_config({\n \"tag_classifier\": {\n \"api_key\": \"existing-key\",\n \"base_url\": \"https://judge.example/v1\",\n \"model\": \"existing-model\",\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"judges\"], [\"tag_classifier_default\"])\n self.assertEqual(cfg[\"judge_profiles\"][\"tag_classifier_default\"][\"api_key\"], \"existing-key\")\n self.assertEqual(cfg[\"judge_profiles\"][\"tag_classifier_default\"][\"model\"], \"existing-model\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "5418924f264d14603f3b16589bab8106d5835a41": {"t": 1785768383.0651581, "n": 5204, "h": "299cd1c9a5164d87", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"base_url\"], \"https://api.deepseek.com/v1\")\n self.assertEqual(cfg[\"tag_classifier\"][\"model\"], \"deepseek-v4-flash\")\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer + tag_classifier 自动继承\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-shared\")\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-own\")\n\n def test_normalize_multi_judges_and_inherits_danbooru_credentials(self):\n cfg = config.normalize_config({\n \"profiler\": {\"danbooru_login\": \"profile-login\", \"danbooru_api_key\": \"profile-key\"},\n \"tag_classifier\": {\n \"maintenance\": {\"max_tags_per_run\": \"3\"},\n \"judges\": [\n {\"name\": \"first\", \"api_key\": \"key-1\", \"base_url\": \"https://a.example/v1\", \"model\": \"model-a\"},\n {\"name\": \"duplicate\", \"api_key\": \"key-2\", \"base_url\": \"https://a.example/v1\", \"model\": \"model-a\"},\n ],\n \"danbooru\": {\"enabled\": True},\n },\n })\n\n self.assertEqual(cfg[\"tag_classifier\"][\"maintenance\"][\"max_tags_per_run\"], 3)\n self.assertEqual(len(cfg[\"tag_classifier\"][\"judges\"]), 2)\n self.assertEqual(cfg[\"tag_classifier\"][\"danbooru\"][\"login\"], \"profile-login\")\n self.assertEqual(cfg[\"tag_classifier\"][\"danbooru\"][\"api_key\"], \"profile-key\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "81fec2b108f71e1f92dc7e8d883ad2bc21e48d24": {"t": 1785768384.4876783, "n": 4221, "h": "05e9ecab6816df52", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"base_url\"], \"https://api.deepseek.com/v1\")\n self.assertEqual(cfg[\"tag_classifier\"][\"model\"], \"deepseek-v4-flash\")\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer + tag_classifier 自动继承\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-shared\")\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-own\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n def test_normalize_proxy_url_string_none_to_null(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"None\"}}})\n self.assertIsNone(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"])\n\n def test_normalize_proxy_url_adds_http_scheme(self):\n cfg = config.normalize_config({\"notifier\": {\"telegram\": {\"proxy_url\": \"127.0.0.1:7890\"}}})\n self.assertEqual(cfg[\"notifier\"][\"telegram\"][\"proxy_url\"], \"http://127.0.0.1:7890\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "d0acc91b36585d349427fcdf04213a19fc9cc3f9": {"t": 1785768385.8065372, "n": 3754, "h": "118ad99bcb8789bc", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"base_url\"], \"https://api.deepseek.com/v1\")\n self.assertEqual(cfg[\"tag_classifier\"][\"model\"], \"deepseek-v4-flash\")\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer + tag_classifier 自动继承\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-shared\")\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-own\")\n\n def test_normalize_ip_diversity_defaults_and_invalid_values(self):\n cfg = config.normalize_config({\"filter\": {\"ip_diversity\": {\"enabled\": 1, \"decay_factor\": \"bad\", \"floor\": 2}}})\n self.assertTrue(cfg[\"filter\"][\"ip_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"decay_factor\"], 0.6)\n self.assertEqual(cfg[\"filter\"][\"ip_diversity\"][\"floor\"], 1.0)\n\n def test_normalize_author_diversity_uses_tuned_default(self):\n cfg = config.normalize_config({\"filter\": {\"author_diversity\": \"bad\"}})\n self.assertFalse(cfg[\"filter\"][\"author_diversity\"][\"enabled\"])\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"decay_factor\"], 0.5)\n self.assertEqual(cfg[\"filter\"][\"author_diversity\"][\"floor\"], 0.1)\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "f108c922537ef2f4a6f02fe2dd3f442406b90c67": {"t": 1785768387.1062188, "n": 2974, "h": "faacea8c9b83f87e", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_normalize_display_tags_max_ip_count_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": {\"max_ip_count\": \"3\"}}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 3)\n\n def test_normalize_display_tags_max_ip_count_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"display_tags\": \"bad\"}})\n self.assertEqual(cfg[\"filter\"][\"display_tags\"][\"max_ip_count\"], 2)\n\n def test_normalize_tag_classifier_defaults_and_ints(self):\n cfg = config.normalize_config({\"tag_classifier\": {\"ttl_days\": \"14\", \"batch_size\": \"bad\"}})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"base_url\"], \"https://api.deepseek.com/v1\")\n self.assertEqual(cfg[\"tag_classifier\"][\"model\"], \"deepseek-chat\")\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 14)\n self.assertEqual(cfg[\"tag_classifier\"][\"batch_size\"], 50)\n self.assertEqual(cfg[\"tag_classifier\"][\"concurrency\"], 5)\n\n def test_normalize_tag_classifier_invalid_section_uses_defaults(self):\n cfg = config.normalize_config({\"tag_classifier\": \"bad\"})\n self.assertFalse(cfg[\"tag_classifier\"][\"enabled\"])\n self.assertEqual(cfg[\"tag_classifier\"][\"ttl_days\"], 30)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n def test_global_key_inheritance(self):\n \"\"\"profiler.ai.api_key → scorer + tag_classifier 自动继承\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"ai\": {\"scorer\": {\"enabled\": True}},\n \"tag_classifier\": {\"enabled\": True},\n })\n self.assertEqual(cfg[\"ai\"][\"scorer\"][\"api_key\"], \"sk-shared\")\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-shared\")\n\n def test_global_key_no_override_existing(self):\n \"\"\"已有自己的 key 时不被覆盖\"\"\"\n cfg = config.normalize_config({\n \"profiler\": {\"ai\": {\"api_key\": \"sk-shared\"}},\n \"tag_classifier\": {\"api_key\": \"sk-own\", \"enabled\": True},\n })\n self.assertEqual(cfg[\"tag_classifier\"][\"api_key\"], \"sk-own\")\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}, "4b2d7f5d391cc7950b9b96f4195fd203bbd8ce14": {"t": 1785768388.4963307, "n": 865, "h": "a7ac2a71d113fc15", "c": "import tempfile\nimport unittest\nfrom pathlib import Path\n\nimport config\n\n\nclass ConfigNormalizationTests(unittest.TestCase):\n def test_normalize_daily_limit_from_string(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": \"30\"}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 30)\n\n def test_normalize_daily_limit_from_invalid_type_uses_default(self):\n cfg = config.normalize_config({\"filter\": {\"daily_limit\": {\"bad\": True}}})\n self.assertEqual(cfg[\"filter\"][\"daily_limit\"], 20)\n\n def test_load_config_rejects_non_mapping_root(self):\n with tempfile.TemporaryDirectory() as tmpdir:\n path = Path(tmpdir) / \"config.yaml\"\n path.write_text(\"- just\\n- a\\n- list\\n\", encoding=\"utf-8\")\n self.assertEqual(config.load_config(path), {})\n\n\nif __name__ == \"__main__\":\n unittest.main()\n"}}}} |