feat(search): 面板上终于能改「结果条数上限」(原来改了会被静默丢弃)
真缺陷(上一片登记):`maxResults` 在面板上**根本改不了** —— ① 字段表里没有这个
字段(UI 不显示输入框);② `saveProvider` 只并 provider/apiKey/baseUrl/enabled;
③ `mergeSelectionPatch` 只处理 primary/fallback ⇒ **手工塞进 values 也会被静默丢弃**
(文件不变、也不报错)。「我改了它却没变」正是要消灭的那种假话。
- `panel.go`:表单加 `{Key: FieldMaxResults, Type: sdk.FieldNumber, Min: 1}`(**SDK 本就有
数字类型**,未自造;前端本就渲染 number ⇒ **不需要改前端**)。刻意不设 `Default`
(设了会让一次「只改 key」的保存把默认值固化进文件、还可能盖掉 `.env`)。
- `websearch_selection.go`:`mergeSelectionPatch` 里按**三态**落 `maxResults` ——
缺位/null ⇒ **不改动**;正整数(含整数字符串)⇒ 采用;0/负数/小数/空串/非数/
布尔/数组 ⇒ **400 `invalid_payload`** 且**盘上一字节不动**(绝不静默忽略)。
- `reauth` 回填该值(否则一保存就把上限清掉)。
- `.env.example`:该键的示例值与「默认 5」注释同步为 **10**(代码默认已改;⚠️ `.env` 未动)。
验收(主会话复跑 + 亲自变异):**整树 `go build ./...` 通过**;三包 vet/测试绿;
端到端「面板存 20 ⇒ 模型要 50 也只给 20、要 3 给 3」(走真 action hub → 落盘 →
热重载 → WithSearchMaxResults → 真 dispatcher,未重启后端);
我用 -overlay 把 `f.MaxResults = maxResults` 摘掉 ⇒ 两条用例变红
(「面板给的值必须真的落盘」/「回填的 maxResults = 5, want 20」)。
⚠️ 已知取舍(如实登记):**清空输入框 = 400**(后端按「非数 ⇒ 报错」拒),因此面板上
没有「去掉上限」的手势 —— 要回到 `.env` 兜底得改文件;要支持「清空 = 去掉覆盖」
需另定哨兵语义,未自行发挥。
⚠️ 契约面已变更(面板声明多一个 number 字段 + saveProvider 的 values 多一个可写键 +
响应多一个回显键)⇒ 主会话登记。
This commit is contained in:
1 parent
8325d1bb8c
commit
c68387a69a
8 files changed
+387
-31
No files matched your search
@@ -3,13 +3,15 @@ package config
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 本文件钉住切片 WS1-B 的搜索配置键(design/27 §3):
|
||||
// WEB_SEARCH_PROVIDER / _FALLBACK / _MAX_RESULTS 与各家独立一组键
|
||||
// (WEB_SEARCH_<NAME>_API_KEY / _BASE_URL),以及 ResolveProvider 的关闭语义。
|
||||
// (WEB_SEARCH_<NAME>_API_KEY / _BASE_URL),以及 ResolveProvider 的关闭语义;
|
||||
// 末尾还钉住 `.env.example` 的取值与代码默认值同口径(模板写错就是一句假话)。
|
||||
|
||||
// wsTestLoad 写一份只含必填键加 extra 行的 .env,加载出配置。
|
||||
func wsTestLoad(t *testing.T, extra ...string) (*Config, error) {
|
||||
@@ -126,3 +128,44 @@ func TestWebSearchConfig_ResolveProvider(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWebSearchMaxResults_EnvExampleMatchesCodeDefault 是「配置事实 ↔ 模板」守卫:
|
||||
// `.env.example` 是给人照抄的模板,它给出的取值必须等于代码默认值 —— 模板写 5 而代码默认
|
||||
// 已改成 10(2026-10-07 的口径调整漏改了模板),照抄模板的人拿到的就不是默认行为。
|
||||
//
|
||||
// ⚠️ 只读模板;`.env` 已冻结(基础键不加/不改/不删),本用例不碰它。
|
||||
func TestWebSearchMaxResults_EnvExampleMatchesCodeDefault(t *testing.T) {
|
||||
cfg, err := wsTestLoad(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Load: %v", err)
|
||||
}
|
||||
raw, err := os.ReadFile(filepath.Join("..", "..", ".env.example"))
|
||||
if err != nil {
|
||||
t.Fatalf("read .env.example: %v", err)
|
||||
}
|
||||
const key = "WEB_SEARCH_MAX_RESULTS"
|
||||
value, err := strconv.Atoi(wsEnvExampleValue(t, string(raw), key))
|
||||
if err != nil {
|
||||
t.Fatalf(".env.example 的 %s 取值不是整数: %v", key, err)
|
||||
}
|
||||
if value != cfg.WebSearch.MaxResults {
|
||||
t.Fatalf(".env.example 的 %s = %d,代码默认 = %d(模板与代码必须同口径)",
|
||||
key, value, cfg.WebSearch.MaxResults)
|
||||
}
|
||||
}
|
||||
|
||||
// wsEnvExampleValue 取 `.env.example` 里某个键的**生效**取值(跳过注释行)。
|
||||
func wsEnvExampleValue(t *testing.T, raw, key string) string {
|
||||
t.Helper()
|
||||
for _, line := range strings.Split(raw, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if rest, ok := strings.CutPrefix(line, key+"="); ok {
|
||||
return strings.TrimSpace(rest)
|
||||
}
|
||||
}
|
||||
t.Fatalf(".env.example 里没有生效的 %s=…(模板必须给出可照抄的取值)", key)
|
||||
return ""
|
||||
}
|
||||
Reference in new issue
Block a user