package worker import ( "testing" "github.com/gogf/gf/v2/os/gcfg" ) // loadImeiTestConfig loads the real shipped config file. // // A plain g.Cfg() lookup is not enough here: the test binary does not resolve // `config_default.yaml` (GoFrame searches for a file literally named // `config`), so every MustGet would silently fall back to the default argument // and a typo in the YAML key would go undetected. Loading the adapter // explicitly makes the assertions below test the file, not the defaults. func loadImeiTestConfig(t *testing.T) *gcfg.Config { t.Helper() adapter, err := gcfg.NewAdapterFile("config_default.yaml") if err != nil { t.Fatalf("load config_default.yaml: %v", err) } // Resolve relative to the package directory (internal/worker) up to the // repository root, where config/ lives. adapter.AddPath("../../config") return gcfg.NewWithAdapter(adapter) } // TestImeiConfigFileResolves 验证 imeiFullSync 配置段真实存在于发布配置中, // 且与状态链路结构对齐。键名拼错或段落缺失会让链路静默使用零值。 func TestImeiConfigFileResolves(t *testing.T) { config := loadImeiTestConfig(t) // enabled 必须存在且为 false:未完成预演验证前不得默认开启。 enabled, err := config.Get(nil, "imeiFullSync.enabled") if err != nil { t.Fatalf("读取 imeiFullSync.enabled 失败(配置段可能缺失): %v", err) } if enabled.Bool() { t.Fatal("imeiFullSync.enabled 必须为 false(预演验证后再开)") } prefix, err := config.Get(nil, "imeiFullSync.redisKeyPrefix") if err != nil || prefix.String() != "imei:" { t.Fatalf("redisKeyPrefix = %q (err=%v), want imei:", prefix.String(), err) } // 三条链路的前缀必须互不相同,否则 Run 命名空间会串场。 for _, other := range []struct { key string want string }{ {"trafficUsageFullSync.redisKeyPrefix", "flow:"}, {"simStatusQueueSync.redisKeyPrefix", "sim-status:"}, } { value, err := config.Get(nil, other.key) if err != nil { t.Fatalf("读取 %s 失败: %v", other.key, err) } if value.String() == prefix.String() { t.Fatalf("imei 前缀 %q 与 %s (%q) 重复", prefix.String(), other.key, value.String()) } } // 关键数值项逐条断言(对齐状态链路的键名)。 checks := []struct { key string want string }{ {"imeiFullSync.producer.pageSize", "1000"}, {"imeiFullSync.producer.runLockKey", "imei:producer:active"}, {"imeiFullSync.producer.runLockTTL", "5m"}, {"imeiFullSync.consumer.lockKey", "imei:consumer:active"}, {"imeiFullSync.consumer.claimBatch", "100"}, {"imeiFullSync.consumer.mobileConcurrency", "100"}, {"imeiFullSync.consumer.telecomConcurrency", "8"}, {"imeiFullSync.consumer.networkRetries", "2"}, {"imeiFullSync.consumer.chanClientTimeout", "30s"}, {"imeiFullSync.retry.maxRounds", "5"}, {"imeiFullSync.retry.failureRetention", "720h"}, // 失败池告警阀值:漏配会静默退化为默认 50000,但键名拼错时 // 我们希望测试直接报错,而不是让链路跑在一个意外阀值上。 {"imeiFullSync.consumer.failurePoolAlertThreshold", "50000"}, } for _, check := range checks { value, err := config.Get(nil, check.key) if err != nil { t.Fatalf("读取 %s 失败: %v", check.key, err) } if value.String() != check.want { t.Fatalf("%s = %q, want %q", check.key, value.String(), check.want) } } // consumer.enabled 与 producer 同为 false:链路默认不跑。 consumerEnabled, err := config.Get(nil, "imeiFullSync.consumer.enabled") if err != nil { t.Fatalf("读取 consumer.enabled 失败: %v", err) } if consumerEnabled.Bool() { t.Fatal("imeiFullSync.consumer.enabled 必须为 false") } } // TestImeiTemplateQPSInConfigFile 模板限速必须覆盖生产实际存在的移动族模板, // 否则会退回通用 1.5 QPS(255,976 张卡将耗时数天)。 func TestImeiTemplateQPSInConfigFile(t *testing.T) { config := loadImeiTestConfig(t) for _, tempKey := range []string{ "sim_yd_v5", "sim_yd_mock_v5", "sim_onelink_tengyu_v1", "sim_onelink_xuzhou_mobile_v1", "sim_onelink_lelian_v1", "sim_onelink_beixin_v1", } { value, err := config.Get(nil, "imeiFullSync.consumer.templateQPS."+tempKey) if err != nil { t.Fatalf("templateQPS 缺少 %s(会退回通用 1.5 QPS): %v", tempKey, err) } if value.Float64() != 100 { t.Fatalf("templateQPS.%s = %v, want 100", tempKey, value.Float64()) } } // 电信额度与状态/流量链路共享,必须压低。 telecom, err := config.Get(nil, "imeiFullSync.consumer.templateQPS.sim_dx_v1") if err != nil { t.Fatalf("templateQPS 缺少 sim_dx_v1: %v", err) } if telecom.Float64() != 5 { t.Fatalf("templateQPS.sim_dx_v1 = %v, want 5(电信额度共享,必须压低)", telecom.Float64()) } } // TestFailurePoolAlertConfiguredForEveryChain 三条全量同步链路都要有失败池 // 告警阀值。漏配某一条时该链路会退化为「无告警」,而这正是内存问题最需要 // 被看见的地方,所以这里显式要求三条都在配置文件里出现。 func TestFailurePoolAlertConfiguredForEveryChain(t *testing.T) { config := loadImeiTestConfig(t) chains := []string{ "trafficUsageFullSync", "simStatusQueueSync", "imeiFullSync", } for _, chain := range chains { key := chain + ".consumer.failurePoolAlertThreshold" value, err := config.Get(nil, key) if err != nil { t.Fatalf("%s 未配置(该链路将无失败池告警): %v", key, err) } if value.Int64() <= 0 { t.Fatalf("%s = %d,应为正数(0 表示关闭告警)", key, value.Int64()) } } }