mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-12 15:21:09 +00:00
fix(billing): settle tiered retries with final group (#6518)
This commit is contained in:
@@ -5,10 +5,15 @@ import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
"github.com/QuantumNous/new-api/pkg/billingexpr"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
"github.com/QuantumNous/new-api/relaykit/dto"
|
||||
"github.com/QuantumNous/new-api/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Claude Sonnet-style tiered expression: standard vs long-context
|
||||
@@ -309,6 +314,139 @@ func TestTryTieredSettle_NoRequestInput_FallsBackToDefault(t *testing.T) {
|
||||
// Group ratio tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type recordingBillingSettler struct {
|
||||
preConsumedQuota int
|
||||
reserveTargets []int
|
||||
}
|
||||
|
||||
func (*recordingBillingSettler) Settle(int) error { return nil }
|
||||
|
||||
func (*recordingBillingSettler) Refund(*gin.Context) {}
|
||||
|
||||
func (*recordingBillingSettler) NeedsRefund() bool { return false }
|
||||
|
||||
func (s *recordingBillingSettler) GetPreConsumedQuota() int {
|
||||
return s.preConsumedQuota
|
||||
}
|
||||
|
||||
func (s *recordingBillingSettler) Reserve(targetQuota int) error {
|
||||
s.reserveTargets = append(s.reserveTargets, targetQuota)
|
||||
if targetQuota > s.preConsumedQuota {
|
||||
s.preConsumedQuota = targetQuota
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestPrepareTieredBillingForSelectedGroupUpdatesReservation(t *testing.T) {
|
||||
const expr = `tier("base", p)`
|
||||
billing := &recordingBillingSettler{preConsumedQuota: 50_000}
|
||||
relayInfo := &relaycommon.RelayInfo{
|
||||
Billing: billing,
|
||||
FinalPreConsumedQuota: 50_000,
|
||||
TieredBillingSnapshot: &billingexpr.BillingSnapshot{
|
||||
BillingMode: "tiered_expr",
|
||||
ExprString: expr,
|
||||
ExprHash: billingexpr.ExprHashString(expr),
|
||||
GroupRatio: 0.10,
|
||||
EstimatedQuotaBeforeGroup: 500_000,
|
||||
EstimatedQuotaAfterGroup: 50_000,
|
||||
QuotaPerUnit: testQuotaPerUnit,
|
||||
},
|
||||
PriceData: types.PriceData{
|
||||
GroupRatioInfo: types.GroupRatioInfo{GroupRatio: 0.20},
|
||||
},
|
||||
}
|
||||
|
||||
require.Nil(t, PrepareTieredBillingForSelectedGroup(nil, relayInfo))
|
||||
require.Equal(t, []int{100_000}, billing.reserveTargets)
|
||||
assert.Equal(t, 100_000, billing.preConsumedQuota)
|
||||
assert.Equal(t, 100_000, relayInfo.FinalPreConsumedQuota)
|
||||
assert.Equal(t, 0.20, relayInfo.TieredBillingSnapshot.GroupRatio)
|
||||
assert.Equal(t, 100_000, relayInfo.TieredBillingSnapshot.EstimatedQuotaAfterGroup)
|
||||
}
|
||||
|
||||
func TestPrepareTieredBillingForSelectedGroupStartsBillingAfterFreeGroup(t *testing.T) {
|
||||
truncate(t)
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
const userID = 700
|
||||
seedUser(t, userID, 500_000)
|
||||
|
||||
relayInfo := &relaycommon.RelayInfo{
|
||||
UserId: userID,
|
||||
IsPlayground: true,
|
||||
ForcePreConsume: true,
|
||||
OriginModelName: "gpt-test",
|
||||
UserSetting: dto.UserSetting{
|
||||
BillingPreference: "wallet_only",
|
||||
},
|
||||
TieredBillingSnapshot: &billingexpr.BillingSnapshot{
|
||||
BillingMode: "tiered_expr",
|
||||
ExprString: `tier("base", p)`,
|
||||
ExprHash: billingexpr.ExprHashString(`tier("base", p)`),
|
||||
GroupRatio: 0,
|
||||
EstimatedQuotaBeforeGroup: 500_000,
|
||||
QuotaPerUnit: testQuotaPerUnit,
|
||||
},
|
||||
PriceData: types.PriceData{
|
||||
GroupRatioInfo: types.GroupRatioInfo{GroupRatio: 0.20},
|
||||
},
|
||||
}
|
||||
ctx, _ := gin.CreateTestContext(nil)
|
||||
|
||||
require.Nil(t, PrepareTieredBillingForSelectedGroup(ctx, relayInfo))
|
||||
require.NotNil(t, relayInfo.Billing)
|
||||
assert.Equal(t, 100_000, relayInfo.FinalPreConsumedQuota)
|
||||
assert.Equal(t, 0.20, relayInfo.TieredBillingSnapshot.GroupRatio)
|
||||
assert.Equal(t, 100_000, relayInfo.TieredBillingSnapshot.EstimatedQuotaAfterGroup)
|
||||
|
||||
userQuota, err := model.GetUserQuota(userID, false)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 400_000, userQuota)
|
||||
}
|
||||
|
||||
func TestTryTieredSettleUsesFinalGroupAfterRetry(t *testing.T) {
|
||||
const expr = `tier("base", p)`
|
||||
tests := []struct {
|
||||
name string
|
||||
finalGroupRatio float64
|
||||
wantQuota int
|
||||
}{
|
||||
{name: "more expensive final group", finalGroupRatio: 0.20, wantQuota: 100_000},
|
||||
{name: "free final group", finalGroupRatio: 0, wantQuota: 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
relayInfo := &relaycommon.RelayInfo{
|
||||
Billing: &recordingBillingSettler{preConsumedQuota: 50_000},
|
||||
FinalPreConsumedQuota: 50_000,
|
||||
TieredBillingSnapshot: &billingexpr.BillingSnapshot{
|
||||
BillingMode: "tiered_expr",
|
||||
ExprString: expr,
|
||||
ExprHash: billingexpr.ExprHashString(expr),
|
||||
GroupRatio: 0.10,
|
||||
EstimatedQuotaBeforeGroup: 500_000,
|
||||
EstimatedQuotaAfterGroup: 50_000,
|
||||
QuotaPerUnit: testQuotaPerUnit,
|
||||
},
|
||||
PriceData: types.PriceData{
|
||||
GroupRatioInfo: types.GroupRatioInfo{GroupRatio: tt.finalGroupRatio},
|
||||
},
|
||||
}
|
||||
|
||||
require.Nil(t, PrepareTieredBillingForSelectedGroup(nil, relayInfo))
|
||||
ok, quota, result := TryTieredSettle(relayInfo, billingexpr.TokenParams{P: 1_000_000})
|
||||
|
||||
require.True(t, ok)
|
||||
require.NotNil(t, result)
|
||||
assert.Equal(t, tt.wantQuota, quota)
|
||||
assert.Equal(t, tt.finalGroupRatio, relayInfo.TieredBillingSnapshot.GroupRatio)
|
||||
assert.Equal(t, tt.wantQuota, relayInfo.TieredBillingSnapshot.EstimatedQuotaAfterGroup)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTryTieredSettle_GroupRatioScaling(t *testing.T) {
|
||||
info := makeRelayInfo(flatExpr, 1.5, 1000, 500)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user