- 计费:19 个金额字段 float64→decimal.Decimal,DB 列保持 numeric(20,8) 零迁移; API 金额仍为 JSON number(MarshalJSONWithoutQuotes),前端零改动 - failover:保留时间预算,新增 OT_PROXY_FAILOVER_CAPS_NET/SERVER/RATE/QUOTA 按失败类型封顶换渠道次数(429/配额类瞬时失败时间预算形同虚设) - 预聚合:新增 usage_channel_hourlies(schema v10),记账事务内同步 upsert, 长窗口渠道统计走桶不再扫明细,spent_last_30d 改扫 usage_daily,v10 一次性回填幂等 - 守护:internal/arch_test.go 用 go/parser 强制分层依赖(fail-closed)
86 lines
2.7 KiB
Go
86 lines
2.7 KiB
Go
package usage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/openteam/server/internal/store"
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
// 保留期只删过期明细,余额流水一条不动;未开启(0)时什么都不做。
|
|
func TestRetentionPurgesOldLogsOnly(t *testing.T) {
|
|
db := openTestDB(t)
|
|
u := seedUser(t, db, 100)
|
|
|
|
old := time.Now().UTC().AddDate(0, 0, -40)
|
|
fresh := time.Now().UTC()
|
|
for i := 0; i < 5; i++ {
|
|
db.Create(&store.UsageLog{TraceID: fmt.Sprintf("old-%d", i), UserID: u, ModelName: "m1",
|
|
Cost: decimal.NewFromFloat(0.01), Status: store.UsageStatusSuccess, CreatedAt: old})
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
db.Create(&store.UsageLog{TraceID: fmt.Sprintf("new-%d", i), UserID: u, ModelName: "m1",
|
|
Cost: decimal.NewFromFloat(0.01), Status: store.UsageStatusSuccess, CreatedAt: fresh})
|
|
}
|
|
db.Create(&store.BalanceLog{UserID: u, Change: decimal.NewFromFloat(-0.05), BalanceAfter: decimal.NewFromFloat(99.95),
|
|
Type: store.BalanceTypeUsage, RefID: "usage:old-0"})
|
|
|
|
n := purgeOlderThan(context.Background(), db, 30)
|
|
if n != 5 {
|
|
t.Fatalf("应删除 5 条过期明细,实际 %d", n)
|
|
}
|
|
var logs, ledgers int64
|
|
db.Model(&store.UsageLog{}).Count(&logs)
|
|
db.Model(&store.BalanceLog{}).Count(&ledgers)
|
|
if logs != 3 {
|
|
t.Fatalf("应保留 3 条新明细,实际 %d", logs)
|
|
}
|
|
if ledgers != 1 {
|
|
t.Fatalf("余额流水不应被清理,实际 %d 条", ledgers)
|
|
}
|
|
}
|
|
|
|
// 未配置保留期时不启动、不删数据。
|
|
func TestRetentionDisabled(t *testing.T) {
|
|
db := openTestDB(t)
|
|
u := seedUser(t, db, 100)
|
|
db.Create(&store.UsageLog{TraceID: "x", UserID: u, ModelName: "m1",
|
|
Cost: decimal.NewFromFloat(0.01), Status: store.UsageStatusSuccess,
|
|
CreatedAt: time.Now().UTC().AddDate(0, 0, -400)})
|
|
|
|
stop := StartRetention(context.Background(), db, 0)
|
|
stop()
|
|
var n int64
|
|
db.Model(&store.UsageLog{}).Count(&n)
|
|
if n != 1 {
|
|
t.Fatalf("未开启保留期时不应删除数据,实际剩 %d", n)
|
|
}
|
|
}
|
|
|
|
// 分批删除:行数超过一批时应全部删完。
|
|
func TestRetentionBatches(t *testing.T) {
|
|
db := openTestDB(t)
|
|
u := seedUser(t, db, 100)
|
|
old := time.Now().UTC().AddDate(0, 0, -90)
|
|
logs := make([]store.UsageLog, 0, retentionBatch+50)
|
|
for i := 0; i < retentionBatch+50; i++ {
|
|
logs = append(logs, store.UsageLog{TraceID: fmt.Sprintf("b-%d", i), UserID: u,
|
|
ModelName: "m1", Cost: decimal.NewFromFloat(0.001), Status: store.UsageStatusSuccess, CreatedAt: old})
|
|
}
|
|
if err := db.CreateInBatches(logs, 500).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
n := purgeOlderThan(context.Background(), db, 30)
|
|
if n != int64(len(logs)) {
|
|
t.Fatalf("应删除 %d 条,实际 %d", len(logs), n)
|
|
}
|
|
var left int64
|
|
db.Model(&store.UsageLog{}).Count(&left)
|
|
if left != 0 {
|
|
t.Fatalf("应全部删完,实际剩 %d", left)
|
|
}
|
|
}
|