fix(v2.1.1): 修复执行问题记录中的真实 bug + 文档对齐
源码修复(5 项): 1. search.py --time-range choices 加入 week(对齐 SearXNG API 四档) 2. fetch.py stdlib 路径处理 gzip/deflate 解压(被沙箱伪响应掩盖的真实 bug, 无 requests 环境抓取压缩服务器会全页 U+FFFD 乱码) 3. search.py --research 模式实现跨角度合并去重,输出 merged_results 字段 (兑现文档承诺 "Results are merged and deduplicated") 4. search.py fetch_page 返回 error_code 字段 + AdaptiveThrottle 用 E_RATE_LIMIT 结构化检测 429(原字符串匹配 "429" 会漏判 "Too Many Requests") 5. search.py _retry_with_backoff 复用 compute_backoff_delay(60s 封顶) + 处理 Retry-After header,与 fetch.py 保持一致 增强(3 项): - common.py 精确化 baidu 子域列表(pan.baidu.com/cloud.baidu.com 不再误伤) - search.py expand_research_queries 根据主题语言切换中英文后缀 - search.py 新增 _warn_unresponsive_engines,识别实例侧引擎挂起并提示 文档/版本: - _config.py VERSION 2.1.0 → 2.1.1 - SKILL.md 同步更新(time-range week、merged_results、error_code、baidu 精确化) - README.md 同步更新 + 测试数量 503 → 539 测试: 539 个全部通过,含 6 个新增验证测试
This commit is contained in:
@@ -44,7 +44,7 @@ class TestExpandResearchQueries:
|
||||
def test_angles_match_definition(self):
|
||||
queries = expand_research_queries("test")
|
||||
angles = [a for a, _ in queries]
|
||||
assert angles == [a for a, _ in _RESEARCH_ANGLES]
|
||||
assert angles == [a for a, _, _ in _RESEARCH_ANGLES]
|
||||
|
||||
def test_empty_topic_returns_empty(self):
|
||||
assert expand_research_queries("") == []
|
||||
@@ -65,7 +65,8 @@ class TestExpandResearchQueries:
|
||||
def test_multi_word_topic(self):
|
||||
queries = expand_research_queries("Python asyncio tutorial")
|
||||
assert queries[0] == ("overview", "Python asyncio tutorial")
|
||||
assert "简介" in queries[1][1]
|
||||
# v2.1.0:英文主题用英文后缀(避免跨语言组合匹配度低)
|
||||
assert "profile" in queries[1][1]
|
||||
|
||||
|
||||
# ===== --research CLI mutex checks =====
|
||||
|
||||
@@ -14,6 +14,7 @@ from search import (
|
||||
_merge_headers,
|
||||
_normalize_csv,
|
||||
_read_queries_file,
|
||||
_warn_unresponsive_engines,
|
||||
deduplicate_results,
|
||||
filter_results_by_domain,
|
||||
load_config,
|
||||
@@ -224,6 +225,14 @@ def test_build_params_time_range_none_excluded():
|
||||
assert "time_range" not in p
|
||||
|
||||
|
||||
def test_build_params_time_range_week_included():
|
||||
"""v2.1.1: 'week' is a valid SearXNG API time_range tier and must be
|
||||
passed through to params. Previously argparse choices omitted 'week',
|
||||
forcing users to use config-file workaround."""
|
||||
p = _build_params("x", _Args(time_range="week"))
|
||||
assert p["time_range"] == "week"
|
||||
|
||||
|
||||
def test_build_params_pageno_as_string():
|
||||
p = _build_params("x", _Args(pageno=3))
|
||||
assert p["pageno"] == "3"
|
||||
@@ -252,6 +261,40 @@ def test_merge_headers_all_none():
|
||||
assert _merge_headers(None, None) == {}
|
||||
|
||||
|
||||
# ----- _warn_unresponsive_engines (v2.1.1) -----
|
||||
|
||||
def test_warn_unresponsive_no_field():
|
||||
"""No unresponsive_engines field → no warning, no exception."""
|
||||
_warn_unresponsive_engines({}, "test")
|
||||
|
||||
|
||||
def test_warn_unresponsive_empty_list():
|
||||
"""Empty unresponsive_engines list → no warning."""
|
||||
_warn_unresponsive_engines({"unresponsive_engines": []}, "test")
|
||||
|
||||
|
||||
def test_warn_unresponsive_with_reasons():
|
||||
"""[engine, reason] format should be formatted as 'engine (reason)'."""
|
||||
results = {"unresponsive_engines": [
|
||||
["brave", "Suspended: too many requests"],
|
||||
["duckduckgo", "CAPTCHA"],
|
||||
]}
|
||||
# Should not raise; logger.warning is called internally
|
||||
_warn_unresponsive_engines(results, "test", result_count=1)
|
||||
|
||||
|
||||
def test_warn_unresponsive_engine_only():
|
||||
"""[engine] single-element format should be handled."""
|
||||
results = {"unresponsive_engines": [["brave"]]}
|
||||
_warn_unresponsive_engines(results, "test", result_count=5)
|
||||
|
||||
|
||||
def test_warn_unresponsive_string_format():
|
||||
"""Plain string entries (non-list) should be handled gracefully."""
|
||||
results = {"unresponsive_engines": ["brave", "duckduckgo"]}
|
||||
_warn_unresponsive_engines(results, "test", result_count=0)
|
||||
|
||||
|
||||
# ----- deduplicate_results -----
|
||||
|
||||
def test_dedup_removes_exact_duplicate_url():
|
||||
|
||||
@@ -113,12 +113,21 @@ class TestIsHardBlockedDomain:
|
||||
assert is_hard_blocked_domain("https://github.com/python/cpython") is False
|
||||
|
||||
def test_baidu_search_not_blocked(self):
|
||||
"""baidu.com search page is NOT in the hard-blocked list — only subdomains
|
||||
like baike.baidu.com, zhidao.baidu.com are."""
|
||||
# Actually, baidu.com is in _SUBDOMAIN_BLOCKED, so www.baidu.com matches.
|
||||
# This is intentional — Baidu's main search also has strong anti-bot.
|
||||
"""www.baidu.com search page is in the hard-blocked list.
|
||||
v2.1.0: moved from _SUBDOMAIN_BLOCKED (baidu.com) to HARD_BLOCKED_DOMAINS
|
||||
(www.baidu.com) to avoid blocking pan.baidu.com / cloud.baidu.com."""
|
||||
assert is_hard_blocked_domain("https://www.baidu.com/s?wd=test") is True
|
||||
|
||||
def test_baidu_pan_not_blocked(self):
|
||||
"""pan.baidu.com (百度网盘) should NOT be blocked after v2.1.0 fix.
|
||||
Previously matched by _SUBDOMAIN_BLOCKED 'baidu.com' — now only
|
||||
www/baike/zhidao/tieba/wenku are in the precise list."""
|
||||
assert is_hard_blocked_domain("https://pan.baidu.com/s/abc123") is False
|
||||
|
||||
def test_baidu_cloud_not_blocked(self):
|
||||
"""cloud.baidu.com (百度智能云) should NOT be blocked after v2.1.0 fix."""
|
||||
assert is_hard_blocked_domain("https://cloud.baidu.com/product/abc") is False
|
||||
|
||||
def test_empty_url(self):
|
||||
assert is_hard_blocked_domain("") is False
|
||||
|
||||
|
||||
Reference in New Issue
Block a user