Files
cpa-plugin/internal/repository/sqlite_pricing_test.go
T
chuan 6a461bf1f3 feat: 添加持久化用量计费与管理面板
- 使用 SQLite 保存关联后的请求生命周期和用量记录
- 支持长上下文阶梯价格和可配置的 Fast 计费倍率
- 添加管理接口和可配置列的用量面板
- 保留失败、取消、重试和 compact 请求,便于计费核对
2026-08-14 21:08:02 +08:00

89 lines
2.9 KiB
Go

package repository_test
import (
"context"
"path/filepath"
"testing"
"time"
"cpa-ext/internal/collection"
"cpa-ext/internal/pricing"
"cpa-ext/internal/repository"
)
func TestSQLitePricingRoundTripAndDelete(t *testing.T) {
store, err := repository.OpenSQLiteUsage(filepath.Join(t.TempDir(), "usage.db"))
if err != nil {
t.Fatal(err)
}
defer store.Close()
policy := pricing.Policy{
Model: "gpt-5.6-sol",
Base: pricing.Rates{InputMicrosPer1M: 2_500_000, CacheReadMicrosPer1M: 250_000, CacheWriteMicrosPer1M: 3_125_000, OutputMicrosPer1M: 15_000_000},
LongContext: &pricing.LongContext{ThresholdInputTokens: 272_000, Comparison: "gt", Rates: pricing.Rates{InputMicrosPer1M: 5_000_000, OutputMicrosPer1M: 22_500_000}},
FastPricingEnabled: true,
FastMultiplier: pricing.Ratio{Numerator: 5, Denominator: 2},
}
if err := store.UpsertPrice(context.Background(), policy); err != nil {
t.Fatal(err)
}
prices, err := store.ListPrices(context.Background())
if err != nil {
t.Fatal(err)
}
if len(prices) != 1 || prices[0].Model != policy.Model || prices[0].LongContext == nil || !prices[0].FastPricingEnabled || prices[0].FastMultiplier != policy.FastMultiplier {
t.Fatalf("unexpected prices: %+v", prices)
}
if err := store.DeletePrice(context.Background(), policy.Model); err != nil {
t.Fatal(err)
}
prices, err = store.ListPrices(context.Background())
if err != nil || len(prices) != 0 {
t.Fatalf("prices after delete: %+v, %v", prices, err)
}
}
func TestBackfillMissingCostsDoesNotRewriteExistingCost(t *testing.T) {
store, err := repository.OpenSQLiteUsage(filepath.Join(t.TempDir(), "usage.db"))
if err != nil {
t.Fatal(err)
}
defer store.Close()
missing := collection.Record{RequestedAt: time.Now(), Model: "deepseek-v4-flash", InputTokens: 1_000_000, OutputTokens: 100_000}
existingCost := int64(7)
priced := collection.Record{RequestedAt: time.Now(), Model: "deepseek-v4-flash", InputTokens: 1_000_000, CostMicros: &existingCost}
if err := store.Insert(context.Background(), missing); err != nil {
t.Fatal(err)
}
if err := store.Insert(context.Background(), priced); err != nil {
t.Fatal(err)
}
policy := pricing.Policy{
Model: "deepseek-v4-flash", Base: pricing.Rates{InputMicrosPer1M: 2_500_000, OutputMicrosPer1M: 15_000_000},
FastMultiplier: pricing.Ratio{Numerator: 5, Denominator: 2},
}
updated, err := store.BackfillMissingCosts(context.Background(), policy)
if err != nil {
t.Fatal(err)
}
if updated != 1 {
t.Fatalf("updated = %d, want 1", updated)
}
records, err := store.ListRecent(context.Background(), 10)
if err != nil {
t.Fatal(err)
}
if len(records) != 2 || records[0].CostMicros == nil || records[1].CostMicros == nil {
t.Fatalf("unexpected records: %+v", records)
}
got := map[int64]int{}
for _, record := range records {
got[*record.CostMicros]++
}
if got[4_000_000] != 1 || got[7] != 1 {
t.Fatalf("costs = %#v", got)
}
}