package core import ( "testing" ) // 源注册必须**串行 + 节流**,而不是并发 2、无间隔。 // // 照参考 model-gate.ts 的实测依据:这些平台的**并发配额按模型**分别计量, // 与"还剩多少 token"无关(那边实测撞 429 时 token 还剩 299.4 万)。同账号下: // // GLM-5.3-Flash 605 次 200 0 次限流 ← 从未撞过 // GLM-5.3 74 次 200 21 次重试 6 次最终 429 // // 两条措施缺一不可: // // ① **串行**(max_concurrent=1):同一时刻只允许一个上游请求在飞 —— // 并发发起必然浪费一个配额。 // ② **最小间隔**(rpm):串行只保证"不重叠",不保证"有间隔" —— 连续放行的 // 两次调用可能只隔几十毫秒,仍会撞并发窗口。 // // 此前插件注册源时一律 max_concurrent=2 且**从不设 rpm** ⇒ 既会重叠又无间隔。 func TestChannelSourcesAreSerialWithThrottle(t *testing.T) { c, ps := poolCore(t) m, err := ps.Call("channels", "account_add", map[string]interface{}{ "platform": "buddy", "account_id": "gated", "credential": `{"access_token":"TOK"}`, }) if err != nil { t.Fatalf("account_add: %v", err) } j, _ := m.(map[string]interface{})["json"].(map[string]interface{}) if j == nil || j["error"] != nil { t.Fatalf("account_add 失败: %#v", m) } aid, _ := j["account_id"].(string) srcs := listSourcesViaKernel(t, c) // 一个通道 = 一个源:源名不含账号 id,按**本用例实际用的平台**找。 sm, ok := srcHasAccount(srcs, "buddy") if !ok { t.Fatalf("账号未出通道源,实际源: %v", srcs) } _ = aid // ① 串行 if mc, _ := toFloat(sm["max_concurrent"]); mc != 1 { t.Errorf("源必须 max_concurrent=1(并发发起必然浪费一个配额),实得 %v", sm["max_concurrent"]) } // ② 节流(rpm>0 才有间隔) if rpm, _ := toFloat(sm["rpm"]); rpm <= 0 { t.Errorf("源必须设 rpm>0(串行不保证间隔),实得 %v", sm["rpm"]) } } func toFloat(v interface{}) (float64, bool) { switch x := v.(type) { case float64: return x, true case int: return float64(x), true case int64: return float64(x), true } return 0, false }