fix(usage): subscription billing honours group rate multiplier
Subscription-mode billing was consuming quota at TotalCost (raw) instead of ActualCost (TotalCost * RateMultiplier), so per-group rate multipliers — including free subscriptions (multiplier = 0) — were silently ignored. Switch the three subscription cost writes in buildUsageBillingCommand, finalizePostUsageBilling, and the legacy postUsageBilling fallback to ActualCost, and add a table-driven test covering 2x / 0.5x / free multipliers plus a balance-mode regression check.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBuildUsageBillingCommand_SubscriptionAppliesRateMultiplier locks in the fix
|
||||
// that subscription-mode billing honours the group (and any user-specific) rate
|
||||
// multiplier — i.e. cmd.SubscriptionCost tracks ActualCost (= TotalCost *
|
||||
// RateMultiplier), not raw TotalCost.
|
||||
func TestBuildUsageBillingCommand_SubscriptionAppliesRateMultiplier(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
groupID := int64(7)
|
||||
subID := int64(42)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
totalCost float64
|
||||
actualCost float64
|
||||
isSubscription bool
|
||||
wantSub float64
|
||||
wantBalance float64
|
||||
}{
|
||||
{
|
||||
name: "subscription with 2x multiplier consumes 2x quota",
|
||||
totalCost: 1.0,
|
||||
actualCost: 2.0,
|
||||
isSubscription: true,
|
||||
wantSub: 2.0,
|
||||
wantBalance: 0,
|
||||
},
|
||||
{
|
||||
name: "subscription with 0.5x multiplier consumes 0.5x quota",
|
||||
totalCost: 1.0,
|
||||
actualCost: 0.5,
|
||||
isSubscription: true,
|
||||
wantSub: 0.5,
|
||||
wantBalance: 0,
|
||||
},
|
||||
{
|
||||
name: "free subscription (multiplier 0) consumes no quota",
|
||||
totalCost: 1.0,
|
||||
actualCost: 0,
|
||||
isSubscription: true,
|
||||
wantSub: 0,
|
||||
wantBalance: 0,
|
||||
},
|
||||
{
|
||||
name: "balance billing keeps using ActualCost (regression)",
|
||||
totalCost: 1.0,
|
||||
actualCost: 2.0,
|
||||
isSubscription: false,
|
||||
wantSub: 0,
|
||||
wantBalance: 2.0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := &postUsageBillingParams{
|
||||
Cost: &CostBreakdown{TotalCost: tt.totalCost, ActualCost: tt.actualCost},
|
||||
User: &User{ID: 1},
|
||||
APIKey: &APIKey{ID: 2, GroupID: &groupID},
|
||||
Account: &Account{ID: 3},
|
||||
Subscription: &UserSubscription{ID: subID},
|
||||
IsSubscriptionBill: tt.isSubscription,
|
||||
}
|
||||
|
||||
cmd := buildUsageBillingCommand("req-1", nil, p)
|
||||
if cmd == nil {
|
||||
t.Fatal("buildUsageBillingCommand returned nil")
|
||||
}
|
||||
if cmd.SubscriptionCost != tt.wantSub {
|
||||
t.Errorf("SubscriptionCost = %v, want %v", cmd.SubscriptionCost, tt.wantSub)
|
||||
}
|
||||
if cmd.BalanceCost != tt.wantBalance {
|
||||
t.Errorf("BalanceCost = %v, want %v", cmd.BalanceCost, tt.wantBalance)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user