mirror of
https://github.com/ZhuLinsen/daily_stock_analysis
synced 2026-09-20 10:53:33 +08:00
fix: 公共 SearXNG 实例发现默认关闭 (#2226)
* fix: 公共 SearXNG 实例发现默认关闭 公共实例普遍存在限流(429)、下线(503)或未开启 JSON 输出的情况, 默认开启会让未配置任何搜索 key 的用户每次分析多耗 30~60 秒, 且新闻面最终为空却不在报告中提示(详见 #2225 的实测数据)。 - .env.example: 默认值改为 false,并说明原因与推荐做法 - docs/full-guide.md、docs/full-guide_EN.md: 同步默认值与说明 - tests: 放宽段落定位断言以适配新默认值,并新增一条测试固化该默认值 * fix: 公共 SearXNG 实例发现改在运行时层面默认关闭 按 review 意见修正:仅改 .env.example 只影响复制新模板的用户, 已有安装与 GitHub Actions(未配置 Variable/Secret 时变量为空) 仍会走 default=True 的运行时分支,继续承受 30~60 秒失败重试。 - src/config.py: parse_env_bool 的 default 由 True 改为 False(真正生效处) - src/core/config_registry.py: 示例顺序调整为 false 优先 - .github/workflows/00-daily-analysis.yml: 变量未设置时诊断显示「默认关闭」, 与运行时行为保持一致,不再误报为已开启 - tests: 新增三条回归测试,分别锁住运行时默认值、config.py 源码中的 default=False、以及工作流诊断文案 - docs/CHANGELOG.md: 在 [Unreleased] 追加条目 显式设为 true 的用户行为不变。27 passed * fix: align SearXNG public instance defaults * fix: complete SearXNG opt-in defaults --------- Co-authored-by: Mach-Chan <zz-b240@zz-b240deMacBook-Air.local> Co-authored-by: ZhuLinsen <zhuls97@163.com>
This commit is contained in:
@@ -396,9 +396,11 @@ SERPAPI_API_KEYS=
|
||||
BRAVE_API_KEYS=
|
||||
# SearXNG 实例地址(逗号分隔,私有部署无配额,需在 settings.yml 启用 format: json)
|
||||
# GitHub Actions 每日分析工作流支持同名 Variables 优先、Secrets 回退;公网地址可配置为 Variable。
|
||||
# 留空时默认自动从 searx.space 拉取公共实例;若不希望访问公共实例,可将下方开关设为 false
|
||||
# 留空且下方开关为 true 时,会自动从 searx.space 拉取公共实例。公共实例普遍存在限流(429)、
|
||||
# 下线(503)或未开启 JSON 输出的情况,逐个重试会让每次分析多耗 30~60 秒且新闻面最终为空,
|
||||
# 因此默认关闭。推荐配置自建实例地址(SEARXNG_BASE_URLS),或使用上方任一带 key 的搜索渠道。
|
||||
SEARXNG_BASE_URLS=
|
||||
SEARXNG_PUBLIC_INSTANCES_ENABLED=true
|
||||
SEARXNG_PUBLIC_INSTANCES_ENABLED=false
|
||||
|
||||
# ===================================
|
||||
# Social Sentiment Intelligence (US stocks only)
|
||||
|
||||
2
.github/workflows/00-daily-analysis.yml
vendored
2
.github/workflows/00-daily-analysis.yml
vendored
@@ -497,7 +497,7 @@ jobs:
|
||||
echo " SearXNG Public Instances: ❌ 已禁用"
|
||||
;;
|
||||
"")
|
||||
echo " SearXNG Public Instances: ✅ 默认开启"
|
||||
echo " SearXNG Public Instances: ⚪ 默认关闭(公共实例普遍限流,如需启用请显式设为 true)"
|
||||
;;
|
||||
*)
|
||||
echo " SearXNG Public Instances: ✅ 已启用"
|
||||
|
||||
@@ -11,6 +11,16 @@ const flattenHelp = (help: ReturnType<typeof getSettingsHelpContent>) => [
|
||||
...(help?.notes ?? []),
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
describe('SearXNG settings help', () => {
|
||||
it('describes public SearXNG discovery as opt-in in Chinese', () => {
|
||||
const description = getFieldDescriptionZh('SEARXNG_PUBLIC_INSTANCES_ENABLED');
|
||||
|
||||
expect(description).toContain('默认关闭');
|
||||
expect(description).toContain('设为 true');
|
||||
expect(description).not.toContain('设为 false 可禁用该默认行为');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Skill Outcome auto-weight settings help', () => {
|
||||
it('describes the attributable Outcome threshold in Chinese', () => {
|
||||
const help = getSettingsHelpContent(
|
||||
|
||||
@@ -226,7 +226,7 @@ const fieldDescriptionMap: Record<string, string> = {
|
||||
BRAVE_API_KEYS: '用于新闻检索的 Brave Search 密钥,支持逗号分隔多个。',
|
||||
STOCK_INDEX_REMOTE_UPDATE_ENABLED: '控制是否从 GitHub main 远程刷新股票自动补全索引;失败会降级到本地缓存或内置索引,不影响主分析流程。',
|
||||
SEARXNG_BASE_URLS: 'SearXNG 自建实例地址(逗号分隔,无配额兜底,需在 settings.yml 启用 format: json)。',
|
||||
SEARXNG_PUBLIC_INSTANCES_ENABLED: '当未配置 SearXNG 自建实例时,自动从 searx.space 获取公共实例并轮询使用;设为 false 可禁用该默认行为。',
|
||||
SEARXNG_PUBLIC_INSTANCES_ENABLED: '默认关闭;设为 true 后,当未配置 SearXNG 自建实例时,会从 searx.space 获取公共实例并轮询使用。',
|
||||
MINIMAX_API_KEYS: '用于新闻检索的 MiniMax 密钥,支持逗号分隔多个(最低优先级)。',
|
||||
NEWS_STRATEGY_PROFILE: '新闻窗口档位:ultra_short=1天,short=3天,medium=7天,long=30天。',
|
||||
NEWS_MAX_AGE_DAYS: '新闻最大时效上限。实际窗口 = min(策略档位天数, NEWS_MAX_AGE_DAYS)。例如 ultra_short + 7 仍为 1 天。',
|
||||
|
||||
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
- [改进] AIHubMix 注册与引流链接统一使用 inferera.com,改善中国大陆网络直连体验。
|
||||
- [修复] 单股推送模式在未配置通知渠道时仍会落盘本地个股报告;CLI 启动分析若因空股票列表、个股结果全失败或本地报告保存失败而未生成报告,会显式返回失败并记录原因。
|
||||
- [修复] 合并推送模式下即使个股汇总报告落盘失败,仍会先发送已有的合并通知;仅启用大盘复盘但最终未生成任何复盘内容时,分析任务会显式返回失败。
|
||||
- [修复] SearXNG 公共实例发现的默认值由启用改为关闭:公共实例普遍存在限流、下线或不返回 JSON 的情况,默认开启会让未配置搜索 key 的用户每次分析多耗 30~60 秒且新闻面最终为空。运行时默认值、配置模板、中英文档与工作流诊断同步调整;显式设为 true 的用户行为不变。
|
||||
|
||||
- [新功能] Agent 工具调用支持按类别(data/search/analysis/action/market)配置默认超时,并允许单工具声明 `timeout_seconds`;有效超时按 first-wins 优先级解析(显式 per-run `tool_call_timeout_seconds` > 单工具显式 `timeout_seconds` > 类别默认 > 无限制),剩余 wall-clock 预算仅作不可突破的外层 cap,超时后返回结构化 `{"timeout": true}` 错误(标记 `retriable: false` 并写入 `non_retriable_tool_results` 防重试重复执行)供 Agent 继续执行而非中断循环(fixes #1890)。
|
||||
- [修复] Agent 工具注册表(`src/agent/factory.get_tool_registry`)由模块级缓存改为按「类别超时映射的值」比对失效,规避 CPython 回收对象后地址复用(`id(config)` 相同)导致配置 reload 后的 `Config` 被误判为未变、沿用过期超时的真 bug;新增 `_coerce_config_timeout` 类型白名单,使调用方传入 `MagicMock` / 缺属性 stub / 脏字符串(如 `float(MagicMock())` 静默得到 1.0)时降级为「无类别限制」而非崩溃或强加 1 秒超时;`build_agent_executor(config)` / `build_agent_chat_executor(config)` 现已把调用方 `config` 透传给 `get_tool_registry(config)`(不再无参调用冻结首构 registry);`main._reload_runtime_config` 与 `SystemConfigService._reload_runtime_singletons`(及 `update()`→`reload_now` 路径)在配置热重载时调用 `reset_tool_registry()` 强制重建;回归测试补充「传入新 config 后 registry 重建」「reload 后新超时应生效」及「builder 透传 config」三类场景(#1890 的 review follow-up,闭环 OR-COM-dd1e8fa7 / OR-COM-bff42110)
|
||||
|
||||
@@ -434,8 +434,8 @@ git push -u origin main
|
||||
| `BOCHA_API_KEYS` | 博查搜索 API Key | 可选 |
|
||||
| `BRAVE_API_KEYS` | Brave Search API Key | 可选 |
|
||||
| `MINIMAX_API_KEYS` | MiniMax Coding Plan Web Search | 可选 |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 自建实例(无配额兜底,需在 settings.yml 启用 format: json);留空时默认自动发现公共实例 | 可选 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `true`) | 可选 |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 自建实例(无配额兜底,需在 settings.yml 启用 format: json);留空时仅在显式启用公共实例发现后使用 `searx.space` | 可选 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `false`) | 可选 |
|
||||
| `TUSHARE_TOKEN` | Tushare Token | 可选 |
|
||||
| `GEMINI_MODEL` | 模型名称(默认 gemini-2.0-flash) | 可选 |
|
||||
|
||||
|
||||
@@ -163,8 +163,8 @@ Zeabur 服务建议从 `1G` 内存起步;`512M` 仅适合轻量 Web/API、单
|
||||
| `BOCHA_API_KEYS` | Bocha API 密钥(用逗号分隔) |
|
||||
| `BRAVE_API_KEYS` | Brave Search API 密钥(用逗号分隔) |
|
||||
| `MINIMAX_API_KEYS` | MiniMax API 密钥(用逗号分隔) |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 实例地址(逗号分隔,无配额兜底,需在 settings.yml 启用 format: json);留空时默认自动发现公共实例 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `true`) |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 实例地址(逗号分隔,无配额兜底,需在 settings.yml 启用 format: json);留空时仅在显式启用公共实例发现后使用 `searx.space` |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `false`) |
|
||||
|
||||
### 5.4 配置方法
|
||||
|
||||
|
||||
@@ -163,8 +163,8 @@ daily_stock_analysis/
|
||||
| `BOCHA_API_KEYS` | [博查搜索](https://open.bocha.cn/) Web Search API(中文搜索优化,支持AI摘要,多个key用逗号分隔) | 可选 |
|
||||
| `BRAVE_API_KEYS` | [Brave Search](https://brave.com/search/api/) API(隐私优先,美股优化,多个key用逗号分隔) | 可选 |
|
||||
| `MINIMAX_API_KEYS` | [MiniMax](https://platform.minimax.io/) Coding Plan Web Search(结构化搜索结果) | 可选 |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 自建实例(无配额兜底,需在 settings.yml 启用 format: json);留空时默认自动发现公共实例 | 可选 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `true`) | 可选 |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 自建实例(无配额兜底,需在 settings.yml 启用 format: json);留空时仅在显式启用公共实例发现后使用 `searx.space` | 可选 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `false`)。公共实例普遍限流或未开启 JSON 输出,开启后每次分析可能多耗 30~60 秒且新闻面为空 | 可选 |
|
||||
| `TUSHARE_TOKEN` | [Tushare Pro](https://tushare.pro/weborder/#/login?reg=834638 ) Token | 可选 |
|
||||
| `TUSHARE_HTTP_URL` | Tushare Pro HTTP 接入地址;留空(或未设置/空白)时使用官方端点 `http://api.tushare.pro`,仅在需通过公司内网代理、跨境网络或自建镜像时填写 `http://` 或 `https://` 开头的完整地址 | 可选 |
|
||||
| `TICKFLOW_API_KEY` | [TickFlow](https://tickflow.org) API Key;可选,用于 A 股日 K、实时行情、股票列表/名称与大盘复盘增强;失败或权限不足时自动回退。 | 可选 |
|
||||
@@ -373,8 +373,8 @@ daily_stock_analysis/
|
||||
| `MINIMAX_API_KEYS` | MiniMax Coding Plan Web Search(结构化搜索结果) | 可选 |
|
||||
| `SOCIAL_SENTIMENT_API_KEY` | Stock Sentiment API Key(Reddit / X / Polymarket,可选) | 可选 |
|
||||
| `SOCIAL_SENTIMENT_API_URL` | Stock Sentiment API 地址(默认 `https://api.adanos.org`) | 可选 |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 自建实例(无配额兜底,需在 settings.yml 启用 format: json);留空时默认自动发现公共实例 | 可选 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `true`) | 可选 |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG 自建实例(无配额兜底,需在 settings.yml 启用 format: json);留空时仅在显式启用公共实例发现后使用 `searx.space` | 可选 |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | 是否在 `SEARXNG_BASE_URLS` 为空时自动从 `searx.space` 获取公共实例(默认 `false`)。公共实例普遍限流或未开启 JSON 输出,开启后每次分析可能多耗 30~60 秒且新闻面为空 | 可选 |
|
||||
| `NEWS_STRATEGY_PROFILE` | 新闻策略窗口档位:`ultra_short`(1天)/`short`(3天)/`medium`(7天)/`long`(30天);实际窗口取与 `NEWS_MAX_AGE_DAYS` 的最小值 | 默认 `short` |
|
||||
| `NEWS_MAX_AGE_DAYS` | 新闻最大时效(天),搜索时限制结果在近期内 | 默认 `3` |
|
||||
| `BIAS_THRESHOLD` | 乖离率阈值(%),超过提示不追高;强势趋势股自动放宽到 1.5 倍 | 默认 `5.0` |
|
||||
|
||||
@@ -156,8 +156,8 @@ Go to your forked repo → `Settings` → `Secrets and variables` → `Actions`
|
||||
| `BOCHA_API_KEYS` | [Bocha Search](https://open.bocha.cn/) Web Search API (Chinese search optimized, supports AI summaries, multiple keys comma-separated) | Optional |
|
||||
| `BRAVE_API_KEYS` | [Brave Search](https://brave.com/search/api/) API (privacy-first, US-stock news enrichment, comma-separated for multiple keys) | Optional |
|
||||
| `MINIMAX_API_KEYS` | [MiniMax](https://platform.minimax.io/) Coding Plan Web Search (structured search results) | Optional |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG self-hosted instances (quota-free fallback, enable format: json in settings.yml); when empty the app auto-discovers public instances | Optional |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | Auto-discover public SearXNG instances from `searx.space` when `SEARXNG_BASE_URLS` is empty (default `true`) | Optional |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG self-hosted instances (quota-free fallback, enable format: json in settings.yml); when empty, `searx.space` discovery is used only if public instances are explicitly enabled | Optional |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | Auto-discover public SearXNG instances from `searx.space` when `SEARXNG_BASE_URLS` is empty (default `false`). Public instances are commonly rate-limited or do not return JSON, so enabling this can add 30-60s per run and still yield no news | Optional |
|
||||
| `TUSHARE_TOKEN` | [Tushare Pro](https://tushare.pro/weborder/#/login?reg=834638) Token | Optional |
|
||||
| `TUSHARE_HTTP_URL` | Tushare Pro HTTP endpoint; when unset/empty defaults to the official `http://api.tushare.pro`. Set to a `http://` or `https://` URL only when routing through a corporate proxy, cross-border network, or a self-hosted mirror | Optional |
|
||||
| `TICKFLOW_API_KEY` | [TickFlow](https://tickflow.org) API key for optional A-share daily K-lines, realtime quotes, stock list/name lookup, and CN market review enhancement; permission or entitlement failures fall back to existing providers | Optional |
|
||||
@@ -336,8 +336,8 @@ For the notification baseline, diagnostics, and deployment notes, see [Notificat
|
||||
| `MINIMAX_API_KEYS` | MiniMax Coding Plan Web Search (structured results) | Optional |
|
||||
| `SOCIAL_SENTIMENT_API_KEY` | Stock Sentiment API Key (Reddit / X / Polymarket, US stocks optional) | Optional |
|
||||
| `SOCIAL_SENTIMENT_API_URL` | Stock Sentiment API endpoint (default `https://api.adanos.org`) | Optional |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG self-hosted instances (quota-free fallback, enable format: json in settings.yml); when empty the app auto-discovers public instances | Optional |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | Auto-discover public SearXNG instances from `searx.space` when `SEARXNG_BASE_URLS` is empty (default `true`) | Optional |
|
||||
| `SEARXNG_BASE_URLS` | SearXNG self-hosted instances (quota-free fallback, enable format: json in settings.yml); when empty, `searx.space` discovery is used only if public instances are explicitly enabled | Optional |
|
||||
| `SEARXNG_PUBLIC_INSTANCES_ENABLED` | Auto-discover public SearXNG instances from `searx.space` when `SEARXNG_BASE_URLS` is empty (default `false`). Public instances are commonly rate-limited or do not return JSON, so enabling this can add 30-60s per run and still yield no news | Optional |
|
||||
|
||||
> Behavior note: Search and social sentiment are optional enhancement services. If either service fails to initialize, the system logs a warning and degrades gracefully by skipping that stage without blocking the core analysis flow.
|
||||
|
||||
|
||||
@@ -979,7 +979,7 @@ class Config:
|
||||
brave_api_keys: List[str] = field(default_factory=list) # Brave Search API Keys
|
||||
serpapi_keys: List[str] = field(default_factory=list) # SerpAPI Keys
|
||||
searxng_base_urls: List[str] = field(default_factory=list) # SearXNG instance URLs (self-hosted, no quota)
|
||||
searxng_public_instances_enabled: bool = True # Auto-discover public SearXNG instances when base URLs are absent
|
||||
searxng_public_instances_enabled: bool = False # Opt in to public discovery when base URLs are absent
|
||||
|
||||
# === Social Sentiment (US stocks only, api.adanos.org) ===
|
||||
social_sentiment_api_key: Optional[str] = None
|
||||
@@ -1714,7 +1714,7 @@ class Config:
|
||||
)
|
||||
searxng_public_instances_enabled = parse_env_bool(
|
||||
os.getenv('SEARXNG_PUBLIC_INSTANCES_ENABLED'),
|
||||
default=True,
|
||||
default=False,
|
||||
)
|
||||
|
||||
# 企微消息类型与最大字节数逻辑
|
||||
|
||||
@@ -1102,14 +1102,14 @@ _FIELD_DEFINITIONS: Dict[str, Dict[str, Any]] = {
|
||||
},
|
||||
"SEARXNG_PUBLIC_INSTANCES_ENABLED": {
|
||||
"title": "SearXNG Public Instances",
|
||||
"description": "Auto-discover public SearXNG instances from searx.space when SEARXNG_BASE_URLS is empty. Default: true; set false to disable.",
|
||||
"description": "Auto-discover public SearXNG instances from searx.space when SEARXNG_BASE_URLS is empty. Default: false; set true to enable.",
|
||||
"category": "data_source",
|
||||
"data_type": "boolean",
|
||||
"ui_control": "switch",
|
||||
"is_sensitive": False,
|
||||
"is_required": False,
|
||||
"is_editable": True,
|
||||
"default_value": "true",
|
||||
"default_value": "false",
|
||||
"options": [],
|
||||
"validation": {},
|
||||
"display_order": 53,
|
||||
@@ -4659,8 +4659,8 @@ _FIELD_HELP_METADATA: Dict[str, Dict[str, Any]] = {
|
||||
"SEARXNG_PUBLIC_INSTANCES_ENABLED": {
|
||||
"help_key": "settings.data_source.SEARXNG_BASE_URLS",
|
||||
"examples": [
|
||||
"SEARXNG_PUBLIC_INSTANCES_ENABLED=true",
|
||||
"SEARXNG_PUBLIC_INSTANCES_ENABLED=false",
|
||||
"SEARXNG_PUBLIC_INSTANCES_ENABLED=true",
|
||||
],
|
||||
"docs": _DOC_FULL_GUIDE_SEARCH,
|
||||
"warning_codes": ["public_instance_stability"],
|
||||
|
||||
@@ -103,7 +103,7 @@ def build_market_review_runtime(
|
||||
searxng_public_instances_enabled=getattr(
|
||||
config,
|
||||
"searxng_public_instances_enabled",
|
||||
True,
|
||||
False,
|
||||
),
|
||||
news_max_age_days=getattr(config, "news_max_age_days", 3),
|
||||
news_strategy_profile=getattr(config, "news_strategy_profile", "short"),
|
||||
|
||||
@@ -2389,7 +2389,7 @@ class SearchService:
|
||||
serpapi_keys: Optional[List[str]] = None,
|
||||
minimax_keys: Optional[List[str]] = None,
|
||||
searxng_base_urls: Optional[List[str]] = None,
|
||||
searxng_public_instances_enabled: bool = True,
|
||||
searxng_public_instances_enabled: bool = False,
|
||||
news_max_age_days: int = 3,
|
||||
news_strategy_profile: str = "short",
|
||||
):
|
||||
|
||||
@@ -78,6 +78,24 @@ class TestMarketReviewRuntimeCompatibility(unittest.TestCase):
|
||||
self.assertIs(runtime_analyzer, analyzer)
|
||||
self.assertIsNone(runtime_search)
|
||||
|
||||
def test_build_market_review_runtime_defaults_legacy_public_discovery_off(self) -> None:
|
||||
config = self._base_config()
|
||||
del config.searxng_public_instances_enabled
|
||||
config.has_search_capability_enabled = lambda: True
|
||||
notifier = MagicMock()
|
||||
search_service = MagicMock()
|
||||
|
||||
with patch("src.notification.NotificationService", return_value=notifier), \
|
||||
patch("src.search_service.SearchService", return_value=search_service) as search_cls:
|
||||
runtime_notifier, runtime_analyzer, runtime_search = build_market_review_runtime(config)
|
||||
|
||||
self.assertFalse(
|
||||
search_cls.call_args.kwargs["searxng_public_instances_enabled"]
|
||||
)
|
||||
self.assertIs(runtime_notifier, notifier)
|
||||
self.assertIsNone(runtime_analyzer)
|
||||
self.assertIs(runtime_search, search_service)
|
||||
|
||||
def test_build_market_review_runtime_supports_litellm_channel_model_list(self) -> None:
|
||||
config = self._base_config()
|
||||
config.litellm_model = ""
|
||||
|
||||
@@ -441,6 +441,13 @@ class TestSearXNGSearchProvider(unittest.TestCase):
|
||||
self.assertTrue(service.is_available)
|
||||
self.assertTrue(any(provider.name == "SearXNG" for provider in service._providers))
|
||||
|
||||
def test_search_service_does_not_add_public_searxng_provider_by_default(self):
|
||||
service = SearchService()
|
||||
|
||||
self.assertFalse(service.is_available)
|
||||
self.assertFalse(any(provider.name == "SearXNG" for provider in service._providers))
|
||||
self.assertFalse(service._constructor_kwargs["searxng_public_instances_enabled"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -8,7 +8,7 @@ def test_env_example_documents_searxng_actions_variable_mapping() -> None:
|
||||
env_example = (ROOT_DIR / ".env.example").read_text(encoding="utf-8")
|
||||
|
||||
start = env_example.index("# SearXNG 实例地址")
|
||||
end = env_example.index("SEARXNG_PUBLIC_INSTANCES_ENABLED=true", start)
|
||||
end = env_example.index("SEARXNG_PUBLIC_INSTANCES_ENABLED=", start)
|
||||
searxng_block = env_example[start:end]
|
||||
|
||||
assert "GitHub Actions" in searxng_block
|
||||
@@ -17,6 +17,15 @@ def test_env_example_documents_searxng_actions_variable_mapping() -> None:
|
||||
assert "需配置为 Secret" not in searxng_block
|
||||
|
||||
|
||||
def test_env_example_defaults_public_searxng_instances_off() -> None:
|
||||
"""公共实例发现默认关闭:公共实例普遍限流或不返回 JSON,
|
||||
默认开启会让未配置搜索 key 的用户每次分析白等 30~60 秒且拿到空新闻面。"""
|
||||
env_example = (ROOT_DIR / ".env.example").read_text(encoding="utf-8")
|
||||
|
||||
assert "SEARXNG_PUBLIC_INSTANCES_ENABLED=false" in env_example
|
||||
assert "SEARXNG_PUBLIC_INSTANCES_ENABLED=true" not in env_example
|
||||
|
||||
|
||||
def test_daily_analysis_workflow_matches_documented_searxng_variable_mapping() -> None:
|
||||
workflow = (
|
||||
ROOT_DIR / ".github" / "workflows" / "00-daily-analysis.yml"
|
||||
@@ -36,3 +45,89 @@ def test_changelog_mentions_searxng_actions_variable_mapping() -> None:
|
||||
"- [修复] GitHub Actions 每日分析工作流读取 SearXNG 自建实例地址时"
|
||||
"支持 Variables 优先、Secrets 回退,修复仅配置 Variables 时 URL 不生效的问题。"
|
||||
) in changelog
|
||||
|
||||
|
||||
def test_runtime_default_disables_public_searxng_instances(monkeypatch) -> None:
|
||||
"""未设置环境变量时,运行时必须解析为关闭。
|
||||
|
||||
这是本项改动真正生效的地方:只改 .env.example 只影响复制了新模板的用户,
|
||||
已有安装和 GitHub Actions(未配置 Variable/Secret 时变量为空)仍会
|
||||
走公共实例发现,继续承受 30~60 秒的失败重试。
|
||||
"""
|
||||
from src.config import parse_env_bool
|
||||
|
||||
monkeypatch.delenv("SEARXNG_PUBLIC_INSTANCES_ENABLED", raising=False)
|
||||
import os
|
||||
|
||||
assert (
|
||||
parse_env_bool(os.getenv("SEARXNG_PUBLIC_INSTANCES_ENABLED"), default=False)
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
def test_config_constructor_defaults_public_searxng_instances_off() -> None:
|
||||
"""直接构造 Config 时也必须遵循公共实例发现默认关闭的契约。"""
|
||||
from src.config import Config
|
||||
|
||||
assert Config().searxng_public_instances_enabled is False
|
||||
|
||||
|
||||
def test_config_module_uses_false_as_runtime_default() -> None:
|
||||
"""锁住 config.py 里的默认值,防止后续改动悄悄回退。"""
|
||||
src = (ROOT_DIR / "src" / "config.py").read_text(encoding="utf-8")
|
||||
idx = src.index("SEARXNG_PUBLIC_INSTANCES_ENABLED")
|
||||
window = src[idx : idx + 200]
|
||||
|
||||
assert "default=False" in window
|
||||
assert "default=True" not in window
|
||||
|
||||
|
||||
def test_workflow_reports_disabled_when_variable_unset() -> None:
|
||||
"""工作流诊断不得把「未设置」显示成已开启,否则与运行时行为不一致。"""
|
||||
workflow = (
|
||||
ROOT_DIR / ".github" / "workflows" / "00-daily-analysis.yml"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
assert "默认开启" not in workflow
|
||||
assert "默认关闭" in workflow
|
||||
|
||||
|
||||
def test_docs_describe_public_searxng_discovery_as_opt_in() -> None:
|
||||
chinese_docs = [
|
||||
ROOT_DIR / "docs" / "full-guide.md",
|
||||
ROOT_DIR / "docs" / "DEPLOY.md",
|
||||
ROOT_DIR / "docs" / "docker" / "zeabur-deployment.md",
|
||||
]
|
||||
for path in chinese_docs:
|
||||
content = path.read_text(encoding="utf-8")
|
||||
base_url_lines = [
|
||||
line for line in content.splitlines() if "`SEARXNG_BASE_URLS`" in line
|
||||
]
|
||||
public_toggle_lines = [
|
||||
line
|
||||
for line in content.splitlines()
|
||||
if "`SEARXNG_PUBLIC_INSTANCES_ENABLED`" in line
|
||||
]
|
||||
assert base_url_lines
|
||||
assert public_toggle_lines
|
||||
assert all("留空时默认自动发现公共实例" not in line for line in base_url_lines)
|
||||
assert all("默认 `false`" in line for line in public_toggle_lines)
|
||||
|
||||
english_guide = (ROOT_DIR / "docs" / "full-guide_EN.md").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
english_base_url_lines = [
|
||||
line for line in english_guide.splitlines() if "`SEARXNG_BASE_URLS`" in line
|
||||
]
|
||||
english_public_toggle_lines = [
|
||||
line
|
||||
for line in english_guide.splitlines()
|
||||
if "`SEARXNG_PUBLIC_INSTANCES_ENABLED`" in line
|
||||
]
|
||||
assert english_base_url_lines
|
||||
assert english_public_toggle_lines
|
||||
assert all(
|
||||
"when empty the app auto-discovers public instances" not in line
|
||||
for line in english_base_url_lines
|
||||
)
|
||||
assert all("default `false`" in line for line in english_public_toggle_lines)
|
||||
|
||||
@@ -714,6 +714,16 @@ class SystemConfigServiceTestCase(unittest.TestCase):
|
||||
self.assertEqual(items["REPORT_SHOW_LLM_MODEL"]["value"], "false")
|
||||
self.assertTrue(items["REPORT_SHOW_LLM_MODEL"]["raw_value_exists"])
|
||||
|
||||
def test_get_config_defaults_public_searxng_instances_off(self) -> None:
|
||||
payload = self.service.get_config(include_schema=True)
|
||||
items = {item["key"]: item for item in payload["items"]}
|
||||
public_instances = items["SEARXNG_PUBLIC_INSTANCES_ENABLED"]
|
||||
|
||||
self.assertEqual(public_instances["value"], "false")
|
||||
self.assertFalse(public_instances["raw_value_exists"])
|
||||
self.assertEqual(public_instances["schema"]["default_value"], "false")
|
||||
self.assertIn("Default: false", public_instances["schema"]["description"])
|
||||
|
||||
def test_get_config_preserves_manual_agent_codex_cli_value_without_schema_option(self) -> None:
|
||||
for backend in sorted(GENERATION_ONLY_BACKEND_IDS):
|
||||
with self.subTest(backend=backend):
|
||||
|
||||
Reference in New Issue
Block a user