feat(search): provider 热重载 + primary/fallback(W3)
- 保存被包成事务:快照 → 落盘 → 按新配置重建工具 → 原子替换注册表条目(registry.ReplaceTool 窄方法,保留运行期 Policy);下一轮即生效,不要求重启 - 若新配置会让后端下次起不来(主家未配齐 fail-fast)⇒ 文件按字节回滚 + 409 config_unstartable - 声明面补 primary + fallback[](可配可生效);否决「另开 form」方案(会破坏既有 reauth 回填) - 主 provider 解析口径核查结论:没被 PLG-4 改坏(顺手修了一处日志缺陷) - 4 个变异自检(丢 Policy/不换条目/回滚 no-op/忽略 fallback)
This commit is contained in:
1 parent
5ee5f9126f
commit
a30f4a49ff
12 files changed
+1696
-16
No files matched your search
@@ -207,6 +207,83 @@ func TestDeclaredActionNamesMatchDesignExample(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// 判据 ⑨(切片 W3;裁决 2026-10-06 #2):表单必须能配「谁先用」——`primary` 与 `fallback`
|
||||
// 两个**文件级**字段就位,且都从 `plugin:providers` 拉候选(否则控件降级成文本框,
|
||||
// 用户得自己记住 provider 名)。
|
||||
func TestAdminPanelsDeclareSelectionFields(t *testing.T) {
|
||||
t.Parallel()
|
||||
form := adminPanelsFormSection(t)
|
||||
byKey := make(map[string]sdk.Field, len(form.Fields))
|
||||
for _, f := range form.Fields {
|
||||
byKey[f.Key] = f
|
||||
}
|
||||
cases := []struct {
|
||||
key string
|
||||
wantType sdk.FieldType
|
||||
wantOptions string
|
||||
}{
|
||||
{FieldPrimary, sdk.FieldSelect, "plugin:providers"},
|
||||
{FieldFallback, sdk.FieldMultiSelect, "plugin:providers"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
f, ok := byKey[tc.key]
|
||||
if !ok {
|
||||
t.Errorf("表单里缺 %q 字段(主 provider / 降级链就没法配了): %+v", tc.key, byKey)
|
||||
continue
|
||||
}
|
||||
if f.Type != tc.wantType {
|
||||
t.Errorf("字段 %q 类型 = %q, want %q", tc.key, f.Type, tc.wantType)
|
||||
}
|
||||
if f.OptionsFrom != tc.wantOptions {
|
||||
t.Errorf("字段 %q 的 optionsFrom = %q, want %q(下拉必须拉得到候选)", tc.key, f.OptionsFrom, tc.wantOptions)
|
||||
}
|
||||
}
|
||||
// 两个 key 不能互相撞,也不能撞上条目级字段:撞了就是「我在改谁」要猜。
|
||||
if FieldPrimary == FieldFallback {
|
||||
t.Fatalf("两个文件级字段的 key 相同(%q)", FieldPrimary)
|
||||
}
|
||||
for _, taken := range []string{"provider", "apiKey", "baseUrl", "enabled"} {
|
||||
if FieldPrimary == taken || FieldFallback == taken {
|
||||
t.Fatalf("文件级字段的 key 撞上了条目级字段 %q", taken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判据 ⑩(切片 W3):面板**恰好一个 form 区块**这条约束是有原因的,钉在这里免得后人
|
||||
// 顺手拆成两个表单:
|
||||
//
|
||||
// 前端的「带回表单」(reauth)只在恰好一个 form 时转交(`PluginPanel.tsx` 的 singleForm:
|
||||
// 两个表单没有归属,宁可不填也不猜)。第二处增补因此必须挂在既有表单上 —— 拆成两个 form
|
||||
// 会让「重配凭据」的回填整条失灵(那是客户端行为,改它不在本片文件面内)。
|
||||
func TestAdminPanelsKeepASingleFormSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
forms := 0
|
||||
for _, p := range AdminPanels() {
|
||||
for _, s := range p.Sections {
|
||||
if s.Type == sdk.SectionForm {
|
||||
forms++
|
||||
}
|
||||
}
|
||||
}
|
||||
if forms != 1 {
|
||||
t.Fatalf("面板里有 %d 个 form 区块,want 1(多一个会让前端的 reauth 回填失效)", forms)
|
||||
}
|
||||
}
|
||||
|
||||
// adminPanelsFormSection 取唯一那个 form 区块(没有就失败)。
|
||||
func adminPanelsFormSection(t *testing.T) sdk.Section {
|
||||
t.Helper()
|
||||
for _, p := range AdminPanels() {
|
||||
for _, s := range p.Sections {
|
||||
if s.Type == sdk.SectionForm {
|
||||
return s
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatal("面板里没有 form 区块")
|
||||
return sdk.Section{}
|
||||
}
|
||||
|
||||
// contains 是字符串切片成员判定(测试里只此一处,不值得抽包)。
|
||||
func contains(list []string, want string) bool {
|
||||
for _, s := range list {
|
||||
|
||||
Reference in new issue
Block a user