mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-07 09:47:23 +00:00
按发布纪律回流。这四笔原本**误提交在 main** 上(我的操作失误): 纪律要求修复先在 release/v2.0.x 提交、再回流 main。已迁移: ① release 上 cherry-pick 这四笔(干净落地); ② main reset 回 9bec158(那四笔未推送,无远端影响); ③ 内容对齐回 main —— 等价于只捡拾修复、不带版本号。 内容: - channels-qoder 适配器:10605(排队) 与 110(额度耗尽) 分开说,厂商码留在插件域 - channels-cline 适配器:解析英文句子里的等待时长(内核按额度耗尽冷却而非 30s 猛敲) - auto_claim:按 covers_today 记账(Qoder 10:00 刷新前不得算已处理)+ 间隔 2 小时 - isFree:改为按平台声明权威来源(cline 用远端 free[]/命名约定,opencode 用 cost) 校验:git diff release/v2.0.x = 0 行;go build 通过。
160 lines
6.2 KiB
Go
160 lines
6.2 KiB
Go
package core
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// 自动签到的**能力表**必须与参考真值一致 —— 这张表是 auto_claim 的唯一依据,
|
||
// 错了就是"静默少领"或"每次调用都报自相矛盾的错"。
|
||
//
|
||
// 实测踩到的三处偏差(本轮修正):
|
||
//
|
||
// buddy 我们写 claim=true,但 buddy 既无 checkin 又无 claim_fn →
|
||
// 每次调用返回"不支持每日签到(平台未声明 checkin)"(表说支持、
|
||
// 函数说不支持)。参考里 buddy **没有** claim。
|
||
// raccoon 我们声明空表 → 白丢一个可领渠道。参考有:桌面端登录奖励
|
||
// POST /api/web/desktop/v1/login/points/grant(每号一次)。
|
||
// zcode 有签到,但每次领取现场产阿里云 captcha(headful Chromium,
|
||
// 按设备限流 150 次/小时)→ 参考把它列进 auto-checkin 的**排除表**。
|
||
// 自动跳过,手动仍可领。
|
||
func TestAutoClaimCapabilityTableMatchesReference(t *testing.T) {
|
||
c, ps := poolCore(t)
|
||
_ = c
|
||
|
||
// 参考真值(逐个 grep 参考仓的 claim 导出确认)
|
||
wantClaim := map[string]bool{
|
||
"trae": true, "minimax": true, "lobsterai": true, "qoder": true,
|
||
"loomy": true, "raccoon": true, "codearts": true,
|
||
"cline": false, "gemini": false, "zcode": true, "buddy": false,
|
||
}
|
||
// 自动签到必须跳过的(领取有代价)
|
||
wantAutoExclude := map[string]bool{"zcode": true}
|
||
|
||
for plat, want := range wantClaim {
|
||
t.Run("claim_"+plat, func(t *testing.T) {
|
||
got := creditsCapability(t, ps, plat)
|
||
if got == nil {
|
||
t.Fatalf("%s 无能力声明", plat)
|
||
}
|
||
if has := got["claim"] == true; has != want {
|
||
t.Errorf("%s claim = %v,参考为 %v", plat, has, want)
|
||
}
|
||
if got, ok := got["auto_exclude"].(bool); ok != wantAutoExclude[plat] {
|
||
t.Errorf("%s auto_exclude = %v,应为 %v", plat, got, wantAutoExclude[plat])
|
||
}
|
||
})
|
||
}
|
||
|
||
// 关键一致性:声称 claim=true 的平台**必须**真的可领
|
||
//(有 checkin 表或有 claim_fn)—— 否则就是 buddy 那种自相矛盾。
|
||
for plat, want := range wantClaim {
|
||
if !want {
|
||
continue
|
||
}
|
||
m, err := ps.Call("channels", "claims_capable", map[string]interface{}{"platform": plat})
|
||
if err != nil {
|
||
t.Fatalf("%s: %v", plat, err)
|
||
}
|
||
w, _ := m.(map[string]interface{})
|
||
j, _ := w["json"].(map[string]interface{})
|
||
if j == nil {
|
||
t.Fatalf("%s 无返回", plat)
|
||
}
|
||
if j["capable"] != true {
|
||
t.Errorf("%s 声称 claim=true 但不可领(%v)—— 会出现「表说支持、"+
|
||
"函数说不支持」的自相矛盾", plat, j["reason"])
|
||
}
|
||
}
|
||
// 反向:不声称 claim 的平台必须被判为不可领
|
||
m, _ := ps.Call("channels", "claims_capable", map[string]interface{}{"platform": "buddy"})
|
||
w, _ := m.(map[string]interface{})
|
||
j, _ := w["json"].(map[string]interface{})
|
||
if j != nil && j["capable"] == true {
|
||
t.Error("buddy 不该被判为可领(它没有 claim)")
|
||
}
|
||
}
|
||
|
||
// 自动签到的**调度配置**必须覆盖两项需求:领取 + 模型获取。
|
||
//
|
||
// auto_claim 走调度器(不是 HTTP export),故断言调度声明。
|
||
func TestAutoClaimScheduledWithModelRefresh(t *testing.T) {
|
||
b, err := readBundledChannels(t)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
for _, want := range []struct{ name, needle string }{
|
||
{"每日领取", `fn = "auto_claim"`},
|
||
{"模型目录自动获取", `fn = "refresh_models"`},
|
||
{"凭据自动续期", `fn = "refresh_creds"`},
|
||
{"启动即补跑", `first_due_now = true`},
|
||
} {
|
||
if !strings.Contains(b, want.needle) {
|
||
t.Errorf("调度表缺 %s(%s)", want.name, want.needle)
|
||
}
|
||
}
|
||
// 模型刷新间隔照参考 MODEL_REFRESH_INTERVAL_MS(2 小时)
|
||
if !strings.Contains(b, `fn = "refresh_models", every = 7200`) {
|
||
t.Error("refresh_models 间隔应为 7200(参考 2h)")
|
||
}
|
||
// 按**行**判断(固定字符窗会溢到下一行,误报)
|
||
lineOf := func(fn string) string {
|
||
for _, ln := range strings.Split(b, "\n") {
|
||
if strings.Contains(ln, `fn = "`+fn+`"`) {
|
||
return ln
|
||
}
|
||
}
|
||
return ""
|
||
}
|
||
// auto_claim **现在**也 first_due_now,且间隔远小于一天。
|
||
//
|
||
// ★ 这里改过一次,原因值得记下:原判据写的是「auto_claim 是每日任务,
|
||
// 不该 first_due_now(重启就重领是错的)」—— 那个假设**本身是缺陷的一部分**。
|
||
// 活动刷新时刻**各平台不同**(Qoder 是每日 10:00 UTC+8),一天只跑一次
|
||
// 必然有平台整天漏领(参考实测:每天静默漏 100 积分)。修法有两半:
|
||
// ① 频率提高 + first_due_now(任何时刻重启都能补上);
|
||
// ② 「今天已覆盖」靠 covers_today 记账跳过(**这才是防重领的正确机制**,
|
||
// 而不是靠"一天只跑一次"来碰巧避开)。
|
||
// 只做 ② 不做 ① 仍会漏领;只做 ① 不做 ② 会重复打上游。
|
||
if ln := lineOf("auto_claim"); !strings.Contains(ln, "first_due_now = true") {
|
||
t.Errorf("auto_claim 应 first_due_now(否则重启后当天漏领):%s", ln)
|
||
}
|
||
// 两个自动任务必须 first_due_now(否则重启要等满一轮才自愈)
|
||
for _, fn := range []string{"refresh_models", "refresh_creds"} {
|
||
ln := lineOf(fn)
|
||
if ln == "" {
|
||
continue
|
||
}
|
||
if !strings.Contains(ln, "first_due_now = true") {
|
||
t.Errorf("%s 应声明 first_due_now(参考点名的重启自愈缺陷):%s", fn, ln)
|
||
}
|
||
}
|
||
// auto_exclude 必须挡住 zcode(领取有代价:headful Chromium)
|
||
if !strings.Contains(b, "auto_exclude = true") {
|
||
t.Error("zcode 应标记 auto_exclude(照参考 isAutoCheckinExcluded)")
|
||
}
|
||
if i := strings.Index(b, `if cap.auto_exclude then`); i < 0 {
|
||
t.Error("auto_claim 必须尊重 auto_exclude(否则会拉起浏览器)")
|
||
}
|
||
}
|
||
|
||
// creditsCapability 读插件的能力表条目。
|
||
func creditsCapability(t *testing.T, ps interface {
|
||
Call(string, string, map[string]interface{}) (interface{}, error)
|
||
}, plat string) map[string]interface{} {
|
||
t.Helper()
|
||
m, err := ps.Call("channels", "credits_capability", map[string]interface{}{"platform": plat})
|
||
if err != nil {
|
||
t.Fatalf("credits_capability(%s): %v", plat, err)
|
||
}
|
||
w, _ := m.(map[string]interface{})
|
||
j, _ := w["json"].(map[string]interface{})
|
||
if j == nil {
|
||
return nil
|
||
}
|
||
if cap, ok := j["capability"].(map[string]interface{}); ok {
|
||
return cap
|
||
}
|
||
return nil
|
||
}
|