package runtimeconfig import ( "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/config" ) // 本文件钉住组级 `contextWindow`(design/04 附录 A.3 第 1 条:声明式窗口)。 // 窗口是上下文预算推导的输入,因此它必须:能校验、能随 env 播种、能按既有 // materialize 语义(面板优先、只补缺失键)落进面板配置。 // TestGroupValidate_RejectsNegativeContextWindow 钉住负数窗口在校验阶段报错: // 窗口没有负数语义,静默当成「未声明」会让一个配错的部署悄悄用回落窗口。 func TestGroupValidate_RejectsNegativeContextWindow(t *testing.T) { t.Parallel() base := Group{ID: "g", Provider: ProviderOpenAI, BaseURL: "https://x/v1", Model: "m"} if err := base.Validate(); err != nil { t.Fatalf("未声明窗口(0)应当合法: %v", err) } declared := base declared.ContextWindow = 1048576 if err := declared.Validate(); err != nil { t.Fatalf("正数窗口应当合法: %v", err) } negative := base negative.ContextWindow = -1 err := negative.Validate() if err == nil { t.Fatal("负数窗口必须报错") } if !strings.Contains(err.Error(), "contextWindow") { t.Fatalf("错误信息没点出字段名: %v", err) } } // TestGroupFromEnv_CarriesContextWindow 钉住 .env 合成的组带上 LLM_CONTEXT_WINDOW, // 否则「本仓没有 provider 侧窗口发现代码」的部署永远只能吃回落窗口。 func TestGroupFromEnv_CarriesContextWindow(t *testing.T) { t.Parallel() for _, provider := range []string{ProviderOpenAI, ProviderAnthropic} { env := config.LLMConfig{Provider: provider, ContextWindow: 1048576} got := GroupFromEnv(env)[0] if got.ContextWindow != 1048576 { t.Fatalf("provider=%s: ContextWindow = %d, want 1048576", provider, got.ContextWindow) } } } // TestMaterializeFillsContextWindowFromEnv 钉住 per-group 播种:面板缺该键 ⇒ 由 // LLM_CONTEXT_WINDOW 补上并落盘;面板已有该键 ⇒ 面板优先(与其余键同一规则)。 func TestMaterializeFillsContextWindowFromEnv(t *testing.T) { t.Parallel() env := materializeEnv() env.ContextWindow = 1048576 // 缺失 ⇒ 补齐。 path := writeModelsFile(t, `[ {"id":"openai","provider":"openai","baseUrl":"https://panel.example/v1","apiKey":"sk-panel","model":"panel-model"} ]`) res, err := Materialize(path, env, discardLog()) if err != nil { t.Fatalf("Materialize: %v", err) } if got, want := fillStrings(res.Filled), []string{"openai.contextWindow"}; !equalStrings(got, want) { t.Fatalf("Filled = %v, want %v", got, want) } if res.Groups[0].ContextWindow != 1048576 { t.Fatalf("补齐后的窗口 = %d, want 1048576", res.Groups[0].ContextWindow) } // 面板已有 ⇒ 不覆盖,且一个字节不动。 declared := writeModelsFile(t, `[ {"id":"openai","provider":"openai","baseUrl":"https://panel.example/v1","apiKey":"sk-panel","model":"panel-model","contextWindow":32768} ]`) before := readRaw(t, declared) res2, err := Materialize(declared, env, discardLog()) if err != nil { t.Fatalf("Materialize: %v", err) } if res2.Groups[0].ContextWindow != 32768 { t.Fatalf("面板声明的窗口被 env 覆盖:%d", res2.Groups[0].ContextWindow) } if res2.Written || readRaw(t, declared) != before { t.Fatal("面板已有该键时不得写文件") } } // TestMaterializeNeverFillsZeroContextWindow 钉住「0 不是可补的默认值」:env 未声明 // 窗口(0)时绝不写一个 0 进面板配置 —— 键的缺席才是「未声明」的唯一形态 // (omitempty),写进去只会制造 schema 噪声。 func TestMaterializeNeverFillsZeroContextWindow(t *testing.T) { t.Parallel() env := materializeEnv() // ContextWindow 零值 path := writeModelsFile(t, `[ {"id":"openai","provider":"openai","baseUrl":"https://panel.example/v1","apiKey":"sk-panel","model":"panel-model"} ]`) res, err := Materialize(path, env, discardLog()) if err != nil { t.Fatalf("Materialize: %v", err) } if len(res.Filled) != 0 { t.Fatalf("Filled = %v, want 空(env 没有声明窗口)", fillStrings(res.Filled)) } if strings.Contains(readRaw(t, path), "contextWindow") { t.Fatal("env 未声明窗口却把该键写进了面板配置") } }