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 个包全绿;内核非测试代码零厂商名。
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 ""
|
||
}
|