Download internal/pool/transition_test.go from a3216/gcli2api: direct link, hf CLI and curl.
- Browser
- Download file 8.08 kB
-
https://huggingface.co/spaces/a3216/gcli2api/resolve/main/internal/pool/transition_test.go
- Command line
-
hf download hf://spaces/a3216/gcli2api/internal/pool/transition_test.go
-
curl -L -o transition_test.go https://huggingface.co/spaces/a3216/gcli2api/resolve/main/internal/pool/transition_test.go
8.08 kB
| package pool | |
| import ( | |
| "testing" | |
| "time" | |
| "github.com/linguo2625469/workbuddy2api-panel/internal/auth" | |
| ) | |
| // 状态机迁移正交性测试:聚焦 transition.go 收敛出的「单一权威状态机」语义。 | |
| // entry 的可选择性由四个正交维度决定(disabled / 冷却域 until+coolKind+softStreak+ | |
| // modelCooldowns / 熔断器 fails+retryCount+breakerUntil / sessionDeadFails),本文件 | |
| // 锁定迁移原语对这四个维度的边界,特别是旧实现的缺陷点: | |
| // - Disable 旧实现只置 disabled+reason,不碰 until/modelCooldowns → 「disabled 但 | |
| // cooling」杂交态(疑点 4)。新语义:disableLocked 置 disabled 并清冷却域。 | |
| // - reviveCoolingLocked(签到解冻)只清冷却域、不动熔断器(C5 语义)。 | |
| // | |
| // 与 pool_test.go / modelcooldown_test.go / sessiondead_test.go 的差异:那些测试 | |
| // 锁定各维度的行为,本文件锁定「跨维度」的迁移边界(冷却↔禁用↔熔断互不越界)。 | |
| // 直接读取 entry 的冷却域原始字段(Status 不暴露 coolKind 数值与 modelCooldowns)。 | |
| func coolingDomain(t *testing.T, p *Pool, uid string) (until time.Time, coolKind CoolKind, reason string, softStreak int, modelCooldowns int) { | |
| t.Helper() | |
| p.mu.RLock() | |
| defer p.mu.RUnlock() | |
| e, ok := p.byUID[uid] | |
| if !ok { | |
| t.Fatalf("uid %s missing", uid) | |
| } | |
| return e.until, e.coolKind, e.reason, e.softStreak, len(e.modelCooldowns) | |
| } | |
| // TestTransitionDisableClearsCoolingDomain 疑点 4 修正:先冷却(软冷却 + 6004 模型级 | |
| // 冷却)再 Disable → 冷却域全清(until/coolKind/reason/softStreak/modelCooldowns), | |
| // 只留 disabled 终态。旧实现只置 disabled+reason,会出现「disabled=true 但 cooling=true」 | |
| // 与残留 modelCooldowns 的杂交态。 | |
| func TestTransitionDisableClearsCoolingDomain(t *testing.T) { | |
| p := New("") | |
| p.Add(&auth.Auth{UID: "u1"}) | |
| p.Cooldown("u1", CoolSoft, 600*time.Second, "429") // until + coolKind=soft + softStreak=1 | |
| p.CooldownSoftForModel("u1", time.Minute, time.Now().Add(5*time.Minute), "glm-5.3", "6004") // modelCooldowns=1(softStreak 增到 2) | |
| _, _, reason, _, mc := coolingDomain(t, p, "u1") | |
| if reason == "" || mc != 1 { | |
| t.Fatalf("precondition: 应先处于软冷却+模型级冷却态 (reason=%q modelCooldowns=%d)", reason, mc) | |
| } | |
| p.Disable("u1", "account banned") | |
| st, _ := p.Status("u1") | |
| until, kind, reason, streak, mc := coolingDomain(t, p, "u1") | |
| if !st.Disabled { | |
| t.Fatal("Disable 后应 disabled") | |
| } | |
| if !until.IsZero() { | |
| t.Errorf("Disable 后 until=%v 应为零值(冷却域清零)", until) | |
| } | |
| if kind != 0 { | |
| t.Errorf("Disable 后 coolKind=%v 应为零值", kind) | |
| } | |
| if reason != "account banned" { | |
| t.Errorf("Disable 后 reason=%q 应为禁用原因", reason) | |
| } | |
| if streak != 0 { | |
| t.Errorf("Disable 后 softStreak=%d 应为 0(冷却域清零)", streak) | |
| } | |
| if mc != 0 { | |
| t.Errorf("Disable 后 modelCooldowns=%d 应为 0(模型豁免随冷却域清零)", mc) | |
| } | |
| if st.Cooling { | |
| t.Errorf("Disable 后不应呈现 cooling(禁用是更强终态,绝不杂交): %+v", st) | |
| } | |
| } | |
| // TestTransitionDisablePreservesBreaker 疑点 4 修正的另一半:Disable 只清冷却域、 | |
| // 不动熔断器。熔断是「连续 5xx 失败」信号(与授权/session 正交),禁用后再复活时 | |
| // 熔断观测仍有效,不应被禁用覆盖。 | |
| func TestTransitionDisablePreservesBreaker(t *testing.T) { | |
| p := New("") | |
| p.Add(&auth.Auth{UID: "u1"}) | |
| p.SetBreaker(1, time.Hour, time.Hour) | |
| p.NoteError("u1") // 触发熔断(fails=0, retryCount=1, breakerUntil=1h) | |
| bt, ok := p.breakerUntil("u1") | |
| if !ok || bt.IsZero() { | |
| t.Fatal("precondition: breaker should be open") | |
| } | |
| p.Disable("u1", "session dead") | |
| if bt, ok := p.breakerUntil("u1"); !ok || bt.IsZero() { | |
| t.Fatal("Disable 后 breakerUntil 应保留(熔断与禁用正交)") | |
| } | |
| if fails := p.breakerFails("u1"); fails != 0 { | |
| t.Errorf("Disable 后 fails=%d(已置 0,不应被 Disable 刻意改动)", fails) | |
| } | |
| // disabled 优先:即使熔断仍在,账号也不可选。 | |
| if got := p.Pick(); got != nil { | |
| t.Fatalf("disabled 账号不可选(含熔断期),got %+v", got) | |
| } | |
| } | |
| // TestTransitionSessionDeadDisableClearsCooling session 死亡走 NoteSessionDead 连续 | |
| // 计数,达阈值后经 disableLocked:冷却域一并清零,不得留下「disabled 但仍 cooling」 | |
| // 的杂交态(此前 handler 走 Disable、阈值路径却残留冷却,同一信号不同处置)。 | |
| func TestTransitionSessionDeadDisableClearsCooling(t *testing.T) { | |
| p := New("") | |
| p.Add(&auth.Auth{UID: "u1"}) | |
| p.Cooldown("u1", CoolSoft, time.Hour, "429 rate limit") // 软冷却中 | |
| until, _, _, _, _ := coolingDomain(t, p, "u1") | |
| if until.IsZero() { | |
| t.Fatal("precondition: 应处于软冷却") | |
| } | |
| if p.NoteSessionDead("u1") || p.NoteSessionDead("u1") || !p.NoteSessionDead("u1") { | |
| t.Fatal("第 3 次 NoteSessionDead 应达阈值禁用") | |
| } | |
| st, _ := p.Status("u1") | |
| if !st.Disabled { | |
| t.Fatal("应 disabled") | |
| } | |
| if st.Cooling || !st.Until.IsZero() || st.SoftStreak != 0 { | |
| t.Errorf("session dead 禁用应清冷却域(无杂交态): Cooling=%v Until=%v SoftStreak=%d", | |
| st.Cooling, st.Until, st.SoftStreak) | |
| } | |
| if st.DisabledReason != sessionDeadReason { | |
| t.Errorf("disabled_reason=%q want %q", st.DisabledReason, sessionDeadReason) | |
| } | |
| } | |
| // TestTransitionReviveKeepsSoftCoolingKeepsBreaker reviveCoolingLocked(签到/余额 | |
| // 刷新解冻)语义:只解冻余额耗尽冷却(CoolHard),软限流冷却与 6004 模型级台账 | |
| // **保留**(限流恢复证据是重置墙钟到期,不是余额恢复)+ 更新 credits,不动熔断。 | |
| // 既有单维度测试已各自锁定 reason/softStreak/modelCooldowns,本用例一次性断言完整 | |
| // 字段集,锁定迁移原语对冷却域/熔断域的处置永远一致。 | |
| func TestTransitionReviveKeepsSoftCoolingKeepsBreaker(t *testing.T) { | |
| p := New("") | |
| p.Add(&auth.Auth{UID: "u1"}) | |
| // 冷却域:软冷却 + 6004 模型级冷却(softStreak 累计)。 | |
| p.Cooldown("u1", CoolSoft, 600*time.Second, "429") | |
| p.CooldownSoftForModel("u1", time.Minute, time.Now().Add(5*time.Minute), "glm-5.3", "6004") | |
| // 熔断域:独立信号,签到不解冻。 | |
| p.SetBreaker(1, time.Hour, time.Hour) | |
| p.NoteError("u1") | |
| p.ReenableIfCredits("u1", 700, 0) | |
| st, _ := p.Status("u1") | |
| if st.Credits != 700 { | |
| t.Errorf("revive 后 credits=%d want 700", st.Credits) | |
| } | |
| until, kind, reason, streak, mc := coolingDomain(t, p, "u1") | |
| if until.IsZero() || kind == 0 || reason == "" { | |
| t.Errorf("revive 不得解除软限流冷却:until=%v kind=%v reason=%q", until, kind, reason) | |
| } | |
| if mc == 0 { | |
| t.Error("revive 不得清 6004 模型级台账(余额恢复不构成限流解除证据)") | |
| } | |
| _ = streak // 本场景 streak 恒 0(Cooldown 固定时长入口不累计、带 resetAt 的模型级写入也不累计) | |
| if bt, ok := p.breakerUntil("u1"); !ok || bt.IsZero() { | |
| t.Fatal("revive 不得清熔断(chat 通道健康未证明)") | |
| } | |
| // 熔断域保留 → 账号不进 normal 候选(仅全冷却兜底仍可能选中,与熔断兜底语义一致)。 | |
| if p.internalHealthy("u1") { | |
| t.Fatal("revive 后熔断期内不应 healthy(熔断域未被签到覆盖)") | |
| } | |
| } | |
| // TestTransitionReviveUnfreezesHardCooling 余额耗尽冷却(CoolHard)才是 | |
| // ReenableIfCredits 的解冻对象:余额恢复(remain>0)正是它的权威恢复证据。 | |
| func TestTransitionReviveUnfreezesHardCooling(t *testing.T) { | |
| p := New("") | |
| p.Add(&auth.Auth{UID: "u1"}) | |
| p.CooldownUntilTomorrow4AM("u1", "余额不足") | |
| p.ReenableIfCredits("u1", 700, 0) | |
| until, kind, reason, _, _ := coolingDomain(t, p, "u1") | |
| if !until.IsZero() || kind != 0 || reason != "" { | |
| t.Errorf("余额恢复应解冻 CoolHard:until=%v kind=%v reason=%q", until, kind, reason) | |
| } | |
| } | |