mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-11-12 20:27: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 通过。
112 lines
4.1 KiB
Go
112 lines
4.1 KiB
Go
package core
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
)
|
||
|
||
// ★ 自动领取的「今天已处理」记账(照参考 coversToday / shouldMarkToday)。
|
||
//
|
||
// 真实缺陷(参考 2026-10-02 定位,每天静默漏 100 积分):Qoder 的活动
|
||
// **每日 10:00(UTC+8)才刷新**,而自动签到在上午跑 —— 上午看到的是**昨天**
|
||
// 那条 CLAIMED,返回 `already-claimed`。若拿它记账,当天 10 点刷新后就再也
|
||
// 不去领。
|
||
//
|
||
// 修法不靠「给每个渠道配刷新时刻表」(必然漂移),而是让**产出方自己回答**
|
||
// 「我这条结果覆盖的是不是今天这一轮」(covers_today),记账方只数
|
||
// 「有几条能证明今天已被处理」。
|
||
func TestAutoClaimCountsCoversTodayNotKind(t *testing.T) {
|
||
_, ps := poolCore(t)
|
||
|
||
// ① Qoder 在刷新前必须声明 covers_today=false(否则这条链就白修)
|
||
out, err := ps.Call("channels", "__qoder_claim_outcome_probe", map[string]interface{}{
|
||
"refreshed": false,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("探针: %v", err)
|
||
}
|
||
j, _ := out.(map[string]interface{})["json"].(map[string]interface{})
|
||
if j == nil || j["kind"] == nil {
|
||
t.Fatalf("探针应返回 kind,实得 %#v", out)
|
||
}
|
||
if j["covers_today"] != false {
|
||
t.Errorf("刷新前必须声明 covers_today=false(这正是漏领的根因),实得 %v", j["covers_today"])
|
||
}
|
||
if j["kind"] == "already-claimed" {
|
||
t.Error("刷新前**绝不能**报 already-claimed(那会被记账成「今天已处理」)")
|
||
}
|
||
|
||
// ② 刷新后:正常报已领,且 covers_today=true
|
||
out2, _ := ps.Call("channels", "__qoder_claim_outcome_probe", map[string]interface{}{
|
||
"refreshed": true,
|
||
})
|
||
j2, _ := out2.(map[string]interface{})["json"].(map[string]interface{})
|
||
if j2["covers_today"] != true {
|
||
t.Errorf("刷新后应声明 covers_today=true,实得 %v", j2["covers_today"])
|
||
}
|
||
|
||
// ③ 记账判据必须**只看 covers_today**,不能用 kind 反推
|
||
//(`already-claimed` 天然有歧义 —— 可能就是昨天那一轮的痕迹)
|
||
cases := []struct {
|
||
name string
|
||
kind string
|
||
cov interface{}
|
||
want bool
|
||
}{
|
||
{"刷新前的 already-claimed 不得算已处理", "already-claimed", false, false},
|
||
{"刷新后的 already-claimed 算已处理", "already-claimed", true, true},
|
||
{"success 算已处理", "success", true, true},
|
||
{"covers_today 缺省为 true", "claimed", nil, true},
|
||
{"inactive 不算已处理", "inactive", true, false},
|
||
{"failed 不算已处理", "failed", true, false},
|
||
}
|
||
for _, c := range cases {
|
||
o, err := ps.Call("channels", "__claim_covers_probe", map[string]interface{}{
|
||
"kind": c.kind, "covers_today": c.cov,
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("%s: %v", c.name, err)
|
||
}
|
||
oj, _ := o.(map[string]interface{})["json"].(map[string]interface{})
|
||
if got, _ := oj["covers"].(bool); got != c.want {
|
||
t.Errorf("%s: 应 %v,实得 %v", c.name, c.want, got)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 调度频率:一天只跑一次会让「刷新时刻晚于执行时刻」的平台整天漏领。
|
||
func TestAutoClaimScheduledMoreThanOnceDaily(t *testing.T) {
|
||
_, ps := poolCore(t)
|
||
out, err := ps.Call("channels", "__schedule_probe", nil)
|
||
if err != nil {
|
||
t.Fatalf("探针: %v", err)
|
||
}
|
||
j, _ := out.(map[string]interface{})["json"].(map[string]interface{})
|
||
every, _ := j["auto_claim_every"].(float64)
|
||
if every <= 0 {
|
||
t.Fatalf("找不到 auto_claim 的调度间隔,实得 %v", j)
|
||
}
|
||
// 必须显著小于一天:活动刷新时刻各平台不同(Qoder 是每日 10:00 UTC+8),
|
||
// 一天只跑一次就必然有平台漏领。
|
||
if every >= 86400 {
|
||
t.Errorf("auto_claim 间隔应远小于一天(否则刷新晚的平台整天漏领),实得 %.0f 秒", every)
|
||
}
|
||
if j["first_due_now"] != true {
|
||
t.Error("auto_claim 应 first_due_now(重启立即补跑,不等一个周期)")
|
||
}
|
||
// 已覆盖今天的平台必须靠记账跳过,否则会重复打上游
|
||
if !strings.Contains(toString(j["has_coverage_key"]), "true") {
|
||
t.Error("需要「今天已覆盖」的持久化记账键")
|
||
}
|
||
}
|
||
|
||
func toString(v interface{}) string {
|
||
if b, ok := v.(bool); ok {
|
||
if b {
|
||
return "true"
|
||
}
|
||
return "false"
|
||
}
|
||
return ""
|
||
}
|