Files

85 lines
3.5 KiB
Go
Raw Permalink Normal View History

2026-08-26 06:41:07 +08:00
package billing
import (
"context"
"errors"
"testing"
"time"
)
func TestValidatePriceInput(t *testing.T) {
input := CreatePriceBookInput{
TenantID: "10000000-0000-0000-0000-000000000010", Provider: "mock", Currency: "CNY",
SourceRef: "contract://mock/2026-v1", ChangeSummary: "本地回归单价", EffectiveFrom: time.Now().UTC(),
Rates: []RateInput{{UsageCategory: "LLM", MetricCode: "input_tokens", Unit: "token", UnitPriceMicros: 2}},
}
if err := validatePriceInput(input); err != nil {
t.Fatalf("valid price input rejected: %v", err)
}
input.Rates = append(input.Rates, input.Rates[0])
if err := validatePriceInput(input); !errors.Is(err, ErrInvalid) {
t.Fatalf("duplicate rate error = %v, want ErrInvalid", err)
}
}
func TestValidateQuotaInput(t *testing.T) {
valid := QuotaInput{ScopeType: "TENANT", BillingTimezone: "Asia/Shanghai", Currency: "CNY", DailyLimitMicros: 10_000_000, MonthlyLimitMicros: 100_000_000, MaxConcurrentCalls: 10, MaxCallDurationSec: 3600, InProgressAction: "COMPLETE_CURRENT_TURN", ChangeSummary: "家庭试点额度"}
if err := validateQuotaInput(valid); err != nil {
t.Fatalf("valid quota rejected: %v", err)
}
for name, mutate := range map[string]func(*QuotaInput){
"tenant scope id": func(item *QuotaInput) { item.ScopeID = "10000000-0000-0000-0000-000000000511" },
"monthly below daily": func(item *QuotaInput) { item.MonthlyLimitMicros = item.DailyLimitMicros - 1 },
"invalid timezone": func(item *QuotaInput) { item.BillingTimezone = "China/Unknown" },
"short duration": func(item *QuotaInput) { item.MaxCallDurationSec = 59 },
} {
t.Run(name, func(t *testing.T) {
item := valid
mutate(&item)
if err := validateQuotaInput(item); !errors.Is(err, ErrInvalid) {
t.Fatalf("error = %v, want ErrInvalid", err)
}
})
}
}
func TestUsageQuantityPrecision(t *testing.T) {
for _, value := range []string{"0", "1", "1000", "0.000001", "999999999999999999.999999"} {
if !quantityPattern.MatchString(value) {
t.Fatalf("valid quantity %q rejected", value)
}
}
for _, value := range []string{"-1", "01", "1.0000001", "1e3", "9999999999999999999"} {
if quantityPattern.MatchString(value) {
t.Fatalf("invalid quantity %q accepted", value)
}
}
}
func TestRecordProviderUsageValidation(t *testing.T) {
ctx := context.Background()
if _, err := RecordProviderUsageTx(ctx, nil, "", "", "", "", "", time.Time{}, nil); !errors.Is(err, ErrInvalid) {
t.Fatalf("empty usage error=%v", err)
}
metrics := []UsageMetricInput{
{UsageCategory: " llm ", MetricCode: " INPUT_TOKENS ", Quantity: "12", Unit: " TOKEN "},
{UsageCategory: "LLM", MetricCode: "input_tokens", Quantity: "13", Unit: "token"},
}
if _, err := RecordProviderUsageTx(ctx, nil,
"10000000-0000-0000-0000-000000000001", "10000000-0000-0000-0000-000000000901",
"10000000-0000-0000-0000-000000000902", "mock", "usage-validation", time.Now().UTC(), metrics,
); !errors.Is(err, ErrInvalid) {
t.Fatalf("duplicate metric error=%v", err)
}
metrics = []UsageMetricInput{{UsageCategory: "UNKNOWN", MetricCode: "input_tokens", Quantity: "1", Unit: "token"}}
if _, err := RecordProviderUsageTx(ctx, nil,
"10000000-0000-0000-0000-000000000001", "10000000-0000-0000-0000-000000000901",
"10000000-0000-0000-0000-000000000902", "mock", "usage-validation", time.Now().UTC(), metrics,
); !errors.Is(err, ErrInvalid) {
t.Fatalf("invalid category error=%v", err)
}
if _, err := NewService(nil, nil); err == nil {
t.Fatal("invalid billing service dependencies were accepted")
}
}