Merge pull request #5194 from wucm667/fix/issue-5189-persist-unsettled-usage

fix(billing): retain usage logs on billing failure
This commit is contained in:
Wesley Liddick
2026-08-03 10:27:30 +08:00
committed by GitHub
4 changed files with 28 additions and 8 deletions
@@ -577,9 +577,10 @@ func TestGatewayServiceRecordUsage_DroppedUsageLogFallsBackToSyncCreate(t *testi
require.NoError(t, usageRepo.lastCtxErr)
}
func TestGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testing.T) {
func TestGatewayServiceRecordUsage_BillingErrorWritesUnsettledUsageLog(t *testing.T) {
usageRepo := &openAIRecordUsageLogRepoStub{}
billingRepo := &openAIRecordUsageBillingRepoStub{err: context.DeadlineExceeded}
billingErr := errors.New("billing tx failed")
billingRepo := &openAIRecordUsageBillingRepoStub{err: billingErr}
userRepo := &openAIRecordUsageUserRepoStub{}
subRepo := &openAIRecordUsageSubRepoStub{}
svc := newGatewayRecordUsageServiceWithBillingRepoForTest(usageRepo, billingRepo, userRepo, subRepo)
@@ -599,9 +600,16 @@ func TestGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testing.T)
Account: &Account{ID: 705},
})
require.Error(t, err)
require.ErrorIs(t, err, billingErr)
require.Equal(t, 1, billingRepo.calls)
require.Equal(t, 0, usageRepo.calls)
require.Equal(t, 1, usageRepo.calls)
require.NotNil(t, usageRepo.lastLog)
require.Equal(t, 10, usageRepo.lastLog.InputTokens)
require.Equal(t, 6, usageRepo.lastLog.OutputTokens)
require.Greater(t, usageRepo.lastLog.InputCost, 0.0)
require.Greater(t, usageRepo.lastLog.OutputCost, 0.0)
require.Greater(t, usageRepo.lastLog.TotalCost, 0.0)
require.Zero(t, usageRepo.lastLog.ActualCost)
}
func TestGatewayServiceRecordUsage_ReasoningEffortPersisted(t *testing.T) {
@@ -786,6 +786,8 @@ func (s *GatewayService) recordUsageCore(ctx context.Context, input *recordUsage
}, s.billingDeps(), s.usageBillingRepo)
if billingErr != nil {
usageLog.ActualCost = 0
writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.gateway")
return billingErr
}
writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.gateway")
@@ -936,9 +936,10 @@ func TestOpenAIGatewayServiceRecordUsage_GeneratesRequestIDWhenAllSourcesMissing
require.Equal(t, billingRepo.lastCmd.RequestID, usageRepo.lastLog.RequestID)
}
func TestOpenAIGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testing.T) {
func TestOpenAIGatewayServiceRecordUsage_BillingErrorWritesUnsettledUsageLog(t *testing.T) {
usageRepo := &openAIRecordUsageLogRepoStub{}
billingRepo := &openAIRecordUsageBillingRepoStub{err: errors.New("billing tx failed")}
billingErr := errors.New("billing tx failed")
billingRepo := &openAIRecordUsageBillingRepoStub{err: billingErr}
userRepo := &openAIRecordUsageUserRepoStub{}
subRepo := &openAIRecordUsageSubRepoStub{}
svc := newOpenAIRecordUsageServiceWithBillingRepoForTest(usageRepo, billingRepo, userRepo, subRepo, nil)
@@ -958,9 +959,16 @@ func TestOpenAIGatewayServiceRecordUsage_BillingErrorSkipsUsageLogWrite(t *testi
Account: &Account{ID: 30048},
})
require.Error(t, err)
require.ErrorIs(t, err, billingErr)
require.Equal(t, 1, billingRepo.calls)
require.Equal(t, 0, usageRepo.calls)
require.Equal(t, 1, usageRepo.calls)
require.NotNil(t, usageRepo.lastLog)
require.Equal(t, 8, usageRepo.lastLog.InputTokens)
require.Equal(t, 4, usageRepo.lastLog.OutputTokens)
require.Greater(t, usageRepo.lastLog.InputCost, 0.0)
require.Greater(t, usageRepo.lastLog.OutputCost, 0.0)
require.Greater(t, usageRepo.lastLog.TotalCost, 0.0)
require.Zero(t, usageRepo.lastLog.ActualCost)
}
func TestOpenAIGatewayServiceRecordUsage_UpdatesAPIKeyQuotaWhenConfigured(t *testing.T) {
@@ -400,6 +400,8 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
}()
if billingErr != nil {
usageLog.ActualCost = 0
writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.openai_gateway")
return billingErr
}
writeUsageLogBestEffort(ctx, s.usageLogRepo, usageLog, "service.openai_gateway")