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

147 lines
3.6 KiB
Go

package data_test
import (
"math"
"testing"
"time"
"cpa-ext/internal/data"
)
func validUsage() data.Usage {
return data.Usage{
Provider: "codex",
Model: "gpt-5.5",
RequestedAt: time.Date(2026, time.August, 14, 8, 0, 0, 0, time.UTC),
Generated: true,
Quality: data.UsageQualityComplete,
Tokens: data.TokenUsage{
UncachedInputTokens: 10,
CacheReadTokens: 20,
CacheCreationTokens: 30,
NonReasoningTokens: 40,
ReasoningTokens: 50,
TotalTokens: 150,
},
}
}
func TestUsageValidateAcceptsCompleteBreakdown(t *testing.T) {
t.Parallel()
if err := validUsage().Validate(); err != nil {
t.Fatalf("Validate() error = %v", err)
}
}
func TestUsageDistinguishesMissingFromMeasuredZero(t *testing.T) {
t.Parallel()
measured := validUsage()
measured.Tokens = data.TokenUsage{}
if err := measured.Validate(); err != nil {
t.Fatalf("measured zero Validate() error = %v", err)
}
missing := measured
missing.Quality = data.UsageQualityMissing
if err := missing.Validate(); err != nil {
t.Fatalf("missing Validate() error = %v", err)
}
missing.Tokens.TotalTokens = 1
if err := missing.Validate(); err == nil {
t.Fatal("missing usage with tokens unexpectedly passed validation")
}
}
func TestUsageValidateRejectsInvalidIdentityAndQuality(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mutate func(*data.Usage)
}{
{"missing provider", func(u *data.Usage) { u.Provider = " " }},
{"missing model", func(u *data.Usage) { u.Model = "" }},
{"missing requested time", func(u *data.Usage) { u.RequestedAt = time.Time{} }},
{"unknown quality", func(u *data.Usage) { u.Quality = "other" }},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
usage := validUsage()
test.mutate(&usage)
if err := usage.Validate(); err == nil {
t.Fatal("Validate() unexpectedly succeeded")
}
})
}
}
func TestUsageValidateRejectsInvalidTokenBreakdown(t *testing.T) {
t.Parallel()
tests := []struct {
name string
mutate func(*data.Usage)
}{
{"negative token", func(u *data.Usage) { u.Tokens.CacheReadTokens = -1 }},
{"wrong total", func(u *data.Usage) { u.Tokens.TotalTokens++ }},
{"unclassified complete", func(u *data.Usage) {
u.Tokens.UnclassifiedTokens = 1
u.Tokens.TotalTokens++
}},
{"overflow", func(u *data.Usage) {
u.Tokens = data.TokenUsage{
UncachedInputTokens: math.MaxInt64,
CacheReadTokens: 1,
TotalTokens: math.MaxInt64,
}
}},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
usage := validUsage()
test.mutate(&usage)
if err := usage.Validate(); err == nil {
t.Fatal("Validate() unexpectedly succeeded")
}
})
}
}
func TestUsageValidatePreservesExplicitQualityStates(t *testing.T) {
t.Parallel()
normalized := validUsage()
normalized.Quality = data.UsageQualityNormalized
if err := normalized.Validate(); err != nil {
t.Fatalf("normalized Validate() error = %v", err)
}
partial := validUsage()
partial.Quality = data.UsageQualityPartial
if err := partial.Validate(); err != nil {
t.Fatalf("partial Validate() error = %v", err)
}
unclassified := validUsage()
unclassified.Quality = data.UsageQualityUnclassified
unclassified.Tokens.UnclassifiedTokens = 5
unclassified.Tokens.TotalTokens += 5
if err := unclassified.Validate(); err != nil {
t.Fatalf("unclassified Validate() error = %v", err)
}
inconsistent := validUsage()
inconsistent.Quality = data.UsageQualityInconsistent
inconsistent.Tokens.TotalTokens++
if err := inconsistent.Validate(); err != nil {
t.Fatalf("inconsistent Validate() error = %v", err)
}
}