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") } }