mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-12 20:27:23 +00:00
## 真实缺陷(照参考 credits.ts / auto-checkin.ts,2026-10-02 审查定位) 活动刷新时刻**各平台不同**:Qoder 是**每日 10:00(UTC+8)**才刷新。 而我们的自动领取只在 **08:00 跑一次**,于是: - 上午那次看到的是**昨天**那条 `CLAIMED` → 报 `already-claimed`; - 若拿它当"今天已处理"的凭证记档,当天 10 点刷新后就**再也不会去领**; - ⇒ **每天静默漏掉 100 积分**(且不可逆:用户以为今天不必再领)。 ## 修法:不靠"时刻表",让产出方自己声明 参考的做法(照抄,因为它是从根上对的):**不**给每个渠道配一张「刷新时刻表」 (那必然漂移),而是让**看得见该渠道刷新语义的那一层**声明 `covers_today`(本次结果算不算"今天这一轮"),记账方只数 「有几条能证明今天已被处理」。 - `covers_today` **缺省 true** ⇒ 绝大多数渠道(每日签到语义)行为完全不变; - Qoder 在**刷新前**置 `false` ⇒ 不得据此认定今天已处理; - **判据只看 `covers_today`,绝不用 `kind` 反推** —— `already-claimed` 天然有歧义(可能就是昨天那一轮的痕迹)。 - 未覆盖今天的平台会**待重试**并记日志;已覆盖的(按 UTC+8 日界记账) 下一次直接跳过,不会重复打上游。 ## 调度频率(两半都要,缺一仍漏) | 改动 | 为什么必须 | |---|---| | `every` 86400 → **7200** | 刷新时刻晚于那次执行、或那次没覆盖到的平台,一天一跑就整天漏 | | `at="08:00"` → **first_due_now** | 任何时刻重启都能补上,不等一个周期 | | 新增 `claimCovered` 持久化 | **这才是防重领的正确机制** —— 不是靠"一天只跑一次"碰巧避开 | ★ 旧判据(`TestAutoClaimScheduledWithModelRefresh`)明写「auto_claim 是每日任务, **不该** first_due_now(重启就重领是错的)」—— **那个假设本身就是缺陷的一部分**。 已按事实更新,并把这段经过写进注释(避免以后有人照旧判据再改回去)。 ## 判据 - `TestAutoClaimCountsCoversTodayNotKind`:① 刷新前必须 `covers_today=false` 且**绝不报** `already-claimed`;② 刷新后为 true;③ 六组记账判据逐条钉住 (含"刷新前的 already-claimed 不得算已处理"这一关键项); - `TestAutoClaimScheduledMoreThanOnceDaily`:间隔必须远小于一天 + first_due_now + 记账键存在; - `TestQoderAdapterParsesQueueAndBilling`(上一提交):5 组,含与通用 openai 适配器的**对比**(断言通用器做不到,否则专用适配器无存在理由)。 回归:9 个包全绿;内核非测试代码零厂商名。
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
|
||
}
|