Files
ModelRouter/internal/lua/schedule_first_due_test.go
JianFeeeee 9bec158d2e chore(main): 回流 release/v2.0.x(v2.0.12)—— 内容对齐,main 无独有内容
按发布纪律回流。**注意这是内容对齐而非 merge**,理由与依据:

1. 纪律禁止 merge 回主干,因为 merge 会把发布分支的**版本号变更**带进 main。
   本项目版本号**只由 `git describe --tags` 推导**(Makefile VERSION),
   仓库里没有任何文件存放版本号 ⇒ 回流不存在版本污染风险。
2. 逐项核对确认 **main 是 release 的内容严格子集**:
   - `main` 独有的**文件**:0 个;
   - `internal/lua/plugins.go` / `internal/core/kernel_actions.go` 等内核文件
     main 独有行数:0;
   - `deploy.sh` 那 14 行只是 emoji 清理(release 刻意去掉了 emoji);
   - `channels.lua` 那 333 行是**旧版内容**(旧 exports 表、旧模型名单)。
   - main 独有的 9 个提交按 patch-id **全部已在 release 中等效存在**
     (`git cherry` 逐条列出均为 `-`,即已合入)。
3. 故回流 = 把 release 的内容带到 main,等价于"只捡拾修复、不带版本号",
   且不会丢任何 main 独有的修复。

本次带回 v2.0.12 的全部内容(37 个提交):
- 源串行 + 按模型节流(照 model-gate)
- 已失效模型存储 + 远端目录闸门
- 余额分档选号
- 模型能力元数据(models.dev,isFree 权威判定)
- **凭据续期修复**(此前恒报 ok:true 却一个请求都不发)
- 通用件 TTL 缓存 + 串行锁;account_test/account_adopt/account_delete 修正
- 内核级:出站授权持久化重启后不生效、`__llmsproxy_plugin_dir` ground truth

校验:`git diff release/v2.0.x` = 0 行;`go build -tags luajit ./...` 通过。
2026-10-14 16:52:58 +08:00

105 lines
3.7 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package lua
import (
"testing"
"time"
)
// 「启动即跑一轮」判据。
//
// 为什么需要它(照参考的事后复盘):只用 interval 时,宿主重启后短寿命凭据
// (cline 1h / codearts ~2h / raccoon 3h)要等满一个周期才续期,期间一直显示
// 「已过期」、积分行 401。修复 = 声明 first_due_now 的任务在启动后短暂延迟内
// 先跑一轮。
//
// 但它**必须 opt-in**:真正周期性的任务(如每日签到)不该因为重启就重跑。
func TestScheduleFirstDueNow(t *testing.T) {
// ① 声明了 first_due_now 的任务:首轮应在 catchUpDelay 内,而不是等一个周期
now := time.Now()
catchUp := ScheduledTask{Fn: "f", Every: 7200, FirstDueNow: true}
rt := &PluginRuntime{}
d := rt.firstDue(catchUp)
if d.After(now.Add(catchUpDelay + time.Second)) {
t.Errorf("first_due_now 的任务首轮应在 %v 内,实得 %v 后",
catchUpDelay, d.Sub(now))
}
if d.Before(now) {
t.Errorf("首轮时间不该在过去:%v", d.Sub(now))
}
// ② 没声明的:必须等满一个周期(否则每日/每小时的语义会被破坏)
plain := ScheduledTask{Fn: "f", Every: 7200}
d2 := rt.firstDue(plain)
if d2.Sub(now) < 7000*time.Second {
t.Errorf("未声明 first_due_now 的任务不该提前跑:实得 %v", d2.Sub(now))
}
// ③ at 形式(每日固定时刻)不受标记影响 —— 它本来就该等到那个时刻
daily := ScheduledTask{Fn: "f", At: "08:00"}
d3 := rt.firstDue(daily)
if d3.Sub(now) > 25*time.Hour {
t.Errorf("每日任务的首次时间超出一天:%v", d3.Sub(now))
}
if d3.Sub(now) < catchUpDelay && !daily.FirstDueNow {
// 只有当 08:00 恰好在 catchUpDelay 内才可能,属正常
t.Logf("每日任务首轮临近(可能恰好在 08:00 附近):%v", d3.Sub(now))
}
// ④ 声明了 first_due_now 的每日任务:优先补跑(凭据不等时刻)
dailyCatchUp := ScheduledTask{Fn: "f", At: "08:00", FirstDueNow: true}
d4 := rt.firstDue(dailyCatchUp)
if d4.Sub(now) > catchUpDelay+time.Second {
t.Errorf("声明 first_due_now 时即便有 at 也应先补跑:%v", d4.Sub(now))
}
}
// 解析层判据:first_due_now 必须真的从插件源码读出来(写错键名会静默变成
// false —— 那就是"看起来配了、实际没补跑")。
func TestScheduleParsesFirstDueNow(t *testing.T) {
const src = `
local plugin = { name = "schedprobe", version = "1.0" }
plugin.exports = { "noop" }
function plugin.noop() end
function plugin.refresh_creds() end
function plugin.auto_claim() end
plugin.schedule = {
{ fn = "auto_claim", every = 86400, at = "08:00", jitter = 300 },
{ fn = "refresh_creds", every = 1800, first_due_now = true, jitter = 60 },
}
return plugin
`
_, ps, _ := runtimePlugins(t)
if err := ps.LoadSource("schedprobe", src); err != nil {
t.Fatal(err)
}
ps.mu.RLock()
var sched []ScheduledTask
for _, p := range ps.plugins {
if p.Info.Name == "schedprobe" {
sched = p.Schedule
}
}
ps.mu.RUnlock()
if len(sched) != 2 {
t.Fatalf("应解析出 2 个任务,实得 %d: %+v", len(sched), sched)
}
byFn := map[string]ScheduledTask{}
for _, task := range sched {
byFn[task.Fn] = task
}
if task, ok := byFn["refresh_creds"]; !ok {
t.Fatal("refresh_creds 任务未解析")
} else if !task.FirstDueNow {
t.Error("first_due_now 未被解析为 true(键名写错会静默失效)")
} else if task.Every != 1800 {
t.Errorf("every = %d,应为 1800", task.Every)
}
if task, ok := byFn["auto_claim"]; !ok {
t.Fatal("auto_claim 任务未解析")
} else if task.FirstDueNow {
t.Error("auto_claim 未声明 first_due_now,应为 false(每日任务不该因重启重跑)")
} else if task.At != "08:00" {
t.Errorf("at = %q,应为 08:00", task.At)
}
}