package config import ( "strings" "testing" "time" ) // 本文件钉住切片 WS1-A 的两个配置键:`WEB_SEARCH_ENDPOINT`(默认 = 本机兜底)与 // `WEB_SEARCH_TIMEOUT`(可选,未配置 = 沿用 io 包既有超时)。 // // 关闭语义(空串 / off / none)由 ResolveEndpoint 定死:组合根据此**不注册** // web-search 工具,因此这三种写法必须与「可用端点」区分得干干净净。 // TestWebSearch_DefaultEndpointIsLocalFallback 钉住默认值字面量:模板与代码承诺的 // 就是本机兜底地址(scripts/searxng/settings.yml 的配套实例)。 func TestWebSearch_DefaultEndpointIsLocalFallback(t *testing.T) { t.Parallel() cfg, err := Load(writeEnvFile(t, "JWT_SECRET="+strings.Repeat("s", 40)+"\n")) if err != nil { t.Fatalf("Load: %v", err) } const want = "http://127.0.0.1:38471" if cfg.WebSearch.Endpoint != want { t.Fatalf("WEB_SEARCH_ENDPOINT 默认值 = %q, want %q", cfg.WebSearch.Endpoint, want) } decision := cfg.WebSearch.ResolveEndpoint() if !decision.Enabled || decision.URL != want { t.Fatalf("默认端点必须可用: %+v", decision) } // 未配置超时 = 零值 ⇒ 沿用 io 包既有超时(本层不得另造默认值)。 if cfg.WebSearch.Timeout != 0 { t.Fatalf("WEB_SEARCH_TIMEOUT 未配置时应为零值,got %v", cfg.WebSearch.Timeout) } } // TestWebSearch_EnvOverridesEndpointAndTimeout 钉住两个键都能被 .env 覆盖。 func TestWebSearch_EnvOverridesEndpointAndTimeout(t *testing.T) { t.Parallel() cfg, err := Load(writeEnvFile(t, strings.Join([]string{ "JWT_SECRET=" + strings.Repeat("s", 40), "WEB_SEARCH_ENDPOINT=https://searx.example.com/search", "WEB_SEARCH_TIMEOUT=25s", "", }, "\n"))) if err != nil { t.Fatalf("Load: %v", err) } if cfg.WebSearch.Endpoint != "https://searx.example.com/search" { t.Fatalf("Endpoint = %q", cfg.WebSearch.Endpoint) } if cfg.WebSearch.Timeout != 25*time.Second { t.Fatalf("Timeout = %v, want 25s", cfg.WebSearch.Timeout) } } // TestWebSearch_ExplicitEmptyMeansOff 钉住「写了键但留空 = 关闭」,它与「没写这个键 // (⇒ 默认兜底)」是两件事:前者必须让工具从注册面消失,后者必须开箱可用。 func TestWebSearch_ExplicitEmptyMeansOff(t *testing.T) { t.Parallel() cfg, err := Load(writeEnvFile(t, strings.Join([]string{ "JWT_SECRET=" + strings.Repeat("s", 40), "WEB_SEARCH_ENDPOINT=", "", }, "\n"))) if err != nil { t.Fatalf("Load: %v", err) } if cfg.WebSearch.Endpoint != "" { t.Fatalf("显式留空后 Endpoint = %q, want \"\"", cfg.WebSearch.Endpoint) } decision := cfg.WebSearch.ResolveEndpoint() if decision.Enabled || decision.Reason != WebSearchReasonOff { t.Fatalf("空串必须是「显式关闭」: %+v", decision) } } // TestWebSearch_InvalidTimeoutFailsLoad 钉住非法 duration 的明确行为:启动即报错 // (fail fast),绝不静默当成 0 或别的值。 func TestWebSearch_InvalidTimeoutFailsLoad(t *testing.T) { t.Parallel() _, err := Load(writeEnvFile(t, strings.Join([]string{ "JWT_SECRET=" + strings.Repeat("s", 40), "WEB_SEARCH_TIMEOUT=25", "", }, "\n"))) if err == nil { t.Fatal("WEB_SEARCH_TIMEOUT=25 不是合法 duration,Load 必须报错而不是静默取值") } if !strings.Contains(err.Error(), "WEB_SEARCH_TIMEOUT") { t.Fatalf("错误信息必须点出是哪个键: %v", err) } } // TestWebSearchConfig_ResolveEndpoint 是关闭 / 非法 / 可用三种去处的表驱动裁决。 func TestWebSearchConfig_ResolveEndpoint(t *testing.T) { t.Parallel() cases := []struct { name string endpoint string wantEnabled bool wantURL string wantReason string }{ {"空串 = 关闭", "", false, "", WebSearchReasonOff}, {"off", "off", false, "", WebSearchReasonOff}, {"none", "none", false, "", WebSearchReasonOff}, {"大写 OFF", "OFF", false, "", WebSearchReasonOff}, {"带空白 None", " None ", false, "", WebSearchReasonOff}, {"http 可用", "http://127.0.0.1:38471", true, "http://127.0.0.1:38471", ""}, {"https 带路径", "https://searx.example.com/search", true, "https://searx.example.com/search", ""}, {"周围空白被裁掉", " http://searx.example.com ", true, "http://searx.example.com", ""}, {"scheme 非 http(s)", "ftp://searx.example.com", false, "", WebSearchReasonInvalid}, {"缺主机", "http:///search", false, "", WebSearchReasonInvalid}, {"裸 host:port", "127.0.0.1:38471", false, "", WebSearchReasonInvalid}, {"纯文字", "searx", false, "", WebSearchReasonInvalid}, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() got := WebSearchConfig{Endpoint: tc.endpoint}.ResolveEndpoint() if got.Enabled != tc.wantEnabled || got.URL != tc.wantURL || got.Reason != tc.wantReason { t.Fatalf("ResolveEndpoint(%q) = %+v, want {Enabled:%v URL:%q Reason:%q}", tc.endpoint, got, tc.wantEnabled, tc.wantURL, tc.wantReason) } }) } }