package console import ( "testing" "yunyan/pb" ) // 迁移口径必须与迁移**前**的下发结果逐键一致——错一个键,已发布的客户端就拿不到凭据, // 而且只表现为「某个能力鉴权失败」,不报配置错误。下面的数据取自 2026-09-12 线上真实布局: // global_config 里 31 个键中只有 4 组因区域而异,其余 27 个是 14 个区域同值复制。 func TestPlanEnvSvcValues(t *testing.T) { const ( cn = int32(pb.Region_RegionChina) us = int32(pb.Region_RegionUSA) ea = int32(pb.Region_RegionEastAsia) ) spec := envSvcSpec{ Id: "stt_azure", Fields: []envFieldSpec{ {EnvKey: "AZURE_SPEECH_KEY", FieldKey: "subscription_key", Encrypted: true}, {EnvKey: "AZURE_SPEECH_REGION", FieldKey: "region"}, {EnvKey: "AZURE_SPEECH_ENDPOINT", FieldKey: "endpoint"}, }, } t.Run("基础值取中国区,同值区域不写分叉", func(t *testing.T) { byKey := map[string]map[int32]string{ // 真实布局:key 每区不同,region 每区不同,但很多区其实同值 "AZURE_SPEECH_KEY": {cn: "k-cn", us: "k-us", ea: "k-cn"}, "AZURE_SPEECH_REGION": {cn: "southeastasia", us: "southeastasia", ea: "eastasia"}, } base, regions := planEnvSvcValues(spec, byKey, nil) if base["subscription_key"] != "k-cn" { t.Fatalf("基础值应取中国区,实际 %q", base["subscription_key"]) } if base["region"] != "southeastasia" { t.Fatalf("基础值应取中国区,实际 %q", base["region"]) } // 美区 key 不同 → 要分叉;美区 region 与中国区同值 → 不该出现在分叉里 if regions[us]["subscription_key"] != "k-us" { t.Fatalf("美区 key 不同,应写分叉,实际 %+v", regions[us]) } if _, ok := regions[us]["region"]; ok { t.Fatal("与基础值同值的区域字段不能写分叉——那正是老表 473 行重复的来源") } // 东亚 key 与中国区同值、region 不同 → 只分叉 region if _, ok := regions[ea]["subscription_key"]; ok { t.Fatal("东亚 key 与中国区同值,不该写分叉") } if regions[ea]["region"] != "eastasia" { t.Fatalf("东亚 region 不同,应写分叉,实际 %+v", regions[ea]) } }) t.Run("global_config 没有该键时回落业务库 config", func(t *testing.T) { byKey := map[string]map[int32]string{} base, regions := planEnvSvcValues(spec, byKey, map[string]string{ "AZURE_SPEECH_KEY": "from-app-config", }) if base["subscription_key"] != "from-app-config" { t.Fatalf("应回落业务库 config 的值,实际 %q", base["subscription_key"]) } if len(regions) != 0 { t.Fatalf("没有区域数据就不该有分叉,实际 %+v", regions) } }) t.Run("中国区缺行时退回区域0,再退回业务库", func(t *testing.T) { byKey := map[string]map[int32]string{ "AZURE_SPEECH_KEY": {0: "k-default", us: "k-us"}, } base, regions := planEnvSvcValues(spec, byKey, map[string]string{"AZURE_SPEECH_KEY": "k-app"}) if base["subscription_key"] != "k-default" { t.Fatalf("中国区缺行应退回区域0,实际 %q", base["subscription_key"]) } if regions[us]["subscription_key"] != "k-us" { t.Fatal("与基础值不同的区域仍要分叉") } }) t.Run("区域行是空串按没配处理", func(t *testing.T) { byKey := map[string]map[int32]string{ "AZURE_SPEECH_KEY": {cn: "k-cn", us: " "}, } _, regions := planEnvSvcValues(spec, byKey, nil) if _, ok := regions[us]; ok { t.Fatal("空值区域不该写分叉——老逻辑下它会下发空串覆盖,那本身就是配置事故") } }) t.Run("整份取不到值的服务不建", func(t *testing.T) { base, _ := planEnvSvcValues(spec, nil, nil) if hasAnyValue(base) { t.Fatalf("一个值都没有时不该判定为可建,实际 %+v", base) } }) } // 落点表本身的自检:EnvKey 是发给已发布客户端的键名,全局不能重名——重名时 // 下发结果取决于服务 id 排序,表现为「凭据时对时不对」。FieldKey 在同一个服务内也不能重名。 func TestEnvToSvcPlanNoDuplicateKeys(t *testing.T) { seenEnv := map[string]string{} seenSvc := map[string]bool{} for _, spec := range envToSvcPlan { if seenSvc[spec.Id] { t.Fatalf("服务 id 重复: %s", spec.Id) } seenSvc[spec.Id] = true fields := map[string]bool{} for _, f := range spec.Fields { if f.EnvKey == "" { t.Fatalf("服务 %s 的字段 %s 没有 env_key——那它就不会下发给老客户端", spec.Id, f.FieldKey) } if owner, ok := seenEnv[f.EnvKey]; ok { t.Fatalf("下发键名 %s 被 %s 和 %s 同时占用", f.EnvKey, owner, spec.Id) } seenEnv[f.EnvKey] = spec.Id if fields[f.FieldKey] { t.Fatalf("服务 %s 的字段名 %s 重复", spec.Id, f.FieldKey) } fields[f.FieldKey] = true } if spec.Categories == "" { t.Fatalf("服务 %s 没有类别——第三方服务配置页按类别分组,没类别就只会落进「未分类」", spec.Id) } } // 被明确丢弃的键不能同时又出现在落点表里 for k := range envKeysDropped { if owner, ok := seenEnv[k]; ok { t.Fatalf("键 %s 既在丢弃清单里、又被服务 %s 迁移,口径矛盾", k, owner) } } } // 线上存在「填错列」的 config 行(group 里是键名、key 里是值、value 空), // 后果是客户端读不到这个键、后台怎么改都没效果。迁移必须按它真正表达的含义读, // 否则 ALIBABA_BAILIAN_* 会在归一之后继续丢失。 func TestNormalizeAppCfgRow(t *testing.T) { t.Run("填错列的行按 group 当键名读", func(t *testing.T) { k, v := normalizeAppCfgRow(&pb.DBAppConfigItem{ Group: "ALIBABA_BAILIAN_APP_ID", Key: "mm_982f215733384f79aa5072c3c2bf", Value: "", }) if k != "ALIBABA_BAILIAN_APP_ID" || v != "mm_982f215733384f79aa5072c3c2bf" { t.Fatalf("应识别为 (键=group, 值=key),实际 (%q, %q)", k, v) } }) t.Run("正常行原样返回", func(t *testing.T) { k, v := normalizeAppCfgRow(&pb.DBAppConfigItem{Group: "AI", Key: "AGENT_TYPE", Value: "default"}) if k != "AGENT_TYPE" || v != "default" { t.Fatalf("正常行不该被改写,实际 (%q, %q)", k, v) } }) t.Run("group 不是落点表里的键名就不猜", func(t *testing.T) { // 值确实为空的普通配置行很常见,绝不能把它的 group 当成键名 k, v := normalizeAppCfgRow(&pb.DBAppConfigItem{Group: "APP", Key: "APP_AUTHCODE_OPEN", Value: ""}) if k != "APP_AUTHCODE_OPEN" || v != "" { t.Fatalf("不满足识别条件的行必须原样返回,实际 (%q, %q)", k, v) } }) t.Run("value 非空时一律原样", func(t *testing.T) { k, v := normalizeAppCfgRow(&pb.DBAppConfigItem{ Group: "ALIBABA_BAILIAN_APP_ID", Key: "x", Value: "real", }) if k != "x" || v != "real" { t.Fatalf("value 有值说明这行是正常的,实际 (%q, %q)", k, v) } }) } // 多应用部署下的清理判定:这是个 SaaS 平台,一台 console 管多个应用的业务库。 // 删错一行就等于让某个应用改用别人的凭据,且不报任何错。 func TestGlobalCovered(t *testing.T) { byKey := map[string]map[int32]string{ "OPENAI_API_KEY": {1: "k-cn", 2: "k-us"}, "ALIBABA_BAILIAN_APP_ID": {}, // 全局表里没有 "EMPTY_EVERYWHERE": {1: "", 2: " "}, // 有行但全是空 } if !globalCovered(byKey, "OPENAI_API_KEY") { t.Fatal("全局表有非空值就算覆盖——应用自己那行在迁移前就没生效过") } if globalCovered(byKey, "ALIBABA_BAILIAN_APP_ID") { t.Fatal("全局表没有该键,应用自己那行才是生效值,不能当死值删") } if globalCovered(byKey, "EMPTY_EVERYWHERE") { t.Fatal("全是空值不算覆盖") } if globalCovered(byKey, "NOT_PRESENT") { t.Fatal("键根本不存在时不算覆盖") } }